1/*
2 * Copyright 2003-2005, Waldemar Kornewald <wkornew@gmx.net>
3 * Distributed under the terms of the MIT License.
4 */
5
6#include "DialUpView.h"
7#include "DialUpAddon.h"
8
9#include <cstring>
10#include "InterfaceUtils.h"
11#include "MessageDriverSettingsUtils.h"
12#include "TextRequestDialog.h"
13
14#include <PPPInterface.h>
15#include <settings_tools.h>
16#include <TemplateList.h>
17
18#include <Application.h>
19
20#include <Alert.h>
21#include <Button.h>
22#include <CheckBox.h>
23#include <MenuBar.h>
24#include <MenuField.h>
25#include <MenuItem.h>
26#include <Messenger.h>
27#include <PopUpMenu.h>
28#include <StringView.h>
29#include <TabView.h>
30
31#include <Directory.h>
32#include <Entry.h>
33#include <Path.h>
34
35
36// GUI constants
37static const uint32 kInterfaceFieldWidth = 175;
38
39// message constants
40static const uint32 kMsgCreateNew = 'NEWI';
41static const uint32 kMsgFinishCreateNew = 'FNEW';
42static const uint32 kMsgDeleteCurrent = 'DELI';
43static const uint32 kMsgSelectInterface = 'SELI';
44static const uint32 kMsgConnectButton = 'CONI';
45static const uint32 kMsgUpdateDefaultInterface = 'UPDT';
46
47// labels
48static const char *kLabelInterface = "Interface: ";
49static const char *kLabelInterfaceName = "Interface Name: ";
50static const char *kLabelCreateNewInterface = "Create New Interface";
51static const char *kLabelCreateNew = "Create New...";
52static const char *kLabelDefaultInterface = "Default";
53static const char *kLabelDeleteCurrent = "Delete Current";
54static const char *kLabelConnect = "Connect";
55static const char *kLabelDisconnect = "Disconnect";
56static const char *kLabelOK = "OK";
57
58// connection status strings
59static const char *kTextConnecting = "Connecting...";
60static const char *kTextConnectionEstablished = "Connection established.";
61static const char *kTextNotConnected = "Not connected.";
62static const char *kTextDeviceUpFailed = "Failed to connect.";
63static const char *kTextAuthenticating = "Authenticating...";
64static const char *kTextAuthenticationFailed = "Authentication failed!";
65static const char *kTextConnectionLost = "Connection lost!";
66static const char *kTextCreationError = "Error creating interface!";
67static const char *kTextNoInterfacesFound = "Please create a new interface...";
68static const char *kTextChooseInterfaceName = "Please choose a new name for this "
69											"interface.";
70
71static const char *kErrorTitle = "Error";
72static const char *kErrorNoPPPStack = "Error: Could not access the PPP stack!";
73static const char *kErrorInterfaceExists = "Error: An interface with this name "
74										"already exists!";
75static const char *kErrorLoadingFailed = "Error: Failed loading interface! The "
76										"current settings will be deleted.";
77static const char *kErrorSavingFailed = "Error: Failed saving interface settings!";
78
79
80DialUpView::DialUpView(BRect frame)
81	: BView(frame, "DialUpView", B_FOLLOW_NONE, 0),
82	fListener(this),
83	fCurrentItem(NULL),
84	fKeepLabel(false)
85{
86	BRect bounds = Bounds();
87		// for caching
88	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
89
90	// add messenger to us so add-ons can contact us
91	BMessenger messenger(this);
92	fSettings.Addons().AddMessenger(DUN_MESSENGER, messenger);
93
94	// create pop-up with all interfaces and "New..."/"Delete current" items
95	fInterfaceMenu = new BPopUpMenu(kLabelCreateNew);
96	BRect rect = bounds;
97	rect.InsetBy(5, 5);
98	rect.right = kInterfaceFieldWidth;
99	rect.bottom = rect.top + 20;
100	fMenuField = new BMenuField(rect, "Interfaces", kLabelInterface, fInterfaceMenu);
101	fMenuField->SetDivider(StringWidth(fMenuField->Label()) + 5);
102	rect.top += 3;
103	rect.bottom -= 2;
104	rect.left = rect.right + 5;
105	rect.right = bounds.right - 5;
106	fDefaultInterface = new BCheckBox(rect, "Default", kLabelDefaultInterface,
107		new BMessage(kMsgUpdateDefaultInterface));
108	rect.left = bounds.left + 5;
109	rect.top = rect.bottom + 12;
110	rect.bottom = bounds.bottom
111		- 20 // height of bottom controls
112		- 20; // space for bottom controls
113	fTabView = new BTabView(rect, "TabView", B_WIDTH_FROM_LABEL);
114	BRect tabViewRect(fTabView->Bounds());
115	tabViewRect.bottom -= fTabView->TabHeight();
116	fSettings.Addons().AddRect(DUN_TAB_VIEW_RECT, tabViewRect);
117
118	BRect tmpRect(rect);
119	tmpRect.top += (tmpRect.Height() - 15) / 2;
120	tmpRect.bottom = tmpRect.top + 15;
121	fStringView = new BStringView(tmpRect, "NoInterfacesFound",
122		kTextNoInterfacesFound);
123	fStringView->SetAlignment(B_ALIGN_CENTER);
124	fStringView->Hide();
125	tmpRect.top = tmpRect.bottom + 10;
126	tmpRect.bottom = tmpRect.top + 25;
127	fCreateNewButton = new BButton(tmpRect, "CreateNewButton",
128		kLabelCreateNewInterface, new BMessage(kMsgCreateNew));
129	fCreateNewButton->ResizeToPreferred();
130	tmpRect.left = (rect.Width() - fCreateNewButton->Bounds().Width()) / 2 + rect.left;
131	fCreateNewButton->MoveTo(tmpRect.left, tmpRect.top);
132	fCreateNewButton->Hide();
133
134	rect.top = rect.bottom + 15;
135	rect.bottom = rect.top + 15;
136	rect.right = rect.left + 200;
137	fStatusView = new BStringView(rect, "StatusView", kTextNotConnected);
138
139	rect.InsetBy(0, -5);
140	rect.left = rect.right + 5;
141	rect.right = bounds.right - 5;
142	fConnectButton = new BButton(rect, "ConnectButton", kLabelConnect,
143		new BMessage(kMsgConnectButton));
144
145	AddChild(fMenuField);
146	AddChild(fDefaultInterface);
147	AddChild(fTabView);
148	AddChild(fStringView);
149	AddChild(fCreateNewButton);
150	AddChild(fStatusView);
151	AddChild(fConnectButton);
152
153	// initialize
154	fListener.WatchManager();
155	LoadInterfaces();
156	fSettings.LoadAddons();
157	CreateTabs();
158	fCurrentItem = NULL;
159		// reset, otherwise SelectInterface will not load the settings
160	SelectInterface(0);
161	UpdateControls();
162}
163
164
165DialUpView::~DialUpView()
166{
167	fListener.StopWatchingInterface();
168	fListener.StopWatchingManager();
169	fSettings.SaveSettingsToFile();
170}
171
172
173void
174DialUpView::AttachedToWindow()
175{
176	fInterfaceMenu->SetTargetForItems(this);
177	fCreateNewButton->SetTarget(this);
178	fConnectButton->SetTarget(this);
179	fDefaultInterface->SetTarget(this);
180	fDefaultInterface->Hide();
181		// TODO: remove this when we have full COD support
182
183	if(fListener.InitCheck() != B_OK) {
184		(new BAlert(kErrorTitle, kErrorNoPPPStack, kLabelOK,
185			NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(NULL);
186		fConnectButton->Hide();
187	}
188}
189
190
191void
192DialUpView::MessageReceived(BMessage *message)
193{
194	switch(message->what) {
195		case PPP_REPORT_MESSAGE:
196			HandleReportMessage(message);
197		break;
198
199		// -------------------------------------------------
200		case kMsgCreateNew: {
201			UpdateControls();
202			(new TextRequestDialog(kLabelCreateNewInterface, kTextChooseInterfaceName,
203					kLabelInterfaceName))->Go(
204				new BInvoker(new BMessage(kMsgFinishCreateNew), this));
205		} break;
206
207		case kMsgFinishCreateNew: {
208			int32 which;
209			message->FindInt32("which", &which);
210			const char *name = message->FindString("text");
211			if(which == 1 && name && strlen(name) > 0)
212				AddInterface(name, true);
213
214			if(fCurrentItem)
215				fCurrentItem->SetMarked(true);
216
217			UpdateControls();
218
219			// a newly created interface is set to default if there is no default one
220			if(PPPManager::DefaultInterface() == "") {
221				fDefaultInterface->SetValue(true);
222				PPPManager::SetDefaultInterface(name);
223			}
224		} break;
225		// -------------------------------------------------
226
227		case kMsgDeleteCurrent: {
228			if(!fCurrentItem)
229				return;
230
231			const char *name = fCurrentItem->Message()->FindString("name");
232			if(PPPManager::DefaultInterface() == name)
233				PPPManager::SetDefaultInterface("");
234			fInterfaceMenu->RemoveItem(fCurrentItem);
235			BDirectory settings;
236			PPPManager::GetSettingsDirectory(&settings);
237			BEntry entry;
238			settings.FindEntry(name, &entry);
239			entry.Remove();
240			delete fCurrentItem;
241			fCurrentItem = NULL;
242
243			BMenuItem *marked = fInterfaceMenu->FindMarked();
244			if(marked)
245				marked->SetMarked(false);
246
247			UpdateControls();
248			SelectInterface(0);
249				// this stops watching the deleted interface
250		} break;
251
252		case kMsgSelectInterface: {
253			int32 index;
254			message->FindInt32("index", &index);
255			SelectInterface(index);
256		} break;
257
258		case kMsgConnectButton: {
259			if(!fCurrentItem)
260				return;
261
262			BringUpOrDown();
263		} break;
264
265		case kMsgUpdateDefaultInterface:
266			UpdateDefaultInterface();
267		break;
268
269		default:
270			BView::MessageReceived(message);
271	}
272}
273
274
275void
276DialUpView::BringUpOrDown()
277{
278	fSettings.SaveSettingsToFile();
279	BMessage settings;
280	fSettings.SaveSettings(&settings);
281
282	PPPInterface interface;
283	ppp_interface_info_t info;
284
285	// if going up: delete interface in order for the settings changes to take effect
286	interface = fListener.Manager().InterfaceWithName(
287		fCurrentItem->Message()->FindString("name"));
288	interface.GetInterfaceInfo(&info);
289	if(interface.InitCheck() == B_OK && info.info.state == PPP_INITIAL_STATE
290			&& info.info.phase == PPP_DOWN_PHASE)
291		fListener.Manager().DeleteInterface(interface.ID());
292
293	interface = fListener.Manager().CreateInterfaceWithName(
294		fCurrentItem->Message()->FindString("name"));
295
296	if(interface.InitCheck() != B_OK) {
297		Window()->Lock();
298		fStatusView->SetText(kTextCreationError);
299		Window()->Unlock();
300		return;
301	}
302
303	interface.GetInterfaceInfo(&info);
304	if(info.info.state == PPP_INITIAL_STATE && info.info.phase == PPP_DOWN_PHASE) {
305		interface.SetPassword(fSettings.SessionPassword());
306		interface.SetAskBeforeConnecting(false);
307		interface.Up();
308	} else
309		interface.Down();
310}
311
312
313void
314DialUpView::HandleReportMessage(BMessage *message)
315{
316	if(!fCurrentItem)
317		return;
318
319	ppp_interface_id id;
320	if(message->FindInt32("interface", reinterpret_cast<int32*>(&id)) != B_OK
321			|| (fListener.Interface() != PPP_UNDEFINED_INTERFACE_ID
322				&& id != fListener.Interface()))
323		return;
324
325	int32 type, code;
326	message->FindInt32("type", &type);
327	message->FindInt32("code", &code);
328
329	if(type == PPP_MANAGER_REPORT && code == PPP_REPORT_INTERFACE_CREATED) {
330		PPPInterface interface(id);
331		if(interface.InitCheck() != B_OK
332				|| strcasecmp(interface.Name(),
333					fCurrentItem->Message()->FindString("name")))
334			return;
335
336		WatchInterface(id);
337	} else if(type == PPP_CONNECTION_REPORT)
338		UpdateStatus(code);
339	else if(type == PPP_DESTRUCTION_REPORT)
340		WatchInterface(fListener.Manager().InterfaceWithName(
341			fCurrentItem->Message()->FindString("name")));
342}
343
344
345void
346DialUpView::CreateTabs()
347{
348	// create tabs for all registered and valid tab add-ons
349	DialUpAddon *addon;
350	BView *target;
351	float width, height;
352	TemplateList<DialUpAddon*> addons;
353
354	for(int32 index = 0;
355			fSettings.Addons().FindPointer(DUN_TAB_ADDON_TYPE, index,
356				reinterpret_cast<void**>(&addon)) == B_OK;
357			index++) {
358		if(!addon || addon->Position() < 0)
359			continue;
360
361		int32 insertIndex = 0;
362		for(; insertIndex < addons.CountItems(); insertIndex++)
363			if(addons.ItemAt(insertIndex)->Position() > addon->Position())
364				break;
365
366		addons.AddItem(addon, insertIndex);
367	}
368
369	for(int32 index = 0; index < addons.CountItems(); index++) {
370		addon = addons.ItemAt(index);
371
372		if(!addon->GetPreferredSize(&width, &height))
373			continue;
374
375		target = addon->CreateView(BPoint(0, 0));
376		if(!target)
377			continue;
378
379		fTabView->AddTab(target, NULL);
380	}
381}
382
383
384void
385DialUpView::UpdateStatus(int32 code)
386{
387	switch(code) {
388		case PPP_REPORT_DEVICE_UP_FAILED:
389		case PPP_REPORT_AUTHENTICATION_FAILED:
390		case PPP_REPORT_DOWN_SUCCESSFUL:
391		case PPP_REPORT_CONNECTION_LOST:
392			fConnectButton->SetLabel(kLabelConnect);
393		break;
394
395		default:
396			fConnectButton->SetLabel(kLabelDisconnect);
397	}
398
399	// maybe the status string must not be changed (codes that set fKeepLabel to false
400	// should still be handled)
401	if(fKeepLabel && code != PPP_REPORT_GOING_UP && code != PPP_REPORT_UP_SUCCESSFUL)
402		return;
403
404	if(fListener.InitCheck() != B_OK) {
405		fStatusView->SetText(kErrorNoPPPStack);
406		return;
407	}
408
409	// only errors should set fKeepLabel to true
410	switch(code) {
411		case PPP_REPORT_GOING_UP:
412			fKeepLabel = false;
413			fStatusView->SetText(kTextConnecting);
414		break;
415
416		case PPP_REPORT_UP_SUCCESSFUL:
417			fKeepLabel = false;
418			fStatusView->SetText(kTextConnectionEstablished);
419		break;
420
421		case PPP_REPORT_DOWN_SUCCESSFUL:
422			fStatusView->SetText(kTextNotConnected);
423		break;
424
425		case PPP_REPORT_DEVICE_UP_FAILED:
426			fKeepLabel = true;
427			fStatusView->SetText(kTextDeviceUpFailed);
428		break;
429
430		case PPP_REPORT_AUTHENTICATION_REQUESTED:
431			fStatusView->SetText(kTextAuthenticating);
432		break;
433
434		case PPP_REPORT_AUTHENTICATION_FAILED:
435			fKeepLabel = true;
436			fStatusView->SetText(kTextAuthenticationFailed);
437		break;
438
439		case PPP_REPORT_CONNECTION_LOST:
440			fKeepLabel = true;
441			fStatusView->SetText(kTextConnectionLost);
442		break;
443	}
444}
445
446
447void
448DialUpView::WatchInterface(ppp_interface_id ID)
449{
450	fListener.WatchInterface(ID);
451
452	// update status
453	PPPInterface interface(fListener.Interface());
454	if(interface.InitCheck() != B_OK)
455		UpdateStatus(PPP_REPORT_DOWN_SUCCESSFUL);
456}
457
458
459void
460DialUpView::LoadInterfaces()
461{
462	fInterfaceMenu->AddSeparatorItem();
463	fInterfaceMenu->AddItem(new BMenuItem(kLabelCreateNewInterface,
464		new BMessage(kMsgCreateNew)));
465	fDeleterItem = new BMenuItem(kLabelDeleteCurrent,
466		new BMessage(kMsgDeleteCurrent));
467	fInterfaceMenu->AddItem(fDeleterItem);
468
469	BDirectory settingsDirectory;
470	BEntry entry;
471	BPath path;
472	PPPManager::GetSettingsDirectory(&settingsDirectory);
473	while(settingsDirectory.GetNextEntry(&entry) == B_OK) {
474		if(entry.IsFile()) {
475			entry.GetPath(&path);
476			AddInterface(path.Leaf(), true);
477		}
478	}
479}
480
481
482void
483DialUpView::AddInterface(const char *name, bool isNew = false)
484{
485	if(FindInterface(name)) {
486		(new BAlert(kErrorTitle, kErrorInterfaceExists, kLabelOK,
487			NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(NULL);
488		return;
489	}
490
491	BMessage *message = new BMessage(kMsgSelectInterface);
492	message->AddString("name", name);
493	BString label(name);
494	if(PPPManager::DefaultInterface() == label)
495		label << " (" << kLabelDefaultInterface << ")";
496	BMenuItem *item = new BMenuItem(label.String(), message);
497	item->SetTarget(this);
498	int32 index = FindNextMenuInsertionIndex(fInterfaceMenu, name);
499	if(index > CountInterfaces())
500		index = CountInterfaces();
501	fInterfaceMenu->AddItem(item, index);
502	UpdateControls();
503
504	item->SetMarked(true);
505	SelectInterface(index, isNew);
506}
507
508
509void
510DialUpView::SelectInterface(int32 index, bool isNew = false)
511{
512	BMenuItem *item = fInterfaceMenu->FindMarked();
513	if(fCurrentItem && item == fCurrentItem)
514		return;
515
516	if(fCurrentItem && !fSettings.SaveSettingsToFile())
517		(new BAlert(kErrorTitle, kErrorSavingFailed, kLabelOK,
518			NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(NULL);
519
520	if(index >= CountInterfaces() || index < 0) {
521		if(CountInterfaces() > 0)
522			SelectInterface(0);
523		else {
524			fCurrentItem = NULL;
525			WatchInterface(PPP_UNDEFINED_INTERFACE_ID);
526		}
527	} else {
528		fCurrentItem = fInterfaceMenu->ItemAt(index);
529		if(!fCurrentItem) {
530			SelectInterface(0);
531			return;
532		}
533
534		const char *name = fCurrentItem->Message() ?
535			fCurrentItem->Message()->FindString("name") : NULL;
536		fDefaultInterface->SetValue(name && PPPManager::DefaultInterface() == name);
537		fCurrentItem->SetMarked(true);
538		fDeleterItem->SetEnabled(true);
539		fInterfaceMenu->Superitem()->SetLabel(name);
540		WatchInterface(fListener.Manager().InterfaceWithName(name));
541	}
542
543	UpdateControls();
544
545	if(!fCurrentItem)
546		fSettings.LoadSettings(NULL, false);
547			// tell modules to unload all settings
548	else if(!isNew && !fSettings.LoadSettings(
549			fCurrentItem->Message()->FindString("name"), false)) {
550		(new BAlert(kErrorTitle, kErrorLoadingFailed, kLabelOK,
551			NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(NULL);
552		fSettings.LoadSettings(fCurrentItem->Message()->FindString("name"), true);
553	} else if(isNew && !fSettings.LoadSettings(
554			fCurrentItem->Message()->FindString("name"), true))
555		(new BAlert(kErrorTitle, kErrorLoadingFailed, kLabelOK,
556			NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(NULL);
557}
558
559
560int32
561DialUpView::CountInterfaces() const
562{
563	return fInterfaceMenu->CountItems() - 3;
564}
565
566
567BMenuItem*
568DialUpView::FindInterface(BString name)
569{
570	BMenuItem *item;
571	for(int32 index = 0; index < CountInterfaces(); index++) {
572		item = fInterfaceMenu->ItemAt(index);
573		if(item && item->Message() && item->Message()->HasString("name")
574				&& name == item->Message()->FindString("name"))
575			return item;
576	}
577
578	return NULL;
579}
580
581
582void
583DialUpView::UpdateControls()
584{
585	if(fTabView->IsHidden() && CountInterfaces() > 0) {
586		fInterfaceMenu->SetLabelFromMarked(true);
587		fStringView->Hide();
588		fCreateNewButton->Hide();
589		fTabView->Show();
590		fDefaultInterface->Show();
591		fConnectButton->SetEnabled(true);
592	} else if(!fTabView->IsHidden() && CountInterfaces() == 0) {
593		fDeleterItem->SetEnabled(false);
594		fInterfaceMenu->SetRadioMode(false);
595		fInterfaceMenu->Superitem()->SetLabel(kLabelCreateNew);
596		fTabView->Hide();
597		fDefaultInterface->Hide();
598		fStringView->Show();
599		fCreateNewButton->Show();
600		fConnectButton->SetEnabled(false);
601	}
602
603	// move default checkbox next to interface menu (its size might have changed)
604	float width = fInterfaceMenu->StringWidth(fMenuField->Label())
605		+ fInterfaceMenu->StringWidth(fInterfaceMenu->Superitem()->Label()) + 30;
606	if(width > kInterfaceFieldWidth)
607		width = kInterfaceFieldWidth;
608	fDefaultInterface->MoveTo(fMenuField->Frame().left + width,
609		fDefaultInterface->Frame().top);
610}
611
612
613void
614DialUpView::UpdateDefaultInterface()
615{
616	const char *name = fCurrentItem->Message()->FindString("name");
617	BMenuItem *defaultItem = FindInterface(PPPManager::DefaultInterface());
618	if(fDefaultInterface->Value()) {
619		if(!PPPManager::SetDefaultInterface(name)) {
620			fDefaultInterface->SetValue(0);
621			return;
622		}
623		if(defaultItem)
624			defaultItem->SetLabel(defaultItem->Message()->FindString("name"));
625
626		BString label(name);
627		label << " (" << kLabelDefaultInterface << ")";
628		fCurrentItem->SetLabel(label.String());
629	} else {
630		PPPManager::SetDefaultInterface("");
631		fCurrentItem->SetLabel(name);
632	}
633}
634