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