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