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