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