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