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