1/*
2 * Copyright (C) 2007 Andrea Anzani <andrea.anzani@gmail.com>
3 * Copyright (C) 2007, 2010 Ryan Leavengood <leavengood@gmail.com>
4 * Copyright (C) 2009 Maxime Simon <simon.maxime@gmail.com>
5 * Copyright (C) 2010 Stephan A��mus <superstippi@gmx.de>
6 * Copyright (C) 2010 Michael Lotz <mmlr@mlotz.ch>
7 * Copyright (C) 2010 Rene Gollent <rene@gollent.com>
8 *
9 * All rights reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 *    notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in the
18 *    documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
21 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
28 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33#include "BrowserWindow.h"
34
35#include <Alert.h>
36#include <Application.h>
37#include <Bitmap.h>
38#include <Button.h>
39#include <Catalog.h>
40#include <CheckBox.h>
41#include <Clipboard.h>
42#include <Directory.h>
43#include <Entry.h>
44#include <File.h>
45#include <FindDirectory.h>
46#include <GridLayoutBuilder.h>
47#include <GroupLayout.h>
48#include <GroupLayoutBuilder.h>
49#include <LayoutBuilder.h>
50#include <Locale.h>
51#include <MenuBar.h>
52#include <MenuItem.h>
53#include <MessageRunner.h>
54#include <NodeInfo.h>
55#include <Path.h>
56#include <Roster.h>
57#include <Screen.h>
58#include <SeparatorView.h>
59#include <SpaceLayoutItem.h>
60#include <StatusBar.h>
61#include <StringView.h>
62#include <TextControl.h>
63
64#include <stdio.h>
65
66#include "AuthenticationPanel.h"
67#include "BaseURL.h"
68#include "BitmapButton.h"
69#include "BrowserApp.h"
70#include "BrowsingHistory.h"
71#include "CredentialsStorage.h"
72#include "IconButton.h"
73#include "NavMenu.h"
74#include "SettingsKeys.h"
75#include "SettingsMessage.h"
76#include "TabManager.h"
77#include "URLInputGroup.h"
78#include "WebPage.h"
79#include "WebView.h"
80#include "WebViewConstants.h"
81#include "WindowIcon.h"
82
83
84#undef B_TRANSLATION_CONTEXT
85#define B_TRANSLATION_CONTEXT "WebPositive Window"
86
87enum {
88	OPEN_LOCATION								= 'open',
89	GO_BACK										= 'goba',
90	GO_FORWARD									= 'gofo',
91	STOP										= 'stop',
92	HOME										= 'home',
93	GOTO_URL									= 'goul',
94	RELOAD										= 'reld',
95	CLEAR_HISTORY								= 'clhs',
96
97	CREATE_BOOKMARK								= 'crbm',
98	SHOW_BOOKMARKS								= 'shbm',
99
100	ZOOM_FACTOR_INCREASE						= 'zfin',
101	ZOOM_FACTOR_DECREASE						= 'zfdc',
102	ZOOM_FACTOR_RESET							= 'zfrs',
103	ZOOM_TEXT_ONLY								= 'zfto',
104
105	TOGGLE_FULLSCREEN							= 'tgfs',
106	TOGGLE_AUTO_HIDE_INTERFACE_IN_FULLSCREEN	= 'tgah',
107	CHECK_AUTO_HIDE_INTERFACE					= 'cahi',
108
109	SHOW_PAGE_SOURCE							= 'spgs',
110
111	EDIT_SHOW_FIND_GROUP						= 'sfnd',
112	EDIT_HIDE_FIND_GROUP						= 'hfnd',
113	EDIT_FIND_NEXT								= 'fndn',
114	EDIT_FIND_PREVIOUS							= 'fndp',
115	FIND_TEXT_CHANGED							= 'ftxt',
116
117	SELECT_TAB									= 'sltb',
118};
119
120
121static BLayoutItem*
122layoutItemFor(BView* view)
123{
124	BLayout* layout = view->Parent()->GetLayout();
125	int32 index = layout->IndexOfView(view);
126	return layout->ItemAt(index);
127}
128
129
130class BookmarkMenu : public BNavMenu {
131public:
132	BookmarkMenu(const char* title, BHandler* target, const entry_ref* navDir)
133		:
134		BNavMenu(title, B_REFS_RECEIVED, target)
135	{
136		// Add these items here already, so the shortcuts work even when
137		// the menu has never been opened yet.
138		_AddStaticItems();
139
140		SetNavDir(navDir);
141	}
142
143	virtual void AttachedToWindow()
144	{
145		RemoveItems(0, CountItems(), true);
146		ForceRebuild();
147		BNavMenu::AttachedToWindow();
148		if (CountItems() > 0)
149			AddItem(new BSeparatorItem(), 0);
150		_AddStaticItems();
151		DoLayout();
152	}
153
154private:
155	void _AddStaticItems()
156	{
157		AddItem(new BMenuItem(B_TRANSLATE("Manage bookmarks"),
158			new BMessage(SHOW_BOOKMARKS), 'M'), 0);
159		AddItem(new BMenuItem(B_TRANSLATE("Bookmark this page"),
160			new BMessage(CREATE_BOOKMARK), 'B'), 0);
161	}
162};
163
164
165class PageUserData : public BWebView::UserData {
166public:
167	PageUserData(BView* focusedView)
168		:
169		fFocusedView(focusedView),
170		fPageIcon(NULL),
171		fURLInputSelectionStart(-1),
172		fURLInputSelectionEnd(-1)
173	{
174	}
175
176	~PageUserData()
177	{
178		delete fPageIcon;
179	}
180
181	void SetFocusedView(BView* focusedView)
182	{
183		fFocusedView = focusedView;
184	}
185
186	BView* FocusedView() const
187	{
188		return fFocusedView;
189	}
190
191	void SetPageIcon(const BBitmap* icon)
192	{
193		delete fPageIcon;
194		if (icon)
195			fPageIcon = new BBitmap(icon);
196		else
197			fPageIcon = NULL;
198	}
199
200	const BBitmap* PageIcon() const
201	{
202		return fPageIcon;
203	}
204
205	void SetURLInputContents(const char* text)
206	{
207		fURLInputContents = text;
208	}
209
210	const BString& URLInputContents() const
211	{
212		return fURLInputContents;
213	}
214
215	void SetURLInputSelection(int32 selectionStart, int32 selectionEnd)
216	{
217		fURLInputSelectionStart = selectionStart;
218		fURLInputSelectionEnd = selectionEnd;
219	}
220
221	int32 URLInputSelectionStart() const
222	{
223		return fURLInputSelectionStart;
224	}
225
226	int32 URLInputSelectionEnd() const
227	{
228		return fURLInputSelectionEnd;
229	}
230
231private:
232	BView*		fFocusedView;
233	BBitmap*	fPageIcon;
234	BString		fURLInputContents;
235	int32		fURLInputSelectionStart;
236	int32		fURLInputSelectionEnd;
237};
238
239
240// #pragma mark - BrowserWindow
241
242
243BrowserWindow::BrowserWindow(BRect frame, SettingsMessage* appSettings,
244		const BString& url, uint32 interfaceElements, BWebView* webView)
245	:
246	BWebWindow(frame, kApplicationName,
247		B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
248		B_AUTO_UPDATE_SIZE_LIMITS | B_ASYNCHRONOUS_CONTROLS),
249	fIsFullscreen(false),
250	fInterfaceVisible(false),
251	fPulseRunner(NULL),
252	fVisibleInterfaceElements(interfaceElements),
253	fAppSettings(appSettings),
254	fZoomTextOnly(true),
255	fShowTabsIfSinglePageOpen(true),
256	fAutoHideInterfaceInFullscreenMode(false),
257	fAutoHidePointer(false)
258{
259	// Begin listening to settings changes and read some current values.
260	fAppSettings->AddListener(BMessenger(this));
261//	fZoomTextOnly = fAppSettings->GetValue("zoom text only", fZoomTextOnly);
262	fShowTabsIfSinglePageOpen = fAppSettings->GetValue(
263		kSettingsKeyShowTabsIfSinglePageOpen, fShowTabsIfSinglePageOpen);
264
265	fAutoHidePointer = fAppSettings->GetValue(kSettingsKeyAutoHidePointer,
266		fAutoHidePointer);
267
268	fNewWindowPolicy = fAppSettings->GetValue(kSettingsKeyNewWindowPolicy,
269		(uint32)OpenStartPage);
270	fNewTabPolicy = fAppSettings->GetValue(kSettingsKeyNewTabPolicy,
271		(uint32)OpenBlankPage);
272	fStartPageURL = fAppSettings->GetValue(kSettingsKeyStartPageURL,
273		kDefaultStartPageURL);
274	fSearchPageURL = fAppSettings->GetValue(kSettingsKeySearchPageURL,
275		kDefaultSearchPageURL);
276
277	// Create the interface elements
278	BMessage* newTabMessage = new BMessage(NEW_TAB);
279	newTabMessage->AddString("url", "");
280	newTabMessage->AddPointer("window", this);
281	newTabMessage->AddBool("select", true);
282	fTabManager = new TabManager(BMessenger(this), newTabMessage);
283
284	// Menu
285#if INTEGRATE_MENU_INTO_TAB_BAR
286	BMenu* mainMenu = fTabManager->Menu();
287#else
288	BMenu* mainMenu = new BMenuBar("Main menu");
289#endif
290	BMenu* menu = new BMenu(B_TRANSLATE("Window"));
291	BMessage* newWindowMessage = new BMessage(NEW_WINDOW);
292	newWindowMessage->AddString("url", "");
293	BMenuItem* newItem = new BMenuItem(B_TRANSLATE("New window"),
294		newWindowMessage, 'N');
295	menu->AddItem(newItem);
296	newItem->SetTarget(be_app);
297	newItem = new BMenuItem(B_TRANSLATE("New tab"),
298		new BMessage(*newTabMessage), 'T');
299	menu->AddItem(newItem);
300	newItem->SetTarget(be_app);
301	menu->AddItem(new BMenuItem(B_TRANSLATE("Open location"),
302		new BMessage(OPEN_LOCATION), 'L'));
303	menu->AddSeparatorItem();
304	menu->AddItem(new BMenuItem(B_TRANSLATE("Close window"),
305		new BMessage(B_QUIT_REQUESTED), 'W', B_SHIFT_KEY));
306	menu->AddItem(new BMenuItem(B_TRANSLATE("Close tab"),
307		new BMessage(CLOSE_TAB), 'W'));
308	menu->AddSeparatorItem();
309	menu->AddItem(new BMenuItem(B_TRANSLATE("Downloads"),
310		new BMessage(SHOW_DOWNLOAD_WINDOW), 'D'));
311	menu->AddItem(new BMenuItem(B_TRANSLATE("Settings"),
312		new BMessage(SHOW_SETTINGS_WINDOW)));
313	BMenuItem* aboutItem = new BMenuItem(B_TRANSLATE("About"),
314		new BMessage(B_ABOUT_REQUESTED));
315	menu->AddItem(aboutItem);
316	aboutItem->SetTarget(be_app);
317	menu->AddSeparatorItem();
318	BMenuItem* quitItem = new BMenuItem(B_TRANSLATE("Quit"),
319		new BMessage(B_QUIT_REQUESTED), 'Q');
320	menu->AddItem(quitItem);
321	quitItem->SetTarget(be_app);
322	mainMenu->AddItem(menu);
323
324	menu = new BMenu(B_TRANSLATE("Edit"));
325	menu->AddItem(fCutMenuItem = new BMenuItem(B_TRANSLATE("Cut"),
326		new BMessage(B_CUT), 'X'));
327	menu->AddItem(fCopyMenuItem = new BMenuItem(B_TRANSLATE("Copy"),
328		new BMessage(B_COPY), 'C'));
329	menu->AddItem(fPasteMenuItem = new BMenuItem(B_TRANSLATE("Paste"),
330		new BMessage(B_PASTE), 'V'));
331	menu->AddSeparatorItem();
332	menu->AddItem(new BMenuItem(B_TRANSLATE("Find"),
333		new BMessage(EDIT_SHOW_FIND_GROUP), 'F'));
334	menu->AddItem(fFindPreviousMenuItem
335		= new BMenuItem(B_TRANSLATE("Find previous"),
336		new BMessage(EDIT_FIND_PREVIOUS), 'G', B_SHIFT_KEY));
337	menu->AddItem(fFindNextMenuItem = new BMenuItem(B_TRANSLATE("Find next"),
338		new BMessage(EDIT_FIND_NEXT), 'G'));
339	mainMenu->AddItem(menu);
340	fFindPreviousMenuItem->SetEnabled(false);
341	fFindNextMenuItem->SetEnabled(false);
342
343	menu = new BMenu(B_TRANSLATE("View"));
344	menu->AddItem(new BMenuItem(B_TRANSLATE("Reload"), new BMessage(RELOAD),
345		'R'));
346	menu->AddSeparatorItem();
347	menu->AddItem(new BMenuItem(B_TRANSLATE("Increase size"),
348		new BMessage(ZOOM_FACTOR_INCREASE), '+'));
349	menu->AddItem(new BMenuItem(B_TRANSLATE("Decrease size"),
350		new BMessage(ZOOM_FACTOR_DECREASE), '-'));
351	menu->AddItem(new BMenuItem(B_TRANSLATE("Reset size"),
352		new BMessage(ZOOM_FACTOR_RESET), '0'));
353	fZoomTextOnlyMenuItem = new BMenuItem(B_TRANSLATE("Zoom text only"),
354		new BMessage(ZOOM_TEXT_ONLY));
355	fZoomTextOnlyMenuItem->SetMarked(fZoomTextOnly);
356	menu->AddItem(fZoomTextOnlyMenuItem);
357
358	menu->AddSeparatorItem();
359	fFullscreenItem = new BMenuItem(B_TRANSLATE("Full screen"),
360		new BMessage(TOGGLE_FULLSCREEN), B_RETURN);
361	menu->AddItem(fFullscreenItem);
362	menu->AddItem(new BMenuItem(B_TRANSLATE("Page source"),
363		new BMessage(SHOW_PAGE_SOURCE), 'U'));
364	mainMenu->AddItem(menu);
365
366	fHistoryMenu = new BMenu(B_TRANSLATE("History"));
367	fHistoryMenu->AddItem(fBackMenuItem = new BMenuItem(B_TRANSLATE("Back"),
368		new BMessage(GO_BACK), B_LEFT_ARROW));
369	fHistoryMenu->AddItem(fForwardMenuItem
370		= new BMenuItem(B_TRANSLATE("Forward"), new BMessage(GO_FORWARD),
371		B_RIGHT_ARROW));
372	fHistoryMenu->AddSeparatorItem();
373	fHistoryMenuFixedItemCount = fHistoryMenu->CountItems();
374	mainMenu->AddItem(fHistoryMenu);
375
376	BPath bookmarkPath;
377	entry_ref bookmarkRef;
378	if (_BookmarkPath(bookmarkPath) == B_OK
379		&& get_ref_for_path(bookmarkPath.Path(), &bookmarkRef) == B_OK) {
380		BMenu* bookmarkMenu
381			= new BookmarkMenu(B_TRANSLATE("Bookmarks"), this, &bookmarkRef);
382		mainMenu->AddItem(bookmarkMenu);
383	}
384
385	// Back, Forward, Stop & Home buttons
386	fBackButton = new IconButton("Back", 0, NULL, new BMessage(GO_BACK));
387	fBackButton->SetIcon(201);
388	fBackButton->TrimIcon();
389
390	fForwardButton = new IconButton("Forward", 0, NULL, new BMessage(GO_FORWARD));
391	fForwardButton->SetIcon(202);
392	fForwardButton->TrimIcon();
393
394	fStopButton = new IconButton("Stop", 0, NULL, new BMessage(STOP));
395	fStopButton->SetIcon(204);
396	fStopButton->TrimIcon();
397
398	fHomeButton = new IconButton("Home", 0, NULL, new BMessage(HOME));
399	fHomeButton->SetIcon(206);
400	fHomeButton->TrimIcon();
401	if (!fAppSettings->GetValue(kSettingsKeyShowHomeButton, true))
402		fHomeButton->Hide();
403
404	// URL input group
405	fURLInputGroup = new URLInputGroup(new BMessage(GOTO_URL));
406
407	// Status Bar
408	fStatusText = new BStringView("status", "");
409	fStatusText->SetAlignment(B_ALIGN_LEFT);
410	fStatusText->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
411	fStatusText->SetExplicitMinSize(BSize(150, 12));
412		// Prevent the window from growing to fit a long status message...
413	BFont font(be_plain_font);
414	font.SetSize(ceilf(font.Size() * 0.8));
415	fStatusText->SetFont(&font, B_FONT_SIZE);
416
417	// Loading progress bar
418	fLoadingProgressBar = new BStatusBar("progress");
419	fLoadingProgressBar->SetMaxValue(100);
420	fLoadingProgressBar->Hide();
421	fLoadingProgressBar->SetBarHeight(12);
422
423	const float kInsetSpacing = 3;
424	const float kElementSpacing = 5;
425
426	// Find group
427	fFindTextControl = new BTextControl("find", B_TRANSLATE("Find:"), "",
428		new BMessage(EDIT_FIND_NEXT));
429	fFindTextControl->SetModificationMessage(new BMessage(FIND_TEXT_CHANGED));
430	fFindPreviousButton = new BButton(B_TRANSLATE("Previous"),
431		new BMessage(EDIT_FIND_PREVIOUS));
432	fFindNextButton = new BButton(B_TRANSLATE("Next"),
433		new BMessage(EDIT_FIND_NEXT));
434	fFindCloseButton = new BButton(B_TRANSLATE("Close"),
435		new BMessage(EDIT_HIDE_FIND_GROUP));
436	fFindCaseSensitiveCheckBox = new BCheckBox(B_TRANSLATE("Match case"));
437	BGroupLayout* findGroup = BLayoutBuilder::Group<>(B_VERTICAL, 0.0)
438		.Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER))
439		.Add(BGroupLayoutBuilder(B_HORIZONTAL, kElementSpacing)
440			.Add(fFindTextControl)
441			.Add(fFindPreviousButton)
442			.Add(fFindNextButton)
443			.Add(fFindCaseSensitiveCheckBox)
444			.Add(BSpaceLayoutItem::CreateGlue())
445			.Add(fFindCloseButton)
446			.SetInsets(kInsetSpacing, kInsetSpacing,
447				kInsetSpacing, kInsetSpacing)
448		)
449	;
450
451	// Navigation group
452	BGroupLayout* navigationGroup = BLayoutBuilder::Group<>(B_VERTICAL, 0.0)
453		.Add(BLayoutBuilder::Group<>(B_HORIZONTAL, kElementSpacing)
454			.Add(fBackButton)
455			.Add(fForwardButton)
456			.Add(fStopButton)
457			.Add(fHomeButton)
458			.Add(fURLInputGroup)
459			.SetInsets(kInsetSpacing, kInsetSpacing, kInsetSpacing,
460				kInsetSpacing)
461		)
462		.Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER))
463	;
464
465	// Status bar group
466	BGroupLayout* statusGroup = BLayoutBuilder::Group<>(B_VERTICAL, 0.0)
467		.Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER))
468		.Add(BLayoutBuilder::Group<>(B_HORIZONTAL, kElementSpacing)
469			.Add(fStatusText)
470			.Add(fLoadingProgressBar, 0.2)
471			.AddStrut(12 - kElementSpacing)
472			.SetInsets(kInsetSpacing, 0, kInsetSpacing, 0)
473		)
474	;
475
476	BitmapButton* toggleFullscreenButton = new BitmapButton(kWindowIconBits,
477		kWindowIconWidth, kWindowIconHeight, kWindowIconFormat,
478		new BMessage(TOGGLE_FULLSCREEN));
479	toggleFullscreenButton->SetBackgroundMode(BitmapButton::MENUBAR_BACKGROUND);
480
481	BGroupLayout* menuBarGroup = BLayoutBuilder::Group<>(B_HORIZONTAL, 0.0)
482		.Add(mainMenu)
483		.Add(toggleFullscreenButton, 0.0f)
484	;
485
486	// Layout
487	AddChild(BLayoutBuilder::Group<>(B_VERTICAL, 0.0)
488#if !INTEGRATE_MENU_INTO_TAB_BAR
489		.Add(menuBarGroup)
490#endif
491		.Add(fTabManager->TabGroup())
492		.Add(navigationGroup)
493		.Add(fTabManager->ContainerView())
494		.Add(findGroup)
495		.Add(statusGroup)
496	);
497
498	fURLInputGroup->MakeFocus(true);
499
500	fMenuGroup = menuBarGroup;
501	fTabGroup = fTabManager->TabGroup()->GetLayout();
502	fNavigationGroup = navigationGroup;
503	fFindGroup = findGroup;
504	fStatusGroup = statusGroup;
505	fToggleFullscreenButton = layoutItemFor(toggleFullscreenButton);
506
507	fFindGroup->SetVisible(false);
508	fToggleFullscreenButton->SetVisible(false);
509
510	CreateNewTab(url, true, webView);
511	_ShowInterface(true);
512	_SetAutoHideInterfaceInFullscreen(fAppSettings->GetValue(
513		kSettingsKeyAutoHideInterfaceInFullscreenMode,
514		fAutoHideInterfaceInFullscreenMode));
515
516	AddShortcut('F', B_COMMAND_KEY | B_SHIFT_KEY,
517		new BMessage(EDIT_HIDE_FIND_GROUP));
518	// TODO: Should be a different shortcut, H is usually for Find selection.
519	AddShortcut('H', B_COMMAND_KEY,	new BMessage(HOME));
520
521	// Add shortcuts to select a particular tab
522	for (int32 i = 1; i <= 9; i++) {
523		BMessage* selectTab = new BMessage(SELECT_TAB);
524		selectTab->AddInt32("tab index", i - 1);
525		char numStr[2];
526		snprintf(numStr, sizeof(numStr), "%d", (int) i);
527		AddShortcut(numStr[0], B_COMMAND_KEY, selectTab);
528	}
529
530	be_app->PostMessage(WINDOW_OPENED);
531}
532
533
534BrowserWindow::~BrowserWindow()
535{
536	fAppSettings->RemoveListener(BMessenger(this));
537	delete fTabManager;
538	delete fPulseRunner;
539}
540
541
542void
543BrowserWindow::DispatchMessage(BMessage* message, BHandler* target)
544{
545	const char* bytes;
546	uint32 modifierKeys;
547	if ((message->what == B_KEY_DOWN || message->what == B_UNMAPPED_KEY_DOWN)
548		&& message->FindString("bytes", &bytes) == B_OK
549		&& message->FindInt32("modifiers", (int32*)&modifierKeys) == B_OK) {
550
551		modifierKeys = modifierKeys & 0x000000ff;
552		if (bytes[0] == B_LEFT_ARROW && modifierKeys == B_COMMAND_KEY) {
553			PostMessage(GO_BACK);
554			return;
555		} else if (bytes[0] == B_RIGHT_ARROW && modifierKeys == B_COMMAND_KEY) {
556			PostMessage(GO_FORWARD);
557			return;
558		} else if (bytes[0] == B_FUNCTION_KEY) {
559			// Some function key Firefox compatibility
560			int32 key;
561			if (message->FindInt32("key", &key) == B_OK) {
562				switch (key) {
563					case B_F5_KEY:
564						PostMessage(RELOAD);
565						break;
566					case B_F11_KEY:
567						PostMessage(TOGGLE_FULLSCREEN);
568						break;
569					default:
570						break;
571				}
572			}
573		} else if (target == fURLInputGroup->TextView()) {
574			// Handle B_RETURN in the URL text control. This is the easiest
575			// way to react *only* when the user presses the return key in the
576			// address bar, as opposed to trying to load whatever is in there
577			// when the text control just goes out of focus.
578			if (bytes[0] == B_RETURN) {
579				// Do it in such a way that the user sees the Go-button go down.
580				_InvokeButtonVisibly(fURLInputGroup->GoButton());
581				return;
582			}
583		} else if (target == fFindTextControl->TextView()) {
584			// Handle B_RETURN when the find text control has focus.
585			if (bytes[0] == B_RETURN) {
586				if ((modifierKeys & B_SHIFT_KEY) != 0)
587					_InvokeButtonVisibly(fFindPreviousButton);
588				else
589					_InvokeButtonVisibly(fFindNextButton);
590				return;
591			} else if (bytes[0] == B_ESCAPE) {
592				_InvokeButtonVisibly(fFindCloseButton);
593				return;
594			}
595		} else if (bytes[0] == B_ESCAPE) {
596			// Default escape key behavior:
597			PostMessage(STOP);
598			return;
599		}
600	}
601	if (message->what == B_MOUSE_MOVED || message->what == B_MOUSE_DOWN
602		|| message->what == B_MOUSE_UP) {
603		message->FindPoint("where", &fLastMousePos);
604		if (message->FindInt64("when", &fLastMouseMovedTime) != B_OK)
605			fLastMouseMovedTime = system_time();
606		_CheckAutoHideInterface();
607	}
608	if (message->what == B_MOUSE_WHEEL_CHANGED) {
609		BPoint where;
610		uint32 buttons;
611		CurrentWebView()->GetMouse(&where, &buttons, false);
612		// Only do this when the mouse is over the web view
613		if (CurrentWebView()->Bounds().Contains(where)) {
614			// Zoom and unzoom text on Command + mouse wheel.
615			// This could of course (and maybe should be) implemented in the
616			// WebView, but there would need to be a way for the WebView to
617			// know the setting of the fZoomTextOnly member here. Plus other
618			// clients of the API may not want this feature.
619			if ((modifiers() & B_COMMAND_KEY) != 0) {
620				float dy;
621				if (message->FindFloat("be:wheel_delta_y", &dy) == B_OK) {
622					if (dy < 0)
623						CurrentWebView()->IncreaseZoomFactor(fZoomTextOnly);
624					else
625						CurrentWebView()->DecreaseZoomFactor(fZoomTextOnly);
626					return;
627				}
628			}
629		} else // Also don't scroll up and down if the mouse is not over the web view
630			return;
631	}
632	BWebWindow::DispatchMessage(message, target);
633}
634
635
636void
637BrowserWindow::MessageReceived(BMessage* message)
638{
639	switch (message->what) {
640		case OPEN_LOCATION:
641			_ShowInterface(true);
642			if (fURLInputGroup->TextView()->IsFocus())
643				fURLInputGroup->TextView()->SelectAll();
644			else
645				fURLInputGroup->MakeFocus(true);
646			break;
647		case RELOAD:
648			CurrentWebView()->Reload();
649			break;
650		case GOTO_URL:
651		{
652			BString url;
653			if (message->FindString("url", &url) != B_OK)
654				url = fURLInputGroup->Text();
655
656			_SetPageIcon(CurrentWebView(), NULL);
657			_SmartURLHandler(url);
658
659			break;
660		}
661		case GO_BACK:
662			CurrentWebView()->GoBack();
663			break;
664		case GO_FORWARD:
665			CurrentWebView()->GoForward();
666			break;
667		case STOP:
668			CurrentWebView()->StopLoading();
669			break;
670		case HOME:
671			CurrentWebView()->LoadURL(fStartPageURL);
672			break;
673
674		case CLEAR_HISTORY: {
675			BrowsingHistory* history = BrowsingHistory::DefaultInstance();
676			if (history->CountItems() == 0)
677				break;
678			BAlert* alert = new BAlert(B_TRANSLATE("Confirmation"),
679				B_TRANSLATE("Do you really want to "
680				"clear the browsing history?"), B_TRANSLATE("Clear"),
681				B_TRANSLATE("Cancel"));
682			alert->SetShortcut(1, B_ESCAPE);
683
684			if (alert->Go() == 0)
685				history->Clear();
686			break;
687		}
688
689		case CREATE_BOOKMARK:
690			_CreateBookmark();
691			break;
692		case SHOW_BOOKMARKS:
693			_ShowBookmarks();
694			break;
695
696		case B_REFS_RECEIVED:
697		{
698			// Currently the only source of these messages is the bookmarks menu.
699			// Filter refs into URLs, this also gets rid of refs for folders.
700			// For clicks on sub-folders in the bookmarks menu, we have Tracker
701			// open the corresponding folder.
702			entry_ref ref;
703			uint32 addedCount = 0;
704			for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) {
705				BEntry entry(&ref);
706				uint32 addedSubCount = 0;
707				if (entry.IsDirectory()) {
708					BDirectory directory(&entry);
709					_AddBookmarkURLsRecursively(directory, message,
710						addedSubCount);
711				} else {
712					BFile file(&ref, B_READ_ONLY);
713					BString url;
714					if (_ReadURLAttr(file, url)) {
715						message->AddString("url", url.String());
716						addedSubCount++;
717					}
718				}
719				if (addedSubCount == 0) {
720					// Don't know what to do with this entry, just pass it
721					// on to the system to handle. Note that this may result
722					// in us opening other supported files via the application
723					// mechanism.
724					be_roster->Launch(&ref);
725				}
726				addedCount += addedSubCount;
727			}
728			message->RemoveName("refs");
729			if (addedCount > 10) {
730				BString string(B_TRANSLATE_COMMENT("Do you want to open %addedCount "
731					"bookmarks all at once?", "Don't translate variable %addedCount."));
732				string.ReplaceFirst("%addedCount", BString() << addedCount);
733
734				BAlert* alert = new BAlert(B_TRANSLATE("Open bookmarks confirmation"),
735					string.String(), B_TRANSLATE("Cancel"), B_TRANSLATE("Open all"));
736				alert->SetShortcut(0, B_ESCAPE);
737				if (alert->Go() == 0)
738					break;
739			}
740			be_app->PostMessage(message);
741			break;
742		}
743		case B_SIMPLE_DATA:
744		{
745			// User possibly dropped files on this window.
746			// If there is more than one entry_ref, let the app handle it
747			// (open one new page per ref). If there is one ref, open it in
748			// this window.
749			type_code type;
750			int32 countFound;
751			if (message->GetInfo("refs", &type, &countFound) != B_OK
752				|| type != B_REF_TYPE) {
753				break;
754			}
755			if (countFound > 1) {
756				message->what = B_REFS_RECEIVED;
757				be_app->PostMessage(message);
758				break;
759			}
760			entry_ref ref;
761			if (message->FindRef("refs", &ref) != B_OK)
762				break;
763			BEntry entry(&ref, true);
764			BPath path;
765			if (!entry.Exists() || entry.GetPath(&path) != B_OK)
766				break;
767			CurrentWebView()->LoadURL(path.Path());
768			break;
769		}
770
771		case ZOOM_FACTOR_INCREASE:
772			CurrentWebView()->IncreaseZoomFactor(fZoomTextOnly);
773			break;
774		case ZOOM_FACTOR_DECREASE:
775			CurrentWebView()->DecreaseZoomFactor(fZoomTextOnly);
776			break;
777		case ZOOM_FACTOR_RESET:
778			CurrentWebView()->ResetZoomFactor();
779			break;
780		case ZOOM_TEXT_ONLY:
781			fZoomTextOnly = !fZoomTextOnly;
782			fZoomTextOnlyMenuItem->SetMarked(fZoomTextOnly);
783			// TODO: Would be nice to have an instant update if the page is
784			// already zoomed.
785			break;
786
787		case TOGGLE_FULLSCREEN:
788			ToggleFullscreen();
789			break;
790
791		case TOGGLE_AUTO_HIDE_INTERFACE_IN_FULLSCREEN:
792			_SetAutoHideInterfaceInFullscreen(
793				!fAutoHideInterfaceInFullscreenMode);
794			break;
795
796		case CHECK_AUTO_HIDE_INTERFACE:
797			_CheckAutoHideInterface();
798			break;
799
800		case SHOW_PAGE_SOURCE:
801			CurrentWebView()->WebPage()->SendPageSource();
802			break;
803		case B_PAGE_SOURCE_RESULT:
804			_HandlePageSourceResult(message);
805			break;
806
807		case EDIT_FIND_NEXT:
808			CurrentWebView()->FindString(fFindTextControl->Text(), true,
809				fFindCaseSensitiveCheckBox->Value());
810			break;
811		case FIND_TEXT_CHANGED:
812		{
813			bool findTextAvailable = strlen(fFindTextControl->Text()) > 0;
814			fFindPreviousMenuItem->SetEnabled(findTextAvailable);
815			fFindNextMenuItem->SetEnabled(findTextAvailable);
816			break;
817		}
818		case EDIT_FIND_PREVIOUS:
819			CurrentWebView()->FindString(fFindTextControl->Text(), false,
820				fFindCaseSensitiveCheckBox->Value());
821			break;
822		case EDIT_SHOW_FIND_GROUP:
823			if (!fFindGroup->IsVisible())
824				fFindGroup->SetVisible(true);
825			fFindTextControl->MakeFocus(true);
826			break;
827		case EDIT_HIDE_FIND_GROUP:
828			if (fFindGroup->IsVisible()) {
829				fFindGroup->SetVisible(false);
830				if (CurrentWebView() != NULL)
831					CurrentWebView()->MakeFocus(true);
832			}
833			break;
834
835		case B_CUT:
836		case B_COPY:
837		case B_PASTE:
838		{
839			BTextView* textView = dynamic_cast<BTextView*>(CurrentFocus());
840			if (textView != NULL)
841				textView->MessageReceived(message);
842			else if (CurrentWebView() != NULL)
843				CurrentWebView()->MessageReceived(message);
844			break;
845		}
846
847		case B_EDITING_CAPABILITIES_RESULT:
848		{
849			BWebView* webView;
850			if (message->FindPointer("view",
851					reinterpret_cast<void**>(&webView)) != B_OK
852				|| webView != CurrentWebView()) {
853				break;
854			}
855			bool canCut;
856			bool canCopy;
857			bool canPaste;
858			if (message->FindBool("can cut", &canCut) != B_OK)
859				canCut = false;
860			if (message->FindBool("can copy", &canCopy) != B_OK)
861				canCopy = false;
862			if (message->FindBool("can paste", &canPaste) != B_OK)
863				canPaste = false;
864			fCutMenuItem->SetEnabled(canCut);
865			fCopyMenuItem->SetEnabled(canCopy);
866			fPasteMenuItem->SetEnabled(canPaste);
867			break;
868		}
869
870		case SHOW_DOWNLOAD_WINDOW:
871		case SHOW_SETTINGS_WINDOW:
872			message->AddUInt32("workspaces", Workspaces());
873			be_app->PostMessage(message);
874			break;
875
876		case CLOSE_TAB:
877			if (fTabManager->CountTabs() > 1) {
878				int32 index;
879				if (message->FindInt32("tab index", &index) != B_OK)
880					index = fTabManager->SelectedTabIndex();
881				_ShutdownTab(index);
882				_UpdateTabGroupVisibility();
883			} else
884				PostMessage(B_QUIT_REQUESTED);
885			break;
886
887		case SELECT_TAB:
888		{
889			int32 index;
890			if (message->FindInt32("tab index", &index) == B_OK
891				&& fTabManager->SelectedTabIndex() != index
892				&& fTabManager->CountTabs() > index) {
893				fTabManager->SelectTab(index);
894			}
895
896			break;
897		}
898
899		case TAB_CHANGED:
900		{
901			// This message may be received also when the last tab closed,
902			// i.e. with index == -1.
903			int32 index;
904			if (message->FindInt32("tab index", &index) != B_OK)
905				index = -1;
906			_TabChanged(index);
907			break;
908		}
909
910		case SETTINGS_VALUE_CHANGED:
911		{
912			BString name;
913			if (message->FindString("name", &name) != B_OK)
914				break;
915			bool flag;
916			BString string;
917			uint32 value;
918			if (name == kSettingsKeyShowTabsIfSinglePageOpen
919				&& message->FindBool("value", &flag) == B_OK) {
920				if (fShowTabsIfSinglePageOpen != flag) {
921					fShowTabsIfSinglePageOpen = flag;
922					_UpdateTabGroupVisibility();
923				}
924			} else if (name == kSettingsKeyAutoHidePointer
925				&& message->FindBool("value", &flag) == B_OK) {
926				fAutoHidePointer = flag;
927				if (CurrentWebView())
928					CurrentWebView()->SetAutoHidePointer(fAutoHidePointer);
929			} else if (name == kSettingsKeyStartPageURL
930				&& message->FindString("value", &string) == B_OK) {
931				fStartPageURL = string;
932			} else if (name == kSettingsKeySearchPageURL
933				&& message->FindString("value", &string) == B_OK) {
934				fSearchPageURL = string;
935			} else if (name == kSettingsKeyNewWindowPolicy
936				&& message->FindUInt32("value", &value) == B_OK) {
937				fNewWindowPolicy = value;
938			} else if (name == kSettingsKeyNewTabPolicy
939				&& message->FindUInt32("value", &value) == B_OK) {
940				fNewTabPolicy = value;
941			} else if (name == kSettingsKeyAutoHideInterfaceInFullscreenMode
942				&& message->FindBool("value", &flag) == B_OK) {
943				_SetAutoHideInterfaceInFullscreen(flag);
944			} else if (name == kSettingsKeyShowHomeButton
945				&& message->FindBool("value", &flag) == B_OK) {
946				if (flag)
947					fHomeButton->Show();
948				else
949					fHomeButton->Hide();
950			}
951			break;
952		}
953
954		default:
955			BWebWindow::MessageReceived(message);
956			break;
957	}
958}
959
960
961bool
962BrowserWindow::QuitRequested()
963{
964	// TODO: Check for modified form data and ask user for confirmation, etc.
965
966	// Iterate over all tabs to delete all BWebViews.
967	// Do this here, so WebKit tear down happens earlier.
968	SetCurrentWebView(NULL);
969	while (fTabManager->CountTabs() > 0)
970		_ShutdownTab(0);
971
972	BMessage message(WINDOW_CLOSED);
973	message.AddRect("window frame", WindowFrame());
974	be_app->PostMessage(&message);
975	return true;
976}
977
978
979void
980BrowserWindow::MenusBeginning()
981{
982	_UpdateHistoryMenu();
983	_UpdateClipboardItems();
984	_ShowInterface(true);
985}
986
987
988void
989BrowserWindow::ScreenChanged(BRect screenSize, color_space format)
990{
991	if (fIsFullscreen)
992		_ResizeToScreen();
993}
994
995
996void
997BrowserWindow::WorkspacesChanged(uint32 oldWorkspaces, uint32 newWorkspaces)
998{
999	if (fIsFullscreen)
1000		_ResizeToScreen();
1001}
1002
1003
1004static bool
1005viewIsChild(const BView* parent, const BView* view)
1006{
1007	if (parent == view)
1008		return true;
1009
1010	int32 count = parent->CountChildren();
1011	for (int32 i = 0; i < count; i++) {
1012		BView* child = parent->ChildAt(i);
1013		if (viewIsChild(child, view))
1014			return true;
1015	}
1016	return false;
1017}
1018
1019
1020void
1021BrowserWindow::SetCurrentWebView(BWebView* webView)
1022{
1023	if (webView == CurrentWebView())
1024		return;
1025
1026	if (CurrentWebView() != NULL) {
1027		// Remember the currently focused view before switching tabs,
1028		// so that we can revert the focus when switching back to this tab
1029		// later.
1030		PageUserData* userData = static_cast<PageUserData*>(
1031			CurrentWebView()->GetUserData());
1032		if (userData == NULL) {
1033			userData = new PageUserData(CurrentFocus());
1034			CurrentWebView()->SetUserData(userData);
1035		}
1036		userData->SetFocusedView(CurrentFocus());
1037		userData->SetURLInputContents(fURLInputGroup->Text());
1038		int32 selectionStart;
1039		int32 selectionEnd;
1040		fURLInputGroup->TextView()->GetSelection(&selectionStart,
1041			&selectionEnd);
1042		userData->SetURLInputSelection(selectionStart, selectionEnd);
1043	}
1044
1045	BWebWindow::SetCurrentWebView(webView);
1046
1047	if (webView != NULL) {
1048		webView->SetAutoHidePointer(fAutoHidePointer);
1049
1050		_UpdateTitle(webView->MainFrameTitle());
1051
1052		// Restore the previous focus or focus the web view.
1053		PageUserData* userData = static_cast<PageUserData*>(
1054			webView->GetUserData());
1055		BView* focusedView = NULL;
1056		if (userData != NULL)
1057			focusedView = userData->FocusedView();
1058
1059		if (focusedView != NULL
1060			&& viewIsChild(GetLayout()->View(), focusedView)) {
1061			focusedView->MakeFocus(true);
1062		} else
1063			webView->MakeFocus(true);
1064
1065		if (userData != NULL) {
1066			fURLInputGroup->SetPageIcon(userData->PageIcon());
1067			if (userData->URLInputContents().Length())
1068				fURLInputGroup->SetText(userData->URLInputContents());
1069			else
1070				fURLInputGroup->SetText(webView->MainFrameURL());
1071			if (userData->URLInputSelectionStart() >= 0) {
1072				fURLInputGroup->TextView()->Select(
1073					userData->URLInputSelectionStart(),
1074					userData->URLInputSelectionEnd());
1075			}
1076		} else {
1077			fURLInputGroup->SetPageIcon(NULL);
1078			fURLInputGroup->SetText(webView->MainFrameURL());
1079		}
1080
1081		// Trigger update of the interface to the new page, by requesting
1082		// to resend all notifications.
1083		webView->WebPage()->ResendNotifications();
1084	} else
1085		_UpdateTitle("");
1086}
1087
1088
1089bool
1090BrowserWindow::IsBlankTab() const
1091{
1092	if (CurrentWebView() == NULL)
1093		return false;
1094	BString requestedURL = CurrentWebView()->MainFrameRequestedURL();
1095	return requestedURL.Length() == 0
1096		|| requestedURL == _NewTabURL(fTabManager->CountTabs() == 1);
1097}
1098
1099
1100void
1101BrowserWindow::CreateNewTab(const BString& _url, bool select, BWebView* webView)
1102{
1103	bool applyNewPagePolicy = webView == NULL;
1104	// Executed in app thread (new BWebPage needs to be created in app thread).
1105	if (webView == NULL)
1106		webView = new BWebView("web view");
1107
1108	bool isNewWindow = fTabManager->CountTabs() == 0;
1109
1110	fTabManager->AddTab(webView, B_TRANSLATE("New tab"));
1111
1112	BString url(_url);
1113	if (applyNewPagePolicy && url.Length() == 0)
1114		url = _NewTabURL(isNewWindow);
1115
1116	if (url.Length() > 0)
1117		webView->LoadURL(url.String());
1118
1119	if (select) {
1120		fTabManager->SelectTab(fTabManager->CountTabs() - 1);
1121		SetCurrentWebView(webView);
1122		webView->WebPage()->ResendNotifications();
1123		fURLInputGroup->SetPageIcon(NULL);
1124		fURLInputGroup->SetText(url.String());
1125		fURLInputGroup->MakeFocus(true);
1126	}
1127
1128	_ShowInterface(true);
1129	_UpdateTabGroupVisibility();
1130}
1131
1132
1133BRect
1134BrowserWindow::WindowFrame() const
1135{
1136	if (fIsFullscreen)
1137		return fNonFullscreenWindowFrame;
1138	else
1139		return Frame();
1140}
1141
1142
1143void
1144BrowserWindow::ToggleFullscreen()
1145{
1146	if (fIsFullscreen) {
1147		MoveTo(fNonFullscreenWindowFrame.LeftTop());
1148		ResizeTo(fNonFullscreenWindowFrame.Width(),
1149			fNonFullscreenWindowFrame.Height());
1150
1151		SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_MOVABLE));
1152		SetLook(B_DOCUMENT_WINDOW_LOOK);
1153
1154		_ShowInterface(true);
1155	} else {
1156		fNonFullscreenWindowFrame = Frame();
1157		_ResizeToScreen();
1158
1159		SetFlags(Flags() | (B_NOT_RESIZABLE | B_NOT_MOVABLE));
1160		SetLook(B_TITLED_WINDOW_LOOK);
1161	}
1162	fIsFullscreen = !fIsFullscreen;
1163	fFullscreenItem->SetMarked(fIsFullscreen);
1164	fToggleFullscreenButton->SetVisible(fIsFullscreen);
1165}
1166
1167
1168// #pragma mark - Notification API
1169
1170
1171void
1172BrowserWindow::NavigationRequested(const BString& url, BWebView* view)
1173{
1174}
1175
1176
1177void
1178BrowserWindow::NewWindowRequested(const BString& url, bool primaryAction)
1179{
1180	// Always open new windows in the application thread, since
1181	// creating a BWebView will try to grab the application lock.
1182	// But our own WebPage may already try to lock us from within
1183	// the application thread -> dead-lock. Thus we can't wait for
1184	// a reply here.
1185	BMessage message(NEW_TAB);
1186	message.AddPointer("window", this);
1187	message.AddString("url", url);
1188	message.AddBool("select", primaryAction);
1189	be_app->PostMessage(&message);
1190}
1191
1192
1193void
1194BrowserWindow::NewPageCreated(BWebView* view, BRect windowFrame,
1195	bool modalDialog, bool resizable, bool activate)
1196{
1197	if (windowFrame.IsValid()) {
1198		BrowserWindow* window = new BrowserWindow(windowFrame, fAppSettings,
1199			BString(), INTERFACE_ELEMENT_STATUS, view);
1200		window->Show();
1201	} else
1202		CreateNewTab(BString(), activate, view);
1203}
1204
1205
1206void
1207BrowserWindow::CloseWindowRequested(BWebView* view)
1208{
1209	int32 index = fTabManager->TabForView(view);
1210	if (index < 0) {
1211		// Tab is already gone.
1212		return;
1213	}
1214	BMessage message(CLOSE_TAB);
1215	message.AddInt32("tab index", index);
1216	PostMessage(&message, this);
1217}
1218
1219
1220void
1221BrowserWindow::LoadNegotiating(const BString& url, BWebView* view)
1222{
1223	if (view != CurrentWebView())
1224		return;
1225
1226	fURLInputGroup->SetText(url.String());
1227
1228	BString status(B_TRANSLATE("Requesting %url"));
1229	status.ReplaceFirst("%url", url);
1230	view->WebPage()->SetStatusMessage(status);
1231}
1232
1233
1234void
1235BrowserWindow::LoadCommitted(const BString& url, BWebView* view)
1236{
1237	if (view != CurrentWebView())
1238		return;
1239
1240	// This hook is invoked when the load is commited.
1241	fURLInputGroup->SetText(url.String());
1242
1243	BString status(B_TRANSLATE("Loading %url"));
1244	status.ReplaceFirst("%url", url);
1245	view->WebPage()->SetStatusMessage(status);
1246}
1247
1248
1249void
1250BrowserWindow::LoadProgress(float progress, BWebView* view)
1251{
1252	if (view != CurrentWebView())
1253		return;
1254
1255	if (progress < 100 && fLoadingProgressBar->IsHidden())
1256		_ShowProgressBar(true);
1257	else if (progress == 100 && !fLoadingProgressBar->IsHidden())
1258		_ShowProgressBar(false);
1259	fLoadingProgressBar->SetTo(progress);
1260}
1261
1262
1263void
1264BrowserWindow::LoadFailed(const BString& url, BWebView* view)
1265{
1266	if (view != CurrentWebView())
1267		return;
1268
1269	BString status(B_TRANSLATE_COMMENT("%url failed", "Loading URL failed. "
1270		"Don't translate variable %url."));
1271	status.ReplaceFirst("%url", url);
1272	view->WebPage()->SetStatusMessage(status);
1273	if (!fLoadingProgressBar->IsHidden())
1274		fLoadingProgressBar->Hide();
1275}
1276
1277
1278void
1279BrowserWindow::LoadFinished(const BString& url, BWebView* view)
1280{
1281	if (view != CurrentWebView())
1282		return;
1283
1284	BString status(B_TRANSLATE_COMMENT("%url finished", "Loading URL "
1285		"finished. Don't translate variable %url."));
1286	status.ReplaceFirst("%url", url);
1287	view->WebPage()->SetStatusMessage(status);
1288	if (!fLoadingProgressBar->IsHidden())
1289		fLoadingProgressBar->Hide();
1290
1291	NavigationCapabilitiesChanged(fBackButton->IsEnabled(),
1292		fForwardButton->IsEnabled(), false, view);
1293
1294	int32 tabIndex = fTabManager->TabForView(view);
1295	if (tabIndex > 0 && strcmp(B_TRANSLATE("New tab"),
1296		fTabManager->TabLabel(tabIndex)) == 0)
1297			fTabManager->SetTabLabel(tabIndex, url);
1298}
1299
1300
1301void
1302BrowserWindow::MainDocumentError(const BString& failingURL,
1303	const BString& localizedDescription, BWebView* view)
1304{
1305	// Make sure we show the page that contains the view.
1306	if (!_ShowPage(view))
1307		return;
1308
1309	BWebWindow::MainDocumentError(failingURL, localizedDescription, view);
1310
1311	// TODO: Remove the failing URL from the BrowsingHistory!
1312}
1313
1314
1315void
1316BrowserWindow::TitleChanged(const BString& title, BWebView* view)
1317{
1318	int32 tabIndex = fTabManager->TabForView(view);
1319	if (tabIndex < 0)
1320		return;
1321
1322	fTabManager->SetTabLabel(tabIndex, title);
1323
1324	if (view != CurrentWebView())
1325		return;
1326
1327	_UpdateTitle(title);
1328}
1329
1330
1331void
1332BrowserWindow::IconReceived(const BBitmap* icon, BWebView* view)
1333{
1334	// The view may already be gone, since this notification arrives
1335	// asynchronously.
1336	if (!fTabManager->HasView(view))
1337		return;
1338
1339	_SetPageIcon(view, icon);
1340}
1341
1342
1343void
1344BrowserWindow::ResizeRequested(float width, float height, BWebView* view)
1345{
1346	if (view != CurrentWebView())
1347		return;
1348
1349	// Ignore request when there is more than one BWebView embedded.
1350	if (fTabManager->CountTabs() > 1)
1351		return;
1352
1353	// Make sure the new frame is not larger than the screen frame minus
1354	// window decorator border.
1355	BScreen screen(this);
1356	BRect screenFrame = screen.Frame();
1357	BRect decoratorFrame = DecoratorFrame();
1358	BRect frame = Frame();
1359
1360	screenFrame.left += decoratorFrame.left - frame.left;
1361	screenFrame.right += decoratorFrame.right - frame.right;
1362	screenFrame.top += decoratorFrame.top - frame.top;
1363	screenFrame.bottom += decoratorFrame.bottom - frame.bottom;
1364
1365	width = min_c(width, screen.Frame().Width());
1366	height = min_c(height, screen.Frame().Height());
1367
1368	frame.right = frame.left + width;
1369	frame.bottom = frame.top + height;
1370
1371	// frame is now not larger than screenFrame, but may still be partly outside
1372	if (!screenFrame.Contains(frame)) {
1373		if (frame.left < screenFrame.left)
1374			frame.OffsetBy(screenFrame.left - frame.left, 0);
1375		else if (frame.right > screenFrame.right)
1376			frame.OffsetBy(screenFrame.right - frame.right, 0);
1377		if (frame.top < screenFrame.top)
1378			frame.OffsetBy(screenFrame.top - frame.top, 0);
1379		else if (frame.bottom > screenFrame.bottom)
1380			frame.OffsetBy(screenFrame.bottom - frame.bottom, 0);
1381	}
1382
1383	MoveTo(frame.left, frame.top);
1384	ResizeTo(width, height);
1385}
1386
1387
1388void
1389BrowserWindow::SetToolBarsVisible(bool flag, BWebView* view)
1390{
1391	// TODO
1392	// TODO: Ignore request when there is more than one BWebView embedded!
1393}
1394
1395
1396void
1397BrowserWindow::SetStatusBarVisible(bool flag, BWebView* view)
1398{
1399	// TODO
1400	// TODO: Ignore request when there is more than one BWebView embedded!
1401}
1402
1403
1404void
1405BrowserWindow::SetMenuBarVisible(bool flag, BWebView* view)
1406{
1407	// TODO
1408	// TODO: Ignore request when there is more than one BWebView embedded!
1409}
1410
1411
1412void
1413BrowserWindow::SetResizable(bool flag, BWebView* view)
1414{
1415	// TODO: Ignore request when there is more than one BWebView embedded!
1416
1417	if (flag)
1418		SetFlags(Flags() & ~B_NOT_RESIZABLE);
1419	else
1420		SetFlags(Flags() | B_NOT_RESIZABLE);
1421}
1422
1423
1424void
1425BrowserWindow::StatusChanged(const BString& statusText, BWebView* view)
1426{
1427	if (view != CurrentWebView())
1428		return;
1429
1430	if (fStatusText)
1431		fStatusText->SetText(statusText.String());
1432}
1433
1434
1435void
1436BrowserWindow::NavigationCapabilitiesChanged(bool canGoBackward,
1437	bool canGoForward, bool canStop, BWebView* view)
1438{
1439	if (view != CurrentWebView())
1440		return;
1441
1442	fBackButton->SetEnabled(canGoBackward);
1443	fForwardButton->SetEnabled(canGoForward);
1444	fStopButton->SetEnabled(canStop);
1445
1446	fBackMenuItem->SetEnabled(canGoBackward);
1447	fForwardMenuItem->SetEnabled(canGoForward);
1448}
1449
1450
1451void
1452BrowserWindow::UpdateGlobalHistory(const BString& url)
1453{
1454	BrowsingHistory::DefaultInstance()->AddItem(BrowsingHistoryItem(url));
1455}
1456
1457
1458bool
1459BrowserWindow::AuthenticationChallenge(BString message, BString& inOutUser,
1460	BString& inOutPassword, bool& inOutRememberCredentials,
1461	uint32 failureCount, BWebView* view)
1462{
1463	CredentialsStorage* persistentStorage
1464		= CredentialsStorage::PersistentInstance();
1465	CredentialsStorage* sessionStorage
1466		= CredentialsStorage::SessionInstance();
1467
1468	// TODO: Using the message as key here is not so smart.
1469	HashKeyString key(message);
1470
1471	if (failureCount == 0) {
1472		if (persistentStorage->Contains(key)) {
1473			Credentials credentials = persistentStorage->GetCredentials(key);
1474			inOutUser = credentials.Username();
1475			inOutPassword = credentials.Password();
1476			return true;
1477		} else if (sessionStorage->Contains(key)) {
1478			Credentials credentials = sessionStorage->GetCredentials(key);
1479			inOutUser = credentials.Username();
1480			inOutPassword = credentials.Password();
1481			return true;
1482		}
1483	}
1484	// Switch to the page for which this authentication is required.
1485	if (!_ShowPage(view))
1486		return false;
1487
1488	AuthenticationPanel* panel = new AuthenticationPanel(Frame());
1489		// Panel auto-destructs.
1490	bool success = panel->getAuthentication(message, inOutUser, inOutPassword,
1491		inOutRememberCredentials, failureCount > 0, inOutUser, inOutPassword,
1492		&inOutRememberCredentials);
1493	if (success) {
1494		Credentials credentials(inOutUser, inOutPassword);
1495		if (inOutRememberCredentials)
1496			persistentStorage->PutCredentials(key, credentials);
1497		else
1498			sessionStorage->PutCredentials(key, credentials);
1499	}
1500	return success;
1501}
1502
1503
1504// #pragma mark - private
1505
1506
1507void
1508BrowserWindow::_UpdateTitle(const BString& title)
1509{
1510	BString windowTitle = title;
1511	if (windowTitle.Length() > 0)
1512		windowTitle << " - ";
1513	windowTitle << kApplicationName;
1514	SetTitle(windowTitle.String());
1515}
1516
1517
1518void
1519BrowserWindow::_UpdateTabGroupVisibility()
1520{
1521	if (Lock()) {
1522		if (fInterfaceVisible)
1523			fTabGroup->SetVisible(_TabGroupShouldBeVisible());
1524		fTabManager->SetCloseButtonsAvailable(fTabManager->CountTabs() > 1);
1525		Unlock();
1526	}
1527}
1528
1529
1530bool
1531BrowserWindow::_TabGroupShouldBeVisible() const
1532{
1533	return (fShowTabsIfSinglePageOpen || fTabManager->CountTabs() > 1)
1534		&& (fVisibleInterfaceElements & INTERFACE_ELEMENT_TABS) != 0;
1535}
1536
1537
1538void
1539BrowserWindow::_ShutdownTab(int32 index)
1540{
1541	BView* view = fTabManager->RemoveTab(index);
1542	BWebView* webView = dynamic_cast<BWebView*>(view);
1543	if (webView == CurrentWebView())
1544		SetCurrentWebView(NULL);
1545	if (webView != NULL)
1546		webView->Shutdown();
1547	else
1548		delete view;
1549}
1550
1551
1552void
1553BrowserWindow::_TabChanged(int32 index)
1554{
1555	SetCurrentWebView(dynamic_cast<BWebView*>(fTabManager->ViewForTab(index)));
1556}
1557
1558
1559status_t
1560BrowserWindow::_BookmarkPath(BPath& path) const
1561{
1562	status_t ret = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
1563	if (ret != B_OK)
1564		return ret;
1565
1566	ret = path.Append(kApplicationName);
1567	if (ret != B_OK)
1568		return ret;
1569
1570	ret = path.Append("Bookmarks");
1571	if (ret != B_OK)
1572		return ret;
1573
1574	return create_directory(path.Path(), 0777);
1575}
1576
1577
1578void
1579BrowserWindow::_CreateBookmark()
1580{
1581	BPath path;
1582	status_t status = _BookmarkPath(path);
1583	if (status != B_OK) {
1584		BString message(B_TRANSLATE_COMMENT("There was an error retrieving "
1585			"the bookmark folder.\n\nError: %error", "Don't translate the "
1586			"variable %error"));
1587		message.ReplaceFirst("%error", strerror(status));
1588		BAlert* alert = new BAlert(B_TRANSLATE("Bookmark error"),
1589			message.String(), B_TRANSLATE("OK"), NULL, NULL,
1590			B_WIDTH_AS_USUAL, B_STOP_ALERT);
1591		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1592		alert->Go();
1593		return;
1594	}
1595	BWebView* webView = CurrentWebView();
1596	BString url(webView->MainFrameURL());
1597	// Create a bookmark file
1598	BFile bookmarkFile;
1599	BString bookmarkName(webView->MainFrameTitle());
1600	if (bookmarkName.Length() == 0) {
1601		bookmarkName = url;
1602		int32 leafPos = bookmarkName.FindLast('/');
1603		if (leafPos >= 0)
1604			bookmarkName.Remove(0, leafPos + 1);
1605	}
1606	// Make sure the bookmark title does not contain chars that are not
1607	// allowed in file names.
1608	bookmarkName.ReplaceAll('/', '-');
1609
1610	// Check that the bookmark exists nowhere in the bookmark hierarchy,
1611	// though the intended file name must match, we don't search the stored
1612	// URLs, only for matching file names.
1613	BDirectory directory(path.Path());
1614	if (status == B_OK && _CheckBookmarkExists(directory, bookmarkName, url)) {
1615		BString message(B_TRANSLATE_COMMENT("A bookmark for this page "
1616			"(%bookmarkName) already exists.", "Don't translate variable "
1617			"%bookmarkName"));
1618		message.ReplaceFirst("%bookmarkName", bookmarkName);
1619		BAlert* alert = new BAlert(B_TRANSLATE("Bookmark info"),
1620			message.String(), B_TRANSLATE("OK"));
1621		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1622		alert->Go();
1623		return;
1624	}
1625
1626	BPath entryPath(path);
1627	status = entryPath.Append(bookmarkName);
1628	BEntry entry;
1629	if (status == B_OK)
1630		status = entry.SetTo(entryPath.Path(), true);
1631	if (status == B_OK) {
1632		int32 tries = 1;
1633		while (entry.Exists()) {
1634			// Find a unique name for the bookmark, there may still be a
1635			// file in the way that stores a different URL.
1636			bookmarkName = webView->MainFrameTitle();
1637			bookmarkName << " " << tries++;
1638			entryPath = path;
1639			status = entryPath.Append(bookmarkName);
1640			if (status == B_OK)
1641				status = entry.SetTo(entryPath.Path(), true);
1642			if (status != B_OK)
1643				break;
1644		}
1645	}
1646	if (status == B_OK) {
1647		status = bookmarkFile.SetTo(&entry,
1648			B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
1649	}
1650
1651	// Write bookmark meta data
1652	if (status == B_OK)
1653		status = bookmarkFile.WriteAttrString("META:url", &url);
1654	if (status == B_OK) {
1655		BString title = webView->MainFrameTitle();
1656		bookmarkFile.WriteAttrString("META:title", &title);
1657	}
1658
1659	BNodeInfo nodeInfo(&bookmarkFile);
1660	if (status == B_OK) {
1661		status = nodeInfo.SetType("application/x-vnd.Be-bookmark");
1662		if (status == B_OK) {
1663			PageUserData* userData = static_cast<PageUserData*>(
1664				webView->GetUserData());
1665			if (userData != NULL && userData->PageIcon() != NULL) {
1666				BBitmap miniIcon(BRect(0, 0, 15, 15), B_BITMAP_NO_SERVER_LINK,
1667					B_CMAP8);
1668				status_t ret = miniIcon.ImportBits(userData->PageIcon());
1669				if (ret == B_OK)
1670					ret = nodeInfo.SetIcon(&miniIcon, B_MINI_ICON);
1671				if (ret != B_OK) {
1672					fprintf(stderr, "Failed to store mini icon for bookmark: "
1673						"%s\n", strerror(ret));
1674				}
1675				BBitmap largeIcon(BRect(0, 0, 31, 31), B_BITMAP_NO_SERVER_LINK,
1676					B_CMAP8);
1677				// TODO: Store 32x32 favicon which is often provided by sites.
1678				const uint8* src = (const uint8*)miniIcon.Bits();
1679				uint32 srcBPR = miniIcon.BytesPerRow();
1680				uint8* dst = (uint8*)largeIcon.Bits();
1681				uint32 dstBPR = largeIcon.BytesPerRow();
1682				for (uint32 y = 0; y < 16; y++) {
1683					const uint8* s = src;
1684					uint8* d = dst;
1685					for (uint32 x = 0; x < 16; x++) {
1686						*d++ = *s;
1687						*d++ = *s++;
1688					}
1689					dst += dstBPR;
1690					s = src;
1691					for (uint32 x = 0; x < 16; x++) {
1692						*d++ = *s;
1693						*d++ = *s++;
1694					}
1695					dst += dstBPR;
1696					src += srcBPR;
1697				}
1698				if (ret == B_OK)
1699					ret = nodeInfo.SetIcon(&largeIcon, B_LARGE_ICON);
1700				if (ret != B_OK) {
1701					fprintf(stderr, "Failed to store large icon for bookmark: "
1702						"%s\n", strerror(ret));
1703				}
1704			}
1705		}
1706	}
1707
1708	if (status != B_OK) {
1709		BString message(B_TRANSLATE_COMMENT("There was an error creating the "
1710			"bookmark file.\n\nError: %error", "Don't translate variable "
1711			"%error"));
1712		message.ReplaceFirst("%error", strerror(status));
1713		BAlert* alert = new BAlert(B_TRANSLATE("Bookmark error"),
1714			message.String(), B_TRANSLATE("OK"), NULL, NULL,
1715			B_WIDTH_AS_USUAL, B_STOP_ALERT);
1716		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1717		alert->Go();
1718		return;
1719	}
1720}
1721
1722
1723void
1724BrowserWindow::_ShowBookmarks()
1725{
1726	BPath path;
1727	entry_ref ref;
1728	status_t status = _BookmarkPath(path);
1729	if (status == B_OK)
1730		status = get_ref_for_path(path.Path(), &ref);
1731	if (status == B_OK)
1732		status = be_roster->Launch(&ref);
1733
1734	if (status != B_OK && status != B_ALREADY_RUNNING) {
1735		BString message(B_TRANSLATE_COMMENT("There was an error trying to "
1736			"show the Bookmarks folder.\n\nError: %error", "Don't translate variable "
1737			"%error"));
1738		message.ReplaceFirst("%error", strerror(status));
1739		BAlert* alert = new BAlert(B_TRANSLATE("Bookmark error"),
1740			message.String(), B_TRANSLATE("OK"), NULL, NULL,
1741			B_WIDTH_AS_USUAL, B_STOP_ALERT);
1742		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1743		alert->Go();
1744		return;
1745	}
1746}
1747
1748
1749bool BrowserWindow::_CheckBookmarkExists(BDirectory& directory,
1750	const BString& bookmarkName, const BString& url) const
1751{
1752	BEntry entry;
1753	while (directory.GetNextEntry(&entry) == B_OK) {
1754		if (entry.IsDirectory()) {
1755			BDirectory subBirectory(&entry);
1756			// At least preserve the entry file handle when recursing into
1757			// sub-folders... eventually we will run out, though, with very
1758			// deep hierarchy.
1759			entry.Unset();
1760			if (_CheckBookmarkExists(subBirectory, bookmarkName, url))
1761				return true;
1762		} else {
1763			char entryName[B_FILE_NAME_LENGTH];
1764			if (entry.GetName(entryName) != B_OK || bookmarkName != entryName)
1765				continue;
1766			BString storedURL;
1767			BFile file(&entry, B_READ_ONLY);
1768			if (_ReadURLAttr(file, storedURL)) {
1769				// Just bail if the bookmark already exists
1770				if (storedURL == url)
1771					return true;
1772			}
1773		}
1774	}
1775	return false;
1776}
1777
1778
1779bool
1780BrowserWindow::_ReadURLAttr(BFile& bookmarkFile, BString& url) const
1781{
1782	return bookmarkFile.InitCheck() == B_OK
1783		&& bookmarkFile.ReadAttrString("META:url", &url) == B_OK;
1784}
1785
1786
1787void
1788BrowserWindow::_AddBookmarkURLsRecursively(BDirectory& directory,
1789	BMessage* message, uint32& addedCount) const
1790{
1791	BEntry entry;
1792	while (directory.GetNextEntry(&entry) == B_OK) {
1793		if (entry.IsDirectory()) {
1794			BDirectory subBirectory(&entry);
1795			// At least preserve the entry file handle when recursing into
1796			// sub-folders... eventually we will run out, though, with very
1797			// deep hierarchy.
1798			entry.Unset();
1799			_AddBookmarkURLsRecursively(subBirectory, message, addedCount);
1800		} else {
1801			BString storedURL;
1802			BFile file(&entry, B_READ_ONLY);
1803			if (_ReadURLAttr(file, storedURL)) {
1804				message->AddString("url", storedURL.String());
1805				addedCount++;
1806			}
1807		}
1808	}
1809}
1810
1811
1812void
1813BrowserWindow::_SetPageIcon(BWebView* view, const BBitmap* icon)
1814{
1815	PageUserData* userData = static_cast<PageUserData*>(view->GetUserData());
1816	if (userData == NULL) {
1817		userData = new(std::nothrow) PageUserData(NULL);
1818		if (userData == NULL)
1819			return;
1820		view->SetUserData(userData);
1821	}
1822	// The PageUserData makes a copy of the icon, which we pass on to
1823	// the TabManager for display in the respective tab.
1824	userData->SetPageIcon(icon);
1825	fTabManager->SetTabIcon(view, userData->PageIcon());
1826	if (view == CurrentWebView())
1827		fURLInputGroup->SetPageIcon(icon);
1828}
1829
1830
1831static void
1832addItemToMenuOrSubmenu(BMenu* menu, BMenuItem* newItem)
1833{
1834	BString baseURLLabel = baseURL(BString(newItem->Label()));
1835	for (int32 i = menu->CountItems() - 1; i >= 0; i--) {
1836		BMenuItem* item = menu->ItemAt(i);
1837		BString label = item->Label();
1838		if (label.FindFirst(baseURLLabel) >= 0) {
1839			if (item->Submenu()) {
1840				// Submenu was already added in previous iteration.
1841				item->Submenu()->AddItem(newItem);
1842				return;
1843			} else {
1844				menu->RemoveItem(item);
1845				BMenu* subMenu = new BMenu(baseURLLabel.String());
1846				subMenu->AddItem(item);
1847				subMenu->AddItem(newItem);
1848				// Add common submenu for this base URL, clickable.
1849				BMessage* message = new BMessage(GOTO_URL);
1850				message->AddString("url", baseURLLabel.String());
1851				menu->AddItem(new BMenuItem(subMenu, message), i);
1852				return;
1853			}
1854		}
1855	}
1856	menu->AddItem(newItem);
1857}
1858
1859
1860static void
1861addOrDeleteMenu(BMenu* menu, BMenu* toMenu)
1862{
1863	if (menu->CountItems() > 0)
1864		toMenu->AddItem(menu);
1865	else
1866		delete menu;
1867}
1868
1869
1870void
1871BrowserWindow::_UpdateHistoryMenu()
1872{
1873	BMenuItem* menuItem;
1874	while ((menuItem = fHistoryMenu->RemoveItem(fHistoryMenuFixedItemCount)))
1875		delete menuItem;
1876
1877	BrowsingHistory* history = BrowsingHistory::DefaultInstance();
1878	if (!history->Lock())
1879		return;
1880
1881	int32 count = history->CountItems();
1882	BMenuItem* clearHistoryItem = new BMenuItem(B_TRANSLATE("Clear history"),
1883		new BMessage(CLEAR_HISTORY));
1884	clearHistoryItem->SetEnabled(count > 0);
1885	fHistoryMenu->AddItem(clearHistoryItem);
1886	if (count == 0) {
1887		history->Unlock();
1888		return;
1889	}
1890	fHistoryMenu->AddSeparatorItem();
1891
1892	BDateTime todayStart = BDateTime::CurrentDateTime(B_LOCAL_TIME);
1893	todayStart.SetTime(BTime(0, 0, 0));
1894
1895	BDateTime oneDayAgoStart = todayStart;
1896	oneDayAgoStart.Date().AddDays(-1);
1897
1898	BDateTime twoDaysAgoStart = oneDayAgoStart;
1899	twoDaysAgoStart.Date().AddDays(-1);
1900
1901	BDateTime threeDaysAgoStart = twoDaysAgoStart;
1902	threeDaysAgoStart.Date().AddDays(-1);
1903
1904	BDateTime fourDaysAgoStart = threeDaysAgoStart;
1905	fourDaysAgoStart.Date().AddDays(-1);
1906
1907	BDateTime fiveDaysAgoStart = fourDaysAgoStart;
1908	fiveDaysAgoStart.Date().AddDays(-1);
1909
1910	BMenu* todayMenu = new BMenu(B_TRANSLATE("Today"));
1911	BMenu* yesterdayMenu = new BMenu(B_TRANSLATE("Yesterday"));
1912	BMenu* twoDaysAgoMenu = new BMenu(
1913		twoDaysAgoStart.Date().LongDayName().String());
1914	BMenu* threeDaysAgoMenu = new BMenu(
1915		threeDaysAgoStart.Date().LongDayName().String());
1916	BMenu* fourDaysAgoMenu = new BMenu(
1917		fourDaysAgoStart.Date().LongDayName().String());
1918	BMenu* fiveDaysAgoMenu = new BMenu(
1919		fiveDaysAgoStart.Date().LongDayName().String());
1920	BMenu* earlierMenu = new BMenu(B_TRANSLATE("Earlier"));
1921
1922	for (int32 i = 0; i < count; i++) {
1923		BrowsingHistoryItem historyItem = history->HistoryItemAt(i);
1924		BMessage* message = new BMessage(GOTO_URL);
1925		message->AddString("url", historyItem.URL().String());
1926
1927		BString truncatedUrl(historyItem.URL());
1928		be_plain_font->TruncateString(&truncatedUrl, B_TRUNCATE_END, 480);
1929		menuItem = new BMenuItem(truncatedUrl, message);
1930
1931		if (historyItem.DateTime() < fiveDaysAgoStart)
1932			addItemToMenuOrSubmenu(earlierMenu, menuItem);
1933		else if (historyItem.DateTime() < fourDaysAgoStart)
1934			addItemToMenuOrSubmenu(fiveDaysAgoMenu, menuItem);
1935		else if (historyItem.DateTime() < threeDaysAgoStart)
1936			addItemToMenuOrSubmenu(fourDaysAgoMenu, menuItem);
1937		else if (historyItem.DateTime() < twoDaysAgoStart)
1938			addItemToMenuOrSubmenu(threeDaysAgoMenu, menuItem);
1939		else if (historyItem.DateTime() < oneDayAgoStart)
1940			addItemToMenuOrSubmenu(twoDaysAgoMenu, menuItem);
1941		else if (historyItem.DateTime() < todayStart)
1942			addItemToMenuOrSubmenu(yesterdayMenu, menuItem);
1943		else
1944			addItemToMenuOrSubmenu(todayMenu, menuItem);
1945	}
1946	history->Unlock();
1947
1948	addOrDeleteMenu(todayMenu, fHistoryMenu);
1949	addOrDeleteMenu(yesterdayMenu, fHistoryMenu);
1950	addOrDeleteMenu(twoDaysAgoMenu, fHistoryMenu);
1951	addOrDeleteMenu(fourDaysAgoMenu, fHistoryMenu);
1952	addOrDeleteMenu(fiveDaysAgoMenu, fHistoryMenu);
1953	addOrDeleteMenu(earlierMenu, fHistoryMenu);
1954}
1955
1956
1957void
1958BrowserWindow::_UpdateClipboardItems()
1959{
1960	BTextView* focusTextView = dynamic_cast<BTextView*>(CurrentFocus());
1961	if (focusTextView != NULL) {
1962		int32 selectionStart;
1963		int32 selectionEnd;
1964		focusTextView->GetSelection(&selectionStart, &selectionEnd);
1965		bool hasSelection = selectionStart < selectionEnd;
1966		bool canPaste = false;
1967		// A BTextView has the focus.
1968		if (be_clipboard->Lock()) {
1969			BMessage* data = be_clipboard->Data();
1970			if (data != NULL)
1971				canPaste = data->HasData("text/plain", B_MIME_TYPE);
1972			be_clipboard->Unlock();
1973		}
1974		fCutMenuItem->SetEnabled(hasSelection);
1975		fCopyMenuItem->SetEnabled(hasSelection);
1976		fPasteMenuItem->SetEnabled(canPaste);
1977	} else if (CurrentWebView() != NULL) {
1978		// Trigger update of the clipboard items, even if the
1979		// BWebView doesn't have focus, we'll dispatch these message
1980		// there anyway. This works so fast that the user can never see
1981		// the wrong enabled state when the menu opens until the result
1982		// message arrives. The initial state needs to be enabled, since
1983		// standard shortcut handling is always wrapped inside MenusBeginning()
1984		// and MenusEnded(), and since we update items asynchronously, we need
1985		// to have them enabled to begin with.
1986		fCutMenuItem->SetEnabled(true);
1987		fCopyMenuItem->SetEnabled(true);
1988		fPasteMenuItem->SetEnabled(true);
1989
1990		CurrentWebView()->WebPage()->SendEditingCapabilities();
1991	}
1992}
1993
1994
1995bool
1996BrowserWindow::_ShowPage(BWebView* view)
1997{
1998	if (view != CurrentWebView()) {
1999		int32 tabIndex = fTabManager->TabForView(view);
2000		if (tabIndex < 0) {
2001			// Page seems to be gone already?
2002			return false;
2003		}
2004		fTabManager->SelectTab(tabIndex);
2005		_TabChanged(tabIndex);
2006		UpdateIfNeeded();
2007	}
2008	return true;
2009}
2010
2011
2012void
2013BrowserWindow::_ResizeToScreen()
2014{
2015	BScreen screen(this);
2016	MoveTo(0, 0);
2017	ResizeTo(screen.Frame().Width(), screen.Frame().Height());
2018}
2019
2020
2021void
2022BrowserWindow::_SetAutoHideInterfaceInFullscreen(bool doIt)
2023{
2024	if (fAutoHideInterfaceInFullscreenMode == doIt)
2025		return;
2026
2027	fAutoHideInterfaceInFullscreenMode = doIt;
2028	if (fAppSettings->GetValue(kSettingsKeyAutoHideInterfaceInFullscreenMode,
2029			doIt) != doIt) {
2030		fAppSettings->SetValue(kSettingsKeyAutoHideInterfaceInFullscreenMode,
2031			doIt);
2032	}
2033
2034	if (fAutoHideInterfaceInFullscreenMode) {
2035		BMessage message(CHECK_AUTO_HIDE_INTERFACE);
2036		fPulseRunner = new BMessageRunner(BMessenger(this), &message, 300000);
2037	} else {
2038		delete fPulseRunner;
2039		fPulseRunner = NULL;
2040		_ShowInterface(true);
2041	}
2042}
2043
2044
2045void
2046BrowserWindow::_CheckAutoHideInterface()
2047{
2048	if (!fIsFullscreen || !fAutoHideInterfaceInFullscreenMode
2049		|| (CurrentWebView() != NULL && !CurrentWebView()->IsFocus())) {
2050		return;
2051	}
2052
2053	if (fLastMousePos.y == 0)
2054		_ShowInterface(true);
2055	else if (fNavigationGroup->IsVisible()
2056		&& fLastMousePos.y > fNavigationGroup->Frame().bottom
2057		&& system_time() - fLastMouseMovedTime > 1000000) {
2058		// NOTE: Do not re-use navigationGroupBottom in the above
2059		// check, since we only want to hide the interface when it is visible.
2060		_ShowInterface(false);
2061	}
2062}
2063
2064
2065void
2066BrowserWindow::_ShowInterface(bool show)
2067{
2068	if (fInterfaceVisible == show)
2069		return;
2070
2071	fInterfaceVisible = show;
2072
2073	if (show) {
2074#if !INTEGRATE_MENU_INTO_TAB_BAR
2075		fMenuGroup->SetVisible(
2076			(fVisibleInterfaceElements & INTERFACE_ELEMENT_MENU) != 0);
2077#endif
2078		fTabGroup->SetVisible(_TabGroupShouldBeVisible());
2079		fNavigationGroup->SetVisible(
2080			(fVisibleInterfaceElements & INTERFACE_ELEMENT_NAVIGATION) != 0);
2081		fStatusGroup->SetVisible(
2082			(fVisibleInterfaceElements & INTERFACE_ELEMENT_STATUS) != 0);
2083	} else {
2084		fMenuGroup->SetVisible(false);
2085		fTabGroup->SetVisible(false);
2086		fNavigationGroup->SetVisible(false);
2087		fStatusGroup->SetVisible(false);
2088	}
2089	// TODO: Setting the group visible seems to unhide the status bar.
2090	// Fix in Haiku?
2091	while (!fLoadingProgressBar->IsHidden())
2092		fLoadingProgressBar->Hide();
2093}
2094
2095
2096void
2097BrowserWindow::_ShowProgressBar(bool show)
2098{
2099	if (show) {
2100		if (!fStatusGroup->IsVisible() && (fVisibleInterfaceElements
2101			& INTERFACE_ELEMENT_STATUS) != 0)
2102				fStatusGroup->SetVisible(true);
2103		fLoadingProgressBar->Show();
2104	} else {
2105		if (!fInterfaceVisible)
2106			fStatusGroup->SetVisible(false);
2107		// TODO: This is also used in _ShowInterface. Without it the status bar
2108		// doesn't always hide again. It may be an Interface Kit bug.
2109		while (!fLoadingProgressBar->IsHidden())
2110			fLoadingProgressBar->Hide();
2111	}
2112}
2113
2114
2115void
2116BrowserWindow::_InvokeButtonVisibly(BButton* button)
2117{
2118	button->SetValue(B_CONTROL_ON);
2119	UpdateIfNeeded();
2120	button->Invoke();
2121	snooze(1000);
2122	button->SetValue(B_CONTROL_OFF);
2123}
2124
2125
2126BString
2127BrowserWindow::_NewTabURL(bool isNewWindow) const
2128{
2129	BString url;
2130	uint32 policy = isNewWindow ? fNewWindowPolicy : fNewTabPolicy;
2131	// Implement new page policy
2132	switch (policy) {
2133		case OpenStartPage:
2134			url = fStartPageURL;
2135			break;
2136		case OpenSearchPage:
2137			url = fSearchPageURL;
2138			break;
2139		case CloneCurrentPage:
2140			if (CurrentWebView() != NULL)
2141				url = CurrentWebView()->MainFrameURL();
2142			break;
2143		case OpenBlankPage:
2144		default:
2145			break;
2146	}
2147	return url;
2148}
2149
2150BString
2151BrowserWindow::_EncodeURIComponent(const BString& search)
2152{
2153	const BString escCharList = " $&`:<>[]{}\"+#%@/;=?\\^|~\',";
2154	BString result = search;
2155	char hexcode[4];
2156
2157	for (int32 i = 0; i < result.Length(); i++) {
2158		if (escCharList.FindFirst(result[i]) != B_ERROR) {
2159			sprintf(hexcode, "%02X", (unsigned int)result[i]);
2160			result[i] = '%';
2161			result.Insert(hexcode, i + 1);
2162			i += 2;
2163		}
2164	}
2165
2166	return result;
2167}
2168
2169
2170void
2171BrowserWindow::_VisitURL(const BString& url)
2172{
2173	//fURLInputGroup->TextView()->SetText(url);
2174	CurrentWebView()->LoadURL(url.String());
2175}
2176
2177
2178void
2179BrowserWindow::_VisitSearchEngine(const BString& search)
2180{
2181	// TODO: Google Code-In Task to make default search
2182	//			engine modifiable from Settings? :)
2183
2184	BString engine = "http://www.google.com/search?q=";
2185	engine += _EncodeURIComponent(search);
2186		// We have to take care of some of the escaping before
2187		// we hand over the string to WebKit, if we want queries
2188		// like "4+3" to not be searched as "4 3".
2189
2190	_VisitURL(engine);
2191}
2192
2193
2194inline bool
2195BrowserWindow::_IsValidDomainChar(char ch)
2196{
2197	// TODO: Currenlty, only a whitespace character
2198	//			breaks a domain name. It might be
2199	//			a good idea (or a bad one) to make
2200	//			character filtering based on the
2201	//			IDNA 2008 standard.
2202
2203	return ch != ' ';
2204}
2205
2206
2207void
2208BrowserWindow::_SmartURLHandler(const BString& url)
2209{
2210	// Only process if this doesn't look like a full URL (http:// or
2211	// file://, etc.)
2212
2213	BString temp;
2214	int32 at = url.FindFirst(":");
2215
2216	if (at != B_ERROR) {
2217		BString proto;
2218		url.CopyInto(proto, 0, at);
2219
2220		if (proto == "http" || 	proto == "https" ||	proto == "file")
2221			_VisitURL(url);
2222		else {
2223			temp = "application/x-vnd.Be.URL.";
2224			temp += proto;
2225
2226			char* argv[1] = { (char*)url.String() };
2227
2228			if (be_roster->Launch(temp.String(), 1, argv) != B_OK)
2229				_VisitSearchEngine(url);
2230		}
2231	} else if (url == "localhost")
2232		_VisitURL("http://localhost/");
2233	else {
2234		const char* localhostPrefix = "localhost/";
2235
2236		if(url.Compare(localhostPrefix, strlen(localhostPrefix)) == 0)
2237			_VisitURL(url);
2238		else {
2239			bool isURL = false;
2240
2241			for (int32 i = 0; i < url.CountChars(); i++) {
2242				if (url[i] == '.')
2243					isURL = true;
2244				else if (url[i] == '/')
2245					break;
2246				else if (!_IsValidDomainChar(url[i])) {
2247					isURL = false;
2248
2249					break;
2250				}
2251			}
2252
2253			if (isURL)
2254				_VisitURL(url);
2255			else
2256				_VisitSearchEngine(url);
2257		}
2258	}
2259}
2260
2261
2262void
2263BrowserWindow::_HandlePageSourceResult(const BMessage* message)
2264{
2265	// TODO: This should be done in an extra thread perhaps. Doing it in
2266	// the application thread is not much better, since it actually draws
2267	// the pages...
2268
2269	BPath pathToPageSource;
2270
2271	BString url;
2272	status_t ret = message->FindString("url", &url);
2273	if (ret == B_OK && url.FindFirst("file://") == 0) {
2274		// Local file
2275		url.Remove(0, strlen("file://"));
2276		pathToPageSource.SetTo(url.String());
2277	} else {
2278		// Something else, store it.
2279		// TODO: What if it isn't HTML, but for example SVG?
2280		BString source;
2281		ret = message->FindString("source", &source);
2282
2283		if (ret == B_OK)
2284			ret = find_directory(B_COMMON_TEMP_DIRECTORY, &pathToPageSource);
2285
2286		BString tmpFileName("PageSource_");
2287		tmpFileName << system_time() << ".html";
2288		if (ret == B_OK)
2289			ret = pathToPageSource.Append(tmpFileName.String());
2290
2291		BFile pageSourceFile(pathToPageSource.Path(),
2292			B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
2293		if (ret == B_OK)
2294			ret = pageSourceFile.InitCheck();
2295
2296		if (ret == B_OK) {
2297			ssize_t written = pageSourceFile.Write(source.String(),
2298				source.Length());
2299			if (written != source.Length())
2300				ret = (status_t)written;
2301		}
2302
2303		if (ret == B_OK) {
2304			const char* type = "text/html";
2305			size_t size = strlen(type);
2306			pageSourceFile.WriteAttr("BEOS:TYPE", B_STRING_TYPE, 0, type, size);
2307				// If it fails we don't care.
2308		}
2309	}
2310
2311	entry_ref ref;
2312	if (ret == B_OK)
2313		ret = get_ref_for_path(pathToPageSource.Path(), &ref);
2314
2315	if (ret == B_OK) {
2316		BMessage refsMessage(B_REFS_RECEIVED);
2317		ret = refsMessage.AddRef("refs", &ref);
2318		if (ret == B_OK) {
2319			ret = be_roster->Launch("text/x-source-code", &refsMessage);
2320			if (ret == B_ALREADY_RUNNING)
2321				ret = B_OK;
2322		}
2323	}
2324
2325	if (ret != B_OK) {
2326		char buffer[1024];
2327		snprintf(buffer, sizeof(buffer), "Failed to show the "
2328			"page source: %s\n", strerror(ret));
2329		BAlert* alert = new BAlert(B_TRANSLATE("Page source error"), buffer,
2330			B_TRANSLATE("OK"));
2331		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2332		alert->Go(NULL);
2333	}
2334}
2335
2336
2337