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