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