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