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