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