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