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