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