Files
haiku-beta6/src/apps/webpositive/BrowserWindow.cpp
T

2179 lines
58 KiB
C++

/*
* Copyright (C) 2007 Andrea Anzani <[email protected]>
* Copyright (C) 2007 Ryan Leavengood <[email protected]>
* Copyright (C) 2009 Maxime Simon <[email protected]>
* Copyright (C) 2010 Stephan Aßmus <[email protected]>
* Copyright (C) 2010 Michael Lotz <[email protected]>
* Copyright (C) 2010 Rene Gollent <[email protected]>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "BrowserWindow.h"
#include "AuthenticationPanel.h"
#include "BaseURL.h"
#include "BitmapButton.h"
#include "BrowserApp.h"
#include "BrowsingHistory.h"
#include "CredentialsStorage.h"
#include "IconButton.h"
#include "NavMenu.h"
#include "SettingsKeys.h"
#include "SettingsMessage.h"
#include "TabManager.h"
#include "URLInputGroup.h"
#include "WebPage.h"
#include "WebView.h"
#include "WebViewConstants.h"
#include "WindowIcon.h"
#include <Alert.h>
#include <Application.h>
#include <Bitmap.h>
#include <Button.h>
#include <CheckBox.h>
#include <Clipboard.h>
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <FindDirectory.h>
#include <GridLayoutBuilder.h>
#include <GroupLayout.h>
#include <GroupLayoutBuilder.h>
#include <LayoutBuilder.h>
#include <MenuBar.h>
#include <MenuItem.h>
#include <MessageRunner.h>
#include <NodeInfo.h>
#include <Path.h>
#include <Roster.h>
#include <Screen.h>
#include <SeparatorView.h>
#include <SpaceLayoutItem.h>
#include <StatusBar.h>
#include <StringView.h>
#include <TextControl.h>
#include <stdio.h>
enum {
OPEN_LOCATION = 'open',
GO_BACK = 'goba',
GO_FORWARD = 'gofo',
STOP = 'stop',
HOME = 'home',
GOTO_URL = 'goul',
RELOAD = 'reld',
CLEAR_HISTORY = 'clhs',
CREATE_BOOKMARK = 'crbm',
SHOW_BOOKMARKS = 'shbm',
ZOOM_FACTOR_INCREASE = 'zfin',
ZOOM_FACTOR_DECREASE = 'zfdc',
ZOOM_FACTOR_RESET = 'zfrs',
ZOOM_TEXT_ONLY = 'zfto',
TOGGLE_FULLSCREEN = 'tgfs',
TOGGLE_AUTO_HIDE_INTERFACE_IN_FULLSCREEN = 'tgah',
CHECK_AUTO_HIDE_INTERFACE = 'cahi',
SHOW_PAGE_SOURCE = 'spgs',
EDIT_SHOW_FIND_GROUP = 'sfnd',
EDIT_HIDE_FIND_GROUP = 'hfnd',
EDIT_FIND_NEXT = 'fndn',
EDIT_FIND_PREVIOUS = 'fndp',
FIND_TEXT_CHANGED = 'ftxt',
SELECT_TAB = 'sltb',
};
static BLayoutItem*
layoutItemFor(BView* view)
{
BLayout* layout = view->Parent()->GetLayout();
int32 index = layout->IndexOfView(view);
return layout->ItemAt(index);
}
class BookmarkMenu : public BNavMenu {
public:
BookmarkMenu(const char* title, BHandler* target, const entry_ref* navDir)
:
BNavMenu(title, B_REFS_RECEIVED, target)
{
// Add these items here already, so the shortcuts work even when
// the menu has never been opened yet.
_AddStaticItems();
SetNavDir(navDir);
}
virtual void AttachedToWindow()
{
RemoveItems(0, CountItems(), true);
ForceRebuild();
BNavMenu::AttachedToWindow();
if (CountItems() > 0)
AddItem(new BSeparatorItem(), 0);
_AddStaticItems();
DoLayout();
}
private:
void _AddStaticItems()
{
AddItem(new BMenuItem("Manage bookmarks",
new BMessage(SHOW_BOOKMARKS), 'M'), 0);
AddItem(new BMenuItem("Bookmark this page",
new BMessage(CREATE_BOOKMARK), 'B'), 0);
}
};
class PageUserData : public BWebView::UserData {
public:
PageUserData(BView* focusedView)
:
fFocusedView(focusedView),
fPageIcon(NULL),
fURLInputSelectionStart(-1),
fURLInputSelectionEnd(-1)
{
}
~PageUserData()
{
delete fPageIcon;
}
void SetFocusedView(BView* focusedView)
{
fFocusedView = focusedView;
}
BView* FocusedView() const
{
return fFocusedView;
}
void SetPageIcon(const BBitmap* icon)
{
delete fPageIcon;
if (icon)
fPageIcon = new BBitmap(icon);
else
fPageIcon = NULL;
}
const BBitmap* PageIcon() const
{
return fPageIcon;
}
void SetURLInputContents(const char* text)
{
fURLInputContents = text;
}
const BString& URLInputContents() const
{
return fURLInputContents;
}
void SetURLInputSelection(int32 selectionStart, int32 selectionEnd)
{
fURLInputSelectionStart = selectionStart;
fURLInputSelectionEnd = selectionEnd;
}
int32 URLInputSelectionStart() const
{
return fURLInputSelectionStart;
}
int32 URLInputSelectionEnd() const
{
return fURLInputSelectionEnd;
}
private:
BView* fFocusedView;
BBitmap* fPageIcon;
BString fURLInputContents;
int32 fURLInputSelectionStart;
int32 fURLInputSelectionEnd;
};
// #pragma mark - BrowserWindow
BrowserWindow::BrowserWindow(BRect frame, SettingsMessage* appSettings,
const BString& url, uint32 interfaceElements, BWebView* webView)
:
BWebWindow(frame, kApplicationName,
B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
B_AUTO_UPDATE_SIZE_LIMITS | B_ASYNCHRONOUS_CONTROLS),
fIsFullscreen(false),
fInterfaceVisible(false),
fPulseRunner(NULL),
fVisibleInterfaceElements(interfaceElements),
fAppSettings(appSettings),
fZoomTextOnly(true),
fShowTabsIfSinglePageOpen(true),
fAutoHideInterfaceInFullscreenMode(false),
fAutoHidePointer(false)
{
// Begin listening to settings changes and read some current values.
fAppSettings->AddListener(BMessenger(this));
// fZoomTextOnly = fAppSettings->GetValue("zoom text only", fZoomTextOnly);
fShowTabsIfSinglePageOpen = fAppSettings->GetValue(
kSettingsKeyShowTabsIfSinglePageOpen, fShowTabsIfSinglePageOpen);
fAutoHidePointer = fAppSettings->GetValue(kSettingsKeyAutoHidePointer,
fAutoHidePointer);
fNewWindowPolicy = fAppSettings->GetValue(kSettingsKeyNewWindowPolicy,
(uint32)OpenStartPage);
fNewTabPolicy = fAppSettings->GetValue(kSettingsKeyNewTabPolicy,
(uint32)OpenBlankPage);
fStartPageURL = fAppSettings->GetValue(kSettingsKeyStartPageURL,
kDefaultStartPageURL);
fSearchPageURL = fAppSettings->GetValue(kSettingsKeySearchPageURL,
kDefaultSearchPageURL);
// Create the interface elements
BMessage* newTabMessage = new BMessage(NEW_TAB);
newTabMessage->AddString("url", "");
newTabMessage->AddPointer("window", this);
newTabMessage->AddBool("select", true);
fTabManager = new TabManager(BMessenger(this), newTabMessage);
// Menu
#if INTEGRATE_MENU_INTO_TAB_BAR
BMenu* mainMenu = fTabManager->Menu();
#else
BMenu* mainMenu = new BMenuBar("Main menu");
#endif
BMenu* menu = new BMenu("Window");
BMessage* newWindowMessage = new BMessage(NEW_WINDOW);
newWindowMessage->AddString("url", "");
BMenuItem* newItem = new BMenuItem("New window", newWindowMessage, 'N');
menu->AddItem(newItem);
newItem->SetTarget(be_app);
newItem = new BMenuItem("New tab", new BMessage(*newTabMessage), 'T');
menu->AddItem(newItem);
newItem->SetTarget(be_app);
menu->AddItem(new BMenuItem("Open location", new BMessage(OPEN_LOCATION),
'L'));
menu->AddSeparatorItem();
menu->AddItem(new BMenuItem("Close window", new BMessage(B_QUIT_REQUESTED),
'W', B_SHIFT_KEY));
menu->AddItem(new BMenuItem("Close tab", new BMessage(CLOSE_TAB), 'W'));
menu->AddSeparatorItem();
menu->AddItem(new BMenuItem("Downloads",
new BMessage(SHOW_DOWNLOAD_WINDOW), 'D'));
menu->AddItem(new BMenuItem("Settings",
new BMessage(SHOW_SETTINGS_WINDOW)));
BMenuItem* aboutItem = new BMenuItem("About",
new BMessage(B_ABOUT_REQUESTED));
menu->AddItem(aboutItem);
aboutItem->SetTarget(be_app);
menu->AddSeparatorItem();
BMenuItem* quitItem = new BMenuItem("Quit",
new BMessage(B_QUIT_REQUESTED), 'Q');
menu->AddItem(quitItem);
quitItem->SetTarget(be_app);
mainMenu->AddItem(menu);
menu = new BMenu("Edit");
menu->AddItem(fCutMenuItem = new BMenuItem("Cut", new BMessage(B_CUT),
'X'));
menu->AddItem(fCopyMenuItem = new BMenuItem("Copy", new BMessage(B_COPY),
'C'));
menu->AddItem(fPasteMenuItem = new BMenuItem("Paste", new BMessage(B_PASTE),
'V'));
menu->AddSeparatorItem();
menu->AddItem(new BMenuItem("Find", new BMessage(EDIT_SHOW_FIND_GROUP),
'F'));
menu->AddItem(fFindPreviousMenuItem = new BMenuItem("Find previous",
new BMessage(EDIT_FIND_PREVIOUS), 'G', B_SHIFT_KEY));
menu->AddItem(fFindNextMenuItem = new BMenuItem("Find next",
new BMessage(EDIT_FIND_NEXT), 'G'));
mainMenu->AddItem(menu);
fFindPreviousMenuItem->SetEnabled(false);
fFindNextMenuItem->SetEnabled(false);
menu = new BMenu("View");
menu->AddItem(new BMenuItem("Reload", new BMessage(RELOAD), 'R'));
menu->AddSeparatorItem();
menu->AddItem(new BMenuItem("Increase size",
new BMessage(ZOOM_FACTOR_INCREASE), '+'));
menu->AddItem(new BMenuItem("Decrease size",
new BMessage(ZOOM_FACTOR_DECREASE), '-'));
menu->AddItem(new BMenuItem("Reset size",
new BMessage(ZOOM_FACTOR_RESET), '0'));
fZoomTextOnlyMenuItem = new BMenuItem("Zoom text only",
new BMessage(ZOOM_TEXT_ONLY));
fZoomTextOnlyMenuItem->SetMarked(fZoomTextOnly);
menu->AddItem(fZoomTextOnlyMenuItem);
menu->AddSeparatorItem();
fFullscreenItem = new BMenuItem("Fullscreen",
new BMessage(TOGGLE_FULLSCREEN), B_RETURN);
menu->AddItem(fFullscreenItem);
menu->AddItem(new BMenuItem("Page source",
new BMessage(SHOW_PAGE_SOURCE), 'U'));
mainMenu->AddItem(menu);
fHistoryMenu = new BMenu("History");
fHistoryMenu->AddItem(fBackMenuItem = new BMenuItem("Back",
new BMessage(GO_BACK), B_LEFT_ARROW));
fHistoryMenu->AddItem(fForwardMenuItem = new BMenuItem("Forward",
new BMessage(GO_FORWARD), B_RIGHT_ARROW));
fHistoryMenu->AddSeparatorItem();
fHistoryMenuFixedItemCount = fHistoryMenu->CountItems();
mainMenu->AddItem(fHistoryMenu);
BPath bookmarkPath;
entry_ref bookmarkRef;
if (_BookmarkPath(bookmarkPath) == B_OK
&& get_ref_for_path(bookmarkPath.Path(), &bookmarkRef) == B_OK) {
BMenu* bookmarkMenu
= new BookmarkMenu("Bookmarks", this, &bookmarkRef);
mainMenu->AddItem(bookmarkMenu);
}
// Back, Forward, Stop & Home buttons
fBackButton = new IconButton("Back", 0, NULL, new BMessage(GO_BACK));
fBackButton->SetIcon(201);
fBackButton->TrimIcon();
fForwardButton = new IconButton("Forward", 0, NULL, new BMessage(GO_FORWARD));
fForwardButton->SetIcon(202);
fForwardButton->TrimIcon();
fStopButton = new IconButton("Stop", 0, NULL, new BMessage(STOP));
fStopButton->SetIcon(204);
fStopButton->TrimIcon();
fHomeButton = new IconButton("Home", 0, NULL, new BMessage(HOME));
fHomeButton->SetIcon(206);
fHomeButton->TrimIcon();
// URL input group
fURLInputGroup = new URLInputGroup(new BMessage(GOTO_URL));
// Status Bar
fStatusText = new BStringView("status", "");
fStatusText->SetAlignment(B_ALIGN_LEFT);
fStatusText->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
fStatusText->SetExplicitMinSize(BSize(150, 12));
// Prevent the window from growing to fit a long status message...
BFont font(be_plain_font);
font.SetSize(ceilf(font.Size() * 0.8));
fStatusText->SetFont(&font, B_FONT_SIZE);
// Loading progress bar
fLoadingProgressBar = new BStatusBar("progress");
fLoadingProgressBar->SetMaxValue(100);
fLoadingProgressBar->Hide();
fLoadingProgressBar->SetBarHeight(12);
const float kInsetSpacing = 3;
const float kElementSpacing = 5;
// Find group
fFindTextControl = new BTextControl("find", "Find:", "",
new BMessage(EDIT_FIND_NEXT));
fFindTextControl->SetModificationMessage(new BMessage(FIND_TEXT_CHANGED));
fFindPreviousButton = new BButton("Previous",
new BMessage(EDIT_FIND_PREVIOUS));
fFindNextButton = new BButton("Next", new BMessage(EDIT_FIND_NEXT));
fFindCloseButton = new BButton("Close",
new BMessage(EDIT_HIDE_FIND_GROUP));
fFindCaseSensitiveCheckBox = new BCheckBox("Match case");
BGroupLayout* findGroup = BLayoutBuilder::Group<>(B_VERTICAL, 0.0)
.Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER))
.Add(BGroupLayoutBuilder(B_HORIZONTAL, kElementSpacing)
.Add(fFindTextControl)
.Add(fFindPreviousButton)
.Add(fFindNextButton)
.Add(fFindCaseSensitiveCheckBox)
.Add(BSpaceLayoutItem::CreateGlue())
.Add(fFindCloseButton)
.SetInsets(kInsetSpacing, kInsetSpacing,
kInsetSpacing, kInsetSpacing)
)
;
// Navigation group
BGroupLayout* navigationGroup = BLayoutBuilder::Group<>(B_VERTICAL, 0.0)
.Add(BLayoutBuilder::Group<>(B_HORIZONTAL, kElementSpacing)
.Add(fBackButton)
.Add(fForwardButton)
.Add(fStopButton)
.Add(fHomeButton)
.Add(fURLInputGroup)
.SetInsets(kInsetSpacing, kInsetSpacing, kInsetSpacing,
kInsetSpacing)
)
.Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER))
;
// Status bar group
BGroupLayout* statusGroup = BLayoutBuilder::Group<>(B_VERTICAL, 0.0)
.Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER))
.Add(BLayoutBuilder::Group<>(B_HORIZONTAL, kElementSpacing)
.Add(fStatusText)
.Add(fLoadingProgressBar, 0.2)
.AddStrut(12 - kElementSpacing)
.SetInsets(kInsetSpacing, 0, kInsetSpacing, 0)
)
;
BitmapButton* toggleFullscreenButton = new BitmapButton(kWindowIconBits,
kWindowIconWidth, kWindowIconHeight, kWindowIconFormat,
new BMessage(TOGGLE_FULLSCREEN));
toggleFullscreenButton->SetBackgroundMode(BitmapButton::MENUBAR_BACKGROUND);
BGroupLayout* menuBarGroup = BLayoutBuilder::Group<>(B_HORIZONTAL)
.Add(mainMenu)
.Add(toggleFullscreenButton, 0.0f)
;
// Layout
AddChild(BLayoutBuilder::Group<>(B_VERTICAL, 0.0)
#if !INTEGRATE_MENU_INTO_TAB_BAR
.Add(menuBarGroup)
#endif
.Add(fTabManager->TabGroup())
.Add(navigationGroup)
.Add(fTabManager->ContainerView())
.Add(findGroup)
.Add(statusGroup)
);
// TODO: Small hack for fixing some invalidation problems with BMenuBar...
mainMenu->SetFlags(mainMenu->Flags() | B_FULL_UPDATE_ON_RESIZE);
mainMenu->SetViewColor(B_TRANSPARENT_COLOR);
fURLInputGroup->MakeFocus(true);
fMenuGroup = menuBarGroup;
fTabGroup = fTabManager->TabGroup()->GetLayout();
fNavigationGroup = navigationGroup;
fFindGroup = findGroup;
fStatusGroup = statusGroup;
fToggleFullscreenButton = layoutItemFor(toggleFullscreenButton);
fFindGroup->SetVisible(false);
fToggleFullscreenButton->SetVisible(false);
CreateNewTab(url, true, webView);
_ShowInterface(true);
_SetAutoHideInterfaceInFullscreen(fAppSettings->GetValue(
kSettingsKeyAutoHideInterfaceInFullscreenMode,
fAutoHideInterfaceInFullscreenMode));
AddShortcut('F', B_COMMAND_KEY | B_SHIFT_KEY,
new BMessage(EDIT_HIDE_FIND_GROUP));
// TODO: Should be a different shortcut, H is usually for Find selection.
AddShortcut('H', B_COMMAND_KEY, new BMessage(HOME));
// Add shortcuts to select a particular tab
for (int32 i = 1; i <= 9; i++) {
BMessage *selectTab = new BMessage(SELECT_TAB);
selectTab->AddInt32("tab index", i - 1);
char numStr[2];
snprintf(numStr, sizeof(numStr), "%d", (int) i);
AddShortcut(numStr[0], B_COMMAND_KEY, selectTab);
}
be_app->PostMessage(WINDOW_OPENED);
}
BrowserWindow::~BrowserWindow()
{
fAppSettings->RemoveListener(BMessenger(this));
delete fTabManager;
delete fPulseRunner;
}
void
BrowserWindow::DispatchMessage(BMessage* message, BHandler* target)
{
const char* bytes;
uint32 modifiers;
if ((message->what == B_KEY_DOWN || message->what == B_UNMAPPED_KEY_DOWN)
&& message->FindString("bytes", &bytes) == B_OK
&& message->FindInt32("modifiers", (int32*)&modifiers) == B_OK) {
modifiers = modifiers & 0x000000ff;
if (bytes[0] == B_LEFT_ARROW && modifiers == B_COMMAND_KEY) {
PostMessage(GO_BACK);
return;
} else if (bytes[0] == B_RIGHT_ARROW && modifiers == B_COMMAND_KEY) {
PostMessage(GO_FORWARD);
return;
} else if (bytes[0] == B_ESCAPE) {
PostMessage(STOP);
return;
} else if (bytes[0] == B_FUNCTION_KEY) {
// Some function key Firefox compatibility
int32 key;
if (message->FindInt32("key", &key) == B_OK) {
switch (key) {
case B_F5_KEY:
PostMessage(RELOAD);
break;
case B_F11_KEY:
PostMessage(TOGGLE_FULLSCREEN);
break;
default:
break;
}
}
} else if (target == fURLInputGroup->TextView()) {
// Handle B_RETURN in the URL text control. This is the easiest
// way to react *only* when the user presses the return key in the
// address bar, as opposed to trying to load whatever is in there
// when the text control just goes out of focus.
if (bytes[0] == B_RETURN) {
// Do it in such a way that the user sees the Go-button go down.
_InvokeButtonVisibly(fURLInputGroup->GoButton());
return;
}
} else if (target == fFindTextControl->TextView()) {
// Handle B_RETURN when the find text control has focus.
if (bytes[0] == B_RETURN) {
if ((modifiers & B_SHIFT_KEY) != 0)
_InvokeButtonVisibly(fFindPreviousButton);
else
_InvokeButtonVisibly(fFindNextButton);
return;
} else if (bytes[0] == B_ESCAPE) {
_InvokeButtonVisibly(fFindCloseButton);
return;
}
}
}
if (message->what == B_MOUSE_MOVED || message->what == B_MOUSE_DOWN
|| message->what == B_MOUSE_UP) {
message->FindPoint("where", &fLastMousePos);
if (message->FindInt64("when", &fLastMouseMovedTime) != B_OK)
fLastMouseMovedTime = system_time();
_CheckAutoHideInterface();
}
BWebWindow::DispatchMessage(message, target);
}
void
BrowserWindow::MessageReceived(BMessage* message)
{
switch (message->what) {
case OPEN_LOCATION:
_ShowInterface(true);
if (fURLInputGroup->TextView()->IsFocus())
fURLInputGroup->TextView()->SelectAll();
else
fURLInputGroup->MakeFocus(true);
break;
case RELOAD:
CurrentWebView()->Reload();
break;
case GOTO_URL:
{
BString url;
if (message->FindString("url", &url) != B_OK)
url = fURLInputGroup->Text();
_SetPageIcon(CurrentWebView(), NULL);
BString newUrl = _SmartURLHandler(url);
if (newUrl != url)
fURLInputGroup->TextView()->SetText(newUrl);
CurrentWebView()->LoadURL(newUrl.String());
break;
}
case GO_BACK:
CurrentWebView()->GoBack();
break;
case GO_FORWARD:
CurrentWebView()->GoForward();
break;
case STOP:
CurrentWebView()->StopLoading();
break;
case HOME:
CurrentWebView()->LoadURL(fStartPageURL);
break;
case CLEAR_HISTORY: {
BrowsingHistory* history = BrowsingHistory::DefaultInstance();
if (history->CountItems() == 0)
break;
BAlert* alert = new BAlert("Confirmation", "Do you really want to "
"clear the browsing history?", "Clear", "Cancel");
if (alert->Go() == 0)
history->Clear();
break;
}
case CREATE_BOOKMARK:
_CreateBookmark();
break;
case SHOW_BOOKMARKS:
_ShowBookmarks();
break;
case B_REFS_RECEIVED:
{
// Currently the only source of these messages is the bookmarks menu.
// Filter refs into URLs, this also gets rid of refs for folders.
// For clicks on sub-folders in the bookmarks menu, we have Tracker
// open the corresponding folder.
entry_ref ref;
uint32 addedCount = 0;
for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) {
BEntry entry(&ref);
uint32 addedSubCount = 0;
if (entry.IsDirectory()) {
BDirectory directory(&entry);
_AddBookmarkURLsRecursively(directory, message,
addedSubCount);
} else {
BFile file(&ref, B_READ_ONLY);
BString url;
if (_ReadURLAttr(file, url)) {
message->AddString("url", url.String());
addedSubCount++;
}
}
if (addedSubCount == 0) {
// Don't know what to do with this entry, just pass it
// on to the system to handle. Note that this may result
// in us opening other supported files via the application
// mechanism.
be_roster->Launch(&ref);
}
addedCount += addedSubCount;
}
message->RemoveName("refs");
if (addedCount > 10) {
BString string;
string << "Do you want to open " << addedCount;
string << " bookmarks all at once?";
BAlert* alert = new BAlert("Open bookmarks confirmation",
string.String(), "Cancel", "Open all");
if (alert->Go() == 0)
break;
}
be_app->PostMessage(message);
break;
}
case B_SIMPLE_DATA:
{
// User possibly dropped files on this window.
// If there is more than one entry_ref, let the app handle it
// (open one new page per ref). If there is one ref, open it in
// this window.
type_code type;
int32 countFound;
if (message->GetInfo("refs", &type, &countFound) != B_OK
|| type != B_REF_TYPE) {
break;
}
if (countFound > 1) {
message->what = B_REFS_RECEIVED;
be_app->PostMessage(message);
break;
}
entry_ref ref;
if (message->FindRef("refs", &ref) != B_OK)
break;
BEntry entry(&ref, true);
BPath path;
if (!entry.Exists() || entry.GetPath(&path) != B_OK)
break;
CurrentWebView()->LoadURL(path.Path());
break;
}
case ZOOM_FACTOR_INCREASE:
CurrentWebView()->IncreaseZoomFactor(fZoomTextOnly);
break;
case ZOOM_FACTOR_DECREASE:
CurrentWebView()->DecreaseZoomFactor(fZoomTextOnly);
break;
case ZOOM_FACTOR_RESET:
CurrentWebView()->ResetZoomFactor();
break;
case ZOOM_TEXT_ONLY:
fZoomTextOnly = !fZoomTextOnly;
fZoomTextOnlyMenuItem->SetMarked(fZoomTextOnly);
// TODO: Would be nice to have an instant update if the page is
// already zoomed.
break;
case TOGGLE_FULLSCREEN:
ToggleFullscreen();
break;
case TOGGLE_AUTO_HIDE_INTERFACE_IN_FULLSCREEN:
_SetAutoHideInterfaceInFullscreen(
!fAutoHideInterfaceInFullscreenMode);
break;
case CHECK_AUTO_HIDE_INTERFACE:
_CheckAutoHideInterface();
break;
case SHOW_PAGE_SOURCE:
CurrentWebView()->WebPage()->SendPageSource();
break;
case B_PAGE_SOURCE_RESULT:
_HandlePageSourceResult(message);
break;
case EDIT_FIND_NEXT:
CurrentWebView()->FindString(fFindTextControl->Text(), true,
fFindCaseSensitiveCheckBox->Value());
break;
case FIND_TEXT_CHANGED:
{
bool findTextAvailable = strlen(fFindTextControl->Text()) > 0;
fFindPreviousMenuItem->SetEnabled(findTextAvailable);
fFindNextMenuItem->SetEnabled(findTextAvailable);
break;
}
case EDIT_FIND_PREVIOUS:
CurrentWebView()->FindString(fFindTextControl->Text(), false,
fFindCaseSensitiveCheckBox->Value());
break;
case EDIT_SHOW_FIND_GROUP:
if (!fFindGroup->IsVisible())
fFindGroup->SetVisible(true);
fFindTextControl->MakeFocus(true);
break;
case EDIT_HIDE_FIND_GROUP:
if (fFindGroup->IsVisible()) {
fFindGroup->SetVisible(false);
if (CurrentWebView() != NULL)
CurrentWebView()->MakeFocus(true);
}
break;
case B_CUT:
case B_COPY:
case B_PASTE:
{
BTextView* textView = dynamic_cast<BTextView*>(CurrentFocus());
if (textView != NULL)
textView->MessageReceived(message);
else if (CurrentWebView() != NULL)
CurrentWebView()->MessageReceived(message);
break;
}
case B_EDITING_CAPABILITIES_RESULT:
{
BWebView* webView;
if (message->FindPointer("view",
reinterpret_cast<void**>(&webView)) != B_OK
|| webView != CurrentWebView()) {
break;
}
bool canCut;
bool canCopy;
bool canPaste;
if (message->FindBool("can cut", &canCut) != B_OK)
canCut = false;
if (message->FindBool("can copy", &canCopy) != B_OK)
canCopy = false;
if (message->FindBool("can paste", &canPaste) != B_OK)
canPaste = false;
fCutMenuItem->SetEnabled(canCut);
fCopyMenuItem->SetEnabled(canCopy);
fPasteMenuItem->SetEnabled(canPaste);
break;
}
case SHOW_DOWNLOAD_WINDOW:
case SHOW_SETTINGS_WINDOW:
message->AddUInt32("workspaces", Workspaces());
be_app->PostMessage(message);
break;
case CLOSE_TAB:
if (fTabManager->CountTabs() > 1) {
int32 index;
if (message->FindInt32("tab index", &index) != B_OK)
index = fTabManager->SelectedTabIndex();
_ShutdownTab(index);
_UpdateTabGroupVisibility();
} else
PostMessage(B_QUIT_REQUESTED);
break;
case SELECT_TAB:
{
int32 index;
if (message->FindInt32("tab index", &index) == B_OK
&& fTabManager->SelectedTabIndex() != index
&& fTabManager->CountTabs() > index) {
fTabManager->SelectTab(index);
}
break;
}
case TAB_CHANGED:
{
// This message may be received also when the last tab closed,
// i.e. with index == -1.
int32 index;
if (message->FindInt32("tab index", &index) != B_OK)
index = -1;
_TabChanged(index);
break;
}
case SETTINGS_VALUE_CHANGED:
{
BString name;
if (message->FindString("name", &name) != B_OK)
break;
bool flag;
BString string;
uint32 value;
if (name == kSettingsKeyShowTabsIfSinglePageOpen
&& message->FindBool("value", &flag) == B_OK) {
if (fShowTabsIfSinglePageOpen != flag) {
fShowTabsIfSinglePageOpen = flag;
_UpdateTabGroupVisibility();
}
} else if (name == kSettingsKeyAutoHidePointer
&& message->FindBool("value", &flag) == B_OK) {
fAutoHidePointer = flag;
if (CurrentWebView())
CurrentWebView()->SetAutoHidePointer(fAutoHidePointer);
} else if (name == kSettingsKeyStartPageURL
&& message->FindString("value", &string) == B_OK) {
fStartPageURL = string;
} else if (name == kSettingsKeySearchPageURL
&& message->FindString("value", &string) == B_OK) {
fSearchPageURL = string;
} else if (name == kSettingsKeyNewWindowPolicy
&& message->FindUInt32("value", &value) == B_OK) {
fNewWindowPolicy = value;
} else if (name == kSettingsKeyNewTabPolicy
&& message->FindUInt32("value", &value) == B_OK) {
fNewTabPolicy = value;
} else if (name == kSettingsKeyAutoHideInterfaceInFullscreenMode
&& message->FindBool("value", &flag) == B_OK) {
_SetAutoHideInterfaceInFullscreen(flag);
}
break;
}
default:
BWebWindow::MessageReceived(message);
break;
}
}
bool
BrowserWindow::QuitRequested()
{
// TODO: Check for modified form data and ask user for confirmation, etc.
// Iterate over all tabs to delete all BWebViews.
// Do this here, so WebKit tear down happens earlier.
SetCurrentWebView(NULL);
while (fTabManager->CountTabs() > 0)
_ShutdownTab(0);
BMessage message(WINDOW_CLOSED);
message.AddRect("window frame", WindowFrame());
be_app->PostMessage(&message);
return true;
}
void
BrowserWindow::MenusBeginning()
{
_UpdateHistoryMenu();
_UpdateClipboardItems();
_ShowInterface(true);
}
void
BrowserWindow::Zoom(BPoint origin, float width, float height)
{
ToggleFullscreen();
}
void
BrowserWindow::ScreenChanged(BRect screenSize, color_space format)
{
if (fIsFullscreen)
_ResizeToScreen();
}
void
BrowserWindow::WorkspacesChanged(uint32 oldWorkspaces, uint32 newWorkspaces)
{
if (fIsFullscreen)
_ResizeToScreen();
}
static bool
viewIsChild(const BView* parent, const BView* view)
{
if (parent == view)
return true;
int32 count = parent->CountChildren();
for (int32 i = 0; i < count; i++) {
BView* child = parent->ChildAt(i);
if (viewIsChild(child, view))
return true;
}
return false;
}
void
BrowserWindow::SetCurrentWebView(BWebView* webView)
{
if (webView == CurrentWebView())
return;
if (CurrentWebView() != NULL) {
// Remember the currently focused view before switching tabs,
// so that we can revert the focus when switching back to this tab
// later.
PageUserData* userData = static_cast<PageUserData*>(
CurrentWebView()->GetUserData());
if (userData == NULL) {
userData = new PageUserData(CurrentFocus());
CurrentWebView()->SetUserData(userData);
}
userData->SetFocusedView(CurrentFocus());
userData->SetURLInputContents(fURLInputGroup->Text());
int32 selectionStart;
int32 selectionEnd;
fURLInputGroup->TextView()->GetSelection(&selectionStart,
&selectionEnd);
userData->SetURLInputSelection(selectionStart, selectionEnd);
}
BWebWindow::SetCurrentWebView(webView);
if (webView != NULL) {
webView->SetAutoHidePointer(fAutoHidePointer);
_UpdateTitle(webView->MainFrameTitle());
// Restore the previous focus or focus the web view.
PageUserData* userData = static_cast<PageUserData*>(
webView->GetUserData());
BView* focusedView = NULL;
if (userData != NULL)
focusedView = userData->FocusedView();
if (focusedView != NULL
&& viewIsChild(GetLayout()->View(), focusedView)) {
focusedView->MakeFocus(true);
} else
webView->MakeFocus(true);
if (userData != NULL) {
fURLInputGroup->SetPageIcon(userData->PageIcon());
if (userData->URLInputContents().Length())
fURLInputGroup->SetText(userData->URLInputContents());
else
fURLInputGroup->SetText(webView->MainFrameURL());
if (userData->URLInputSelectionStart() >= 0) {
fURLInputGroup->TextView()->Select(
userData->URLInputSelectionStart(),
userData->URLInputSelectionEnd());
}
} else {
fURLInputGroup->SetPageIcon(NULL);
fURLInputGroup->SetText(webView->MainFrameURL());
}
// Trigger update of the interface to the new page, by requesting
// to resend all notifications.
webView->WebPage()->ResendNotifications();
} else
_UpdateTitle("");
}
bool
BrowserWindow::IsBlankTab() const
{
if (CurrentWebView() == NULL)
return false;
BString requestedURL = CurrentWebView()->MainFrameRequestedURL();
return requestedURL.Length() == 0
|| requestedURL == _NewTabURL(fTabManager->CountTabs() == 1);
}
void
BrowserWindow::CreateNewTab(const BString& _url, bool select, BWebView* webView)
{
bool applyNewPagePolicy = webView == NULL;
// Executed in app thread (new BWebPage needs to be created in app thread).
if (webView == NULL)
webView = new BWebView("web view");
bool isNewWindow = fTabManager->CountTabs() == 0;
fTabManager->AddTab(webView, "New tab");
BString url(_url);
if (applyNewPagePolicy && url.Length() == 0)
url = _NewTabURL(isNewWindow);
if (url.Length() > 0)
webView->LoadURL(url.String());
if (select) {
fTabManager->SelectTab(fTabManager->CountTabs() - 1);
SetCurrentWebView(webView);
webView->WebPage()->ResendNotifications();
fURLInputGroup->SetPageIcon(NULL);
fURLInputGroup->SetText(url.String());
fURLInputGroup->MakeFocus(true);
}
_ShowInterface(true);
_UpdateTabGroupVisibility();
}
BRect
BrowserWindow::WindowFrame() const
{
if (fIsFullscreen)
return fNonFullscreenWindowFrame;
else
return Frame();
}
void
BrowserWindow::ToggleFullscreen()
{
if (fIsFullscreen) {
MoveTo(fNonFullscreenWindowFrame.LeftTop());
ResizeTo(fNonFullscreenWindowFrame.Width(),
fNonFullscreenWindowFrame.Height());
SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_MOVABLE));
SetLook(B_DOCUMENT_WINDOW_LOOK);
_ShowInterface(true);
} else {
fNonFullscreenWindowFrame = Frame();
_ResizeToScreen();
SetFlags(Flags() | (B_NOT_RESIZABLE | B_NOT_MOVABLE));
SetLook(B_TITLED_WINDOW_LOOK);
}
fIsFullscreen = !fIsFullscreen;
fFullscreenItem->SetMarked(fIsFullscreen);
fToggleFullscreenButton->SetVisible(fIsFullscreen);
}
// #pragma mark - Notification API
void
BrowserWindow::NavigationRequested(const BString& url, BWebView* view)
{
}
void
BrowserWindow::NewWindowRequested(const BString& url, bool primaryAction)
{
// Always open new windows in the application thread, since
// creating a BWebView will try to grab the application lock.
// But our own WebPage may already try to lock us from within
// the application thread -> dead-lock. Thus we can't wait for
// a reply here.
BMessage message(NEW_TAB);
message.AddPointer("window", this);
message.AddString("url", url);
message.AddBool("select", primaryAction);
be_app->PostMessage(&message);
}
void
BrowserWindow::NewPageCreated(BWebView* view, BRect windowFrame,
bool modalDialog, bool resizable, bool activate)
{
if (windowFrame.IsValid()) {
BrowserWindow* window = new BrowserWindow(windowFrame, fAppSettings,
BString(), INTERFACE_ELEMENT_STATUS, view);
window->Show();
} else
CreateNewTab(BString(), activate, view);
}
void
BrowserWindow::CloseWindowRequested(BWebView* view)
{
int32 index = fTabManager->TabForView(view);
if (index < 0) {
// Tab is already gone.
return;
}
BMessage message(CLOSE_TAB);
message.AddInt32("tab index", index);
PostMessage(&message, this);
}
void
BrowserWindow::LoadNegotiating(const BString& url, BWebView* view)
{
if (view != CurrentWebView())
return;
fURLInputGroup->SetText(url.String());
BString status("Requesting: ");
status << url;
view->WebPage()->SetStatusMessage(status);
}
void
BrowserWindow::LoadCommitted(const BString& url, BWebView* view)
{
if (view != CurrentWebView())
return;
// This hook is invoked when the load is commited.
fURLInputGroup->SetText(url.String());
BString status("Loading: ");
status << url;
view->WebPage()->SetStatusMessage(status);
}
void
BrowserWindow::LoadProgress(float progress, BWebView* view)
{
if (view != CurrentWebView())
return;
if (progress < 100 && fLoadingProgressBar->IsHidden())
_ShowProgressBar(true);
else if (progress == 100 && !fLoadingProgressBar->IsHidden())
_ShowProgressBar(false);
fLoadingProgressBar->SetTo(progress);
}
void
BrowserWindow::LoadFailed(const BString& url, BWebView* view)
{
if (view != CurrentWebView())
return;
BString status(url);
status << " failed.";
view->WebPage()->SetStatusMessage(status);
if (!fLoadingProgressBar->IsHidden())
fLoadingProgressBar->Hide();
}
void
BrowserWindow::LoadFinished(const BString& url, BWebView* view)
{
if (view != CurrentWebView())
return;
BString status(url);
status << " finished.";
view->WebPage()->SetStatusMessage(status);
if (!fLoadingProgressBar->IsHidden())
fLoadingProgressBar->Hide();
NavigationCapabilitiesChanged(fBackButton->IsEnabled(),
fForwardButton->IsEnabled(), false, view);
int32 tabIndex = fTabManager->TabForView(view);
if (tabIndex > 0 && strcmp("New tab", fTabManager->TabLabel(tabIndex)) == 0)
fTabManager->SetTabLabel(tabIndex, url);
}
void
BrowserWindow::MainDocumentError(const BString& failingURL,
const BString& localizedDescription, BWebView* view)
{
// Make sure we show the page that contains the view.
if (!_ShowPage(view))
return;
BWebWindow::MainDocumentError(failingURL, localizedDescription, view);
// TODO: Remove the failing URL from the BrowsingHistory!
}
void
BrowserWindow::TitleChanged(const BString& title, BWebView* view)
{
int32 tabIndex = fTabManager->TabForView(view);
if (tabIndex < 0)
return;
fTabManager->SetTabLabel(tabIndex, title);
if (view != CurrentWebView())
return;
_UpdateTitle(title);
}
void
BrowserWindow::IconReceived(const BBitmap* icon, BWebView* view)
{
// The view may already be gone, since this notification arrives
// asynchronously.
if (!fTabManager->HasView(view))
return;
_SetPageIcon(view, icon);
}
void
BrowserWindow::ResizeRequested(float width, float height, BWebView* view)
{
if (view != CurrentWebView())
return;
// Ignore request when there is more than one BWebView embedded.
if (fTabManager->CountTabs() > 1)
return;
// Make sure the new frame is not larger than the screen frame minus
// window decorator border.
BScreen screen(this);
BRect screenFrame = screen.Frame();
BRect decoratorFrame = DecoratorFrame();
BRect frame = Frame();
screenFrame.left += decoratorFrame.left - frame.left;
screenFrame.right += decoratorFrame.right - frame.right;
screenFrame.top += decoratorFrame.top - frame.top;
screenFrame.bottom += decoratorFrame.bottom - frame.bottom;
width = min_c(width, screen.Frame().Width());
height = min_c(height, screen.Frame().Height());
frame.right = frame.left + width;
frame.bottom = frame.top + height;
// frame is now not larger than screenFrame, but may still be partly outside
if (!screenFrame.Contains(frame)) {
if (frame.left < screenFrame.left)
frame.OffsetBy(screenFrame.left - frame.left, 0);
else if (frame.right > screenFrame.right)
frame.OffsetBy(screenFrame.right - frame.right, 0);
if (frame.top < screenFrame.top)
frame.OffsetBy(screenFrame.top - frame.top, 0);
else if (frame.bottom > screenFrame.bottom)
frame.OffsetBy(screenFrame.bottom - frame.bottom, 0);
}
MoveTo(frame.left, frame.top);
ResizeTo(width, height);
}
void
BrowserWindow::SetToolBarsVisible(bool flag, BWebView* view)
{
// TODO
// TODO: Ignore request when there is more than one BWebView embedded!
}
void
BrowserWindow::SetStatusBarVisible(bool flag, BWebView* view)
{
// TODO
// TODO: Ignore request when there is more than one BWebView embedded!
}
void
BrowserWindow::SetMenuBarVisible(bool flag, BWebView* view)
{
// TODO
// TODO: Ignore request when there is more than one BWebView embedded!
}
void
BrowserWindow::SetResizable(bool flag, BWebView* view)
{
// TODO: Ignore request when there is more than one BWebView embedded!
if (flag)
SetFlags(Flags() & ~B_NOT_RESIZABLE);
else
SetFlags(Flags() | B_NOT_RESIZABLE);
}
void
BrowserWindow::StatusChanged(const BString& statusText, BWebView* view)
{
if (view != CurrentWebView())
return;
if (fStatusText)
fStatusText->SetText(statusText.String());
}
void
BrowserWindow::NavigationCapabilitiesChanged(bool canGoBackward,
bool canGoForward, bool canStop, BWebView* view)
{
if (view != CurrentWebView())
return;
fBackButton->SetEnabled(canGoBackward);
fForwardButton->SetEnabled(canGoForward);
fStopButton->SetEnabled(canStop);
fBackMenuItem->SetEnabled(canGoBackward);
fForwardMenuItem->SetEnabled(canGoForward);
}
void
BrowserWindow::UpdateGlobalHistory(const BString& url)
{
BrowsingHistory::DefaultInstance()->AddItem(BrowsingHistoryItem(url));
}
bool
BrowserWindow::AuthenticationChallenge(BString message, BString& inOutUser,
BString& inOutPassword, bool& inOutRememberCredentials, uint32 failureCount,
BWebView* view)
{
CredentialsStorage* persistentStorage
= CredentialsStorage::PersistentInstance();
CredentialsStorage* sessionStorage
= CredentialsStorage::SessionInstance();
// TODO: Using the message as key here is not so smart.
HashKeyString key(message);
if (failureCount == 0) {
if (persistentStorage->Contains(key)) {
Credentials credentials = persistentStorage->GetCredentials(key);
inOutUser = credentials.Username();
inOutPassword = credentials.Password();
return true;
} else if (sessionStorage->Contains(key)) {
Credentials credentials = sessionStorage->GetCredentials(key);
inOutUser = credentials.Username();
inOutPassword = credentials.Password();
return true;
}
}
// Switch to the page for which this authentication is required.
if (!_ShowPage(view))
return false;
AuthenticationPanel* panel = new AuthenticationPanel(Frame());
// Panel auto-destructs.
bool success = panel->getAuthentication(message, inOutUser, inOutPassword,
inOutRememberCredentials, failureCount > 0, inOutUser, inOutPassword,
&inOutRememberCredentials);
if (success) {
Credentials credentials(inOutUser, inOutPassword);
if (inOutRememberCredentials)
persistentStorage->PutCredentials(key, credentials);
else
sessionStorage->PutCredentials(key, credentials);
}
return success;
}
// #pragma mark - private
void
BrowserWindow::_UpdateTitle(const BString& title)
{
BString windowTitle = title;
if (windowTitle.Length() > 0)
windowTitle << " - ";
windowTitle << kApplicationName;
SetTitle(windowTitle.String());
}
void
BrowserWindow::_UpdateTabGroupVisibility()
{
if (Lock()) {
if (fInterfaceVisible)
fTabGroup->SetVisible(_TabGroupShouldBeVisible());
fTabManager->SetCloseButtonsAvailable(fTabManager->CountTabs() > 1);
Unlock();
}
}
bool
BrowserWindow::_TabGroupShouldBeVisible() const
{
return (fShowTabsIfSinglePageOpen || fTabManager->CountTabs() > 1)
&& (fVisibleInterfaceElements & INTERFACE_ELEMENT_TABS) != 0;
}
void
BrowserWindow::_ShutdownTab(int32 index)
{
BView* view = fTabManager->RemoveTab(index);
BWebView* webView = dynamic_cast<BWebView*>(view);
if (webView == CurrentWebView())
SetCurrentWebView(NULL);
if (webView != NULL)
webView->Shutdown();
else
delete view;
}
void
BrowserWindow::_TabChanged(int32 index)
{
SetCurrentWebView(dynamic_cast<BWebView*>(fTabManager->ViewForTab(index)));
}
status_t
BrowserWindow::_BookmarkPath(BPath& path) const
{
status_t ret = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
if (ret != B_OK)
return ret;
ret = path.Append(kApplicationName);
if (ret != B_OK)
return ret;
ret = path.Append("Bookmarks");
if (ret != B_OK)
return ret;
return create_directory(path.Path(), 0777);
}
void
BrowserWindow::_CreateBookmark()
{
BPath path;
status_t status = _BookmarkPath(path);
if (status != B_OK) {
BString message("There was an error retrieving the bookmark "
"folder.\n\n");
message << "Error: " << strerror(status);
BAlert* alert = new BAlert("Bookmark error", message.String(), "OK",
NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
alert->Go();
return;
}
BWebView* webView = CurrentWebView();
BString url(webView->MainFrameURL());
// Create a bookmark file
BFile bookmarkFile;
BString bookmarkName(webView->MainFrameTitle());
if (bookmarkName.Length() == 0) {
bookmarkName = url;
int32 leafPos = bookmarkName.FindLast('/');
if (leafPos >= 0)
bookmarkName.Remove(0, leafPos + 1);
}
// Make sure the bookmark title does not contain chars that are not
// allowed in file names.
bookmarkName.ReplaceAll('/', '-');
// Check that the bookmark exists nowhere in the bookmark hierarchy,
// though the intended file name must match, we don't search the stored
// URLs, only for matching file names.
BDirectory directory(path.Path());
if (status == B_OK && _CheckBookmarkExists(directory, bookmarkName, url)) {
BString message("A bookmark for this page (");
message << bookmarkName;
message << ") already exists.";
BAlert* alert = new BAlert("Bookmark info", message.String(), "OK");
alert->Go();
return;
}
BPath entryPath(path);
status = entryPath.Append(bookmarkName);
BEntry entry;
if (status == B_OK)
status = entry.SetTo(entryPath.Path(), true);
if (status == B_OK) {
int32 tries = 1;
while (entry.Exists()) {
// Find a unique name for the bookmark, there may still be a
// file in the way that stores a different URL.
bookmarkName = webView->MainFrameTitle();
bookmarkName << " " << tries++;
entryPath = path;
status = entryPath.Append(bookmarkName);
if (status == B_OK)
status = entry.SetTo(entryPath.Path(), true);
if (status != B_OK)
break;
}
}
if (status == B_OK) {
status = bookmarkFile.SetTo(&entry,
B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
}
// Write bookmark meta data
if (status == B_OK)
status = bookmarkFile.WriteAttrString("META:url", &url);
if (status == B_OK) {
BString title = webView->MainFrameTitle();
bookmarkFile.WriteAttrString("META:title", &title);
}
BNodeInfo nodeInfo(&bookmarkFile);
if (status == B_OK) {
status = nodeInfo.SetType("application/x-vnd.Be-bookmark");
if (status == B_OK) {
PageUserData* userData = static_cast<PageUserData*>(
webView->GetUserData());
if (userData != NULL && userData->PageIcon() != NULL) {
BBitmap miniIcon(BRect(0, 0, 15, 15), B_BITMAP_NO_SERVER_LINK,
B_CMAP8);
status_t ret = miniIcon.ImportBits(userData->PageIcon());
if (ret == B_OK)
ret = nodeInfo.SetIcon(&miniIcon, B_MINI_ICON);
if (ret != B_OK) {
fprintf(stderr, "Failed to store mini icon for bookmark: "
"%s\n", strerror(ret));
}
BBitmap largeIcon(BRect(0, 0, 31, 31), B_BITMAP_NO_SERVER_LINK,
B_CMAP8);
// TODO: Store 32x32 favicon which is often provided by sites.
const uint8* src = (const uint8*)miniIcon.Bits();
uint32 srcBPR = miniIcon.BytesPerRow();
uint8* dst = (uint8*)largeIcon.Bits();
uint32 dstBPR = largeIcon.BytesPerRow();
for (uint32 y = 0; y < 16; y++) {
const uint8* s = src;
uint8* d = dst;
for (uint32 x = 0; x < 16; x++) {
*d++ = *s;
*d++ = *s++;
}
dst += dstBPR;
s = src;
for (uint32 x = 0; x < 16; x++) {
*d++ = *s;
*d++ = *s++;
}
dst += dstBPR;
src += srcBPR;
}
if (ret == B_OK)
ret = nodeInfo.SetIcon(&largeIcon, B_LARGE_ICON);
if (ret != B_OK) {
fprintf(stderr, "Failed to store large icon for bookmark: "
"%s\n", strerror(ret));
}
}
}
}
if (status != B_OK) {
BString message("There was an error creating the bookmark "
"file.\n\n");
message << "Error: " << strerror(status);
BAlert* alert = new BAlert("Bookmark error", message.String(), "OK",
NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
alert->Go();
return;
}
}
void
BrowserWindow::_ShowBookmarks()
{
BPath path;
entry_ref ref;
status_t status = _BookmarkPath(path);
if (status == B_OK)
status = get_ref_for_path(path.Path(), &ref);
if (status == B_OK)
status = be_roster->Launch(&ref);
if (status != B_OK && status != B_ALREADY_RUNNING) {
BString message("There was an error trying to show the Bookmarks "
"folder.\n\n");
message << "Error: " << strerror(status);
BAlert* alert = new BAlert("Bookmark error", message.String(), "OK",
NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
alert->Go();
return;
}
}
bool BrowserWindow::_CheckBookmarkExists(BDirectory& directory,
const BString& bookmarkName, const BString& url) const
{
BEntry entry;
while (directory.GetNextEntry(&entry) == B_OK) {
if (entry.IsDirectory()) {
BDirectory subBirectory(&entry);
// At least preserve the entry file handle when recursing into
// sub-folders... eventually we will run out, though, with very
// deep hierarchy.
entry.Unset();
if (_CheckBookmarkExists(subBirectory, bookmarkName, url))
return true;
} else {
char entryName[B_FILE_NAME_LENGTH];
if (entry.GetName(entryName) != B_OK || bookmarkName != entryName)
continue;
BString storedURL;
BFile file(&entry, B_READ_ONLY);
if (_ReadURLAttr(file, storedURL)) {
// Just bail if the bookmark already exists
if (storedURL == url)
return true;
}
}
}
return false;
}
bool
BrowserWindow::_ReadURLAttr(BFile& bookmarkFile, BString& url) const
{
return bookmarkFile.InitCheck() == B_OK
&& bookmarkFile.ReadAttrString("META:url", &url) == B_OK;
}
void
BrowserWindow::_AddBookmarkURLsRecursively(BDirectory& directory,
BMessage* message, uint32& addedCount) const
{
BEntry entry;
while (directory.GetNextEntry(&entry) == B_OK) {
if (entry.IsDirectory()) {
BDirectory subBirectory(&entry);
// At least preserve the entry file handle when recursing into
// sub-folders... eventually we will run out, though, with very
// deep hierarchy.
entry.Unset();
_AddBookmarkURLsRecursively(subBirectory, message, addedCount);
} else {
BString storedURL;
BFile file(&entry, B_READ_ONLY);
if (_ReadURLAttr(file, storedURL)) {
message->AddString("url", storedURL.String());
addedCount++;
}
}
}
}
void
BrowserWindow::_SetPageIcon(BWebView* view, const BBitmap* icon)
{
PageUserData* userData = static_cast<PageUserData*>(view->GetUserData());
if (userData == NULL) {
userData = new(std::nothrow) PageUserData(NULL);
if (userData == NULL)
return;
view->SetUserData(userData);
}
// The PageUserData makes a copy of the icon, which we pass on to
// the TabManager for display in the respective tab.
userData->SetPageIcon(icon);
fTabManager->SetTabIcon(view, userData->PageIcon());
if (view == CurrentWebView())
fURLInputGroup->SetPageIcon(icon);
}
static void
addItemToMenuOrSubmenu(BMenu* menu, BMenuItem* newItem)
{
BString baseURLLabel = baseURL(BString(newItem->Label()));
for (int32 i = menu->CountItems() - 1; i >= 0; i--) {
BMenuItem* item = menu->ItemAt(i);
BString label = item->Label();
if (label.FindFirst(baseURLLabel) >= 0) {
if (item->Submenu()) {
// Submenu was already added in previous iteration.
item->Submenu()->AddItem(newItem);
return;
} else {
menu->RemoveItem(item);
BMenu* subMenu = new BMenu(baseURLLabel.String());
subMenu->AddItem(item);
subMenu->AddItem(newItem);
// Add common submenu for this base URL, clickable.
BMessage* message = new BMessage(GOTO_URL);
message->AddString("url", baseURLLabel.String());
menu->AddItem(new BMenuItem(subMenu, message), i);
return;
}
}
}
menu->AddItem(newItem);
}
static void
addOrDeleteMenu(BMenu* menu, BMenu* toMenu)
{
if (menu->CountItems() > 0)
toMenu->AddItem(menu);
else
delete menu;
}
void
BrowserWindow::_UpdateHistoryMenu()
{
BMenuItem* menuItem;
while ((menuItem = fHistoryMenu->RemoveItem(fHistoryMenuFixedItemCount)))
delete menuItem;
BrowsingHistory* history = BrowsingHistory::DefaultInstance();
if (!history->Lock())
return;
int32 count = history->CountItems();
BMenuItem* clearHistoryItem = new BMenuItem("Clear history",
new BMessage(CLEAR_HISTORY));
clearHistoryItem->SetEnabled(count > 0);
fHistoryMenu->AddItem(clearHistoryItem);
if (count == 0) {
history->Unlock();
return;
}
fHistoryMenu->AddSeparatorItem();
BDateTime todayStart = BDateTime::CurrentDateTime(B_LOCAL_TIME);
todayStart.SetTime(BTime(0, 0, 0));
BDateTime oneDayAgoStart = todayStart;
oneDayAgoStart.Date().AddDays(-1);
BDateTime twoDaysAgoStart = oneDayAgoStart;
twoDaysAgoStart.Date().AddDays(-1);
BDateTime threeDaysAgoStart = twoDaysAgoStart;
threeDaysAgoStart.Date().AddDays(-1);
BDateTime fourDaysAgoStart = threeDaysAgoStart;
fourDaysAgoStart.Date().AddDays(-1);
BDateTime fiveDaysAgoStart = fourDaysAgoStart;
fiveDaysAgoStart.Date().AddDays(-1);
BMenu* todayMenu = new BMenu("Today");
BMenu* yesterdayMenu = new BMenu("Yesterday");
BMenu* twoDaysAgoMenu = new BMenu(
twoDaysAgoStart.Date().LongDayName().String());
BMenu* threeDaysAgoMenu = new BMenu(
threeDaysAgoStart.Date().LongDayName().String());
BMenu* fourDaysAgoMenu = new BMenu(
fourDaysAgoStart.Date().LongDayName().String());
BMenu* fiveDaysAgoMenu = new BMenu(
fiveDaysAgoStart.Date().LongDayName().String());
BMenu* earlierMenu = new BMenu("Earlier");
for (int32 i = 0; i < count; i++) {
BrowsingHistoryItem historyItem = history->HistoryItemAt(i);
BMessage* message = new BMessage(GOTO_URL);
message->AddString("url", historyItem.URL().String());
BString truncatedUrl(historyItem.URL());
be_plain_font->TruncateString(&truncatedUrl, B_TRUNCATE_END, 480);
menuItem = new BMenuItem(truncatedUrl, message);
if (historyItem.DateTime() < fiveDaysAgoStart)
addItemToMenuOrSubmenu(earlierMenu, menuItem);
else if (historyItem.DateTime() < fourDaysAgoStart)
addItemToMenuOrSubmenu(fiveDaysAgoMenu, menuItem);
else if (historyItem.DateTime() < threeDaysAgoStart)
addItemToMenuOrSubmenu(fourDaysAgoMenu, menuItem);
else if (historyItem.DateTime() < twoDaysAgoStart)
addItemToMenuOrSubmenu(threeDaysAgoMenu, menuItem);
else if (historyItem.DateTime() < oneDayAgoStart)
addItemToMenuOrSubmenu(twoDaysAgoMenu, menuItem);
else if (historyItem.DateTime() < todayStart)
addItemToMenuOrSubmenu(yesterdayMenu, menuItem);
else
addItemToMenuOrSubmenu(todayMenu, menuItem);
}
history->Unlock();
addOrDeleteMenu(todayMenu, fHistoryMenu);
addOrDeleteMenu(yesterdayMenu, fHistoryMenu);
addOrDeleteMenu(twoDaysAgoMenu, fHistoryMenu);
addOrDeleteMenu(fourDaysAgoMenu, fHistoryMenu);
addOrDeleteMenu(fiveDaysAgoMenu, fHistoryMenu);
addOrDeleteMenu(earlierMenu, fHistoryMenu);
}
void
BrowserWindow::_UpdateClipboardItems()
{
BTextView* focusTextView = dynamic_cast<BTextView*>(CurrentFocus());
if (focusTextView != NULL) {
int32 selectionStart;
int32 selectionEnd;
focusTextView->GetSelection(&selectionStart, &selectionEnd);
bool hasSelection = selectionStart < selectionEnd;
bool canPaste = false;
// A BTextView has the focus.
if (be_clipboard->Lock()) {
BMessage* data = be_clipboard->Data();
if (data != NULL)
canPaste = data->HasData("text/plain", B_MIME_TYPE);
be_clipboard->Unlock();
}
fCutMenuItem->SetEnabled(hasSelection);
fCopyMenuItem->SetEnabled(hasSelection);
fPasteMenuItem->SetEnabled(canPaste);
} else if (CurrentWebView() != NULL) {
// Trigger update of the clipboard items, even if the
// BWebView doesn't have focus, we'll dispatch these message
// there anyway. This works so fast that the user can never see
// the wrong enabled state when the menu opens until the result
// message arrives. The initial state needs to be enabled, since
// standard shortcut handling is always wrapped inside MenusBeginning()
// and MenusEnded(), and since we update items asynchronously, we need
// to have them enabled to begin with.
fCutMenuItem->SetEnabled(true);
fCopyMenuItem->SetEnabled(true);
fPasteMenuItem->SetEnabled(true);
CurrentWebView()->WebPage()->SendEditingCapabilities();
}
}
bool
BrowserWindow::_ShowPage(BWebView* view)
{
if (view != CurrentWebView()) {
int32 tabIndex = fTabManager->TabForView(view);
if (tabIndex < 0) {
// Page seems to be gone already?
return false;
}
fTabManager->SelectTab(tabIndex);
_TabChanged(tabIndex);
UpdateIfNeeded();
}
return true;
}
void
BrowserWindow::_ResizeToScreen()
{
BScreen screen(this);
MoveTo(0, 0);
ResizeTo(screen.Frame().Width(), screen.Frame().Height());
}
void
BrowserWindow::_SetAutoHideInterfaceInFullscreen(bool doIt)
{
if (fAutoHideInterfaceInFullscreenMode == doIt)
return;
fAutoHideInterfaceInFullscreenMode = doIt;
if (fAppSettings->GetValue(kSettingsKeyAutoHideInterfaceInFullscreenMode,
doIt) != doIt) {
fAppSettings->SetValue(kSettingsKeyAutoHideInterfaceInFullscreenMode,
doIt);
}
if (fAutoHideInterfaceInFullscreenMode) {
BMessage message(CHECK_AUTO_HIDE_INTERFACE);
fPulseRunner = new BMessageRunner(BMessenger(this), &message, 300000);
} else {
delete fPulseRunner;
fPulseRunner = NULL;
_ShowInterface(true);
}
}
void
BrowserWindow::_CheckAutoHideInterface()
{
if (!fIsFullscreen || !fAutoHideInterfaceInFullscreenMode
|| (CurrentWebView() != NULL && !CurrentWebView()->IsFocus())) {
return;
}
if (fLastMousePos.y == 0)
_ShowInterface(true);
else if (fNavigationGroup->IsVisible()
&& fLastMousePos.y > fNavigationGroup->Frame().bottom
&& system_time() - fLastMouseMovedTime > 1000000) {
// NOTE: Do not re-use navigationGroupBottom in the above
// check, since we only want to hide the interface when it is visible.
_ShowInterface(false);
}
}
void
BrowserWindow::_ShowInterface(bool show)
{
if (fInterfaceVisible == show)
return;
fInterfaceVisible = show;
if (show) {
#if !INTEGRATE_MENU_INTO_TAB_BAR
fMenuGroup->SetVisible(
(fVisibleInterfaceElements & INTERFACE_ELEMENT_MENU) != 0);
#endif
fTabGroup->SetVisible(_TabGroupShouldBeVisible());
fNavigationGroup->SetVisible(
(fVisibleInterfaceElements & INTERFACE_ELEMENT_NAVIGATION) != 0);
fStatusGroup->SetVisible(
(fVisibleInterfaceElements & INTERFACE_ELEMENT_STATUS) != 0);
} else {
fMenuGroup->SetVisible(false);
fTabGroup->SetVisible(false);
fNavigationGroup->SetVisible(false);
fStatusGroup->SetVisible(false);
}
// TODO: Setting the group visible seems to unhide the status bar.
// Fix in Haiku?
while (!fLoadingProgressBar->IsHidden())
fLoadingProgressBar->Hide();
}
void
BrowserWindow::_ShowProgressBar(bool show)
{
if (show) {
if (!fStatusGroup->IsVisible() && (fVisibleInterfaceElements & INTERFACE_ELEMENT_STATUS) != 0)
fStatusGroup->SetVisible(true);
fLoadingProgressBar->Show();
} else {
if (!fInterfaceVisible)
fStatusGroup->SetVisible(false);
// TODO: This is also used in _ShowInterface. Without it the status bar
// doesn't always hide again. It may be an Interface Kit bug.
while (!fLoadingProgressBar->IsHidden())
fLoadingProgressBar->Hide();
}
}
void
BrowserWindow::_InvokeButtonVisibly(BButton* button)
{
button->SetValue(B_CONTROL_ON);
UpdateIfNeeded();
button->Invoke();
snooze(1000);
button->SetValue(B_CONTROL_OFF);
}
BString
BrowserWindow::_NewTabURL(bool isNewWindow) const
{
BString url;
uint32 policy = isNewWindow ? fNewWindowPolicy : fNewTabPolicy;
// Implement new page policy
switch (policy) {
case OpenStartPage:
url = fStartPageURL;
break;
case OpenSearchPage:
url = fSearchPageURL;
break;
case CloneCurrentPage:
if (CurrentWebView() != NULL)
url = CurrentWebView()->MainFrameURL();
break;
case OpenBlankPage:
default:
break;
}
return url;
}
BString
BrowserWindow::_SmartURLHandler(const BString& url) const
{
BString result = url;
if (url.FindFirst(".") == B_ERROR || url.FindFirst(" ") != B_ERROR)
result.Prepend("http://www.google.com/search?q=");
return result;
}
void
BrowserWindow::_HandlePageSourceResult(const BMessage* message)
{
// TODO: This should be done in an extra thread perhaps. Doing it in
// the application thread is not much better, since it actually draws
// the pages...
BPath pathToPageSource;
BString url;
status_t ret = message->FindString("url", &url);
if (ret == B_OK && url.FindFirst("file://") == 0) {
// Local file
url.Remove(0, strlen("file://"));
pathToPageSource.SetTo(url.String());
} else {
// Something else, store it.
// TODO: What if it isn't HTML, but for example SVG?
BString source;
ret = message->FindString("source", &source);
if (ret == B_OK)
ret = find_directory(B_COMMON_TEMP_DIRECTORY, &pathToPageSource);
BString tmpFileName("PageSource_");
tmpFileName << system_time() << ".html";
if (ret == B_OK)
ret = pathToPageSource.Append(tmpFileName.String());
BFile pageSourceFile(pathToPageSource.Path(),
B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
if (ret == B_OK)
ret = pageSourceFile.InitCheck();
if (ret == B_OK) {
ssize_t written = pageSourceFile.Write(source.String(),
source.Length());
if (written != source.Length())
ret = (status_t)written;
}
if (ret == B_OK) {
const char* type = "text/html";
size_t size = strlen(type);
pageSourceFile.WriteAttr("BEOS:TYPE", B_STRING_TYPE, 0, type, size);
// If it fails we don't care.
}
}
entry_ref ref;
if (ret == B_OK)
ret = get_ref_for_path(pathToPageSource.Path(), &ref);
if (ret == B_OK) {
BMessage refsMessage(B_REFS_RECEIVED);
ret = refsMessage.AddRef("refs", &ref);
if (ret == B_OK) {
ret = be_roster->Launch("text/x-source-code", &refsMessage);
if (ret == B_ALREADY_RUNNING)
ret = B_OK;
}
}
if (ret != B_OK) {
char buffer[1024];
snprintf(buffer, sizeof(buffer), "Failed to show the "
"page source: %s\n", strerror(ret));
BAlert* alert = new BAlert("Page source error", buffer, "OK");
alert->Go(NULL);
}
}