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