xref: /haiku/src/apps/webpositive/BrowserWindow.cpp (revision c33a98eebcf5b9ca9612cce60ea5ff405376ee8d)
1 /*
2  * Copyright (C) 2007 Andrea Anzani <andrea.anzani@gmail.com>
3  * Copyright (C) 2007, 2010 Ryan Leavengood <leavengood@gmail.com>
4  * Copyright (C) 2009 Maxime Simon <simon.maxime@gmail.com>
5  * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de>
6  * Copyright (C) 2010 Michael Lotz <mmlr@mlotz.ch>
7  * Copyright (C) 2010 Rene Gollent <rene@gollent.com>
8  * Copyright 2013-2015 Haiku, Inc. All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
27  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include "BrowserWindow.h"
33 
34 #include <Alert.h>
35 #include <Application.h>
36 #include <Bitmap.h>
37 #include <Button.h>
38 #include <Catalog.h>
39 #include <CheckBox.h>
40 #include <Clipboard.h>
41 #include <ControlLook.h>
42 #include <Debug.h>
43 #include <Directory.h>
44 #include <Entry.h>
45 #include <File.h>
46 #include <FilePanel.h>
47 #include <FindDirectory.h>
48 #include <GridLayoutBuilder.h>
49 #include <GroupLayout.h>
50 #include <GroupLayoutBuilder.h>
51 #include <IconMenuItem.h>
52 #include <Keymap.h>
53 #include <LayoutBuilder.h>
54 #include <Locale.h>
55 #include <ObjectList.h>
56 #include <MenuBar.h>
57 #include <MenuItem.h>
58 #include <MessageRunner.h>
59 #include <NodeInfo.h>
60 #include <NodeMonitor.h>
61 #include <Path.h>
62 #include <Roster.h>
63 #include <Screen.h>
64 #include <SeparatorView.h>
65 #include <Size.h>
66 #include <SpaceLayoutItem.h>
67 #include <StatusBar.h>
68 #include <StringView.h>
69 #include <TextControl.h>
70 #include <UnicodeChar.h>
71 #include <Url.h>
72 
73 #include <map>
74 #include <stdio.h>
75 
76 #include "AuthenticationPanel.h"
77 #include "BaseURL.h"
78 #include "BitmapButton.h"
79 #include "BookmarkBar.h"
80 #include "BrowserApp.h"
81 #include "BrowsingHistory.h"
82 #include "CredentialsStorage.h"
83 #include "IconButton.h"
84 #include "NavMenu.h"
85 #include "SettingsKeys.h"
86 #include "SettingsMessage.h"
87 #include "TabManager.h"
88 #include "URLInputGroup.h"
89 #include "WebPage.h"
90 #include "WebView.h"
91 #include "WebViewConstants.h"
92 #include "WindowIcon.h"
93 
94 
95 #undef B_TRANSLATION_CONTEXT
96 #define B_TRANSLATION_CONTEXT "WebPositive Window"
97 
98 
99 enum {
100 	OPEN_LOCATION								= 'open',
101 	SAVE_PAGE									= 'save',
102 	GO_BACK										= 'goba',
103 	GO_FORWARD									= 'gofo',
104 	STOP										= 'stop',
105 	HOME										= 'home',
106 	GOTO_URL									= 'goul',
107 	RELOAD										= 'reld',
108 	SHOW_HIDE_BOOKMARK_BAR						= 'shbb',
109 	CLEAR_HISTORY								= 'clhs',
110 
111 	CREATE_BOOKMARK								= 'crbm',
112 	SHOW_BOOKMARKS								= 'shbm',
113 
114 	ZOOM_FACTOR_INCREASE						= 'zfin',
115 	ZOOM_FACTOR_DECREASE						= 'zfdc',
116 	ZOOM_FACTOR_RESET							= 'zfrs',
117 	ZOOM_TEXT_ONLY								= 'zfto',
118 
119 	TOGGLE_FULLSCREEN							= 'tgfs',
120 	TOGGLE_AUTO_HIDE_INTERFACE_IN_FULLSCREEN	= 'tgah',
121 	CHECK_AUTO_HIDE_INTERFACE					= 'cahi',
122 
123 	SHOW_PAGE_SOURCE							= 'spgs',
124 
125 	EDIT_SHOW_FIND_GROUP						= 'sfnd',
126 	EDIT_HIDE_FIND_GROUP						= 'hfnd',
127 	EDIT_FIND_NEXT								= 'fndn',
128 	EDIT_FIND_PREVIOUS							= 'fndp',
129 	FIND_TEXT_CHANGED							= 'ftxt',
130 
131 	SELECT_TAB									= 'sltb',
132 };
133 
134 
135 static const int32 kModifiers = B_SHIFT_KEY | B_COMMAND_KEY
136 	| B_CONTROL_KEY | B_OPTION_KEY | B_MENU_KEY;
137 
138 
139 static const char* kHandledProtocols[] = {
140 	"http",
141 	"https",
142 	"file",
143 	"about",
144 	"data",
145 	"gopher"
146 };
147 
148 
149 static BLayoutItem*
150 layoutItemFor(BView* view)
151 {
152 	BLayout* layout = view->Parent()->GetLayout();
153 	int32 index = layout->IndexOfView(view);
154 	return layout->ItemAt(index);
155 }
156 
157 
158 class BookmarkMenu : public BNavMenu {
159 public:
160 	BookmarkMenu(const char* title, BHandler* target, const entry_ref* navDir)
161 		:
162 		BNavMenu(title, B_REFS_RECEIVED, target)
163 	{
164 		// Add these items here already, so the shortcuts work even when
165 		// the menu has never been opened yet.
166 		_AddStaticItems();
167 
168 		SetNavDir(navDir);
169 	}
170 
171 	virtual void AttachedToWindow()
172 	{
173 		RemoveItems(0, CountItems(), true);
174 		ForceRebuild();
175 		BNavMenu::AttachedToWindow();
176 		if (CountItems() > 0)
177 			AddItem(new BSeparatorItem(), 0);
178 		_AddStaticItems();
179 		DoLayout();
180 	}
181 
182 private:
183 	void _AddStaticItems()
184 	{
185 		AddItem(new BMenuItem(B_TRANSLATE("Manage bookmarks"),
186 			new BMessage(SHOW_BOOKMARKS), 'M'), 0);
187 		AddItem(new BMenuItem(B_TRANSLATE("Bookmark this page"),
188 			new BMessage(CREATE_BOOKMARK), 'B'), 0);
189 	}
190 };
191 
192 
193 class PageUserData : public BWebView::UserData {
194 public:
195 	PageUserData(BView* focusedView)
196 		:
197 		fFocusedView(focusedView),
198 		fPageIcon(NULL),
199 		fURLInputSelectionStart(-1),
200 		fURLInputSelectionEnd(-1)
201 	{
202 	}
203 
204 	~PageUserData()
205 	{
206 		delete fPageIcon;
207 	}
208 
209 	void SetFocusedView(BView* focusedView)
210 	{
211 		fFocusedView = focusedView;
212 	}
213 
214 	BView* FocusedView() const
215 	{
216 		return fFocusedView;
217 	}
218 
219 	void SetPageIcon(const BBitmap* icon)
220 	{
221 		delete fPageIcon;
222 		if (icon)
223 			fPageIcon = new BBitmap(icon);
224 		else
225 			fPageIcon = NULL;
226 	}
227 
228 	const BBitmap* PageIcon() const
229 	{
230 		return fPageIcon;
231 	}
232 
233 	void SetURLInputContents(const char* text)
234 	{
235 		fURLInputContents = text;
236 	}
237 
238 	const BString& URLInputContents() const
239 	{
240 		return fURLInputContents;
241 	}
242 
243 	void SetURLInputSelection(int32 selectionStart, int32 selectionEnd)
244 	{
245 		fURLInputSelectionStart = selectionStart;
246 		fURLInputSelectionEnd = selectionEnd;
247 	}
248 
249 	int32 URLInputSelectionStart() const
250 	{
251 		return fURLInputSelectionStart;
252 	}
253 
254 	int32 URLInputSelectionEnd() const
255 	{
256 		return fURLInputSelectionEnd;
257 	}
258 
259 private:
260 	BView*		fFocusedView;
261 	BBitmap*	fPageIcon;
262 	BString		fURLInputContents;
263 	int32		fURLInputSelectionStart;
264 	int32		fURLInputSelectionEnd;
265 };
266 
267 
268 class CloseButton : public BButton {
269 public:
270 	CloseButton(BMessage* message)
271 		:
272 		BButton("close button", NULL, message),
273 		fOverCloseRect(false)
274 	{
275 		// Button is 16x16 regardless of font size
276 		SetExplicitMinSize(BSize(15, 15));
277 		SetExplicitMaxSize(BSize(15, 15));
278 	}
279 
280 	virtual void Draw(BRect updateRect)
281 	{
282 		BRect frame = Bounds();
283 		BRect closeRect(frame.InsetByCopy(4, 4));
284 		rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
285 		float tint = B_DARKEN_1_TINT;
286 
287 		if (fOverCloseRect)
288 			tint *= 1.4;
289 		else
290 			tint *= 1.2;
291 
292 		if (Value() == B_CONTROL_ON && fOverCloseRect) {
293 			// Draw the button frame
294 			be_control_look->DrawButtonFrame(this, frame, updateRect,
295 				base, base, BControlLook::B_ACTIVATED
296 					| BControlLook::B_BLEND_FRAME);
297 			be_control_look->DrawButtonBackground(this, frame,
298 				updateRect, base, BControlLook::B_ACTIVATED);
299 			closeRect.OffsetBy(1, 1);
300 			tint *= 1.2;
301 		} else {
302 			SetHighColor(base);
303 			FillRect(updateRect);
304 		}
305 
306 		// Draw the ×
307 		base = tint_color(base, tint);
308 		SetHighColor(base);
309 		SetPenSize(2);
310 		StrokeLine(closeRect.LeftTop(), closeRect.RightBottom());
311 		StrokeLine(closeRect.LeftBottom(), closeRect.RightTop());
312 		SetPenSize(1);
313 	}
314 
315 	virtual void MouseMoved(BPoint where, uint32 transit,
316 		const BMessage* dragMessage)
317 	{
318 		switch (transit) {
319 			case B_ENTERED_VIEW:
320 				fOverCloseRect = true;
321 				Invalidate();
322 				break;
323 			case B_EXITED_VIEW:
324 				fOverCloseRect = false;
325 				Invalidate();
326 				break;
327 			case B_INSIDE_VIEW:
328 				fOverCloseRect = true;
329 				break;
330 			case B_OUTSIDE_VIEW:
331 				fOverCloseRect = false;
332 				break;
333 		}
334 
335 		BButton::MouseMoved(where, transit, dragMessage);
336 	}
337 
338 private:
339 	bool fOverCloseRect;
340 };
341 
342 
343 // #pragma mark - BrowserWindow
344 
345 
346 BrowserWindow::BrowserWindow(BRect frame, SettingsMessage* appSettings,
347 		const BString& url, BUrlContext* context, uint32 interfaceElements,
348 		BWebView* webView)
349 	:
350 	BWebWindow(frame, kApplicationName,
351 		B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
352 		B_AUTO_UPDATE_SIZE_LIMITS | B_ASYNCHRONOUS_CONTROLS),
353 	fIsFullscreen(false),
354 	fInterfaceVisible(false),
355 	fMenusRunning(false),
356 	fPulseRunner(NULL),
357 	fVisibleInterfaceElements(interfaceElements),
358 	fContext(context),
359 	fAppSettings(appSettings),
360 	fZoomTextOnly(true),
361 	fShowTabsIfSinglePageOpen(true),
362 	fAutoHideInterfaceInFullscreenMode(false),
363 	fAutoHidePointer(false),
364 	fBookmarkBar(NULL)
365 {
366 	// Begin listening to settings changes and read some current values.
367 	fAppSettings->AddListener(BMessenger(this));
368 //	fZoomTextOnly = fAppSettings->GetValue("zoom text only", fZoomTextOnly);
369 	fShowTabsIfSinglePageOpen = fAppSettings->GetValue(
370 		kSettingsKeyShowTabsIfSinglePageOpen, fShowTabsIfSinglePageOpen);
371 
372 	fAutoHidePointer = fAppSettings->GetValue(kSettingsKeyAutoHidePointer,
373 		fAutoHidePointer);
374 
375 	fNewWindowPolicy = fAppSettings->GetValue(kSettingsKeyNewWindowPolicy,
376 		(uint32)OpenStartPage);
377 	fNewTabPolicy = fAppSettings->GetValue(kSettingsKeyNewTabPolicy,
378 		(uint32)OpenBlankPage);
379 	fStartPageURL = fAppSettings->GetValue(kSettingsKeyStartPageURL,
380 		kDefaultStartPageURL);
381 	fSearchPageURL = fAppSettings->GetValue(kSettingsKeySearchPageURL,
382 		kDefaultSearchPageURL);
383 	_InitSearchEngines();
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 = fTabManager->Menu();
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 	fLoadingProgressBar->SetBarHeight(12);
552 
553 	const float kInsetSpacing = 3;
554 	const float kElementSpacing = 5;
555 
556 	// Find group
557 	fFindCloseButton = new CloseButton(new BMessage(EDIT_HIDE_FIND_GROUP));
558 	fFindTextControl = new BTextControl("find", B_TRANSLATE("Find:"), "", NULL);
559 	fFindTextControl->SetModificationMessage(new BMessage(FIND_TEXT_CHANGED));
560 	fFindPreviousButton = new BButton(B_TRANSLATE("Previous"),
561 		new BMessage(EDIT_FIND_PREVIOUS));
562 	fFindPreviousButton->SetToolTip(
563 		B_TRANSLATE_COMMENT("Find previous occurrence of search terms",
564 			"find bar previous button tooltip"));
565 	fFindNextButton = new BButton(B_TRANSLATE("Next"),
566 		new BMessage(EDIT_FIND_NEXT));
567 	fFindNextButton->SetToolTip(
568 		B_TRANSLATE_COMMENT("Find next occurrence of search terms",
569 			"find bar next button tooltip"));
570 	fFindCaseSensitiveCheckBox = new BCheckBox(B_TRANSLATE("Match case"));
571 	BGroupLayout* findGroup = BLayoutBuilder::Group<>(B_VERTICAL, 0.0)
572 		.Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER))
573 		.Add(BGroupLayoutBuilder(B_HORIZONTAL, B_USE_SMALL_SPACING)
574 			.Add(fFindCloseButton)
575 			.Add(fFindTextControl)
576 			.Add(fFindPreviousButton)
577 			.Add(fFindNextButton)
578 			.Add(fFindCaseSensitiveCheckBox)
579 			.SetInsets(kInsetSpacing, kInsetSpacing,
580 				kInsetSpacing, kInsetSpacing)
581 		)
582 	;
583 
584 	// Navigation group
585 	BGroupLayout* navigationGroup = BLayoutBuilder::Group<>(B_VERTICAL, 0.0)
586 		.Add(BLayoutBuilder::Group<>(B_HORIZONTAL, kElementSpacing)
587 			.Add(fBackButton)
588 			.Add(fForwardButton)
589 			.Add(fStopButton)
590 			.Add(fHomeButton)
591 			.Add(fURLInputGroup)
592 			.SetInsets(kInsetSpacing, kInsetSpacing, kInsetSpacing,
593 				kInsetSpacing)
594 		)
595 		.Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER))
596 	;
597 
598 	// Status bar group
599 	BGroupLayout* statusGroup = BLayoutBuilder::Group<>(B_VERTICAL, 0.0)
600 		.Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER))
601 		.Add(BLayoutBuilder::Group<>(B_HORIZONTAL, kElementSpacing)
602 			.Add(fStatusText)
603 			.Add(fLoadingProgressBar, 0.2)
604 			.AddStrut(12 - kElementSpacing)
605 			.SetInsets(kInsetSpacing, 0, kInsetSpacing, 0)
606 		)
607 	;
608 
609 	BBitmapButton* toggleFullscreenButton = new BBitmapButton(kWindowIconBits,
610 		kWindowIconWidth, kWindowIconHeight, kWindowIconFormat,
611 		new BMessage(TOGGLE_FULLSCREEN));
612 	toggleFullscreenButton->SetBackgroundMode(BBitmapButton::MENUBAR_BACKGROUND);
613 
614 	BGroupLayout* menuBarGroup = BLayoutBuilder::Group<>(B_HORIZONTAL, 0.0)
615 		.Add(mainMenu)
616 		.Add(toggleFullscreenButton, 0.0f)
617 	;
618 
619 	if (fAppSettings->GetValue(kSettingsShowBookmarkBar, true))
620 		_ShowBookmarkBar(true);
621 	else
622 		_ShowBookmarkBar(false);
623 
624 	fSavePanel = new BFilePanel(B_SAVE_PANEL, new BMessenger(this), NULL, 0,
625 		false);
626 
627 	// Layout
628 	BGroupView* topView = new BGroupView(B_VERTICAL, 0.0);
629 
630 #if !INTEGRATE_MENU_INTO_TAB_BAR
631 	topView->AddChild(menuBarGroup);
632 #endif
633 	topView->AddChild(fTabManager->TabGroup());
634 	topView->AddChild(navigationGroup);
635 	if (fBookmarkBar != NULL)
636 		topView->AddChild(fBookmarkBar);
637 	topView->AddChild(fTabManager->ContainerView());
638 	topView->AddChild(findGroup);
639 	topView->AddChild(statusGroup);
640 
641 	AddChild(topView);
642 
643 	fURLInputGroup->MakeFocus(true);
644 
645 	fMenuGroup = menuBarGroup;
646 	fTabGroup = fTabManager->TabGroup()->GetLayout();
647 	fNavigationGroup = navigationGroup;
648 	fFindGroup = findGroup;
649 	fStatusGroup = statusGroup;
650 	fToggleFullscreenButton = layoutItemFor(toggleFullscreenButton);
651 
652 	fFindGroup->SetVisible(false);
653 	fToggleFullscreenButton->SetVisible(false);
654 
655 	CreateNewTab(url, true, webView);
656 	_ShowInterface(true);
657 	_SetAutoHideInterfaceInFullscreen(fAppSettings->GetValue(
658 		kSettingsKeyAutoHideInterfaceInFullscreenMode,
659 		fAutoHideInterfaceInFullscreenMode));
660 
661 	AddShortcut('F', B_COMMAND_KEY | B_SHIFT_KEY,
662 		new BMessage(EDIT_HIDE_FIND_GROUP));
663 	// TODO: Should be a different shortcut, H is usually for Find selection.
664 	AddShortcut('H', B_COMMAND_KEY,	new BMessage(HOME));
665 
666 	// Add shortcuts to select a particular tab
667 	for (int32 i = 1; i <= 9; i++) {
668 		BMessage* selectTab = new BMessage(SELECT_TAB);
669 		selectTab->AddInt32("tab index", i - 1);
670 		char numStr[2];
671 		snprintf(numStr, sizeof(numStr), "%d", (int) i);
672 		AddShortcut(numStr[0], B_COMMAND_KEY, selectTab);
673 	}
674 
675 	BKeymap keymap;
676 	keymap.SetToCurrent();
677 	BObjectList<const char> unmodified(3, true);
678 	if (keymap.GetModifiedCharacters("+", B_SHIFT_KEY, 0, &unmodified)
679 			== B_OK) {
680 		int32 count = unmodified.CountItems();
681 		for (int32 i = 0; i < count; i++) {
682 			uint32 key = BUnicodeChar::FromUTF8(unmodified.ItemAt(i));
683 			if (!HasShortcut(key, 0)) {
684 				// Add semantic zoom in shortcut, bug #7428
685 				AddShortcut(key, B_COMMAND_KEY,
686 					new BMessage(ZOOM_FACTOR_INCREASE));
687 			}
688 		}
689 	}
690 	unmodified.MakeEmpty();
691 
692 	be_app->PostMessage(WINDOW_OPENED);
693 }
694 
695 
696 BrowserWindow::~BrowserWindow()
697 {
698 	fAppSettings->RemoveListener(BMessenger(this));
699 	delete fTabManager;
700 	delete fPulseRunner;
701 	delete fSavePanel;
702 	delete[] fSearchEngines;
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 commited.
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::_InitSearchEngines()
2485 {
2486 	// TODO make these configurable
2487 	fSearchEngines = new SearchEngine[kSearchEngineCount];
2488 	fSearchEngines[0].url="https://google.com/search?q=%s";
2489 	fSearchEngines[0].shortcut="g ";
2490 	fSearchEngines[1].url="https://bing.com/search?q=%s";
2491 	fSearchEngines[1].shortcut="b ";
2492 	fSearchEngines[2].url="https://en.wikipedia.org/w/index.php?search=%s";
2493 	fSearchEngines[2].shortcut="w ";
2494 	fSearchEngines[3].url="https://duckduckgo.com/?q=%s";
2495 	fSearchEngines[3].shortcut="d ";
2496 	fSearchEngines[4].url="https://www.baidu.com/s?wd=%s";
2497 	fSearchEngines[4].shortcut="a ";
2498 	fSearchEngines[5].url="https://yandex.com/search/?text=%s";
2499 	fSearchEngines[5].shortcut="y ";
2500 	fSearchEngines[6].url="https://www.ecosia.org/search?q=%s";
2501 	fSearchEngines[6].shortcut="e ";
2502 	fSearchEngines[7].url="https://www.qwant.com/?q=%s";
2503 	fSearchEngines[7].shortcut="q ";
2504 }
2505 
2506 
2507 void
2508 BrowserWindow::_VisitURL(const BString& url)
2509 {
2510 	// fURLInputGroup->TextView()->SetText(url);
2511 	CurrentWebView()->LoadURL(url.String());
2512 }
2513 
2514 
2515 void
2516 BrowserWindow::_VisitSearchEngine(const BString& search)
2517 {
2518 	BString searchQuery = search;
2519 
2520 	BString searchPrefix;
2521 	search.CopyCharsInto(searchPrefix, 0, 2);
2522 
2523 	// Default search URL
2524 	BString engine(fSearchPageURL);
2525 
2526 	// Check if the string starts with one of the search engine shortcuts
2527 	for (int i = 0; i < kSearchEngineCount; i++) {
2528 		if (fSearchEngines[i].shortcut == searchPrefix) {
2529 			engine = fSearchEngines[i].url;
2530 			searchQuery.Remove(0, 2);
2531 			break;
2532 		}
2533 	}
2534 
2535 	engine.ReplaceAll("%s", _EncodeURIComponent(searchQuery));
2536 	_VisitURL(engine);
2537 }
2538 
2539 
2540 inline bool
2541 BrowserWindow::_IsValidDomainChar(char ch)
2542 {
2543 	// TODO: Currenlty, only a whitespace character breaks a domain name. It
2544 	// might be a good idea (or a bad one) to make character filtering based on
2545 	// the IDNA 2008 standard.
2546 
2547 	return ch != ' ';
2548 }
2549 
2550 
2551 /*! \brief "smart" parser for user-entered URLs
2552 
2553 	We try to be flexible in what we accept as a valid URL. The protocol may
2554 	be missing, or something we can't handle (in that case we run the matching
2555 	app). If all attempts to make sense of the input fail, we make a search
2556 	engine query for it.
2557  */
2558 void
2559 BrowserWindow::_SmartURLHandler(const BString& url)
2560 {
2561 	// First test if the URL has a protocol field
2562 	int32 at = url.FindFirst(":");
2563 
2564 	if (at != B_ERROR) {
2565 		// There is a protocol, let's see if we can handle it
2566 		BString proto;
2567 		url.CopyInto(proto, 0, at);
2568 
2569 		bool handled = false;
2570 
2571 		// First try the built-in supported ones
2572 		for (unsigned int i = 0; i < sizeof(kHandledProtocols) / sizeof(char*);
2573 				i++) {
2574 			handled = (proto == kHandledProtocols[i]);
2575 			if (handled)
2576 				break;
2577 		}
2578 
2579 		if (handled) {
2580 			// This is the easy case, a complete and well-formed URL, we can
2581 			// navigate to it without further efforts.
2582 			_VisitURL(url);
2583 			return;
2584 		} else {
2585 			// There is what looks like a protocol, but one we don't know.
2586 			// Ask the BRoster if there is a matching filetype and app which
2587 			// can handle it.
2588 			BString temp;
2589 			temp = "application/x-vnd.Be.URL.";
2590 			temp += proto;
2591 
2592 			const char* argv[] = { url.String(), NULL };
2593 
2594 			if (be_roster->Launch(temp.String(), 1, argv) == B_OK)
2595 				return;
2596 		}
2597 	}
2598 
2599 	// There is no protocol or only an unsupported one. So let's try harder to
2600 	// guess what the request is.
2601 
2602 	// "localhost" is a special case, it is a valid domain name but has no dots.
2603 	// Handle it separately.
2604 	if (url == "localhost")
2605 		_VisitURL("http://localhost/");
2606 	else {
2607 		// Also handle URLs starting with "localhost" followed by a path.
2608 		const char* localhostPrefix = "localhost/";
2609 
2610 		if (url.Compare(localhostPrefix, strlen(localhostPrefix)) == 0)
2611 			_VisitURL(url);
2612 		else {
2613 			// In all other cases we try to detect a valid domain name. There
2614 			// must be at least one dot and no spaces until the first / in the
2615 			// URL.
2616 			bool isURL = false;
2617 
2618 			for (int32 i = 0; i < url.CountChars(); i++) {
2619 				if (url[i] == '.')
2620 					isURL = true;
2621 				else if (url[i] == '/')
2622 					break;
2623 				else if (!_IsValidDomainChar(url[i])) {
2624 					isURL = false;
2625 
2626 					break;
2627 				}
2628 			}
2629 
2630 			if (isURL) {
2631 				// This is apparently an URL missing the protocol part. In that
2632 				// case we default to http.
2633 				BString prefixed = "http://";
2634 				prefixed << url;
2635 				_VisitURL(prefixed);
2636 				return;
2637 			} else {
2638 				// We couldn't find anything that looks like an URL. Let's
2639 				// assume what we have is a search request and go to the search
2640 				// engine.
2641 				_VisitSearchEngine(url);
2642 			}
2643 		}
2644 	}
2645 }
2646 
2647 
2648 void
2649 BrowserWindow::_HandlePageSourceResult(const BMessage* message)
2650 {
2651 	// TODO: This should be done in an extra thread perhaps. Doing it in
2652 	// the application thread is not much better, since it actually draws
2653 	// the pages...
2654 
2655 	BPath pathToPageSource;
2656 
2657 	BString url;
2658 	status_t ret = message->FindString("url", &url);
2659 	if (ret == B_OK && url.FindFirst("file://") == 0) {
2660 		// Local file
2661 		url.Remove(0, strlen("file://"));
2662 		pathToPageSource.SetTo(url.String());
2663 	} else {
2664 		// Something else, store it.
2665 		// TODO: What if it isn't HTML, but for example SVG?
2666 		BString source;
2667 		ret = message->FindString("source", &source);
2668 
2669 		if (ret == B_OK)
2670 			ret = find_directory(B_SYSTEM_TEMP_DIRECTORY, &pathToPageSource);
2671 
2672 		BString tmpFileName("PageSource_");
2673 		tmpFileName << system_time() << ".html";
2674 		if (ret == B_OK)
2675 			ret = pathToPageSource.Append(tmpFileName.String());
2676 
2677 		BFile pageSourceFile(pathToPageSource.Path(),
2678 			B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
2679 		if (ret == B_OK)
2680 			ret = pageSourceFile.InitCheck();
2681 
2682 		if (ret == B_OK) {
2683 			ssize_t written = pageSourceFile.Write(source.String(),
2684 				source.Length());
2685 			if (written != source.Length())
2686 				ret = (status_t)written;
2687 		}
2688 
2689 		if (ret == B_OK) {
2690 			const char* type = "text/html";
2691 			size_t size = strlen(type);
2692 			pageSourceFile.WriteAttr("BEOS:TYPE", B_STRING_TYPE, 0, type, size);
2693 				// If it fails we don't care.
2694 		}
2695 	}
2696 
2697 	entry_ref ref;
2698 	if (ret == B_OK)
2699 		ret = get_ref_for_path(pathToPageSource.Path(), &ref);
2700 
2701 	if (ret == B_OK) {
2702 		BMessage refsMessage(B_REFS_RECEIVED);
2703 		ret = refsMessage.AddRef("refs", &ref);
2704 		if (ret == B_OK) {
2705 			ret = be_roster->Launch("text/x-source-code", &refsMessage);
2706 			if (ret == B_ALREADY_RUNNING)
2707 				ret = B_OK;
2708 		}
2709 	}
2710 
2711 	if (ret != B_OK) {
2712 		char buffer[1024];
2713 		snprintf(buffer, sizeof(buffer), "Failed to show the "
2714 			"page source: %s\n", strerror(ret));
2715 		BAlert* alert = new BAlert(B_TRANSLATE("Page source error"), buffer,
2716 			B_TRANSLATE("OK"));
2717 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2718 		alert->Go(NULL);
2719 	}
2720 }
2721 
2722 
2723 void
2724 BrowserWindow::_ShowBookmarkBar(bool show)
2725 {
2726 	// It is not allowed to show the bookmark bar when it is empty
2727 	if (show && (fBookmarkBar == NULL || fBookmarkBar->CountItems() <= 1))
2728 	{
2729 		fBookmarkBarMenuItem->SetMarked(false);
2730 		return;
2731 	}
2732 
2733 	fBookmarkBarMenuItem->SetMarked(show);
2734 
2735 	if (fBookmarkBar == NULL || fBookmarkBar->IsHidden() != show)
2736 		return;
2737 
2738 	fAppSettings->SetValue(kSettingsShowBookmarkBar, show);
2739 
2740 	if (show)
2741 		fBookmarkBar->Show();
2742 	else
2743 		fBookmarkBar->Hide();
2744 }
2745