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