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