diff options
| author | Alexandre Deckner <alexandre.deckner@uzzl.com> | 2012-07-03 19:20:10 +0200 |
|---|---|---|
| committer | Alexandre Deckner <alexandre.deckner@uzzl.com> | 2012-07-03 19:20:10 +0200 |
| commit | de3c221462b22f8d65d51cd96334620b3fd65307 (patch) | |
| tree | b7d3da5e2b87b4b18807de0464c85fe275fbc4ab | |
| parent | 7f7b659ec5eb4911c6a62e86af3a379227c3eeb0 (diff) | |
| parent | a79446f47116535b146eaa022c4b6de4e01bb3ad (diff) | |
Merge WebPositive into the Haiku tree. History is preservedhrev44286
58 files changed, 17581 insertions, 0 deletions
diff --git a/src/apps/webpositive/AuthenticationPanel.cpp b/src/apps/webpositive/AuthenticationPanel.cpp new file mode 100644 index 0000000000..7dcc55260f --- /dev/null +++ b/src/apps/webpositive/AuthenticationPanel.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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 "AuthenticationPanel.h" + +#include <Button.h> +#include <CheckBox.h> +#include <ControlLook.h> +#include <GridLayoutBuilder.h> +#include <GroupLayoutBuilder.h> +#include <Message.h> +#include <Screen.h> +#include <SeparatorView.h> +#include <SpaceLayoutItem.h> +#include <StringView.h> +#include <TextControl.h> +#include <stdio.h> + +static const uint32 kMsgPanelOK = 'pnok'; +static const uint32 kMsgJitter = 'jitr'; +static const uint32 kHidePassword = 'hdpw'; + +AuthenticationPanel::AuthenticationPanel(BRect parentFrame) + : BWindow(BRect(-1000, -1000, -900, -900), "Authentication Required", + B_TITLED_WINDOW_LOOK, B_MODAL_APP_WINDOW_FEEL, + B_ASYNCHRONOUS_CONTROLS | B_NOT_RESIZABLE | B_NOT_ZOOMABLE + | B_CLOSE_ON_ESCAPE | B_AUTO_UPDATE_SIZE_LIMITS) + , m_parentWindowFrame(parentFrame) + , m_usernameTextControl(new BTextControl("user", "Username:", "", NULL)) + , m_passwordTextControl(new BTextControl("pass", "Password:", "", NULL)) + , m_hidePasswordCheckBox(new BCheckBox("hide", "Hide password text", new BMessage(kHidePassword))) + , m_rememberCredentialsCheckBox(new BCheckBox("remember", "Remember username and password for this site", NULL)) + , m_okButton(new BButton("ok", "OK", new BMessage(kMsgPanelOK))) + , m_cancelButton(new BButton("cancel", "Cancel", new BMessage(B_QUIT_REQUESTED))) + , m_cancelled(false) + , m_exitSemaphore(create_sem(0, "Authentication Panel")) +{ +} + +AuthenticationPanel::~AuthenticationPanel() +{ + delete_sem(m_exitSemaphore); +} + +bool +AuthenticationPanel::QuitRequested() +{ + m_cancelled = true; + release_sem(m_exitSemaphore); + return false; +} + +void +AuthenticationPanel::MessageReceived(BMessage* message) +{ + switch (message->what) { + case kMsgPanelOK: + release_sem(m_exitSemaphore); + break; + case kHidePassword: { + // TODO: Toggling this is broken in BTextView. Workaround is to + // set the text and selection again. + BString text = m_passwordTextControl->Text(); + int32 selectionStart; + int32 selectionEnd; + m_passwordTextControl->TextView()->GetSelection(&selectionStart, &selectionEnd); + m_passwordTextControl->TextView()->HideTyping( + m_hidePasswordCheckBox->Value() == B_CONTROL_ON); + m_passwordTextControl->SetText(text.String()); + m_passwordTextControl->TextView()->Select(selectionStart, selectionEnd); + break; + } + case kMsgJitter: { + UpdateIfNeeded(); + BPoint leftTop = Frame().LeftTop(); + const float jitterOffsets[] = { -10, 0, 10, 0 }; + const int32 jitterOffsetCount = sizeof(jitterOffsets) / sizeof(float); + for (int32 i = 0; i < 20; i++) { + float offset = jitterOffsets[i % jitterOffsetCount]; + MoveTo(leftTop.x + offset, leftTop.y); + snooze(15000); + } + MoveTo(leftTop); + break; + } + default: + BWindow::MessageReceived(message); + } +} + +bool AuthenticationPanel::getAuthentication(const BString& text, + const BString& previousUser, const BString& previousPass, + bool previousRememberCredentials, bool badPassword, + BString& user, BString& pass, bool* rememberCredentials) +{ + // Configure panel and layout controls. + rgb_color infoColor = ui_color(B_PANEL_TEXT_COLOR); + BRect textBounds(0, 0, 250, 200); + BTextView* textView = new BTextView(textBounds, "text", textBounds, + be_plain_font, &infoColor, B_FOLLOW_NONE, B_WILL_DRAW | B_SUPPORTS_LAYOUT); + textView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + textView->SetText(text.String()); + textView->MakeEditable(false); + textView->MakeSelectable(false); + + m_usernameTextControl->SetText(previousUser.String()); + m_passwordTextControl->TextView()->HideTyping(true); + // Ignore the previous password, if it didn't work. + if (!badPassword) + m_passwordTextControl->SetText(previousPass.String()); + m_hidePasswordCheckBox->SetValue(B_CONTROL_ON); + m_rememberCredentialsCheckBox->SetValue(previousRememberCredentials); + + // create layout + SetLayout(new BGroupLayout(B_VERTICAL, 0.0)); + float spacing = be_control_look->DefaultItemSpacing(); + AddChild(BGroupLayoutBuilder(B_VERTICAL, 0.0) + .Add(BGridLayoutBuilder(0, spacing) + .Add(textView, 0, 0, 2) + .Add(m_usernameTextControl->CreateLabelLayoutItem(), 0, 1) + .Add(m_usernameTextControl->CreateTextViewLayoutItem(), 1, 1) + .Add(m_passwordTextControl->CreateLabelLayoutItem(), 0, 2) + .Add(m_passwordTextControl->CreateTextViewLayoutItem(), 1, 2) + .Add(BSpaceLayoutItem::CreateGlue(), 0, 3) + .Add(m_hidePasswordCheckBox, 1, 3) + .Add(m_rememberCredentialsCheckBox, 0, 4, 2) + .SetInsets(spacing, spacing, spacing, spacing) + ) + .Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER)) + .Add(BGroupLayoutBuilder(B_HORIZONTAL, spacing) + .AddGlue() + .Add(m_cancelButton) + .Add(m_okButton) + .SetInsets(spacing, spacing, spacing, spacing) + ) + ); + + float textHeight = textView->LineHeight(0) * textView->CountLines(); + textView->SetExplicitMinSize(BSize(B_SIZE_UNSET, textHeight)); + + SetDefaultButton(m_okButton); + if (badPassword && previousUser.Length()) + m_passwordTextControl->MakeFocus(true); + else + m_usernameTextControl->MakeFocus(true); + + if (m_parentWindowFrame.IsValid()) + CenterIn(m_parentWindowFrame); + else + CenterOnScreen(); + + // Start AuthenticationPanel window thread + Show(); + + // Let the window jitter, if the previous password was invalid + if (badPassword) + PostMessage(kMsgJitter); + + // Block calling thread + // Get the originating window, if it exists, to let it redraw itself. + BWindow* window = dynamic_cast<BWindow*>(BLooper::LooperForThread(find_thread(NULL))); + if (window) { + status_t err; + for (;;) { + do { + err = acquire_sem_etc(m_exitSemaphore, 1, B_RELATIVE_TIMEOUT, 10000); + // We've (probably) had our time slice taken away from us + } while (err == B_INTERRUPTED); + + if (err != B_TIMED_OUT) { + // Semaphore was finally released or nuked. + break; + } + window->UpdateIfNeeded(); + } + } else { + // No window to update, so just hang out until we're done. + while (acquire_sem(m_exitSemaphore) == B_INTERRUPTED) { + } + } + + // AuthenticationPanel wants to quit. + Lock(); + + user = m_usernameTextControl->Text(); + pass = m_passwordTextControl->Text(); + if (rememberCredentials) + *rememberCredentials = m_rememberCredentialsCheckBox->Value() == B_CONTROL_ON; + + bool canceled = m_cancelled; + Quit(); + // AuthenticationPanel object is TOAST here. + return !canceled; +} diff --git a/src/apps/webpositive/AuthenticationPanel.h b/src/apps/webpositive/AuthenticationPanel.h new file mode 100644 index 0000000000..5af9496aad --- /dev/null +++ b/src/apps/webpositive/AuthenticationPanel.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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. + */ + +#ifndef AuthenticationPanel_h +#define AuthenticationPanel_h + +#include <String.h> +#include <Window.h> + +class BCheckBox; +class BTextControl; + +class AuthenticationPanel : public BWindow { +public: + AuthenticationPanel(BRect parentFrame = BRect()); + virtual ~AuthenticationPanel(); + + virtual bool QuitRequested(); + + virtual void MessageReceived(BMessage *message); + + bool getAuthentication(const BString& text, const BString& previousUser, + const BString& previousPass, bool previousRememberCredentials, + bool badPassword, BString& user, BString& pass, + bool* rememberCredentials); + +private: + BRect m_parentWindowFrame; + BTextControl* m_usernameTextControl; + BTextControl* m_passwordTextControl; + BCheckBox* m_hidePasswordCheckBox; + BCheckBox* m_rememberCredentialsCheckBox; + BButton* m_okButton; + BButton* m_cancelButton; + + bool m_cancelled; + + sem_id m_exitSemaphore; +}; + +#endif // AuthenticationPanel_h diff --git a/src/apps/webpositive/BrowserApp.cpp b/src/apps/webpositive/BrowserApp.cpp new file mode 100644 index 0000000000..1504638ae7 --- /dev/null +++ b/src/apps/webpositive/BrowserApp.cpp @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2007 Ryan Leavengood <leavengood@gmail.com> + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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 "BrowserApp.h" + +#include "BrowserWindow.h" +#include "BrowsingHistory.h" +#include "DownloadWindow.h" +#include "SettingsMessage.h" +#include "SettingsWindow.h" +#include "svn_revision.h" +#include "NetworkCookieJar.h" +#include "WebPage.h" +#include "WebSettings.h" +#include "WebView.h" +#include <Alert.h> +#include <Autolock.h> +#include <Directory.h> +#include <Entry.h> +#include <FindDirectory.h> +#include <Locale.h> +#include <Path.h> +#include <Screen.h> +#include <debugger.h> +#include <stdio.h> + + +const char* kApplicationSignature = "application/x-vnd.Haiku-WebPositive"; +const char* kApplicationName = "WebPositive"; +static const uint32 PRELOAD_BROWSING_HISTORY = 'plbh'; + +#define ENABLE_NATIVE_COOKIES 0 + + +BrowserApp::BrowserApp() + : + BApplication(kApplicationSignature), + fWindowCount(0), + fLastWindowFrame(50, 50, 950, 750), + fLaunchRefsMessage(0), + fInitialized(false), + fSettings(NULL), + fCookies(NULL), + fCookieJar(NULL), + fDownloadWindow(NULL), + fSettingsWindow(NULL) +{ +} + + +BrowserApp::~BrowserApp() +{ + delete fLaunchRefsMessage; + delete fSettings; + delete fCookies; + delete fCookieJar; +} + + +void +BrowserApp::AboutRequested() +{ + BString aboutText("WebPositive\n\nby Ryan Leavengood, Andrea Anzani, " + "Maxime Simon, Michael Lotz, Rene Gollent and Stephan Aßmus"); + aboutText << "\n\nSVN revision: " << kSVNRevision; + + BAlert* alert = new BAlert("About WebPositive", aboutText.String(), + "Sweet!"); + alert->Go(NULL); +} + + +void +BrowserApp::ArgvReceived(int32 argc, char** argv) +{ + BMessage message(B_REFS_RECEIVED); + for (int i = 1; i < argc; i++) { + if (strcmp("-f", argv[i]) == 0 + || strcmp("--fullscreen", argv[i]) == 0) { + message.AddBool("fullscreen", true); + continue; + } + const char* url = argv[i]; + BEntry entry(argv[i], true); + BPath path; + if (entry.Exists() && entry.GetPath(&path) == B_OK) + url = path.Path(); + message.AddString("url", url); + } + // Upon program launch, it will buffer a copy of the message, since + // ArgReceived() is called before ReadyToRun(). + RefsReceived(&message); +} + + +void +BrowserApp::ReadyToRun() +{ + // Since we will essentially run the GUI... + set_thread_priority(Thread(), B_DISPLAY_PRIORITY); + + BWebPage::InitializeOnce(); + BWebPage::SetCacheModel(B_WEBKIT_CACHE_MODEL_WEB_BROWSER); + + BPath path; + if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK + && path.Append(kApplicationName) == B_OK + && create_directory(path.Path(), 0777) == B_OK) { + + BWebSettings::SetPersistentStoragePath(path.Path()); + } + + BString mainSettingsPath(kApplicationName); + mainSettingsPath << "/Application"; + fSettings = new SettingsMessage(B_USER_SETTINGS_DIRECTORY, + mainSettingsPath.String()); +#if ENABLE_NATIVE_COOKIES + mainSettingsPath = kApplicationName; + mainSettingsPath << "/Cookies"; + fCookies = new SettingsMessage(B_USER_SETTINGS_DIRECTORY, + mainSettingsPath.String()); + BMessage cookieArchive; + cookieArchive = fCookies->GetValue("cookies", cookieArchive); + fCookieJar = new BNetworkCookieJar(cookieArchive); + BWebPage::SetCookieJar(fCookieJar); +#endif + + fLastWindowFrame = fSettings->GetValue("window frame", fLastWindowFrame); + BRect defaultDownloadWindowFrame(-10, -10, 365, 265); + BRect downloadWindowFrame = fSettings->GetValue("downloads window frame", + defaultDownloadWindowFrame); + BRect settingsWindowFrame = fSettings->GetValue("settings window frame", + BRect()); + bool showDownloads = fSettings->GetValue("show downloads", false); + + fDownloadWindow = new DownloadWindow(downloadWindowFrame, showDownloads, + fSettings); + if (downloadWindowFrame == defaultDownloadWindowFrame) { + // Initially put download window in lower right of screen. + BRect screenFrame = BScreen().Frame(); + BMessage decoratorSettings; + fDownloadWindow->GetDecoratorSettings(&decoratorSettings); + float borderWidth = 0; + if (decoratorSettings.FindFloat("border width", &borderWidth) != B_OK) + borderWidth = 5; + fDownloadWindow->MoveTo(screenFrame.Width() - fDownloadWindow->Frame().Width() - borderWidth, + screenFrame.Height() - fDownloadWindow->Frame().Height() - borderWidth); + } + fSettingsWindow = new SettingsWindow(settingsWindowFrame, fSettings); + + BWebPage::SetDownloadListener(BMessenger(fDownloadWindow)); + + fInitialized = true; + + int32 pagesCreated = 0; + bool fullscreen = false; + if (fLaunchRefsMessage) { + _RefsReceived(fLaunchRefsMessage, &pagesCreated, &fullscreen); + delete fLaunchRefsMessage; + fLaunchRefsMessage = NULL; + } + if (pagesCreated == 0) + _CreateNewWindow("", fullscreen); + + PostMessage(PRELOAD_BROWSING_HISTORY); +} + + +void +BrowserApp::MessageReceived(BMessage* message) +{ + switch (message->what) { + case PRELOAD_BROWSING_HISTORY: + // Accessing the default instance will load the history from disk. + BrowsingHistory::DefaultInstance(); + break; + case B_SILENT_RELAUNCH: + _CreateNewPage(""); + break; + case NEW_WINDOW: { + BString url; + if (message->FindString("url", &url) != B_OK) + break; + _CreateNewWindow(url); + break; + } + case NEW_TAB: { + BrowserWindow* window; + if (message->FindPointer("window", reinterpret_cast<void**>(&window)) != B_OK) + break; + BString url; + message->FindString("url", &url); + bool select = false; + message->FindBool("select", &select); + _CreateNewTab(window, url, select); + break; + } + case WINDOW_OPENED: + fWindowCount++; + fDownloadWindow->SetMinimizeOnClose(false); + break; + case WINDOW_CLOSED: + fWindowCount--; + message->FindRect("window frame", &fLastWindowFrame); + if (fWindowCount <= 0) + PostMessage(B_QUIT_REQUESTED); + break; + + case SHOW_DOWNLOAD_WINDOW: + _ShowWindow(message, fDownloadWindow); + break; + case SHOW_SETTINGS_WINDOW: + _ShowWindow(message, fSettingsWindow); + break; + + default: + BApplication::MessageReceived(message); + break; + } +} + + +void +BrowserApp::RefsReceived(BMessage* message) +{ + if (!fInitialized) { + delete fLaunchRefsMessage; + fLaunchRefsMessage = new BMessage(*message); + return; + } + + _RefsReceived(message); +} + + +bool +BrowserApp::QuitRequested() +{ + if (fDownloadWindow->DownloadsInProgress()) { + BAlert* alert = new BAlert("Downloads in progress", + "There are still downloads in progress, do you really want to " + "quit WebPositive now?", "Quit", "Continue downloads"); + int32 choice = alert->Go(); + if (choice == 1) { + if (fWindowCount == 0) { + if (fDownloadWindow->Lock()) { + fDownloadWindow->SetWorkspaces(1 << current_workspace()); + if (fDownloadWindow->IsHidden()) + fDownloadWindow->Show(); + else + fDownloadWindow->Activate(); + fDownloadWindow->SetMinimizeOnClose(true); + fDownloadWindow->Unlock(); + return false; + } + } else + return false; + } + } + + for (int i = 0; BWindow* window = WindowAt(i); i++) { + BrowserWindow* webWindow = dynamic_cast<BrowserWindow*>(window); + if (!webWindow) + continue; + if (!webWindow->Lock()) + continue; + if (webWindow->QuitRequested()) { + fLastWindowFrame = webWindow->WindowFrame(); + webWindow->Quit(); + i--; + } else { + webWindow->Unlock(); + return false; + } + } + + BWebPage::ShutdownOnce(); + + fSettings->SetValue("window frame", fLastWindowFrame); + if (fDownloadWindow->Lock()) { + fSettings->SetValue("downloads window frame", fDownloadWindow->Frame()); + fSettings->SetValue("show downloads", !fDownloadWindow->IsHidden()); + fDownloadWindow->Unlock(); + } + if (fSettingsWindow->Lock()) { + fSettings->SetValue("settings window frame", fSettingsWindow->Frame()); + fSettingsWindow->Unlock(); + } + + BMessage cookieArchive; + if (fCookieJar != NULL && fCookieJar->Archive(&cookieArchive) == B_OK) + fCookies->SetValue("cookies", cookieArchive); + + return true; +} + + +void +BrowserApp::_RefsReceived(BMessage* message, int32* _pagesCreated, + bool* _fullscreen) +{ + int32 pagesCreated = 0; + + bool fullscreen; + if (message->FindBool("fullscreen", &fullscreen) != B_OK) + fullscreen = false; + + entry_ref ref; + for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) { + BEntry entry(&ref, true); + if (!entry.Exists()) + continue; + BPath path; + if (entry.GetPath(&path) != B_OK) + continue; + BString url; + url << path.Path(); + _CreateNewPage(url, fullscreen); + pagesCreated++; + } + + BString url; + for (int32 i = 0; message->FindString("url", i, &url) == B_OK; i++) { + _CreateNewPage(url, fullscreen); + pagesCreated++; + } + + if (_pagesCreated != NULL) + *_pagesCreated = pagesCreated; + if (_fullscreen != NULL) + *_fullscreen = fullscreen; +} + + +void +BrowserApp::_CreateNewPage(const BString& url, bool fullscreen) +{ + uint32 workspace = 1 << current_workspace(); + + bool loadedInWindowOnCurrentWorkspace = false; + for (int i = 0; BWindow* window = WindowAt(i); i++) { + BrowserWindow* webWindow = dynamic_cast<BrowserWindow*>(window); + if (!webWindow) + continue; + if (webWindow->Lock()) { + if (webWindow->Workspaces() & workspace) { + if (webWindow->IsBlankTab()) { + if (url.Length() != 0) + webWindow->CurrentWebView()->LoadURL(url); + } else + webWindow->CreateNewTab(url, true); + webWindow->Activate(); + webWindow->CurrentWebView()->MakeFocus(true); + loadedInWindowOnCurrentWorkspace = true; + } + webWindow->Unlock(); + } + if (loadedInWindowOnCurrentWorkspace) + return; + } + _CreateNewWindow(url, fullscreen); +} + + +void +BrowserApp::_CreateNewWindow(const BString& url, bool fullscreen) +{ + // Offset the window frame unless this is the first window created in the + // session. + if (fWindowCount > 0) + fLastWindowFrame.OffsetBy(20, 20); + if (!BScreen().Frame().Contains(fLastWindowFrame)) + fLastWindowFrame.OffsetTo(50, 50); + + BrowserWindow* window = new BrowserWindow(fLastWindowFrame, fSettings, + url); + if (fullscreen) + window->ToggleFullscreen(); + window->Show(); +} + + +void +BrowserApp::_CreateNewTab(BrowserWindow* window, const BString& url, + bool select) +{ + if (!window->Lock()) + return; + window->CreateNewTab(url, select); + window->Unlock(); +} + + +void +BrowserApp::_ShowWindow(const BMessage* message, BWindow* window) +{ + BAutolock _(window); + uint32 workspaces; + if (message->FindUInt32("workspaces", &workspaces) == B_OK) + window->SetWorkspaces(workspaces); + if (window->IsHidden()) + window->Show(); + else + window->Activate(); +} + + +// #pragma mark - + + +int +main(int, char**) +{ + try { + new BrowserApp(); + be_app->Run(); + delete be_app; + } catch (...) { + debugger("Exception caught."); + } + + return 0; +} + diff --git a/src/apps/webpositive/BrowserApp.h b/src/apps/webpositive/BrowserApp.h new file mode 100644 index 0000000000..05885583f6 --- /dev/null +++ b/src/apps/webpositive/BrowserApp.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2007 Ryan Leavengood <leavengood@gmail.com> + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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. + */ +#ifndef BROWSER_APP_H +#define BROWSER_APP_H + + +#include <Application.h> +#include <Catalog.h> +#include <Rect.h> + +class BNetworkCookieJar; +class DownloadWindow; +class BrowserWindow; +class SettingsMessage; +class SettingsWindow; + + +class BrowserApp : public BApplication { +public: + BrowserApp(); + virtual ~BrowserApp(); + + virtual void AboutRequested(); + virtual void ArgvReceived(int32 agrc, char** argv); + virtual void MessageReceived(BMessage* message); + virtual void RefsReceived(BMessage* message); + virtual void ReadyToRun(); + virtual bool QuitRequested(); + +private: + void _RefsReceived(BMessage* message, + int32* pagesCreated = NULL, + bool* fullscreen = NULL); + void _CreateNewPage(const BString& url, + bool fullscreen = false); + void _CreateNewWindow(const BString& url, + bool fullscreen = false); + void _CreateNewTab(BrowserWindow* window, + const BString& url, bool select); + void _ShowWindow(const BMessage* message, + BWindow* window); + +private: + int fWindowCount; + BRect fLastWindowFrame; + BMessage* fLaunchRefsMessage; + bool fInitialized; + + SettingsMessage* fSettings; + SettingsMessage* fCookies; + BNetworkCookieJar* fCookieJar; + + DownloadWindow* fDownloadWindow; + SettingsWindow* fSettingsWindow; +}; + + +extern const char* kApplicationSignature; +extern const char* kApplicationName; + + +#endif // BROWSER_APP_H + diff --git a/src/apps/webpositive/BrowserWindow.cpp b/src/apps/webpositive/BrowserWindow.cpp new file mode 100644 index 0000000000..c0d5ce63ef --- /dev/null +++ b/src/apps/webpositive/BrowserWindow.cpp @@ -0,0 +1,2216 @@ +/* + * Copyright (C) 2007 Andrea Anzani <andrea.anzani@gmail.com> + * Copyright (C) 2007, 2010 Ryan Leavengood <leavengood@gmail.com> + * Copyright (C) 2009 Maxime Simon <simon.maxime@gmail.com> + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * Copyright (C) 2010 Michael Lotz <mmlr@mlotz.ch> + * Copyright (C) 2010 Rene Gollent <rene@gollent.com> + * + * 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(); + if (!fAppSettings->GetValue(kSettingsKeyShowHomeButton, true)) + fHomeButton->Hide(); + + // 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, 0.0) + .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 modifierKeys; + if ((message->what == B_KEY_DOWN || message->what == B_UNMAPPED_KEY_DOWN) + && message->FindString("bytes", &bytes) == B_OK + && message->FindInt32("modifiers", (int32*)&modifierKeys) == B_OK) { + + modifierKeys = modifierKeys & 0x000000ff; + if (bytes[0] == B_LEFT_ARROW && modifierKeys == B_COMMAND_KEY) { + PostMessage(GO_BACK); + return; + } else if (bytes[0] == B_RIGHT_ARROW && modifierKeys == B_COMMAND_KEY) { + PostMessage(GO_FORWARD); + 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 ((modifierKeys & B_SHIFT_KEY) != 0) + _InvokeButtonVisibly(fFindPreviousButton); + else + _InvokeButtonVisibly(fFindNextButton); + return; + } else if (bytes[0] == B_ESCAPE) { + _InvokeButtonVisibly(fFindCloseButton); + return; + } + } else if (bytes[0] == B_ESCAPE) { + // Default escape key behavior: + PostMessage(STOP); + 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(); + } + if (message->what == B_MOUSE_WHEEL_CHANGED) { + BPoint where; + uint32 buttons; + CurrentWebView()->GetMouse(&where, &buttons, false); + // Only do this when the mouse is over the web view + if (CurrentWebView()->Bounds().Contains(where)) { + // Zoom and unzoom text on Command + mouse wheel. + // This could of course (and maybe should be) implemented in the WebView, but there + // would need to be a way for the WebView to know the setting of the + // fZoomTextOnly member here. Plus other clients of the API may not want + // this feature. + if ((modifiers() & B_COMMAND_KEY) != 0) { + float dy; + if (message->FindFloat("be:wheel_delta_y", &dy) == B_OK) { + if (dy < 0) + CurrentWebView()->IncreaseZoomFactor(fZoomTextOnly); + else + CurrentWebView()->DecreaseZoomFactor(fZoomTextOnly); + return; + } + } + } else // Also don't scroll up and down if the mouse is not over the web view + return; + } + 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); + } else if (name == kSettingsKeyShowHomeButton + && message->FindBool("value", &flag) == B_OK) { + if (flag) + fHomeButton->Show(); + else + fHomeButton->Hide(); + } + 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; + + // Only process if this doesn't look like a full URL (http:// or + // file://, etc.) + if (url.FindFirst("://") == B_ERROR) { + 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); + } +} + + diff --git a/src/apps/webpositive/BrowserWindow.h b/src/apps/webpositive/BrowserWindow.h new file mode 100644 index 0000000000..fcae9d5f7b --- /dev/null +++ b/src/apps/webpositive/BrowserWindow.h @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2007 Ryan Leavengood <leavengood@gmail.com> + * Copyright (C) 2009 Maxime Simon <simon.maxime@gmail.com> + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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. + */ +#ifndef BROWSER_WINDOW_H +#define BROWSER_WINDOW_H + + +#include "WebWindow.h" +#include <Messenger.h> +#include <String.h> + +class BButton; +class BCheckBox; +class BDirectory; +class BFile; +class BLayoutItem; +class BMenu; +class BMenuItem; +class BMessageRunner; +class BPath; +class BStatusBar; +class BStringView; +class BTextControl; +class BWebView; +class IconButton; +class SettingsMessage; +class TabManager; +class URLInputGroup; + +enum { + INTERFACE_ELEMENT_MENU = 1 << 0, + INTERFACE_ELEMENT_TABS = 1 << 1, + INTERFACE_ELEMENT_NAVIGATION = 1 << 2, + INTERFACE_ELEMENT_STATUS = 1 << 3, + + INTERFACE_ELEMENT_ALL = 0xffff +}; + +enum NewPagePolicy { + OpenBlankPage = 0, + OpenStartPage = 1, + OpenSearchPage = 2, + CloneCurrentPage = 3 +}; + +enum { + NEW_WINDOW = 'nwnd', + NEW_TAB = 'ntab', + WINDOW_OPENED = 'wndo', + WINDOW_CLOSED = 'wndc', + SHOW_DOWNLOAD_WINDOW = 'sdwd', + SHOW_SETTINGS_WINDOW = 'sswd' +}; + +#define INTEGRATE_MENU_INTO_TAB_BAR 0 + + +class BrowserWindow : public BWebWindow { +public: + BrowserWindow(BRect frame, + SettingsMessage* appSettings, + const BString& url, + uint32 interfaceElements + = INTERFACE_ELEMENT_ALL, + BWebView* webView = NULL); + virtual ~BrowserWindow(); + + virtual void DispatchMessage(BMessage* message, + BHandler* target); + virtual void MessageReceived(BMessage* message); + virtual bool QuitRequested(); + virtual void MenusBeginning(); + + virtual void Zoom(BPoint origin, float width, float height); + virtual void ScreenChanged(BRect screenSize, + color_space format); + virtual void WorkspacesChanged(uint32 oldWorkspaces, + uint32 newWorkspaces); + + virtual void SetCurrentWebView(BWebView* view); + + bool IsBlankTab() const; + void CreateNewTab(const BString& url, bool select, + BWebView* webView = 0); + + BRect WindowFrame() const; + + void ToggleFullscreen(); + +private: + // WebPage notification API implementations + virtual void NavigationRequested(const BString& url, + BWebView* view); + virtual void NewWindowRequested(const BString& url, + bool primaryAction); + virtual void CloseWindowRequested(BWebView* view); + virtual void NewPageCreated(BWebView* view, + BRect windowFrame, bool modalDialog, + bool resizable, bool activate); + virtual void LoadNegotiating(const BString& url, + BWebView* view); + virtual void LoadCommitted(const BString& url, + BWebView* view); + virtual void LoadProgress(float progress, BWebView* view); + virtual void LoadFailed(const BString& url, BWebView* view); + virtual void LoadFinished(const BString& url, + BWebView* view); + virtual void MainDocumentError(const BString& failingURL, + const BString& localizedDescription, + BWebView* view); + virtual void TitleChanged(const BString& title, + BWebView* view); + virtual void IconReceived(const BBitmap* icon, + BWebView* view); + virtual void ResizeRequested(float width, float height, + BWebView* view); + virtual void SetToolBarsVisible(bool flag, BWebView* view); + virtual void SetStatusBarVisible(bool flag, BWebView* view); + virtual void SetMenuBarVisible(bool flag, BWebView* view); + virtual void SetResizable(bool flag, BWebView* view); + virtual void StatusChanged(const BString& status, + BWebView* view); + virtual void NavigationCapabilitiesChanged( + bool canGoBackward, bool canGoForward, + bool canStop, BWebView* view); + virtual void UpdateGlobalHistory(const BString& url); + virtual bool AuthenticationChallenge(BString message, + BString& inOutUser, BString& inOutPassword, + bool& inOutRememberCredentials, + uint32 failureCount, BWebView* view); + +private: + void _UpdateTitle(const BString &title); + void _UpdateTabGroupVisibility(); + bool _TabGroupShouldBeVisible() const; + void _ShutdownTab(int32 index); + void _TabChanged(int32 index); + + status_t _BookmarkPath(BPath& path) const; + void _CreateBookmark(); + void _ShowBookmarks(); + bool _CheckBookmarkExists(BDirectory& directory, + const BString& fileName, + const BString& url) const; + bool _ReadURLAttr(BFile& bookmarkFile, + BString& url) const; + void _AddBookmarkURLsRecursively( + BDirectory& directory, + BMessage* message, + uint32& addedCount) const; + + void _SetPageIcon(BWebView* view, + const BBitmap* icon); + + void _UpdateHistoryMenu(); + void _UpdateClipboardItems(); + + bool _ShowPage(BWebView* view); + + void _ToggleFullscreen(); + void _ResizeToScreen(); + void _SetAutoHideInterfaceInFullscreen(bool doIt); + void _CheckAutoHideInterface(); + void _ShowInterface(bool show); + void _ShowProgressBar(bool); + void _InvokeButtonVisibly(BButton* button); + + BString _NewTabURL(bool isNewWindow) const; + BString _SmartURLHandler(const BString& url) const; + + void _HandlePageSourceResult( + const BMessage* message); + +private: + BMenu* fHistoryMenu; + int32 fHistoryMenuFixedItemCount; + + BMenuItem* fCutMenuItem; + BMenuItem* fCopyMenuItem; + BMenuItem* fPasteMenuItem; + BMenuItem* fFindPreviousMenuItem; + BMenuItem* fFindNextMenuItem; + BMenuItem* fZoomTextOnlyMenuItem; + BMenuItem* fFullscreenItem; + BMenuItem* fBackMenuItem; + BMenuItem* fForwardMenuItem; + + IconButton* fBackButton; + IconButton* fForwardButton; + IconButton* fStopButton; + IconButton* fHomeButton; + URLInputGroup* fURLInputGroup; + BStringView* fStatusText; + BStatusBar* fLoadingProgressBar; + + BLayoutItem* fMenuGroup; + BLayoutItem* fTabGroup; + BLayoutItem* fNavigationGroup; + BLayoutItem* fFindGroup; + BLayoutItem* fStatusGroup; + BLayoutItem* fToggleFullscreenButton; + + BTextControl* fFindTextControl; + BButton* fFindPreviousButton; + BButton* fFindNextButton; + BButton* fFindCloseButton; + BCheckBox* fFindCaseSensitiveCheckBox; + TabManager* fTabManager; + + bool fIsFullscreen; + bool fInterfaceVisible; + BRect fNonFullscreenWindowFrame; + BMessageRunner* fPulseRunner; + uint32 fVisibleInterfaceElements; + bigtime_t fLastMouseMovedTime; + BPoint fLastMousePos; + + // cached settings + SettingsMessage* fAppSettings; + bool fZoomTextOnly; + bool fShowTabsIfSinglePageOpen; + bool fAutoHideInterfaceInFullscreenMode; + bool fAutoHidePointer; + uint32 fNewWindowPolicy; + uint32 fNewTabPolicy; + BString fStartPageURL; + BString fSearchPageURL; +}; + + +#endif // BROWSER_WINDOW_H + diff --git a/src/apps/webpositive/BrowsingHistory.cpp b/src/apps/webpositive/BrowsingHistory.cpp new file mode 100644 index 0000000000..42a0442d0f --- /dev/null +++ b/src/apps/webpositive/BrowsingHistory.cpp @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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 "BrowsingHistory.h" + +#include <new> +#include <stdio.h> + +#include <Autolock.h> +#include <Entry.h> +#include <File.h> +#include <FindDirectory.h> +#include <Message.h> +#include <Path.h> + +#include "BrowserApp.h" + + +BrowsingHistoryItem::BrowsingHistoryItem(const BString& url) + : + fURL(url), + fDateTime(BDateTime::CurrentDateTime(B_LOCAL_TIME)), + fInvokationCount(0) +{ +} + + +BrowsingHistoryItem::BrowsingHistoryItem(const BrowsingHistoryItem& other) +{ + *this = other; +} + + +BrowsingHistoryItem::BrowsingHistoryItem(const BMessage* archive) +{ + if (!archive) + return; + BMessage dateTimeArchive; + if (archive->FindMessage("date time", &dateTimeArchive) == B_OK) + fDateTime = BDateTime(&dateTimeArchive); + archive->FindString("url", &fURL); + archive->FindUInt32("invokations", &fInvokationCount); +} + + +BrowsingHistoryItem::~BrowsingHistoryItem() +{ +} + + +status_t +BrowsingHistoryItem::Archive(BMessage* archive) const +{ + if (!archive) + return B_BAD_VALUE; + BMessage dateTimeArchive; + status_t status = fDateTime.Archive(&dateTimeArchive); + if (status == B_OK) + status = archive->AddMessage("date time", &dateTimeArchive); + if (status == B_OK) + status = archive->AddString("url", fURL.String()); + if (status == B_OK) + status = archive->AddUInt32("invokations", fInvokationCount); + return status; +} + + +BrowsingHistoryItem& +BrowsingHistoryItem::operator=(const BrowsingHistoryItem& other) +{ + if (this == &other) + return *this; + + fURL = other.fURL; + fDateTime = other.fDateTime; + fInvokationCount = other.fInvokationCount; + + return *this; +} + + +bool +BrowsingHistoryItem::operator==(const BrowsingHistoryItem& other) const +{ + if (this == &other) + return true; + + return fURL == other.fURL && fDateTime == other.fDateTime + && fInvokationCount == other.fInvokationCount; +} + + +bool +BrowsingHistoryItem::operator!=(const BrowsingHistoryItem& other) const +{ + return !(*this == other); +} + + +bool +BrowsingHistoryItem::operator<(const BrowsingHistoryItem& other) const +{ + if (this == &other) + return false; + + return fDateTime < other.fDateTime || fURL < other.fURL; +} + + +bool +BrowsingHistoryItem::operator<=(const BrowsingHistoryItem& other) const +{ + return (*this == other) || (*this < other); +} + + +bool +BrowsingHistoryItem::operator>(const BrowsingHistoryItem& other) const +{ + if (this == &other) + return false; + + return fDateTime > other.fDateTime || fURL > other.fURL; +} + + +bool +BrowsingHistoryItem::operator>=(const BrowsingHistoryItem& other) const +{ + return (*this == other) || (*this > other); +} + + +void +BrowsingHistoryItem::Invoked() +{ + // Eventually, we may overflow... + uint32 count = fInvokationCount + 1; + if (count > fInvokationCount) + fInvokationCount = count; + fDateTime = BDateTime::CurrentDateTime(B_LOCAL_TIME); +} + + +// #pragma mark - BrowsingHistory + + +BrowsingHistory +BrowsingHistory::sDefaultInstance; + + +BrowsingHistory::BrowsingHistory() + : + BLocker("browsing history"), + fHistoryItems(64), + fMaxHistoryItemAge(7), + fSettingsLoaded(false) +{ +} + + +BrowsingHistory::~BrowsingHistory() +{ + _SaveSettings(); + _Clear(); +} + + +/*static*/ BrowsingHistory* +BrowsingHistory::DefaultInstance() +{ + if (sDefaultInstance.Lock()) { + sDefaultInstance._LoadSettings(); + sDefaultInstance.Unlock(); + } + return &sDefaultInstance; +} + + +bool +BrowsingHistory::AddItem(const BrowsingHistoryItem& item) +{ + BAutolock _(this); + + return _AddItem(item, false); +} + + +int32 +BrowsingHistory::BrowsingHistory::CountItems() const +{ + BAutolock _(const_cast<BrowsingHistory*>(this)); + + return fHistoryItems.CountItems(); +} + + +BrowsingHistoryItem +BrowsingHistory::HistoryItemAt(int32 index) const +{ + BAutolock _(const_cast<BrowsingHistory*>(this)); + + BrowsingHistoryItem* existingItem = reinterpret_cast<BrowsingHistoryItem*>( + fHistoryItems.ItemAt(index)); + if (!existingItem) + return BrowsingHistoryItem(BString()); + + return BrowsingHistoryItem(*existingItem); +} + + +void +BrowsingHistory::Clear() +{ + BAutolock _(this); + _Clear(); + _SaveSettings(); +} + + +void +BrowsingHistory::SetMaxHistoryItemAge(int32 days) +{ + BAutolock _(this); + if (fMaxHistoryItemAge != days) { + fMaxHistoryItemAge = days; + _SaveSettings(); + } +} + + +int32 +BrowsingHistory::MaxHistoryItemAge() const +{ + return fMaxHistoryItemAge; +} + + +// #pragma mark - private + + +void +BrowsingHistory::_Clear() +{ + int32 count = CountItems(); + for (int32 i = 0; i < count; i++) { + BrowsingHistoryItem* item = reinterpret_cast<BrowsingHistoryItem*>( + fHistoryItems.ItemAtFast(i)); + delete item; + } + fHistoryItems.MakeEmpty(); +} + + +bool +BrowsingHistory::_AddItem(const BrowsingHistoryItem& item, bool internal) +{ + int32 count = CountItems(); + int32 insertionIndex = count; + for (int32 i = 0; i < count; i++) { + BrowsingHistoryItem* existingItem + = reinterpret_cast<BrowsingHistoryItem*>( + fHistoryItems.ItemAtFast(i)); + if (item.URL() == existingItem->URL()) { + if (!internal) { + existingItem->Invoked(); + _SaveSettings(); + } + return true; + } + if (item < *existingItem) + insertionIndex = i; + } + BrowsingHistoryItem* newItem = new(std::nothrow) BrowsingHistoryItem(item); + if (!newItem || !fHistoryItems.AddItem(newItem, insertionIndex)) { + delete newItem; + return false; + } + + if (!internal) { + newItem->Invoked(); + _SaveSettings(); + } + + return true; +} + + +void +BrowsingHistory::_LoadSettings() +{ + if (fSettingsLoaded) + return; + + fSettingsLoaded = true; + + BFile settingsFile; + if (_OpenSettingsFile(settingsFile, B_READ_ONLY)) { + BMessage settingsArchive; + settingsArchive.Unflatten(&settingsFile); + if (settingsArchive.FindInt32("max history item age", + &fMaxHistoryItemAge) != B_OK) { + fMaxHistoryItemAge = 7; + } + BDateTime oldestAllowedDateTime + = BDateTime::CurrentDateTime(B_LOCAL_TIME); + oldestAllowedDateTime.Date().AddDays(-fMaxHistoryItemAge); + + BMessage historyItemArchive; + for (int32 i = 0; settingsArchive.FindMessage("history item", i, + &historyItemArchive) == B_OK; i++) { + BrowsingHistoryItem item(&historyItemArchive); + if (oldestAllowedDateTime < item.DateTime()) + _AddItem(item, true); + historyItemArchive.MakeEmpty(); + } + } +} + + +void +BrowsingHistory::_SaveSettings() +{ + BFile settingsFile; + if (_OpenSettingsFile(settingsFile, + B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY)) { + BMessage settingsArchive; + settingsArchive.AddInt32("max history item age", fMaxHistoryItemAge); + BMessage historyItemArchive; + int32 count = CountItems(); + for (int32 i = 0; i < count; i++) { + BrowsingHistoryItem item = HistoryItemAt(i); + if (item.Archive(&historyItemArchive) != B_OK) + break; + if (settingsArchive.AddMessage("history item", + &historyItemArchive) != B_OK) { + break; + } + historyItemArchive.MakeEmpty(); + } + settingsArchive.Flatten(&settingsFile); + } +} + + +bool +BrowsingHistory::_OpenSettingsFile(BFile& file, uint32 mode) +{ + BPath path; + if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK + || path.Append(kApplicationName) != B_OK + || path.Append("BrowsingHistory") != B_OK) { + return false; + } + return file.SetTo(path.Path(), mode) == B_OK; +} + diff --git a/src/apps/webpositive/BrowsingHistory.h b/src/apps/webpositive/BrowsingHistory.h new file mode 100644 index 0000000000..bf8f7f15b8 --- /dev/null +++ b/src/apps/webpositive/BrowsingHistory.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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. + */ +#ifndef BROWSING_HISTORY_H +#define BROWSING_HISTORY_H + +#include "DateTime.h" +#include <List.h> +#include <Locker.h> + +class BFile; +class BString; + + +class BrowsingHistoryItem { +public: + BrowsingHistoryItem(const BString& url); + BrowsingHistoryItem( + const BrowsingHistoryItem& other); + BrowsingHistoryItem(const BMessage* archive); + ~BrowsingHistoryItem(); + + status_t Archive(BMessage* archive) const; + + BrowsingHistoryItem& operator=(const BrowsingHistoryItem& other); + + bool operator==( + const BrowsingHistoryItem& other) const; + bool operator!=( + const BrowsingHistoryItem& other) const; + bool operator<( + const BrowsingHistoryItem& other) const; + bool operator<=( + const BrowsingHistoryItem& other) const; + bool operator>( + const BrowsingHistoryItem& other) const; + bool operator>=( + const BrowsingHistoryItem& other) const; + + const BString& URL() const { return fURL; } + const BDateTime& DateTime() const { return fDateTime; } + uint32 InvokationCount() const { + return fInvokationCount; } + void Invoked(); + +private: + BString fURL; + BDateTime fDateTime; + uint32 fInvokationCount; +}; + + +class BrowsingHistory : public BLocker { +public: + static BrowsingHistory* DefaultInstance(); + + bool AddItem(const BrowsingHistoryItem& item); + + // Should Lock() the object when using these in some loop or so: + int32 CountItems() const; + BrowsingHistoryItem HistoryItemAt(int32 index) const; + void Clear(); + + void SetMaxHistoryItemAge(int32 days); + int32 MaxHistoryItemAge() const; + +private: + BrowsingHistory(); + virtual ~BrowsingHistory(); + + void _Clear(); + bool _AddItem(const BrowsingHistoryItem& item, + bool invoke); + + void _LoadSettings(); + void _SaveSettings(); + bool _OpenSettingsFile(BFile& file, uint32 mode); + +private: + BList fHistoryItems; + int32 fMaxHistoryItemAge; + + static BrowsingHistory sDefaultInstance; + bool fSettingsLoaded; +}; + + +#endif // BROWSING_HISTORY_H + diff --git a/src/apps/webpositive/CredentialsStorage.cpp b/src/apps/webpositive/CredentialsStorage.cpp new file mode 100644 index 0000000000..ab5f8eae8d --- /dev/null +++ b/src/apps/webpositive/CredentialsStorage.cpp @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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 "CredentialsStorage.h" + +#include <new> +#include <stdio.h> + +#include <Autolock.h> +#include <Entry.h> +#include <File.h> +#include <FindDirectory.h> +#include <Message.h> +#include <Path.h> + +#include "BrowserApp.h" + + +Credentials::Credentials() + : + fUsername(), + fPassword() +{ +} + + +Credentials::Credentials(const BString& username, const BString& password) + : + fUsername(username), + fPassword(password) +{ +} + + +Credentials::Credentials(const Credentials& other) +{ + *this = other; +} + + +Credentials::Credentials(const BMessage* archive) +{ + if (archive == NULL) + return; + archive->FindString("username", &fUsername); + archive->FindString("password", &fPassword); +} + + +Credentials::~Credentials() +{ +} + + +status_t +Credentials::Archive(BMessage* archive) const +{ + if (archive == NULL) + return B_BAD_VALUE; + status_t status = archive->AddString("username", fUsername); + if (status == B_OK) + status = archive->AddString("password", fPassword); + return status; +} + + +Credentials& +Credentials::operator=(const Credentials& other) +{ + if (this == &other) + return *this; + + fUsername = other.fUsername; + fPassword = other.fPassword; + + return *this; +} + + +bool +Credentials::operator==(const Credentials& other) const +{ + if (this == &other) + return true; + + return fUsername == other.fUsername && fPassword == other.fPassword; +} + + +bool +Credentials::operator!=(const Credentials& other) const +{ + return !(*this == other); +} + + +const BString& +Credentials::Username() const +{ + return fUsername; +} + + +const BString& +Credentials::Password() const +{ + return fPassword; +} + + +// #pragma mark - CredentialsStorage + + +CredentialsStorage +CredentialsStorage::sPersistentInstance(true); + + +CredentialsStorage +CredentialsStorage::sSessionInstance(false); + + +CredentialsStorage::CredentialsStorage(bool persistent) + : + BLocker(persistent ? "persistent credential storage" + : "credential storage"), + fCredentialMap(), + fSettingsLoaded(false), + fPersistent(persistent) +{ +} + + +CredentialsStorage::~CredentialsStorage() +{ + _SaveSettings(); +} + + +/*static*/ CredentialsStorage* +CredentialsStorage::SessionInstance() +{ + return &sSessionInstance; +} + + +/*static*/ CredentialsStorage* +CredentialsStorage::PersistentInstance() +{ + if (sPersistentInstance.Lock()) { + sPersistentInstance._LoadSettings(); + sPersistentInstance.Unlock(); + } + return &sPersistentInstance; +} + + +bool +CredentialsStorage::Contains(const HashKeyString& key) +{ + BAutolock _(this); + + return fCredentialMap.ContainsKey(key); +} + + +status_t +CredentialsStorage::PutCredentials(const HashKeyString& key, + const Credentials& credentials) +{ + BAutolock _(this); + + return fCredentialMap.Put(key, credentials); +} + + +Credentials +CredentialsStorage::GetCredentials(const HashKeyString& key) +{ + BAutolock _(this); + + return fCredentialMap.Get(key); +} + + +// #pragma mark - private + + +void +CredentialsStorage::_LoadSettings() +{ + if (!fPersistent || fSettingsLoaded) + return; + + fSettingsLoaded = true; + + BFile settingsFile; + if (_OpenSettingsFile(settingsFile, B_READ_ONLY)) { + BMessage settingsArchive; + settingsArchive.Unflatten(&settingsFile); + BMessage credentialsArchive; + for (int32 i = 0; settingsArchive.FindMessage("credentials", i, + &credentialsArchive) == B_OK; i++) { + BString key; + if (credentialsArchive.FindString("key", &key) == B_OK) { + Credentials credentials(&credentialsArchive); + fCredentialMap.Put(key, credentials); + } + } + } +} + + +void +CredentialsStorage::_SaveSettings() const +{ + BFile settingsFile; + if (_OpenSettingsFile(settingsFile, + B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY)) { + BMessage settingsArchive; + BMessage credentialsArchive; + CredentialMap::Iterator iterator = fCredentialMap.GetIterator(); + while (iterator.HasNext()) { + const CredentialMap::Entry& entry = iterator.Next(); + if (entry.value.Archive(&credentialsArchive) != B_OK + || credentialsArchive.AddString("key", + entry.key.value) != B_OK) { + break; + } + if (settingsArchive.AddMessage("credentials", + &credentialsArchive) != B_OK) { + break; + } + credentialsArchive.MakeEmpty(); + } + settingsArchive.Flatten(&settingsFile); + } +} + + +bool +CredentialsStorage::_OpenSettingsFile(BFile& file, uint32 mode) const +{ + BPath path; + if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK + || path.Append(kApplicationName) != B_OK + || path.Append("CredentialsStorage") != B_OK) { + return false; + } + return file.SetTo(path.Path(), mode) == B_OK; +} + diff --git a/src/apps/webpositive/CredentialsStorage.h b/src/apps/webpositive/CredentialsStorage.h new file mode 100644 index 0000000000..214c36b371 --- /dev/null +++ b/src/apps/webpositive/CredentialsStorage.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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. + */ +#ifndef CREDENTIAL_STORAGE_H +#define CREDENTIAL_STORAGE_H + +#include "HashKeys.h" +#include "HashMap.h" +#include <Locker.h> +#include <String.h> + + +class BFile; +class BMessage; +class BString; + + +class Credentials { +public: + Credentials(); + Credentials(const BString& username, + const BString& password); + Credentials( + const Credentials& other); + Credentials(const BMessage* archive); + ~Credentials(); + + status_t Archive(BMessage* archive) const; + + Credentials& operator=(const Credentials& other); + + bool operator==(const Credentials& other) const; + bool operator!=(const Credentials& other) const; + + const BString& Username() const; + const BString& Password() const; + +private: + BString fUsername; + BString fPassword; +}; + + +class CredentialsStorage : public BLocker { +public: + static CredentialsStorage* SessionInstance(); + static CredentialsStorage* PersistentInstance(); + + bool Contains(const HashKeyString& key); + status_t PutCredentials(const HashKeyString& key, + const Credentials& credentials); + Credentials GetCredentials(const HashKeyString& key); + +private: + CredentialsStorage(bool persistent); + virtual ~CredentialsStorage(); + + void _LoadSettings(); + void _SaveSettings() const; + bool _OpenSettingsFile(BFile& file, + uint32 mode) const; + +private: + typedef HashMap<HashKeyString, Credentials> CredentialMap; + CredentialMap fCredentialMap; + + static CredentialsStorage sPersistentInstance; + static CredentialsStorage sSessionInstance; + bool fSettingsLoaded; + bool fPersistent; +}; + + +#endif // CREDENTIAL_STORAGE_H + diff --git a/src/apps/webpositive/DownloadProgressView.cpp b/src/apps/webpositive/DownloadProgressView.cpp new file mode 100644 index 0000000000..7031e429c3 --- /dev/null +++ b/src/apps/webpositive/DownloadProgressView.cpp @@ -0,0 +1,838 @@ +/* + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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 "DownloadProgressView.h" + +#include <stdio.h> + +#include <Alert.h> +#include <Bitmap.h> +#include <Button.h> +#include <Clipboard.h> +#include <Directory.h> +#include <Entry.h> +#include <FindDirectory.h> +#include <GroupLayoutBuilder.h> +#include <MenuItem.h> +#include <NodeInfo.h> +#include <NodeMonitor.h> +#include <PopUpMenu.h> +#include <Roster.h> +#include <SpaceLayoutItem.h> +#include <StatusBar.h> +#include <StringView.h> + +#include "WebDownload.h" +#include "WebPage.h" +#include "StringForSize.h" + + +enum { + OPEN_DOWNLOAD = 'opdn', + RESTART_DOWNLOAD = 'rsdn', + CANCEL_DOWNLOAD = 'cndn', + REMOVE_DOWNLOAD = 'rmdn', + COPY_URL_TO_CLIPBOARD = 'curl', + OPEN_CONTAINING_FOLDER = 'opfd', +}; + +const bigtime_t kMaxUpdateInterval = 100000LL; +const bigtime_t kSpeedReferenceInterval = 500000LL; +const bigtime_t kShowSpeedInterval = 8000000LL; +const bigtime_t kShowEstimatedFinishInterval = 4000000LL; + +bigtime_t DownloadProgressView::sLastEstimatedFinishSpeedToggleTime = -1; +bool DownloadProgressView::sShowSpeed = true; + + +class IconView : public BView { +public: + IconView(const BEntry& entry) + : + BView("Download icon", B_WILL_DRAW), + fIconBitmap(BRect(0, 0, 31, 31), 0, B_RGBA32), + fDimmedIcon(false) + { + SetDrawingMode(B_OP_OVER); + SetTo(entry); + } + + IconView() + : + BView("Download icon", B_WILL_DRAW), + fIconBitmap(BRect(0, 0, 31, 31), 0, B_RGBA32), + fDimmedIcon(false) + { + SetDrawingMode(B_OP_OVER); + memset(fIconBitmap.Bits(), 0, fIconBitmap.BitsLength()); + } + + IconView(BMessage* archive) + : + BView("Download icon", B_WILL_DRAW), + fIconBitmap(archive), + fDimmedIcon(true) + { + SetDrawingMode(B_OP_OVER); + } + + void SetTo(const BEntry& entry) + { + BNode node(&entry); + BNodeInfo info(&node); + info.GetTrackerIcon(&fIconBitmap, B_LARGE_ICON); + Invalidate(); + } + + void SetIconDimmed(bool iconDimmed) + { + if (fDimmedIcon != iconDimmed) { + fDimmedIcon = iconDimmed; + Invalidate(); + } + } + + bool IsIconDimmed() const + { + return fDimmedIcon; + } + + status_t SaveSettings(BMessage* archive) + { + return fIconBitmap.Archive(archive); + } + + virtual void AttachedToWindow() + { + SetViewColor(Parent()->ViewColor()); + } + + virtual void Draw(BRect updateRect) + { + if (fDimmedIcon) { + SetDrawingMode(B_OP_ALPHA); + SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY); + SetHighColor(0, 0, 0, 100); + } + DrawBitmapAsync(&fIconBitmap); + } + + virtual BSize MinSize() + { + return BSize(fIconBitmap.Bounds().Width(), fIconBitmap.Bounds().Height()); + } + + virtual BSize PreferredSize() + { + return MinSize(); + } + + virtual BSize MaxSize() + { + return MinSize(); + } + +private: + BBitmap fIconBitmap; + bool fDimmedIcon; +}; + + +class SmallButton : public BButton { +public: + SmallButton(const char* label, BMessage* message = NULL) + : + BButton(label, message) + { + BFont font; + GetFont(&font); + float size = ceilf(font.Size() * 0.8); + font.SetSize(max_c(8, size)); + SetFont(&font, B_FONT_SIZE); + } +}; + + +// #pragma mark - DownloadProgressView + + +DownloadProgressView::DownloadProgressView(BWebDownload* download) + : + BGroupView(B_HORIZONTAL, 8), + fDownload(download), + fURL(download->URL()), + fPath(download->Path()) +{ +} + + +DownloadProgressView::DownloadProgressView(const BMessage* archive) + : + BGroupView(B_HORIZONTAL, 8), + fDownload(NULL), + fURL(), + fPath() +{ + const char* string; + if (archive->FindString("path", &string) == B_OK) + fPath.SetTo(string); + if (archive->FindString("url", &string) == B_OK) + fURL = string; +} + + +bool +DownloadProgressView::Init(BMessage* archive) +{ + fCurrentSize = 0; + fExpectedSize = 0; + fLastUpdateTime = 0; + fBytesPerSecond = 0.0; + for (size_t i = 0; i < kBytesPerSecondSlots; i++) + fBytesPerSecondSlot[i] = 0.0; + fCurrentBytesPerSecondSlot = 0; + fLastSpeedReferenceSize = 0; + fEstimatedFinishReferenceSize = 0; + + fProcessStartTime = fLastSpeedReferenceTime = fEstimatedFinishReferenceTime + = system_time(); + + SetViewColor(245, 245, 245); + SetFlags(Flags() | B_FULL_UPDATE_ON_RESIZE | B_WILL_DRAW); + + if (archive) { + fStatusBar = new BStatusBar("download progress", fPath.Leaf()); + float value; + if (archive->FindFloat("value", &value) == B_OK) + fStatusBar->SetTo(value); + } else + fStatusBar = new BStatusBar("download progress", "Download"); + fStatusBar->SetMaxValue(100); + fStatusBar->SetBarHeight(12); + + // fPath is only valid when constructed from archive (fDownload == NULL) + BEntry entry(fPath.Path()); + + if (archive) { + if (!entry.Exists()) + fIconView = new IconView(archive); + else + fIconView = new IconView(entry); + } else + fIconView = new IconView(); + + if (!fDownload && (fStatusBar->CurrentValue() < 100 || !entry.Exists())) + fTopButton = new SmallButton("Restart", new BMessage(RESTART_DOWNLOAD)); + else { + fTopButton = new SmallButton("Open", new BMessage(OPEN_DOWNLOAD)); + fTopButton->SetEnabled(fDownload == NULL); + } + if (fDownload) + fBottomButton = new SmallButton("Cancel", new BMessage(CANCEL_DOWNLOAD)); + else { + fBottomButton = new SmallButton("Remove", new BMessage(REMOVE_DOWNLOAD)); + fBottomButton->SetEnabled(fDownload == NULL); + } + + fInfoView = new BStringView("info view", ""); + + BGroupLayout* layout = GroupLayout(); + layout->SetInsets(8, 5, 5, 6); + layout->AddView(fIconView); + BView* verticalGroup = BGroupLayoutBuilder(B_VERTICAL, 3) + .Add(fStatusBar) + .Add(fInfoView) + .TopView() + ; + verticalGroup->SetViewColor(ViewColor()); + layout->AddView(verticalGroup); + verticalGroup = BGroupLayoutBuilder(B_VERTICAL, 3) + .Add(fTopButton) + .Add(fBottomButton) + .TopView() + ; + verticalGroup->SetViewColor(ViewColor()); + layout->AddView(verticalGroup); + + BFont font; + fInfoView->GetFont(&font); + float fontSize = font.Size() * 0.8f; + font.SetSize(max_c(8.0f, fontSize)); + fInfoView->SetFont(&font, B_FONT_SIZE); + fInfoView->SetHighColor(tint_color(fInfoView->LowColor(), + B_DARKEN_4_TINT)); + fInfoView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); + + return true; +} + + +status_t +DownloadProgressView::SaveSettings(BMessage* archive) +{ + if (!archive) + return B_BAD_VALUE; + status_t ret = archive->AddString("path", fPath.Path()); + if (ret == B_OK) + ret = archive->AddString("url", fURL.String()); + if (ret == B_OK) + ret = archive->AddFloat("value", fStatusBar->CurrentValue()); + if (ret == B_OK) + ret = fIconView->SaveSettings(archive); + return ret; +} + + +void +DownloadProgressView::AttachedToWindow() +{ + if (fDownload) { + fDownload->SetProgressListener(BMessenger(this)); + // Will start node monitor upon receiving the B_DOWNLOAD_STARTED + // message. + } else { + BEntry entry(fPath.Path()); + if (entry.Exists()) + _StartNodeMonitor(entry); + } + + fTopButton->SetTarget(this); + fBottomButton->SetTarget(this); +} + + +void +DownloadProgressView::DetachedFromWindow() +{ + _StopNodeMonitor(); +} + + +void +DownloadProgressView::AllAttached() +{ + SetViewColor(B_TRANSPARENT_COLOR); + SetLowColor(245, 245, 245); + SetHighColor(tint_color(LowColor(), B_DARKEN_1_TINT)); +} + + +void +DownloadProgressView::Draw(BRect updateRect) +{ + BRect bounds(Bounds()); + bounds.bottom--; + FillRect(bounds, B_SOLID_LOW); + bounds.bottom++; + StrokeLine(bounds.LeftBottom(), bounds.RightBottom()); +} + + +void +DownloadProgressView::MessageReceived(BMessage* message) +{ + switch (message->what) { + case B_DOWNLOAD_STARTED: + { + BString path; + if (message->FindString("path", &path) != B_OK) + break; + fPath.SetTo(path); + BEntry entry(fPath.Path()); + fIconView->SetTo(entry); + fStatusBar->Reset(fPath.Leaf()); + _StartNodeMonitor(entry); + + // Immediately switch to speed display whenever a new download + // starts. + sShowSpeed = true; + sLastEstimatedFinishSpeedToggleTime + = fProcessStartTime = fLastSpeedReferenceTime + = fEstimatedFinishReferenceTime = system_time(); + break; + } + case B_DOWNLOAD_PROGRESS: + { + int64 currentSize; + int64 expectedSize; + if (message->FindInt64("current size", ¤tSize) == B_OK + && message->FindInt64("expected size", &expectedSize) == B_OK) { + _UpdateStatus(currentSize, expectedSize); + } + break; + } + case B_DOWNLOAD_REMOVED: + // TODO: This is a bit asymetric. The removed notification + // arrives here, but it would be nicer if it arrived + // at the window... + Window()->PostMessage(message); + break; + case OPEN_DOWNLOAD: + { + // TODO: In case of executable files, ask the user first! + entry_ref ref; + status_t status = get_ref_for_path(fPath.Path(), &ref); + if (status == B_OK) + status = be_roster->Launch(&ref); + if (status != B_OK && status != B_ALREADY_RUNNING) { + BAlert* alert = new BAlert("Open download error", + "The download could not be opened.", "OK"); + alert->Go(NULL); + } + break; + } + case RESTART_DOWNLOAD: + BWebPage::RequestDownload(fURL); + break; + + case CANCEL_DOWNLOAD: + fDownload->Cancel(); + DownloadCanceled(); + break; + + case REMOVE_DOWNLOAD: + { + Window()->PostMessage(SAVE_SETTINGS); + RemoveSelf(); + delete this; + // TOAST! + return; + } + case B_NODE_MONITOR: + { + int32 opCode; + if (message->FindInt32("opcode", &opCode) != B_OK) + break; + switch (opCode) { + case B_ENTRY_REMOVED: + fIconView->SetIconDimmed(true); + DownloadCanceled(); + break; + case B_ENTRY_MOVED: + { + // Follow the entry to the new location + dev_t device; + ino_t directory; + const char* name; + if (message->FindInt32("device", + reinterpret_cast<int32*>(&device)) != B_OK + || message->FindInt64("to directory", + reinterpret_cast<int64*>(&directory)) != B_OK + || message->FindString("name", &name) != B_OK + || strlen(name) == 0) { + break; + } + // Construct the BEntry and update fPath + entry_ref ref(device, directory, name); + BEntry entry(&ref); + if (entry.GetPath(&fPath) != B_OK) + break; + + // Find out if the directory is the Trash for this + // volume + char trashPath[B_PATH_NAME_LENGTH]; + if (find_directory(B_TRASH_DIRECTORY, device, false, + trashPath, B_PATH_NAME_LENGTH) == B_OK) { + BPath trashDirectory(trashPath); + BPath parentDirectory; + fPath.GetParent(&parentDirectory); + if (parentDirectory == trashDirectory) { + // The entry was moved into the Trash. + // If the download is still in progress, + // cancel it. + if (fDownload) + fDownload->Cancel(); + fIconView->SetIconDimmed(true); + DownloadCanceled(); + break; + } else if (fIconView->IsIconDimmed()) { + // Maybe it was moved out of the trash. + fIconView->SetIconDimmed(false); + } + } + + // Inform download of the new path + if (fDownload) + fDownload->HasMovedTo(fPath); + + float value = fStatusBar->CurrentValue(); + fStatusBar->Reset(name); + fStatusBar->SetTo(value); + Window()->PostMessage(SAVE_SETTINGS); + break; + } + case B_ATTR_CHANGED: + { + BEntry entry(fPath.Path()); + fIconView->SetIconDimmed(false); + fIconView->SetTo(entry); + break; + } + } + break; + } + + // Context menu messages + case COPY_URL_TO_CLIPBOARD: + if (be_clipboard->Lock()) { + BMessage* data = be_clipboard->Data(); + if (data != NULL) { + be_clipboard->Clear(); + data->AddData("text/plain", B_MIME_TYPE, fURL.String(), + fURL.Length()); + } + be_clipboard->Commit(); + be_clipboard->Unlock(); + } + break; + case OPEN_CONTAINING_FOLDER: + if (fPath.InitCheck() == B_OK) { + BPath containingFolder; + if (fPath.GetParent(&containingFolder) != B_OK) + break; + BEntry entry(containingFolder.Path()); + if (!entry.Exists()) + break; + entry_ref ref; + if (entry.GetRef(&ref) != B_OK) + break; + be_roster->Launch(&ref); + + // Use Tracker scripting and select the download pose + // in the window. + // TODO: We should somehow get the window that just openend. + // Using the name like this is broken when there are multiple + // windows open with this name. Also Tracker does not scroll + // to this entry. + BString windowName = ref.name; + BString fullWindowName = containingFolder.Path(); + + BMessenger trackerMessenger("application/x-vnd.Be-TRAK"); + if (trackerMessenger.IsValid() + && get_ref_for_path(fPath.Path(), &ref) == B_OK) { + // We need to wait a bit until the folder is open. + // TODO: This is also too fragile... we should be able + // to wait for the roster message. + snooze(250000); + int32 tries = 2; + while (tries > 0) { + BMessage selectionCommand(B_SET_PROPERTY); + selectionCommand.AddSpecifier("Selection"); + selectionCommand.AddSpecifier("Poses"); + selectionCommand.AddSpecifier("Window", + windowName.String()); + selectionCommand.AddRef("data", &ref); + BMessage reply; + trackerMessenger.SendMessage(&selectionCommand, &reply); + int32 error; + if (reply.FindInt32("error", &error) != B_OK + || error == B_OK) { + break; + } + windowName = fullWindowName; + tries--; + } + } + } + break; + + default: + BGroupView::MessageReceived(message); + } +} + + +void +DownloadProgressView::ShowContextMenu(BPoint screenWhere) +{ + screenWhere += BPoint(2, 2); + + BPopUpMenu* contextMenu = new BPopUpMenu("download context"); + BMenuItem* copyURL = new BMenuItem("Copy URL to clipboard", + new BMessage(COPY_URL_TO_CLIPBOARD)); + copyURL->SetEnabled(fURL.Length() > 0); + contextMenu->AddItem(copyURL); + BMenuItem* openFolder = new BMenuItem("Open containing folder", + new BMessage(OPEN_CONTAINING_FOLDER)); + contextMenu->AddItem(openFolder); + + contextMenu->SetTargetForItems(this); + contextMenu->Go(screenWhere, true, true, true); +} + + +BWebDownload* +DownloadProgressView::Download() const +{ + return fDownload; +} + + +const BString& +DownloadProgressView::URL() const +{ + return fURL; +} + + +bool +DownloadProgressView::IsMissing() const +{ + return fIconView->IsIconDimmed(); +} + + +bool +DownloadProgressView::IsFinished() const +{ + return !fDownload && fStatusBar->CurrentValue() == 100; +} + + +void +DownloadProgressView::DownloadFinished() +{ + fDownload = NULL; + if (fExpectedSize == -1) { + fStatusBar->SetTo(100.0); + fExpectedSize = fCurrentSize; + } + fTopButton->SetEnabled(true); + fBottomButton->SetLabel("Remove"); + fBottomButton->SetMessage(new BMessage(REMOVE_DOWNLOAD)); + fBottomButton->SetEnabled(true); + fInfoView->SetText(""); +} + + +void +DownloadProgressView::DownloadCanceled() +{ + fDownload = NULL; + fTopButton->SetLabel("Restart"); + fTopButton->SetMessage(new BMessage(RESTART_DOWNLOAD)); + fTopButton->SetEnabled(true); + fBottomButton->SetLabel("Remove"); + fBottomButton->SetMessage(new BMessage(REMOVE_DOWNLOAD)); + fBottomButton->SetEnabled(true); + fInfoView->SetText(""); + fPath.Unset(); +} + + +/*static*/ void +DownloadProgressView::SpeedVersusEstimatedFinishTogglePulse() +{ + bigtime_t now = system_time(); + if (sShowSpeed + && sLastEstimatedFinishSpeedToggleTime + kShowSpeedInterval + <= now) { + sShowSpeed = false; + sLastEstimatedFinishSpeedToggleTime = now; + } else if (!sShowSpeed + && sLastEstimatedFinishSpeedToggleTime + + kShowEstimatedFinishInterval <= now) { + sShowSpeed = true; + sLastEstimatedFinishSpeedToggleTime = now; + } +} + + +// #pragma mark - private + + +void +DownloadProgressView::_UpdateStatus(off_t currentSize, off_t expectedSize) +{ + fCurrentSize = currentSize; + fExpectedSize = expectedSize; + + fStatusBar->SetTo(100.0 * currentSize / expectedSize); + + bigtime_t currentTime = system_time(); + if ((currentTime - fLastUpdateTime) > kMaxUpdateInterval) { + fLastUpdateTime = currentTime; + + if (currentTime >= fLastSpeedReferenceTime + kSpeedReferenceInterval) { + // update current speed every kSpeedReferenceInterval + fCurrentBytesPerSecondSlot + = (fCurrentBytesPerSecondSlot + 1) % kBytesPerSecondSlots; + fBytesPerSecondSlot[fCurrentBytesPerSecondSlot] + = (double)(currentSize - fLastSpeedReferenceSize) + * 1000000LL / (currentTime - fLastSpeedReferenceTime); + fLastSpeedReferenceSize = currentSize; + fLastSpeedReferenceTime = currentTime; + fBytesPerSecond = 0.0; + size_t count = 0; + for (size_t i = 0; i < kBytesPerSecondSlots; i++) { + if (fBytesPerSecondSlot[i] != 0.0) { + fBytesPerSecond += fBytesPerSecondSlot[i]; + count++; + } + } + if (count > 0) + fBytesPerSecond /= count; + } + _UpdateStatusText(); + } +} + + +void +DownloadProgressView::_UpdateStatusText() +{ + fInfoView->SetText(""); + BString buffer; + if (sShowSpeed && fBytesPerSecond != 0.0) { + // Draw speed info + char sizeBuffer[128]; + buffer = "("; + // Get strings for current and expected size and remove the unit + // from the current size string if it's the same as the expected + // size unit. + BString currentSize = string_for_size((double)fCurrentSize, sizeBuffer, + sizeof(sizeBuffer)); + BString expectedSize = string_for_size((double)fExpectedSize, sizeBuffer, + sizeof(sizeBuffer)); + int currentSizeUnitPos = currentSize.FindLast(' '); + int expectedSizeUnitPos = expectedSize.FindLast(' '); + if (currentSizeUnitPos >= 0 && expectedSizeUnitPos >= 0 + && strcmp(currentSize.String() + currentSizeUnitPos, + expectedSize.String() + expectedSizeUnitPos) == 0) { + currentSize.Truncate(currentSizeUnitPos); + } + buffer << currentSize; + buffer << " of "; + buffer << expectedSize; + buffer << ", "; + buffer << string_for_size(fBytesPerSecond, sizeBuffer, + sizeof(sizeBuffer)); + buffer << "/s)"; + float stringWidth = fInfoView->StringWidth(buffer.String()); + if (stringWidth < fInfoView->Bounds().Width()) + fInfoView->SetText(buffer.String()); + else { + // complete string too wide, try with shorter version + buffer << string_for_size(fBytesPerSecond, sizeBuffer, + sizeof(sizeBuffer)); + buffer << "/s"; + stringWidth = fInfoView->StringWidth(buffer.String()); + if (stringWidth < fInfoView->Bounds().Width()) + fInfoView->SetText(buffer.String()); + } + } else if (!sShowSpeed && fCurrentSize < fExpectedSize) { + double totalBytesPerSecond = (double)(fCurrentSize + - fEstimatedFinishReferenceSize) + * 1000000LL / (system_time() - fEstimatedFinishReferenceTime); + double secondsRemaining = (fExpectedSize - fCurrentSize) + / totalBytesPerSecond; + time_t now = (time_t)real_time_clock(); + time_t finishTime = (time_t)(now + secondsRemaining); + + tm _time; + tm* time = localtime_r(&finishTime, &_time); + int32 year = time->tm_year + 1900; + + char timeText[32]; + time_t secondsPerDay = 24 * 60 * 60; + // TODO: Localization of time string... + if (now < finishTime - secondsPerDay) { + // process is going to take more than a day! + sprintf(timeText, "%0*d:%0*d %0*d/%0*d/%ld", + 2, time->tm_hour, 2, time->tm_min, + 2, time->tm_mon + 1, 2, time->tm_mday, year); + } else { + sprintf(timeText, "%0*d:%0*d", + 2, time->tm_hour, 2, time->tm_min); + } + + BString buffer1("Finish: "); + buffer1 << timeText; + finishTime -= now; + time = gmtime(&finishTime); + + BString buffer2; + if (finishTime > secondsPerDay) { + int64 days = finishTime / secondsPerDay; + if (days == 1) + buffer2 << "Over 1 day"; + else + buffer2 << "Over " << days << " days"; + } else if (finishTime > 60 * 60) { + int64 hours = finishTime / (60 * 60); + if (hours == 1) + buffer2 << "Over 1 hour"; + else + buffer2 << "Over " << hours << " hours"; + } else if (finishTime > 60) { + int64 minutes = finishTime / 60; + if (minutes == 1) + buffer2 << "Over 1 minute"; + else + buffer2 << minutes << " minutes"; + } else { + if (finishTime == 1) + buffer2 << "1 second"; + else + buffer2 << finishTime << " seconds"; + } + + buffer2 << " left"; + + buffer = "("; + buffer << buffer1 << " - " << buffer2 << ")"; + + float stringWidth = fInfoView->StringWidth(buffer.String()); + if (stringWidth < fInfoView->Bounds().Width()) + fInfoView->SetText(buffer.String()); + else { + // complete string too wide, try with shorter version + buffer = "("; + buffer << buffer1 << ")"; + stringWidth = fInfoView->StringWidth(buffer.String()); + if (stringWidth < fInfoView->Bounds().Width()) + fInfoView->SetText(buffer.String()); + } + } +} + + +void +DownloadProgressView::_StartNodeMonitor(const BEntry& entry) +{ + node_ref nref; + if (entry.GetNodeRef(&nref) == B_OK) + watch_node(&nref, B_WATCH_ALL, this); +} + + +void +DownloadProgressView::_StopNodeMonitor() +{ + stop_watching(this); +} + diff --git a/src/apps/webpositive/DownloadProgressView.h b/src/apps/webpositive/DownloadProgressView.h new file mode 100644 index 0000000000..32a021b169 --- /dev/null +++ b/src/apps/webpositive/DownloadProgressView.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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. + */ +#ifndef DOWNLOAD_PROGRESS_VIEW_H +#define DOWNLOAD_PROGRESS_VIEW_H + + +#include <GroupView.h> +#include <Path.h> +#include <String.h> + +class BEntry; +class BStatusBar; +class BStringView; +class BWebDownload; +class IconView; +class SmallButton; + + +enum { + SAVE_SETTINGS = 'svst' +}; + + +class DownloadProgressView : public BGroupView { +public: + DownloadProgressView(BWebDownload* download); + DownloadProgressView(const BMessage* archive); + + bool Init(BMessage* archive = NULL); + + status_t SaveSettings(BMessage* archive); + virtual void AttachedToWindow(); + virtual void DetachedFromWindow(); + virtual void AllAttached(); + + virtual void Draw(BRect updateRect); + + virtual void MessageReceived(BMessage* message); + + void ShowContextMenu(BPoint screenWhere); + + BWebDownload* Download() const; + const BString& URL() const; + bool IsMissing() const; + bool IsFinished() const; + + void DownloadFinished(); + void DownloadCanceled(); + + static void SpeedVersusEstimatedFinishTogglePulse(); + +private: + void _UpdateStatus(off_t currentSize, + off_t expectedSize); + void _UpdateStatusText(); + void _StartNodeMonitor(const BEntry& entry); + void _StopNodeMonitor(); + +private: + IconView* fIconView; + BStatusBar* fStatusBar; + BStringView* fInfoView; + SmallButton* fTopButton; + SmallButton* fBottomButton; + BWebDownload* fDownload; + BString fURL; + BPath fPath; + + off_t fCurrentSize; + off_t fExpectedSize; + off_t fLastSpeedReferenceSize; + off_t fEstimatedFinishReferenceSize; + bigtime_t fLastUpdateTime; + bigtime_t fLastSpeedReferenceTime; + bigtime_t fProcessStartTime; + bigtime_t fLastSpeedUpdateTime; + bigtime_t fEstimatedFinishReferenceTime; + static const size_t kBytesPerSecondSlots = 10; + size_t fCurrentBytesPerSecondSlot; + double fBytesPerSecondSlot[kBytesPerSecondSlots]; + double fBytesPerSecond; + + static bigtime_t sLastEstimatedFinishSpeedToggleTime; + static bool sShowSpeed; +}; + +#endif // DOWNLOAD_PROGRESS_VIEW_H diff --git a/src/apps/webpositive/DownloadWindow.cpp b/src/apps/webpositive/DownloadWindow.cpp new file mode 100644 index 0000000000..859722df47 --- /dev/null +++ b/src/apps/webpositive/DownloadWindow.cpp @@ -0,0 +1,573 @@ +/* + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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 "DownloadWindow.h" + +#include <stdio.h> + +#include <Alert.h> +#include <Button.h> +#include <ControlLook.h> +#include <Entry.h> +#include <File.h> +#include <FindDirectory.h> +#include <GroupLayout.h> +#include <GroupLayoutBuilder.h> +#include <MenuBar.h> +#include <MenuItem.h> +#include <Path.h> +#include <Roster.h> +#include <ScrollView.h> +#include <SeparatorView.h> +#include <SpaceLayoutItem.h> + +#include "BrowserApp.h" +#include "BrowserWindow.h" +#include "DownloadProgressView.h" +#include "SettingsKeys.h" +#include "SettingsMessage.h" +#include "WebDownload.h" +#include "WebPage.h" + + +enum { + INIT = 'init', + OPEN_DOWNLOADS_FOLDER = 'odnf', + REMOVE_FINISHED_DOWNLOADS = 'rmfd', + REMOVE_MISSING_DOWNLOADS = 'rmmd' +}; + + +class DownloadsContainerView : public BGroupView { +public: + DownloadsContainerView() + : + BGroupView(B_VERTICAL, 0.0) + { + SetFlags(Flags() | B_PULSE_NEEDED); + SetViewColor(245, 245, 245); + AddChild(BSpaceLayoutItem::CreateGlue()); + } + + virtual BSize MinSize() + { + BSize minSize = BGroupView::MinSize(); + return BSize(minSize.width, 80); + } + + virtual void Pulse() + { + DownloadProgressView::SpeedVersusEstimatedFinishTogglePulse(); + } + +protected: + virtual void DoLayout() + { + BGroupView::DoLayout(); + if (BScrollBar* scrollBar = ScrollBar(B_VERTICAL)) { + BSize minSize = BGroupView::MinSize(); + float height = Bounds().Height(); + float max = minSize.height - height; + scrollBar->SetRange(0, max); + if (minSize.height > 0) + scrollBar->SetProportion(height / minSize.height); + else + scrollBar->SetProportion(1); + } + } +}; + + +class DownloadContainerScrollView : public BScrollView { +public: + DownloadContainerScrollView(BView* target) + : + BScrollView("Downloads scroll view", target, 0, false, true, + B_NO_BORDER) + { + } + +protected: + virtual void DoLayout() + { + BScrollView::DoLayout(); + // Tweak scroll bar layout to hide part of the frame for better looks. + BScrollBar* scrollBar = ScrollBar(B_VERTICAL); + scrollBar->MoveBy(1, -1); + scrollBar->ResizeBy(0, 2); + Target()->ResizeBy(1, 0); + // Set the scroll steps + if (BView* item = Target()->ChildAt(0)) { + scrollBar->SetSteps(item->MinSize().height + 1, + item->MinSize().height + 1); + } + } +}; + + +// #pragma mark - + + +DownloadWindow::DownloadWindow(BRect frame, bool visible, + SettingsMessage* settings) + : BWindow(frame, "Downloads", + B_TITLED_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, + B_AUTO_UPDATE_SIZE_LIMITS | B_ASYNCHRONOUS_CONTROLS | B_NOT_ZOOMABLE), + fMinimizeOnClose(false) +{ + SetPulseRate(1000000); + + settings->AddListener(BMessenger(this)); + BPath downloadPath; + if (find_directory(B_DESKTOP_DIRECTORY, &downloadPath) != B_OK) + downloadPath.SetTo("/boot/home/Desktop"); + fDownloadPath = settings->GetValue(kSettingsKeyDownloadPath, + downloadPath.Path()); + settings->SetValue(kSettingsKeyDownloadPath, fDownloadPath); + + SetLayout(new BGroupLayout(B_VERTICAL, 0.0)); + + DownloadsContainerView* downloadsGroupView = new DownloadsContainerView(); + fDownloadViewsLayout = downloadsGroupView->GroupLayout(); + + BMenuBar* menuBar = new BMenuBar("Menu bar"); + BMenu* menu = new BMenu("Downloads"); + menu->AddItem(new BMenuItem("Open downloads folder", + new BMessage(OPEN_DOWNLOADS_FOLDER))); + BMessage* newWindowMessage = new BMessage(NEW_WINDOW); + newWindowMessage->AddString("url", ""); + BMenuItem* newWindowItem = new BMenuItem("New browser window", + newWindowMessage, 'N'); + menu->AddItem(newWindowItem); + newWindowItem->SetTarget(be_app); + menu->AddSeparatorItem(); + menu->AddItem(new BMenuItem("Hide", new BMessage(B_QUIT_REQUESTED), 'D')); + menuBar->AddItem(menu); + + fDownloadsScrollView = new DownloadContainerScrollView(downloadsGroupView); + + fRemoveFinishedButton = new BButton("Remove finished", + new BMessage(REMOVE_FINISHED_DOWNLOADS)); + fRemoveFinishedButton->SetEnabled(false); + + fRemoveMissingButton = new BButton("Remove missing", + new BMessage(REMOVE_MISSING_DOWNLOADS)); + fRemoveMissingButton->SetEnabled(false); + + const float spacing = be_control_look->DefaultItemSpacing(); + + AddChild(BGroupLayoutBuilder(B_VERTICAL, 0.0) + .Add(menuBar) + .Add(fDownloadsScrollView) + .Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER)) + .Add(BGroupLayoutBuilder(B_HORIZONTAL, spacing) + .AddGlue() + .Add(fRemoveMissingButton) + .Add(fRemoveFinishedButton) + .SetInsets(12, 5, 12, 5) + ) + ); + + PostMessage(INIT); + + if (!visible) + Hide(); + Show(); +} + + +DownloadWindow::~DownloadWindow() +{ + // Only necessary to save the current progress of unfinished downloads: + _SaveSettings(); +} + + +void +DownloadWindow::DispatchMessage(BMessage* message, BHandler* target) +{ + // We need to intercept mouse down events inside the area of download + // progress views (regardless of whether they have children at the click), + // so that they may display a context menu. + BPoint where; + int32 buttons; + if (message->what == B_MOUSE_DOWN + && message->FindPoint("screen_where", &where) == B_OK + && message->FindInt32("buttons", &buttons) == B_OK + && (buttons & B_SECONDARY_MOUSE_BUTTON) != 0) { + for (int32 i = fDownloadViewsLayout->CountItems() - 1; + BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { + DownloadProgressView* view = dynamic_cast<DownloadProgressView*>( + item->View()); + if (!view) + continue; + BPoint viewWhere(where); + view->ConvertFromScreen(&viewWhere); + if (view->Bounds().Contains(viewWhere)) { + view->ShowContextMenu(where); + return; + } + } + } + BWindow::DispatchMessage(message, target); +} + + +void +DownloadWindow::MessageReceived(BMessage* message) +{ + switch (message->what) { + case INIT: + { + _LoadSettings(); + // Small trick to get the correct enabled status of the Remove + // finished button + _DownloadFinished(NULL); + break; + } + case B_DOWNLOAD_ADDED: + { + BWebDownload* download; + if (message->FindPointer("download", reinterpret_cast<void**>( + &download)) == B_OK) { + _DownloadStarted(download); + } + break; + } + case B_DOWNLOAD_REMOVED: + { + BWebDownload* download; + if (message->FindPointer("download", reinterpret_cast<void**>( + &download)) == B_OK) { + _DownloadFinished(download); + } + break; + } + case OPEN_DOWNLOADS_FOLDER: + { + entry_ref ref; + status_t status = get_ref_for_path(fDownloadPath.String(), &ref); + if (status == B_OK) + status = be_roster->Launch(&ref); + if (status != B_OK && status != B_ALREADY_RUNNING) { + BString errorString("The downloads folder could not be " + "opened.\n\n"); + errorString << "Error: " << strerror(status); + BAlert* alert = new BAlert("Error opening downloads folder", + errorString.String(), "OK"); + alert->Go(NULL); + } + break; + } + case REMOVE_FINISHED_DOWNLOADS: + _RemoveFinishedDownloads(); + break; + case REMOVE_MISSING_DOWNLOADS: + _RemoveMissingDownloads(); + break; + case SAVE_SETTINGS: + _ValidateButtonStatus(); + _SaveSettings(); + break; + + case SETTINGS_VALUE_CHANGED: + { + BString string; + if (message->FindString("name", &string) == B_OK + && string == kSettingsKeyDownloadPath + && message->FindString("value", &string) == B_OK) { + fDownloadPath = string; + } + break; + } + default: + BWindow::MessageReceived(message); + break; + } +} + + +bool +DownloadWindow::QuitRequested() +{ + if (fMinimizeOnClose) { + if (!IsMinimized()) + Minimize(true); + } else { + if (!IsHidden()) + Hide(); + } + return false; +} + + +bool +DownloadWindow::DownloadsInProgress() +{ + bool downloadsInProgress = false; + if (!Lock()) + return downloadsInProgress; + + for (int32 i = fDownloadViewsLayout->CountItems() - 1; + BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { + DownloadProgressView* view = dynamic_cast<DownloadProgressView*>( + item->View()); + if (!view) + continue; + if (view->Download() != NULL) { + downloadsInProgress = true; + break; + } + } + + Unlock(); + + return downloadsInProgress; +} + + +void +DownloadWindow::SetMinimizeOnClose(bool minimize) +{ + if (Lock()) { + fMinimizeOnClose = minimize; + Unlock(); + } +} + + +// #pragma mark - private + + +void +DownloadWindow::_DownloadStarted(BWebDownload* download) +{ + download->Start(BPath(fDownloadPath.String())); + + int32 finishedCount = 0; + int32 missingCount = 0; + int32 index = 0; + for (int32 i = fDownloadViewsLayout->CountItems() - 1; + BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { + DownloadProgressView* view = dynamic_cast<DownloadProgressView*>( + item->View()); + if (!view) + continue; + if (view->URL() == download->URL()) { + index = i; + view->RemoveSelf(); + delete view; + continue; + } + if (view->IsFinished()) + finishedCount++; + if (view->IsMissing()) + missingCount++; + } + fRemoveFinishedButton->SetEnabled(finishedCount > 0); + fRemoveMissingButton->SetEnabled(missingCount > 0); + DownloadProgressView* view = new DownloadProgressView(download); + if (!view->Init()) { + delete view; + return; + } + fDownloadViewsLayout->AddView(index, view); + + // Scroll new download into view + if (BScrollBar* scrollBar = fDownloadsScrollView->ScrollBar(B_VERTICAL)) { + float min; + float max; + scrollBar->GetRange(&min, &max); + float viewHeight = view->MinSize().height + 1; + float scrollOffset = min + index * viewHeight; + float scrollBarHeight = scrollBar->Bounds().Height() - 1; + float value = scrollBar->Value(); + if (scrollOffset < value) + scrollBar->SetValue(scrollOffset); + else if (scrollOffset + viewHeight > value + scrollBarHeight) { + float diff = scrollOffset + viewHeight - (value + scrollBarHeight); + scrollBar->SetValue(value + diff); + } + } + + _SaveSettings(); + + SetWorkspaces(B_CURRENT_WORKSPACE); + if (IsHidden()) + Show(); +} + + +void +DownloadWindow::_DownloadFinished(BWebDownload* download) +{ + int32 finishedCount = 0; + int32 missingCount = 0; + for (int32 i = 0; + BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i++) { + DownloadProgressView* view = dynamic_cast<DownloadProgressView*>( + item->View()); + if (!view) + continue; + if (download && view->Download() == download) { + view->DownloadFinished(); + finishedCount++; + continue; + } + if (view->IsFinished()) + finishedCount++; + if (view->IsMissing()) + missingCount++; + } + fRemoveFinishedButton->SetEnabled(finishedCount > 0); + fRemoveMissingButton->SetEnabled(missingCount > 0); + if (download) + _SaveSettings(); +} + + +void +DownloadWindow::_RemoveFinishedDownloads() +{ + int32 missingCount = 0; + for (int32 i = fDownloadViewsLayout->CountItems() - 1; + BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { + DownloadProgressView* view = dynamic_cast<DownloadProgressView*>( + item->View()); + if (!view) + continue; + if (view->IsFinished()) { + view->RemoveSelf(); + delete view; + } else if (view->IsMissing()) + missingCount++; + } + fRemoveFinishedButton->SetEnabled(false); + fRemoveMissingButton->SetEnabled(missingCount > 0); + _SaveSettings(); +} + + +void +DownloadWindow::_RemoveMissingDownloads() +{ + int32 finishedCount = 0; + for (int32 i = fDownloadViewsLayout->CountItems() - 1; + BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { + DownloadProgressView* view = dynamic_cast<DownloadProgressView*>( + item->View()); + if (!view) + continue; + if (view->IsMissing()) { + view->RemoveSelf(); + delete view; + } else if (view->IsFinished()) + finishedCount++; + } + fRemoveMissingButton->SetEnabled(false); + fRemoveFinishedButton->SetEnabled(finishedCount > 0); + _SaveSettings(); +} + + +void +DownloadWindow::_ValidateButtonStatus() +{ + int32 finishedCount = 0; + int32 missingCount = 0; + for (int32 i = fDownloadViewsLayout->CountItems() - 1; + BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { + DownloadProgressView* view = dynamic_cast<DownloadProgressView*>( + item->View()); + if (!view) + continue; + if (view->IsFinished()) + finishedCount++; + if (view->IsMissing()) + missingCount++; + } + fRemoveFinishedButton->SetEnabled(finishedCount > 0); + fRemoveMissingButton->SetEnabled(missingCount > 0); +} + + +void +DownloadWindow::_SaveSettings() +{ + BFile file; + if (!_OpenSettingsFile(file, B_ERASE_FILE | B_CREATE_FILE | B_WRITE_ONLY)) + return; + BMessage message; + for (int32 i = fDownloadViewsLayout->CountItems() - 1; + BLayoutItem* item = fDownloadViewsLayout->ItemAt(i); i--) { + DownloadProgressView* view = dynamic_cast<DownloadProgressView*>( + item->View()); + if (!view) + continue; + BMessage downloadArchive; + if (view->SaveSettings(&downloadArchive) == B_OK) + message.AddMessage("download", &downloadArchive); + } + message.Flatten(&file); +} + + +void +DownloadWindow::_LoadSettings() +{ + BFile file; + if (!_OpenSettingsFile(file, B_READ_ONLY)) + return; + BMessage message; + if (message.Unflatten(&file) != B_OK) + return; + BMessage downloadArchive; + for (int32 i = 0; + message.FindMessage("download", i, &downloadArchive) == B_OK; + i++) { + DownloadProgressView* view = new DownloadProgressView( + &downloadArchive); + if (!view->Init(&downloadArchive)) + continue; + fDownloadViewsLayout->AddView(0, view); + } +} + + +bool +DownloadWindow::_OpenSettingsFile(BFile& file, uint32 mode) +{ + BPath path; + if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK + || path.Append(kApplicationName) != B_OK + || path.Append("Downloads") != B_OK) { + return false; + } + return file.SetTo(path.Path(), mode) == B_OK; +} + + diff --git a/src/apps/webpositive/DownloadWindow.h b/src/apps/webpositive/DownloadWindow.h new file mode 100644 index 0000000000..d76661ba4e --- /dev/null +++ b/src/apps/webpositive/DownloadWindow.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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. + */ +#ifndef DOWNLOAD_WINDOW_H +#define DOWNLOAD_WINDOW_H + + +#include <String.h> +#include <Window.h> + +class BButton; +class BFile; +class BGroupLayout; +class BScrollView; +class BWebDownload; +class SettingsMessage; + + +class DownloadWindow : public BWindow { +public: + DownloadWindow(BRect frame, bool visible, + SettingsMessage* settings); + virtual ~DownloadWindow(); + + virtual void DispatchMessage(BMessage* message, + BHandler* target); + virtual void MessageReceived(BMessage* message); + virtual bool QuitRequested(); + + bool DownloadsInProgress(); + void SetMinimizeOnClose(bool minimize); + +private: + void _DownloadStarted(BWebDownload* download); + void _DownloadFinished(BWebDownload* download); + void _RemoveFinishedDownloads(); + void _RemoveMissingDownloads(); + void _ValidateButtonStatus(); + void _SaveSettings(); + void _LoadSettings(); + bool _OpenSettingsFile(BFile& file, uint32 mode); + +private: + BScrollView* fDownloadsScrollView; + BGroupLayout* fDownloadViewsLayout; + BButton* fRemoveFinishedButton; + BButton* fRemoveMissingButton; + BString fDownloadPath; + bool fMinimizeOnClose; +}; + +#endif // DOWNLOAD_WINDOW_H diff --git a/src/apps/webpositive/SettingsKeys.cpp b/src/apps/webpositive/SettingsKeys.cpp new file mode 100644 index 0000000000..b72166cd3a --- /dev/null +++ b/src/apps/webpositive/SettingsKeys.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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 "SettingsKeys.h" + +const char* kSettingsKeyDownloadPath = "download path"; +const char* kSettingsKeyShowTabsIfSinglePageOpen + = "show tabs if single page open"; +const char* kSettingsKeyAutoHideInterfaceInFullscreenMode + = "auto hide interface in full screen mode"; +const char* kSettingsKeyAutoHidePointer = "auto hide pointer"; +const char* kSettingsKeyShowHomeButton = "show home button"; + +const char* kSettingsKeyNewWindowPolicy = "new window policy"; +const char* kSettingsKeyNewTabPolicy = "new tab policy"; +const char* kSettingsKeyStartPageURL = "start page url"; +const char* kSettingsKeySearchPageURL = "search page url"; + + +const char* kDefaultDownloadPath = "/boot/home/Desktop/"; +const char* kDefaultStartPageURL + = "file:///boot/home/config/settings/WebPositive/LoaderPages/Welcome"; +const char* kDefaultSearchPageURL = "http://www.google.com"; + +const char* kSettingsKeyUseProxy = "use http proxy"; +const char* kSettingsKeyProxyAddress = "http proxy address"; +const char* kSettingsKeyProxyPort = "http proxy port"; diff --git a/src/apps/webpositive/SettingsKeys.h b/src/apps/webpositive/SettingsKeys.h new file mode 100644 index 0000000000..1a95bbfe89 --- /dev/null +++ b/src/apps/webpositive/SettingsKeys.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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. + */ +#ifndef SETTINGS_KEYS_H +#define SETTINGS_KEYS_H + +#include <SupportDefs.h> + + +extern const char* kSettingsKeyDownloadPath; +extern const char* kSettingsKeyShowTabsIfSinglePageOpen; +extern const char* kSettingsKeyAutoHideInterfaceInFullscreenMode; +extern const char* kSettingsKeyAutoHidePointer; +extern const char* kSettingsKeyShowHomeButton; + +extern const char* kSettingsKeyNewWindowPolicy; +extern const char* kSettingsKeyNewTabPolicy; +extern const char* kSettingsKeyStartPageURL; +extern const char* kSettingsKeySearchPageURL; + +extern const char* kDefaultDownloadPath; +extern const char* kDefaultStartPageURL; +extern const char* kDefaultSearchPageURL; + +extern const char* kSettingsKeyUseProxy; +extern const char* kSettingsKeyProxyAddress; +extern const char* kSettingsKeyProxyPort; + +#endif // SETTINGS_KEYS_H diff --git a/src/apps/webpositive/SettingsWindow.cpp b/src/apps/webpositive/SettingsWindow.cpp new file mode 100644 index 0000000000..5a5ee41873 --- /dev/null +++ b/src/apps/webpositive/SettingsWindow.cpp @@ -0,0 +1,884 @@ +/* + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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 "SettingsWindow.h" + +#include <Button.h> +#include <CheckBox.h> +#include <ControlLook.h> +#include <GridLayoutBuilder.h> +#include <GroupLayout.h> +#include <GroupLayoutBuilder.h> +#include <Locale.h> +#include <MenuItem.h> +#include <MenuField.h> +#include <Message.h> +#include <PopUpMenu.h> +#include <ScrollView.h> +#include <SeparatorView.h> +#include <SpaceLayoutItem.h> +#include <TabView.h> +#include <TextControl.h> +#include <debugger.h> + +#include <stdio.h> +#include <stdlib.h> + +#include "BrowserApp.h" +#include "BrowsingHistory.h" +#include "BrowserWindow.h" +#include "FontSelectionView.h" +#include "SettingsKeys.h" +#include "SettingsMessage.h" +#include "WebSettings.h" + + +#undef B_TRANSLATION_CONTEXT +#define B_TRANSLATION_CONTEXT "Settings Window" + +enum { + MSG_APPLY = 'aply', + MSG_CANCEL = 'cncl', + MSG_REVERT = 'rvrt', + + MSG_START_PAGE_CHANGED = 'hpch', + MSG_SEARCH_PAGE_CHANGED = 'spch', + MSG_DOWNLOAD_FOLDER_CHANGED = 'dnfc', + MSG_NEW_WINDOWS_BEHAVIOR_CHANGED = 'nwbc', + MSG_NEW_TABS_BEHAVIOR_CHANGED = 'ntbc', + MSG_HISTORY_MENU_DAYS_CHANGED = 'digm', + MSG_TAB_DISPLAY_BEHAVIOR_CHANGED = 'tdbc', + MSG_AUTO_HIDE_INTERFACE_BEHAVIOR_CHANGED = 'ahic', + MSG_AUTO_HIDE_POINTER_BEHAVIOR_CHANGED = 'ahpc', + MSG_SHOW_HOME_BUTTON_CHANGED = 'shbc', + + MSG_STANDARD_FONT_CHANGED = 'stfc', + MSG_SERIF_FONT_CHANGED = 'sefc', + MSG_SANS_SERIF_FONT_CHANGED = 'ssfc', + MSG_FIXED_FONT_CHANGED = 'ffch', + + MSG_STANDARD_FONT_SIZE_SELECTED = 'sfss', + MSG_FIXED_FONT_SIZE_SELECTED = 'ffss', + + MSG_USE_PROXY_CHANGED = 'upsc', + MSG_PROXY_ADDRESS_CHANGED = 'psac', + MSG_PROXY_PORT_CHANGED = 'pspc', +}; + +static const int32 kDefaultFontSize = 14; + + +SettingsWindow::SettingsWindow(BRect frame, SettingsMessage* settings) + : + BWindow(frame, B_TRANSLATE("Settings"), B_TITLED_WINDOW_LOOK, + B_NORMAL_WINDOW_FEEL, B_AUTO_UPDATE_SIZE_LIMITS + | B_ASYNCHRONOUS_CONTROLS | B_NOT_ZOOMABLE), + fSettings(settings) +{ + SetLayout(new BGroupLayout(B_VERTICAL)); + + fApplyButton = new BButton(B_TRANSLATE("Apply"), new BMessage(MSG_APPLY)); + fCancelButton = new BButton(B_TRANSLATE("Cancel"), + new BMessage(MSG_CANCEL)); + fRevertButton = new BButton(B_TRANSLATE("Revert"), + new BMessage(MSG_REVERT)); + + float spacing = be_control_look->DefaultItemSpacing(); + + BTabView* tabView = new BTabView("settings pages", B_WIDTH_FROM_LABEL); + + AddChild(BGroupLayoutBuilder(B_VERTICAL, spacing) + .Add(tabView) + .Add(BGroupLayoutBuilder(B_HORIZONTAL, spacing) + .Add(fRevertButton) + .AddGlue() + .Add(fCancelButton) + .Add(fApplyButton) + ) + .SetInsets(spacing, spacing, spacing, spacing) + ); + + tabView->AddTab(_CreateGeneralPage(spacing)); + tabView->AddTab(_CreateFontsPage(spacing)); + tabView->AddTab(_CreateProxyPage(spacing)); + + _SetupFontSelectionView(fStandardFontView, + new BMessage(MSG_STANDARD_FONT_CHANGED)); + _SetupFontSelectionView(fSerifFontView, + new BMessage(MSG_SERIF_FONT_CHANGED)); + _SetupFontSelectionView(fSansSerifFontView, + new BMessage(MSG_SANS_SERIF_FONT_CHANGED)); + _SetupFontSelectionView(fFixedFontView, + new BMessage(MSG_FIXED_FONT_CHANGED)); + + fApplyButton->MakeDefault(true); + + if (!frame.IsValid()) + CenterOnScreen(); + + // load settings from disk + _RevertSettings(); + // apply to WebKit + _ApplySettings(); + + // Start hidden + Hide(); + Show(); +} + + +SettingsWindow::~SettingsWindow() +{ + RemoveHandler(fStandardFontView); + delete fStandardFontView; + RemoveHandler(fSerifFontView); + delete fSerifFontView; + RemoveHandler(fSansSerifFontView); + delete fSansSerifFontView; + RemoveHandler(fFixedFontView); + delete fFixedFontView; +} + + +void +SettingsWindow::MessageReceived(BMessage* message) +{ + switch (message->what) { + case MSG_APPLY: + _ApplySettings(); + break; + case MSG_CANCEL: + _RevertSettings(); + PostMessage(B_QUIT_REQUESTED); + break; + case MSG_REVERT: + _RevertSettings(); + break; + + case MSG_STANDARD_FONT_SIZE_SELECTED: + { + int32 size = _SizesMenuValue(fStandardSizesMenu->Menu()); + fStandardFontView->SetSize(size); + fSerifFontView->SetSize(size); + fSansSerifFontView->SetSize(size); + _ValidateControlsEnabledStatus(); + break; + } + case MSG_FIXED_FONT_SIZE_SELECTED: + { + int32 size = _SizesMenuValue(fFixedSizesMenu->Menu()); + fFixedFontView->SetSize(size); + _ValidateControlsEnabledStatus(); + break; + } + + case MSG_START_PAGE_CHANGED: + case MSG_SEARCH_PAGE_CHANGED: + case MSG_DOWNLOAD_FOLDER_CHANGED: + case MSG_NEW_WINDOWS_BEHAVIOR_CHANGED: + case MSG_NEW_TABS_BEHAVIOR_CHANGED: + case MSG_HISTORY_MENU_DAYS_CHANGED: + case MSG_TAB_DISPLAY_BEHAVIOR_CHANGED: + case MSG_AUTO_HIDE_INTERFACE_BEHAVIOR_CHANGED: + case MSG_AUTO_HIDE_POINTER_BEHAVIOR_CHANGED: + case MSG_SHOW_HOME_BUTTON_CHANGED: + case MSG_STANDARD_FONT_CHANGED: + case MSG_SERIF_FONT_CHANGED: + case MSG_SANS_SERIF_FONT_CHANGED: + case MSG_FIXED_FONT_CHANGED: + case MSG_USE_PROXY_CHANGED: + case MSG_PROXY_ADDRESS_CHANGED: + case MSG_PROXY_PORT_CHANGED: + // TODO: Some settings could change live, some others not? + _ValidateControlsEnabledStatus(); + break; + + default: + BWindow::MessageReceived(message); + break; + } +} + + +bool +SettingsWindow::QuitRequested() +{ + if (!IsHidden()) + Hide(); + return false; +} + + +void +SettingsWindow::Show() +{ + // When showing the window, the this is always the + // point to which we can revert the settings. + _RevertSettings(); + BWindow::Show(); +} + + +// #pragma mark - private + + +BView* +SettingsWindow::_CreateGeneralPage(float spacing) +{ + fStartPageControl = new BTextControl("start page", + B_TRANSLATE("Start page:"), "", new BMessage(MSG_START_PAGE_CHANGED)); + fStartPageControl->SetModificationMessage( + new BMessage(MSG_START_PAGE_CHANGED)); + fStartPageControl->SetText( + fSettings->GetValue(kSettingsKeyStartPageURL, kDefaultStartPageURL)); + + fSearchPageControl = new BTextControl("search page", + B_TRANSLATE("Search page:"), "", + new BMessage(MSG_SEARCH_PAGE_CHANGED)); + fSearchPageControl->SetModificationMessage( + new BMessage(MSG_SEARCH_PAGE_CHANGED)); + fSearchPageControl->SetText( + fSettings->GetValue(kSettingsKeySearchPageURL, kDefaultSearchPageURL)); + + fDownloadFolderControl = new BTextControl("download folder", + B_TRANSLATE("Download folder:"), "", + new BMessage(MSG_DOWNLOAD_FOLDER_CHANGED)); + fDownloadFolderControl->SetModificationMessage( + new BMessage(MSG_DOWNLOAD_FOLDER_CHANGED)); + fDownloadFolderControl->SetText( + fSettings->GetValue(kSettingsKeyDownloadPath, kDefaultDownloadPath)); + + fNewWindowBehaviorOpenHomeItem = new BMenuItem( + B_TRANSLATE("Open start page"), + new BMessage(MSG_NEW_WINDOWS_BEHAVIOR_CHANGED)); + fNewWindowBehaviorOpenSearchItem = new BMenuItem( + B_TRANSLATE("Open search page"), + new BMessage(MSG_NEW_WINDOWS_BEHAVIOR_CHANGED)); + fNewWindowBehaviorOpenBlankItem = new BMenuItem( + B_TRANSLATE("Open blank page"), + new BMessage(MSG_NEW_WINDOWS_BEHAVIOR_CHANGED)); + + fNewTabBehaviorCloneCurrentItem = new BMenuItem( + B_TRANSLATE("Clone current page"), + new BMessage(MSG_NEW_TABS_BEHAVIOR_CHANGED)); + fNewTabBehaviorOpenHomeItem = new BMenuItem( + B_TRANSLATE("Open start page"), + new BMessage(MSG_NEW_TABS_BEHAVIOR_CHANGED)); + fNewTabBehaviorOpenSearchItem = new BMenuItem( + B_TRANSLATE("Open search page"), + new BMessage(MSG_NEW_TABS_BEHAVIOR_CHANGED)); + fNewTabBehaviorOpenBlankItem = new BMenuItem( + B_TRANSLATE("Open blank page"), + new BMessage(MSG_NEW_TABS_BEHAVIOR_CHANGED)); + + fNewWindowBehaviorOpenHomeItem->SetMarked(true); + fNewTabBehaviorOpenBlankItem->SetMarked(true); + + BPopUpMenu* newWindowBehaviorMenu = new BPopUpMenu("New windows"); + newWindowBehaviorMenu->AddItem(fNewWindowBehaviorOpenHomeItem); + newWindowBehaviorMenu->AddItem(fNewWindowBehaviorOpenSearchItem); + newWindowBehaviorMenu->AddItem(fNewWindowBehaviorOpenBlankItem); + fNewWindowBehaviorMenu = new BMenuField("new window behavior", + B_TRANSLATE("New windows:"), newWindowBehaviorMenu); + + BPopUpMenu* newTabBehaviorMenu = new BPopUpMenu("New tabs"); + newTabBehaviorMenu->AddItem(fNewTabBehaviorOpenBlankItem); + newTabBehaviorMenu->AddItem(fNewTabBehaviorOpenHomeItem); + newTabBehaviorMenu->AddItem(fNewTabBehaviorOpenSearchItem); + newTabBehaviorMenu->AddItem(fNewTabBehaviorCloneCurrentItem); + fNewTabBehaviorMenu = new BMenuField("new tab behavior", + B_TRANSLATE("New tabs:"), newTabBehaviorMenu); + + fDaysInHistoryMenuControl = new BTextControl("days in history", + B_TRANSLATE("Number of days to keep links in History menu:"), "", + new BMessage(MSG_HISTORY_MENU_DAYS_CHANGED)); + fDaysInHistoryMenuControl->SetModificationMessage( + new BMessage(MSG_HISTORY_MENU_DAYS_CHANGED)); + BString maxHistoryAge; + maxHistoryAge << BrowsingHistory::DefaultInstance()->MaxHistoryItemAge(); + fDaysInHistoryMenuControl->SetText(maxHistoryAge.String()); + for (uchar i = 0; i < '0'; i++) + fDaysInHistoryMenuControl->TextView()->DisallowChar(i); + for (uchar i = '9' + 1; i <= 128; i++) + fDaysInHistoryMenuControl->TextView()->DisallowChar(i); + + fShowTabsIfOnlyOnePage = new BCheckBox("show tabs if only one page", + B_TRANSLATE("Show tabs if only one page is open."), + new BMessage(MSG_TAB_DISPLAY_BEHAVIOR_CHANGED)); + fShowTabsIfOnlyOnePage->SetValue(B_CONTROL_ON); + + fAutoHideInterfaceInFullscreenMode = new BCheckBox("auto-hide interface", + B_TRANSLATE("Auto-hide interface in fullscreen mode."), + new BMessage(MSG_AUTO_HIDE_INTERFACE_BEHAVIOR_CHANGED)); + fAutoHideInterfaceInFullscreenMode->SetValue(B_CONTROL_OFF); + + fAutoHidePointer = new BCheckBox("auto-hide pointer", + B_TRANSLATE("Auto-hide mouse pointer."), + new BMessage(MSG_AUTO_HIDE_POINTER_BEHAVIOR_CHANGED)); + fAutoHidePointer->SetValue(B_CONTROL_OFF); + + fShowHomeButton = new BCheckBox("show home button", + B_TRANSLATE("Show Home Button"), + new BMessage(MSG_SHOW_HOME_BUTTON_CHANGED)); + fShowHomeButton->SetValue(B_CONTROL_ON); + + BView* view = BGroupLayoutBuilder(B_VERTICAL, spacing / 2) + .Add(BGridLayoutBuilder(spacing / 2, spacing / 2) + .Add(fStartPageControl->CreateLabelLayoutItem(), 0, 0) + .Add(fStartPageControl->CreateTextViewLayoutItem(), 1, 0) + + .Add(fSearchPageControl->CreateLabelLayoutItem(), 0, 1) + .Add(fSearchPageControl->CreateTextViewLayoutItem(), 1, 1) + + .Add(fDownloadFolderControl->CreateLabelLayoutItem(), 0, 2) + .Add(fDownloadFolderControl->CreateTextViewLayoutItem(), 1, 2) + + .Add(fNewWindowBehaviorMenu->CreateLabelLayoutItem(), 0, 3) + .Add(fNewWindowBehaviorMenu->CreateMenuBarLayoutItem(), 1, 3) + + .Add(fNewTabBehaviorMenu->CreateLabelLayoutItem(), 0, 4) + .Add(fNewTabBehaviorMenu->CreateMenuBarLayoutItem(), 1, 4) + ) + .Add(BSpaceLayoutItem::CreateHorizontalStrut(spacing)) + .Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER)) + .Add(BSpaceLayoutItem::CreateHorizontalStrut(spacing)) + .Add(fShowTabsIfOnlyOnePage) + .Add(fAutoHideInterfaceInFullscreenMode) + .Add(fAutoHidePointer) + .Add(fShowHomeButton) + .Add(fDaysInHistoryMenuControl) + .Add(BSpaceLayoutItem::CreateHorizontalStrut(spacing)) + + .SetInsets(spacing, spacing, spacing, spacing) + + .TopView() + ; + view->SetName(B_TRANSLATE("General")); + return view; +} + + +BView* +SettingsWindow::_CreateFontsPage(float spacing) +{ + fStandardFontView = new FontSelectionView("standard", + B_TRANSLATE("Standard font:"), true, be_plain_font); + BFont defaultSerifFont = _FindDefaultSerifFont(); + fSerifFontView = new FontSelectionView("serif", + B_TRANSLATE("Serif font:"), true, &defaultSerifFont); + fSansSerifFontView = new FontSelectionView("sans serif", + B_TRANSLATE("Sans serif font:"), true, be_plain_font); + fFixedFontView = new FontSelectionView("fixed", + B_TRANSLATE("Fixed font:"), true, be_fixed_font); + + fStandardSizesMenu = new BMenuField("standard font size", + B_TRANSLATE("Default standard font size:"), new BPopUpMenu("sizes"), + NULL); + _BuildSizesMenu(fStandardSizesMenu->Menu(), + MSG_STANDARD_FONT_SIZE_SELECTED); + + fFixedSizesMenu = new BMenuField("fixed font size", + B_TRANSLATE("Default fixed font size:"), new BPopUpMenu("sizes"), + NULL); + _BuildSizesMenu(fFixedSizesMenu->Menu(), MSG_FIXED_FONT_SIZE_SELECTED); + + BView* view = BGridLayoutBuilder(spacing / 2, spacing / 2) + .Add(fStandardFontView->CreateFontsLabelLayoutItem(), 0, 0) + .Add(fStandardFontView->CreateFontsMenuBarLayoutItem(), 1, 0) + .Add(fStandardFontView->PreviewBox(), 0, 1, 2) + .Add(BSpaceLayoutItem::CreateHorizontalStrut(spacing), 0, 2, 2) + + .Add(fSerifFontView->CreateFontsLabelLayoutItem(), 0, 3) + .Add(fSerifFontView->CreateFontsMenuBarLayoutItem(), 1, 3) + .Add(fSerifFontView->PreviewBox(), 0, 4, 2) + .Add(BSpaceLayoutItem::CreateHorizontalStrut(spacing), 0, 5, 2) + + .Add(fSansSerifFontView->CreateFontsLabelLayoutItem(), 0, 6) + .Add(fSansSerifFontView->CreateFontsMenuBarLayoutItem(), 1, 6) + .Add(fSansSerifFontView->PreviewBox(), 0, 7, 2) + .Add(BSpaceLayoutItem::CreateHorizontalStrut(spacing), 0, 8, 2) + + .Add(fFixedFontView->CreateFontsLabelLayoutItem(), 0, 9) + .Add(fFixedFontView->CreateFontsMenuBarLayoutItem(), 1, 9) + .Add(fFixedFontView->PreviewBox(), 0, 10, 2) + .Add(BSpaceLayoutItem::CreateHorizontalStrut(spacing), 0, 11, 2) + + .Add(fStandardSizesMenu->CreateLabelLayoutItem(), 0, 12) + .Add(fStandardSizesMenu->CreateMenuBarLayoutItem(), 1, 12) + .Add(fFixedSizesMenu->CreateLabelLayoutItem(), 0, 13) + .Add(fFixedSizesMenu->CreateMenuBarLayoutItem(), 1, 13) + + .SetInsets(spacing, spacing, spacing, spacing) + + .View() + ; + view->SetName(B_TRANSLATE("Fonts")); + return view; +} + + +BView* +SettingsWindow::_CreateProxyPage(float spacing) +{ + fUseProxyCheckBox = new BCheckBox("use proxy", + B_TRANSLATE("Use proxy server to connect to the internet."), + new BMessage(MSG_USE_PROXY_CHANGED)); + fUseProxyCheckBox->SetValue(B_CONTROL_ON); + + fProxyAddressControl = new BTextControl("proxy address", + B_TRANSLATE("Proxy server address:"), "", + new BMessage(MSG_PROXY_ADDRESS_CHANGED)); + fProxyAddressControl->SetModificationMessage( + new BMessage(MSG_PROXY_ADDRESS_CHANGED)); + fProxyAddressControl->SetText( + fSettings->GetValue(kSettingsKeyProxyAddress, "")); + + fProxyPortControl = new BTextControl("proxy port", + B_TRANSLATE("Proxy server port:"), "", + new BMessage(MSG_PROXY_PORT_CHANGED)); + fProxyPortControl->SetModificationMessage( + new BMessage(MSG_PROXY_PORT_CHANGED)); + fProxyPortControl->SetText( + fSettings->GetValue(kSettingsKeyProxyAddress, "")); + + BView* view = BGroupLayoutBuilder(B_VERTICAL, spacing / 2) + .Add(fUseProxyCheckBox) + .Add(BGridLayoutBuilder(spacing / 2, spacing / 2) + .Add(fProxyAddressControl->CreateLabelLayoutItem(), 0, 0) + .Add(fProxyAddressControl->CreateTextViewLayoutItem(), 1, 0) + + .Add(fProxyPortControl->CreateLabelLayoutItem(), 0, 1) + .Add(fProxyPortControl->CreateTextViewLayoutItem(), 1, 1) + ) + .Add(BSpaceLayoutItem::CreateGlue()) + + .SetInsets(spacing, spacing, spacing, spacing) + + .TopView() + ; + view->SetName(B_TRANSLATE("Proxy server")); + return view; +} + + +void +SettingsWindow::_BuildSizesMenu(BMenu* menu, uint32 messageWhat) +{ + const float kMinSize = 8.0; + const float kMaxSize = 18.0; + + const int32 kSizes[] = {7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 21, 24, 0}; + + for (int32 i = 0; kSizes[i]; i++) { + int32 size = kSizes[i]; + if (size < kMinSize || size > kMaxSize) + continue; + + char label[32]; + snprintf(label, sizeof(label), "%ld", size); + + BMessage* message = new BMessage(messageWhat); + message->AddInt32("size", size); + + BMenuItem* item = new BMenuItem(label, message); + + menu->AddItem(item); + item->SetTarget(this); + } +} + + +void +SettingsWindow::_SetupFontSelectionView(FontSelectionView* view, + BMessage* message) +{ + AddHandler(view); + view->AttachedToLooper(); + view->SetMessage(message); + view->SetTarget(this); +} + + +// #pragma mark - + + +bool +SettingsWindow::_CanApplySettings() const +{ + bool canApply = false; + + // General settings + canApply = canApply || (strcmp(fStartPageControl->Text(), + fSettings->GetValue(kSettingsKeyStartPageURL, + kDefaultStartPageURL)) != 0); + + canApply = canApply || (strcmp(fSearchPageControl->Text(), + fSettings->GetValue(kSettingsKeySearchPageURL, + kDefaultSearchPageURL)) != 0); + + canApply = canApply || (strcmp(fDownloadFolderControl->Text(), + fSettings->GetValue(kSettingsKeyDownloadPath, + kDefaultDownloadPath)) != 0); + + canApply = canApply || ((fShowTabsIfOnlyOnePage->Value() == B_CONTROL_ON) + != fSettings->GetValue(kSettingsKeyShowTabsIfSinglePageOpen, true)); + + canApply = canApply || ( + (fAutoHideInterfaceInFullscreenMode->Value() == B_CONTROL_ON) + != fSettings->GetValue(kSettingsKeyAutoHideInterfaceInFullscreenMode, + false)); + + canApply = canApply || ( + (fAutoHidePointer->Value() == B_CONTROL_ON) + != fSettings->GetValue(kSettingsKeyAutoHidePointer, false)); + + canApply = canApply || ((fShowHomeButton->Value() == B_CONTROL_ON) + != fSettings->GetValue(kSettingsKeyShowHomeButton, true)); + + canApply = canApply || (_MaxHistoryAge() + != BrowsingHistory::DefaultInstance()->MaxHistoryItemAge()); + + // New window policy + canApply = canApply || (_NewWindowPolicy() + != fSettings->GetValue(kSettingsKeyNewWindowPolicy, + (uint32)OpenStartPage)); + + // New tab policy + canApply = canApply || (_NewTabPolicy() + != fSettings->GetValue(kSettingsKeyNewTabPolicy, + (uint32)OpenBlankPage)); + + // Font settings + canApply = canApply || (fStandardFontView->Font() + != fSettings->GetValue("standard font", *be_plain_font)); + + canApply = canApply || (fSerifFontView->Font() + != fSettings->GetValue("serif font", _FindDefaultSerifFont())); + + canApply = canApply || (fSansSerifFontView->Font() + != fSettings->GetValue("sans serif font", *be_plain_font)); + + canApply = canApply || (fFixedFontView->Font() + != fSettings->GetValue("fixed font", *be_fixed_font)); + + canApply = canApply || (_SizesMenuValue(fStandardSizesMenu->Menu()) + != fSettings->GetValue("standard font size", kDefaultFontSize)); + + canApply = canApply || (_SizesMenuValue(fFixedSizesMenu->Menu()) + != fSettings->GetValue("fixed font size", kDefaultFontSize)); + + // Proxy settings + canApply = canApply || ((fUseProxyCheckBox->Value() == B_CONTROL_ON) + != fSettings->GetValue(kSettingsKeyUseProxy, false)); + + canApply = canApply || (strcmp(fProxyAddressControl->Text(), + fSettings->GetValue(kSettingsKeyProxyAddress, "")) != 0); + + canApply = canApply || (_ProxyPort() + != fSettings->GetValue(kSettingsKeyProxyPort, (uint32)0)); + + return canApply; +} + + +void +SettingsWindow::_ApplySettings() +{ + // Store general settings + int32 maxHistoryAge = _MaxHistoryAge(); + BString text; + text << maxHistoryAge; + fDaysInHistoryMenuControl->SetText(text.String()); + BrowsingHistory::DefaultInstance()->SetMaxHistoryItemAge(maxHistoryAge); + + fSettings->SetValue(kSettingsKeyStartPageURL, fStartPageControl->Text()); + fSettings->SetValue(kSettingsKeySearchPageURL, fSearchPageControl->Text()); + fSettings->SetValue(kSettingsKeyDownloadPath, fDownloadFolderControl->Text()); + fSettings->SetValue(kSettingsKeyShowTabsIfSinglePageOpen, + fShowTabsIfOnlyOnePage->Value() == B_CONTROL_ON); + fSettings->SetValue(kSettingsKeyAutoHideInterfaceInFullscreenMode, + fAutoHideInterfaceInFullscreenMode->Value() == B_CONTROL_ON); + fSettings->SetValue(kSettingsKeyAutoHidePointer, + fAutoHidePointer->Value() == B_CONTROL_ON); + fSettings->SetValue(kSettingsKeyShowHomeButton, + fShowHomeButton->Value() == B_CONTROL_ON); + + // New page policies + fSettings->SetValue(kSettingsKeyNewWindowPolicy, _NewWindowPolicy()); + fSettings->SetValue(kSettingsKeyNewTabPolicy, _NewTabPolicy()); + + // Store fond settings + fSettings->SetValue("standard font", fStandardFontView->Font()); + fSettings->SetValue("serif font", fSerifFontView->Font()); + fSettings->SetValue("sans serif font", fSansSerifFontView->Font()); + fSettings->SetValue("fixed font", fFixedFontView->Font()); + int32 standardFontSize = _SizesMenuValue(fStandardSizesMenu->Menu()); + int32 fixedFontSize = _SizesMenuValue(fFixedSizesMenu->Menu()); + fSettings->SetValue("standard font size", standardFontSize); + fSettings->SetValue("fixed font size", fixedFontSize); + + // Store proxy settings + + fSettings->SetValue(kSettingsKeyUseProxy, + fUseProxyCheckBox->Value() == B_CONTROL_ON); + fSettings->SetValue(kSettingsKeyProxyAddress, + fProxyAddressControl->Text()); + uint32 proxyPort = _ProxyPort(); + fSettings->SetValue(kSettingsKeyProxyPort, proxyPort); + + fSettings->Save(); + + // Apply settings to default web page settings. + BWebSettings::Default()->SetStandardFont(fStandardFontView->Font()); + BWebSettings::Default()->SetSerifFont(fSerifFontView->Font()); + BWebSettings::Default()->SetSansSerifFont(fSansSerifFontView->Font()); + BWebSettings::Default()->SetFixedFont(fFixedFontView->Font()); + BWebSettings::Default()->SetDefaultStandardFontSize(standardFontSize); + BWebSettings::Default()->SetDefaultFixedFontSize(fixedFontSize); + + if (fUseProxyCheckBox->Value() == B_CONTROL_ON) { + BWebSettings::Default()->SetProxyInfo(fProxyAddressControl->Text(), + proxyPort, B_PROXY_TYPE_HTTP, "", ""); + } else + BWebSettings::Default()->SetProxyInfo(); + + // This will find all currently instantiated page settings and apply + // the default values, unless the page settings have local overrides. + BWebSettings::Default()->Apply(); + + + _ValidateControlsEnabledStatus(); +} + + +void +SettingsWindow::_RevertSettings() +{ + fStartPageControl->SetText( + fSettings->GetValue(kSettingsKeyStartPageURL, kDefaultStartPageURL)); + + fSearchPageControl->SetText( + fSettings->GetValue(kSettingsKeySearchPageURL, kDefaultSearchPageURL)); + + fDownloadFolderControl->SetText( + fSettings->GetValue(kSettingsKeyDownloadPath, kDefaultDownloadPath)); + fShowTabsIfOnlyOnePage->SetValue( + fSettings->GetValue(kSettingsKeyShowTabsIfSinglePageOpen, true)); + fAutoHideInterfaceInFullscreenMode->SetValue( + fSettings->GetValue(kSettingsKeyAutoHideInterfaceInFullscreenMode, + false)); + fAutoHidePointer->SetValue( + fSettings->GetValue(kSettingsKeyAutoHidePointer, false)); + fShowHomeButton->SetValue( + fSettings->GetValue(kSettingsKeyShowHomeButton, true)); + + BString text; + text << BrowsingHistory::DefaultInstance()->MaxHistoryItemAge(); + fDaysInHistoryMenuControl->SetText(text.String()); + + // New window policy + uint32 newWindowPolicy = fSettings->GetValue(kSettingsKeyNewWindowPolicy, + (uint32)OpenStartPage); + switch (newWindowPolicy) { + default: + case OpenStartPage: + fNewWindowBehaviorOpenHomeItem->SetMarked(true); + break; + case OpenSearchPage: + fNewWindowBehaviorOpenSearchItem->SetMarked(true); + break; + case OpenBlankPage: + fNewWindowBehaviorOpenBlankItem->SetMarked(true); + break; + } + + // New tab policy + uint32 newTabPolicy = fSettings->GetValue(kSettingsKeyNewTabPolicy, + (uint32)OpenBlankPage); + switch (newTabPolicy) { + default: + case OpenBlankPage: + fNewTabBehaviorOpenBlankItem->SetMarked(true); + break; + case OpenStartPage: + fNewTabBehaviorOpenHomeItem->SetMarked(true); + break; + case OpenSearchPage: + fNewTabBehaviorOpenSearchItem->SetMarked(true); + break; + case CloneCurrentPage: + fNewTabBehaviorCloneCurrentItem->SetMarked(true); + break; + } + + // Font settings + int32 defaultFontSize = fSettings->GetValue("standard font size", + kDefaultFontSize); + int32 defaultFixedFontSize = fSettings->GetValue("fixed font size", + kDefaultFontSize); + + _SetSizesMenuValue(fStandardSizesMenu->Menu(), defaultFontSize); + _SetSizesMenuValue(fFixedSizesMenu->Menu(), defaultFixedFontSize); + + fStandardFontView->SetFont(fSettings->GetValue("standard font", + *be_plain_font), defaultFontSize); + fSerifFontView->SetFont(fSettings->GetValue("serif font", + _FindDefaultSerifFont()), defaultFontSize); + fSansSerifFontView->SetFont(fSettings->GetValue("sans serif font", + *be_plain_font), defaultFontSize); + fFixedFontView->SetFont(fSettings->GetValue("fixed font", + *be_fixed_font), defaultFixedFontSize); + + // Proxy settings + fUseProxyCheckBox->SetValue(fSettings->GetValue(kSettingsKeyUseProxy, + false)); + fProxyAddressControl->SetText(fSettings->GetValue(kSettingsKeyProxyAddress, + "")); + text = ""; + text << fSettings->GetValue(kSettingsKeyProxyPort, (uint32)0); + fProxyPortControl->SetText(text.String()); + + _ValidateControlsEnabledStatus(); +} + + +void +SettingsWindow::_ValidateControlsEnabledStatus() +{ + bool canApply = _CanApplySettings(); + fApplyButton->SetEnabled(canApply); + fRevertButton->SetEnabled(canApply); + // Let the Cancel button be enabled always, as another way to close the + // window... + fCancelButton->SetEnabled(true); + + bool useProxy = fUseProxyCheckBox->Value() == B_CONTROL_ON; + fProxyAddressControl->SetEnabled(useProxy); + fProxyPortControl->SetEnabled(useProxy); +} + + +// #pragma mark - + + +uint32 +SettingsWindow::_NewWindowPolicy() const +{ + uint32 newWindowPolicy = OpenStartPage; + BMenuItem* markedItem = fNewWindowBehaviorMenu->Menu()->FindMarked(); + if (markedItem == fNewWindowBehaviorOpenSearchItem) + newWindowPolicy = OpenSearchPage; + else if (markedItem == fNewWindowBehaviorOpenBlankItem) + newWindowPolicy = OpenBlankPage; + return newWindowPolicy; +} + + +uint32 +SettingsWindow::_NewTabPolicy() const +{ + uint32 newTabPolicy = OpenBlankPage; + BMenuItem* markedItem = fNewTabBehaviorMenu->Menu()->FindMarked(); + if (markedItem == fNewTabBehaviorCloneCurrentItem) + newTabPolicy = CloneCurrentPage; + else if (markedItem == fNewTabBehaviorOpenHomeItem) + newTabPolicy = OpenStartPage; + else if (markedItem == fNewTabBehaviorOpenSearchItem) + newTabPolicy = OpenSearchPage; + return newTabPolicy; +} + + +int32 +SettingsWindow::_MaxHistoryAge() const +{ + int32 maxHistoryAge = atoi(fDaysInHistoryMenuControl->Text()); + if (maxHistoryAge <= 0) + maxHistoryAge = 1; + if (maxHistoryAge >= 35) + maxHistoryAge = 35; + return maxHistoryAge; +} + + +void +SettingsWindow::_SetSizesMenuValue(BMenu* menu, int32 value) +{ + for (int32 i = 0; BMenuItem* item = menu->ItemAt(i); i++) { + bool marked = false; + if (BMessage* message = item->Message()) { + int32 size; + if (message->FindInt32("size", &size) == B_OK && size == value) + marked = true; + } + item->SetMarked(marked); + } +} + + +int32 +SettingsWindow::_SizesMenuValue(BMenu* menu) const +{ + if (BMenuItem* item = menu->FindMarked()) { + if (BMessage* message = item->Message()) { + int32 size; + if (message->FindInt32("size", &size) == B_OK) + return size; + } + } + return kDefaultFontSize; +} + + +BFont +SettingsWindow::_FindDefaultSerifFont() const +{ + // Default to the first "serif" font we find. + BFont serifFont(*be_plain_font); + font_family family; + int32 familyCount = count_font_families(); + for (int32 i = 0; i < familyCount; i++) { + if (get_font_family(i, &family) == B_OK) { + BString familyString(family); + if (familyString.IFindFirst("sans") >= 0) + continue; + if (familyString.IFindFirst("serif") >= 0) { + serifFont.SetFamilyAndFace(family, B_REGULAR_FACE); + break; + } + } + } + return serifFont; +} + + +uint32 +SettingsWindow::_ProxyPort() const +{ + return atoul(fProxyPortControl->Text()); +} + + diff --git a/src/apps/webpositive/SettingsWindow.h b/src/apps/webpositive/SettingsWindow.h new file mode 100644 index 0000000000..251d84405a --- /dev/null +++ b/src/apps/webpositive/SettingsWindow.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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. + */ +#ifndef SETTINGS_WINDOW_H +#define SETTINGS_WINDOW_H + +#include <Window.h> + +class BButton; +class BCheckBox; +class BMenu; +class BMenuField; +class BMenuItem; +class BTextControl; +class FontSelectionView; +class SettingsMessage; + + +class SettingsWindow : public BWindow { +public: + SettingsWindow(BRect frame, + SettingsMessage* settings); + virtual ~SettingsWindow(); + + virtual void MessageReceived(BMessage* message); + virtual bool QuitRequested(); + + virtual void Show(); + +private: + BView* _CreateGeneralPage(float spacing); + BView* _CreateFontsPage(float spacing); + BView* _CreateProxyPage(float spacing); + void _BuildSizesMenu(BMenu* menu, + uint32 messageWhat); + void _SetupFontSelectionView( + FontSelectionView* view, + BMessage* message); + + bool _CanApplySettings() const; + void _ApplySettings(); + void _RevertSettings(); + void _ValidateControlsEnabledStatus(); + + uint32 _NewWindowPolicy() const; + uint32 _NewTabPolicy() const; + int32 _MaxHistoryAge() const; + + void _SetSizesMenuValue(BMenu* menu, int32 value); + int32 _SizesMenuValue(BMenu* menu) const; + + BFont _FindDefaultSerifFont() const; + + uint32 _ProxyPort() const; + +private: + SettingsMessage* fSettings; + + BTextControl* fStartPageControl; + BTextControl* fSearchPageControl; + BTextControl* fDownloadFolderControl; + + BMenuField* fNewWindowBehaviorMenu; + BMenuItem* fNewWindowBehaviorOpenHomeItem; + BMenuItem* fNewWindowBehaviorOpenSearchItem; + BMenuItem* fNewWindowBehaviorOpenBlankItem; + + BMenuField* fNewTabBehaviorMenu; + BMenuItem* fNewTabBehaviorCloneCurrentItem; + BMenuItem* fNewTabBehaviorOpenHomeItem; + BMenuItem* fNewTabBehaviorOpenSearchItem; + BMenuItem* fNewTabBehaviorOpenBlankItem; + + BTextControl* fDaysInHistoryMenuControl; + BCheckBox* fShowTabsIfOnlyOnePage; + BCheckBox* fAutoHideInterfaceInFullscreenMode; + BCheckBox* fAutoHidePointer; + BCheckBox* fShowHomeButton; + + FontSelectionView* fStandardFontView; + FontSelectionView* fSerifFontView; + FontSelectionView* fSansSerifFontView; + FontSelectionView* fFixedFontView; + + BCheckBox* fUseProxyCheckBox; + BTextControl* fProxyAddressControl; + BTextControl* fProxyPortControl; + + BButton* fApplyButton; + BButton* fCancelButton; + BButton* fRevertButton; + + BMenuField* fStandardSizesMenu; + BMenuField* fFixedSizesMenu; +}; + + +#endif // SETTINGS_WINDOW_H + diff --git a/src/apps/webpositive/URLInputGroup.cpp b/src/apps/webpositive/URLInputGroup.cpp new file mode 100644 index 0000000000..f84f0e12bf --- /dev/null +++ b/src/apps/webpositive/URLInputGroup.cpp @@ -0,0 +1,654 @@ +/* + * Copyright 2010 Stephan Aßmus <superstippi@gmx.de> + * All rights reserved. Distributed under the terms of the MIT License. + */ + +#include "URLInputGroup.h" + +#include <Bitmap.h> +#include <Button.h> +#include <ControlLook.h> +#include <Clipboard.h> +#include <GroupLayout.h> +#include <GroupLayoutBuilder.h> +#include <LayoutUtils.h> +#include <MenuItem.h> +#include <PopUpMenu.h> +#include <SeparatorView.h> +#include <TextView.h> +#include <Window.h> + +#include <stdio.h> +#include <stdlib.h> + +#include "BaseURL.h" +#include "BitmapButton.h" +#include "BrowsingHistory.h" +#include "IconButton.h" +#include "TextViewCompleter.h" + + +class URLChoice : public BAutoCompleter::Choice { +public: + URLChoice(const BString& choiceText, const BString& displayText, + int32 matchPos, int32 matchLen, int32 priority) + : + BAutoCompleter::Choice(choiceText, displayText, matchPos, matchLen), + fPriority(priority) + { + } + + bool operator<(const URLChoice& other) const + { + if (fPriority > other.fPriority) + return true; + return DisplayText() < other.DisplayText(); + } + + bool operator==(const URLChoice& other) const + { + return fPriority == other.fPriority + && DisplayText() < other.DisplayText(); + } + +private: + int32 fPriority; +}; + + +class BrowsingHistoryChoiceModel : public BAutoCompleter::ChoiceModel { + virtual void FetchChoicesFor(const BString& pattern) + { + int32 count = CountChoices(); + for (int32 i = 0; i < count; i++) { + delete reinterpret_cast<BAutoCompleter::Choice*>( + fChoices.ItemAtFast(i)); + } + fChoices.MakeEmpty(); + + // Search through BrowsingHistory for any matches. + BrowsingHistory* history = BrowsingHistory::DefaultInstance(); + if (!history->Lock()) + return; + + BString lastBaseURL; + int32 priority = INT_MAX; + + count = history->CountItems(); + for (int32 i = 0; i < count; i++) { + BrowsingHistoryItem item = history->HistoryItemAt(i); + const BString& choiceText = item.URL(); + int32 matchPos = choiceText.IFindFirst(pattern); + if (matchPos < 0) + continue; + if (lastBaseURL.Length() > 0 + && choiceText.FindFirst(lastBaseURL) >= 0) { + priority--; + } else + priority = INT_MAX; + lastBaseURL = baseURL(choiceText); + fChoices.AddItem(new URLChoice(choiceText, + choiceText, matchPos, pattern.Length(), priority)); + } + + history->Unlock(); + + fChoices.SortItems(_CompareChoices); + } + + virtual int32 CountChoices() const + { + return fChoices.CountItems(); + } + + virtual const BAutoCompleter::Choice* ChoiceAt(int32 index) const + { + return reinterpret_cast<BAutoCompleter::Choice*>( + fChoices.ItemAt(index)); + } + + static int _CompareChoices(const void* a, const void* b) + { + const URLChoice* aChoice + = *reinterpret_cast<const URLChoice* const *>(a); + const URLChoice* bChoice + = *reinterpret_cast<const URLChoice* const *>(b); + if (*aChoice < *bChoice) + return -1; + else if (*aChoice == *bChoice) + return 0; + return 1; + } + +private: + BList fChoices; +}; + + +// #pragma mark - URLTextView + + +static const float kHorizontalTextRectInset = 4.0; + + +class URLInputGroup::URLTextView : public BTextView { +private: + static const uint32 MSG_CLEAR = 'cler'; + +public: + URLTextView(URLInputGroup* parent); + virtual ~URLTextView(); + + virtual void MessageReceived(BMessage* message); + virtual void FrameResized(float width, float height); + virtual void MouseDown(BPoint where); + virtual void KeyDown(const char* bytes, int32 numBytes); + virtual void MakeFocus(bool focused = true); + + virtual BSize MinSize(); + virtual BSize MaxSize(); + + void SetUpdateAutoCompleterChoices(bool update); + +protected: + virtual void InsertText(const char* inText, int32 inLength, + int32 inOffset, + const text_run_array* inRuns); + virtual void DeleteText(int32 fromOffset, int32 toOffset); + +private: + void _AlignTextRect(); + +private: + URLInputGroup* fURLInputGroup; + TextViewCompleter* fURLAutoCompleter; + BString fPreviousText; + bool fUpdateAutoCompleterChoices; +}; + + +URLInputGroup::URLTextView::URLTextView(URLInputGroup* parent) + : + BTextView("url"), + fURLInputGroup(parent), + fURLAutoCompleter(new TextViewCompleter(this, + new BrowsingHistoryChoiceModel())), + fPreviousText(""), + fUpdateAutoCompleterChoices(true) +{ + MakeResizable(true); + SetStylable(true); + fURLAutoCompleter->SetModificationsReported(true); +} + + +URLInputGroup::URLTextView::~URLTextView() +{ + delete fURLAutoCompleter; +} + + +void +URLInputGroup::URLTextView::MessageReceived(BMessage* message) +{ + switch (message->what) { + case MSG_CLEAR: + SetText(""); + break; + default: + BTextView::MessageReceived(message); + break; + } +} + + +void +URLInputGroup::URLTextView::FrameResized(float width, float height) +{ + BTextView::FrameResized(width, height); + _AlignTextRect(); +} + + +void +URLInputGroup::URLTextView::MouseDown(BPoint where) +{ + bool wasFocus = IsFocus(); + if (!wasFocus) + MakeFocus(true); + + int32 buttons; + if (Window()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK) + buttons = B_PRIMARY_MOUSE_BUTTON; + + if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) { + // Display context menu + int32 selectionStart; + int32 selectionEnd; + GetSelection(&selectionStart, &selectionEnd); + bool canCutOrCopy = selectionEnd > selectionStart; + + bool canPaste = false; + if (be_clipboard->Lock()) { + if (BMessage* data = be_clipboard->Data()) + canPaste = data->HasData("text/plain", B_MIME_TYPE); + be_clipboard->Unlock(); + } + + BMenuItem* cutItem = new BMenuItem("Cut", new BMessage(B_CUT)); + BMenuItem* copyItem = new BMenuItem("Copy", new BMessage(B_COPY)); + BMenuItem* pasteItem = new BMenuItem("Paste", new BMessage(B_PASTE)); + BMenuItem* clearItem = new BMenuItem("Clear", new BMessage(MSG_CLEAR)); + cutItem->SetEnabled(canCutOrCopy); + copyItem->SetEnabled(canCutOrCopy); + pasteItem->SetEnabled(canPaste); + clearItem->SetEnabled(strlen(Text()) > 0); + + BPopUpMenu* menu = new BPopUpMenu("url context"); + menu->AddItem(cutItem); + menu->AddItem(copyItem); + menu->AddItem(pasteItem); + menu->AddItem(clearItem); + + menu->SetTargetForItems(this); + menu->Go(ConvertToScreen(where), true, true, true); + return; + } + + // Only pass through to base class if we already have focus. + if (!wasFocus) + return; + + BTextView::MouseDown(where); +} + + +void +URLInputGroup::URLTextView::KeyDown(const char* bytes, int32 numBytes) +{ + switch (bytes[0]) { + case B_TAB: + BView::KeyDown(bytes, numBytes); + break; + + case B_ESCAPE: + // Revert to text as it was when we received keyboard focus. + SetText(fPreviousText.String()); + SelectAll(); + break; + + case B_RETURN: + // Don't let this through to the text view. + break; + + default: + BTextView::KeyDown(bytes, numBytes); + break; + } +} + +void +URLInputGroup::URLTextView::MakeFocus(bool focus) +{ + if (focus == IsFocus()) + return; + + BTextView::MakeFocus(focus); + + if (focus) { + fPreviousText = Text(); + SelectAll(); + } + + fURLInputGroup->Invalidate(); +} + + +BSize +URLInputGroup::URLTextView::MinSize() +{ + BSize min; + min.height = ceilf(LineHeight(0) + kHorizontalTextRectInset); + // we always add at least one pixel vertical inset top/bottom for + // the text rect. + min.width = min.height * 3; + return BLayoutUtils::ComposeSize(ExplicitMinSize(), min); +} + + +BSize +URLInputGroup::URLTextView::MaxSize() +{ + BSize max(MinSize()); + max.width = B_SIZE_UNLIMITED; + return BLayoutUtils::ComposeSize(ExplicitMaxSize(), max); +} + + +void +URLInputGroup::URLTextView::SetUpdateAutoCompleterChoices(bool update) +{ + fUpdateAutoCompleterChoices = update; +} + + +void +URLInputGroup::URLTextView::InsertText(const char* inText, int32 inLength, + int32 inOffset, const text_run_array* inRuns) +{ + // Filter all line breaks, note that inText is not terminated. + if (inLength == 1) { + if (*inText == '\n' || *inText == '\r') + BTextView::InsertText(" ", 1, inOffset, inRuns); + else + BTextView::InsertText(inText, 1, inOffset, inRuns); + } else { + BString filteredText(inText, inLength); + filteredText.ReplaceAll('\n', ' '); + filteredText.ReplaceAll('\r', ' '); + BTextView::InsertText(filteredText.String(), inLength, inOffset, + inRuns); + } + + // Make the base URL part bold. + BString text(Text(), TextLength()); + int32 baseUrlStart = text.FindFirst("://"); + if (baseUrlStart >= 0) + baseUrlStart += 3; + else + baseUrlStart = 0; + int32 baseUrlEnd = text.FindFirst("/", baseUrlStart); + if (baseUrlEnd < 0) + baseUrlEnd = TextLength(); + BFont font; + GetFont(&font); + const rgb_color black = (rgb_color){ 0, 0, 0, 255 }; + const rgb_color gray = (rgb_color){ 60, 60, 60, 255 }; + if (baseUrlStart > 0) + SetFontAndColor(0, baseUrlStart - 1, &font, B_FONT_ALL, &gray); + if (baseUrlEnd > baseUrlStart) { + font.SetFace(B_BOLD_FACE); + SetFontAndColor(baseUrlStart, baseUrlEnd, &font, B_FONT_ALL, &black); + } + if (baseUrlEnd < TextLength()) { + font.SetFace(B_REGULAR_FACE); + SetFontAndColor(baseUrlEnd, TextLength(), &font, B_FONT_ALL, &gray); + } + + fURLAutoCompleter->TextModified(fUpdateAutoCompleterChoices); +} + + +void +URLInputGroup::URLTextView::DeleteText(int32 fromOffset, int32 toOffset) +{ + BTextView::DeleteText(fromOffset, toOffset); + + fURLAutoCompleter->TextModified(fUpdateAutoCompleterChoices); +} + + +void +URLInputGroup::URLTextView::_AlignTextRect() +{ + // Layout the text rect to be in the middle, normally this means there + // is one pixel spacing on each side. + BRect textRect(Bounds()); + textRect.left = 0.0; + float vInset = max_c(1, + floorf((textRect.Height() - LineHeight(0)) / 2.0 + 0.5)); + float hInset = kHorizontalTextRectInset; + + if (be_control_look) + hInset = be_control_look->DefaultLabelSpacing(); + + textRect.InsetBy(hInset, vInset); + SetTextRect(textRect); +} + + +const uint32 kGoBitmapWidth = 14; +const uint32 kGoBitmapHeight = 14; +const color_space kGoBitmapFormat = B_RGBA32; + +const unsigned char kGoBitmapBits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x17, 0x17, 0x17, 0x21, 0x49, 0x49, 0x49, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21, 0x49, 0x49, 0x49, 0xe0, 0x50, 0x50, 0x50, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21, 0x49, 0x49, 0x49, 0xe0, + 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x2f, 0x2f, 0x2f, 0x56, 0x50, 0x50, 0x50, 0xff, 0x4d, 0x4d, 0x4d, 0xed, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21, + 0x49, 0x49, 0x49, 0xe0, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, + 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, + 0x50, 0x50, 0x50, 0xff, 0x37, 0x37, 0x37, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21, 0x49, 0x49, 0x49, 0xe0, 0x50, 0x50, 0x50, 0xff, + 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, + 0x50, 0x50, 0x50, 0xff, 0x4b, 0x4b, 0x4b, 0xec, 0x37, 0x37, 0x37, 0x77, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x17, 0x17, 0x17, 0x21, 0x49, 0x49, 0x49, 0xe1, 0x50, 0x50, 0x50, 0xff, 0x50, 0x50, 0x50, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21, + 0x49, 0x49, 0x49, 0xe1, 0x50, 0x50, 0x50, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21, 0x49, 0x49, 0x49, 0xe1, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x17, 0x17, 0x17, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + + +// #pragma mark - PageIconView + + +class URLInputGroup::PageIconView : public BView { +public: + PageIconView() + : + BView("page icon view", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), + fIcon(NULL) + { + SetDrawingMode(B_OP_ALPHA); + SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); + } + + ~PageIconView() + { + delete fIcon; + } + + virtual void Draw(BRect updateRect) + { + if (fIcon == NULL) + return; + + BRect bounds(Bounds()); + BRect iconBounds(0, 0, 15, 15); + iconBounds.OffsetTo( + floorf((bounds.left + bounds.right + - (iconBounds.left + iconBounds.right)) / 2 + 0.5f), + floorf((bounds.top + bounds.bottom + - (iconBounds.top + iconBounds.bottom)) / 2 + 0.5f)); + DrawBitmap(fIcon, fIcon->Bounds(), iconBounds, + B_FILTER_BITMAP_BILINEAR); + } + + virtual BSize MinSize() + { + if (fIcon != NULL) + return BSize(18, 18); + return BSize(0, 0); + } + + virtual BSize MaxSize() + { + return BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED); + } + + virtual BSize PreferredSize() + { + return MinSize(); + } + + void SetIcon(const BBitmap* icon) + { + if (icon == NULL && fIcon == NULL) + return; + if (!(fIcon != NULL && icon != NULL)) + InvalidateLayout(); + delete fIcon; + if (icon) + fIcon = new BBitmap(icon); + else + fIcon = NULL; + Invalidate(); + } + +private: + BBitmap* fIcon; +}; + + +// #pragma mark - URLInputGroup + + +URLInputGroup::URLInputGroup(BMessage* goMessage) + : + BGroupView(B_HORIZONTAL, 0.0), + fWindowActive(false) +{ + GroupLayout()->SetInsets(2, 2, 2, 2); + + fIconView = new PageIconView(); + GroupLayout()->AddView(fIconView, 0.0f); + + fTextView = new URLTextView(this); + AddChild(fTextView); + + AddChild(new BSeparatorView(B_VERTICAL, B_PLAIN_BORDER)); + +// TODO: Fix in Haiku, no in-built support for archived BBitmaps from +// resources? +// fGoButton = new BitmapButton("kActionGo", NULL); + fGoButton = new BitmapButton(kGoBitmapBits, kGoBitmapWidth, + kGoBitmapHeight, kGoBitmapFormat, goMessage); + GroupLayout()->AddView(fGoButton, 0.0f); + + SetFlags(Flags() | B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE); + SetLowColor(ViewColor()); + SetViewColor(B_TRANSPARENT_COLOR); + + SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH, + B_ALIGN_VERTICAL_CENTER)); +} + + +URLInputGroup::~URLInputGroup() +{ +} + + +void +URLInputGroup::AttachedToWindow() +{ + BGroupView::AttachedToWindow(); + fWindowActive = Window()->IsActive(); +} + + +void +URLInputGroup::WindowActivated(bool active) +{ + BGroupView::WindowActivated(active); + if (fWindowActive != active) { + fWindowActive = active; + Invalidate(); + } +} + + +void +URLInputGroup::Draw(BRect updateRect) +{ + BRect bounds(Bounds()); + rgb_color base(LowColor()); + uint32 flags = 0; + if (fWindowActive && fTextView->IsFocus()) + flags |= BControlLook::B_FOCUSED; + be_control_look->DrawTextControlBorder(this, bounds, updateRect, base, + flags); +} + + +void +URLInputGroup::MakeFocus(bool focus) +{ + // Forward this to the text view, we never accept focus ourselves. + fTextView->MakeFocus(focus); +} + + +BTextView* +URLInputGroup::TextView() const +{ + return fTextView; +} + + +void +URLInputGroup::SetText(const char* text) +{ + if (!text || !Text() || strcmp(Text(), text) != 0) { + fTextView->SetUpdateAutoCompleterChoices(false); + fTextView->SetText(text); + fTextView->SetUpdateAutoCompleterChoices(true); + } +} + + +const char* +URLInputGroup::Text() const +{ + return fTextView->Text(); +} + + +BButton* +URLInputGroup::GoButton() const +{ + return fGoButton; +} + + +void +URLInputGroup::SetPageIcon(const BBitmap* icon) +{ + fIconView->SetIcon(icon); +} + diff --git a/src/apps/webpositive/URLInputGroup.h b/src/apps/webpositive/URLInputGroup.h new file mode 100644 index 0000000000..d71f713ca7 --- /dev/null +++ b/src/apps/webpositive/URLInputGroup.h @@ -0,0 +1,43 @@ +/* + * Copyright 2010 Stephan Aßmus <superstippi@gmx.de> + * All rights reserved. Distributed under the terms of the MIT License. + */ +#ifndef URL_INPUT_GROUP_H +#define URL_INPUT_GROUP_H + +#include <GroupView.h> + +class BButton; +class BTextView; + + +class URLInputGroup : public BGroupView { +public: + URLInputGroup(BMessage* goMessage); + virtual ~URLInputGroup(); + + virtual void AttachedToWindow(); + virtual void WindowActivated(bool active); + virtual void Draw(BRect updateRect); + virtual void MakeFocus(bool focus = true); + + BTextView* TextView() const; + void SetText(const char* text); + const char* Text() const; + + BButton* GoButton() const; + + void SetPageIcon(const BBitmap* icon); + +private: + class PageIconView; + class URLTextView; + + PageIconView* fIconView; + URLTextView* fTextView; + BButton* fGoButton; + bool fWindowActive; +}; + +#endif // URL_INPUT_GROUP_H + diff --git a/src/apps/webpositive/WebPositive.rdef b/src/apps/webpositive/WebPositive.rdef new file mode 100644 index 0000000000..9b475b34d1 --- /dev/null +++ b/src/apps/webpositive/WebPositive.rdef @@ -0,0 +1,350 @@ +resource app_signature "application/x-vnd.Haiku-WebPositive"; + +resource app_version { + major = 0, + middle = 0, + minor = 1, + variety = B_APPV_ALPHA, + internal = 0, + short_info = "WebPositive", + long_info = "WebPositive ©2007-2010 The WebKit Haiku Project" +}; + +resource app_flags B_SINGLE_LAUNCH; + +resource file_types message { + "types" = "text/html", + "types" = "image/svg+xml", + "types" = "application/x-vnd.Be.URL.file", + "types" = "application/x-vnd.Be.URL.ftp", + "types" = "application/x-vnd.Be.URL.http", + "types" = "application/x-vnd.Be.URL.https" +}; + +resource vector_icon { + $"6E636966450500020106033D0AFE000000000000BD0AFE48F10748A0783133FF" + $"FFC80066FFFF0034CC020106033D0AFE000000000000BD0AFE48F10748A07854" + $"09B4B6D8033784FF051D61020106043D950B0000000000003D950B494FBC479B" + $"EC00FFFFFF4C00FF008F00B200FF008000050002030605B812A5BE03E13DE784" + $"B8021049F79F49EDD800F1F1F136D9DDF48A9996B9B4B8BEDBFFF4F4F404EBD0" + $"020006023C92C0388F5FB854503C576348D8DF48895B004137A9FFB9B9B90401" + $"7E03FF360003FFFF0003FF9C0003FF000002010202BB36AEBA1760B80FFF392B" + $"574ACCFB48652300000001FFFF0000013205400500020004024AFFFC00FFFFBA" + $"360500020106033D0AFE000000000000BD0AFE48F10748A0783133FFFFC80066" + $"FFFF0034CC020106023D0AFE000000000000BD0AFE48F10748A0780033FFFFFF" + $"0033CC020106023D0AFE000000000000BD0AFE48F10748A0780033FFFFFF0066" + $"FF020004027AFFFC00DDFFBA360201040289FF0000A9C000000200040200FFE5" + $"CED0ECB989020004020DEFBC8CFFD4AA820200040200FFCB9CE9ECB989020004" + $"0200FFCB9CE9ECB98902000402B3FFCB9CBBD4AA820500020106033D0AFE0000" + $"00000000BD0AFE48F10748A0783133FFFFC80066FFFF0034CC020106033D0AFE" + $"000000000000BD0AFE48F10748A0785409B4B6D8033784FF051D61020106043D" + $"950B0000000000003D950B494FBC479BEC00FFFFFF4C00FF008F00B200FF0080" + $"00050002030605B812A5BE03E13DE784B8021049F79F49EDD800F1F1F136D9DD" + $"F48A9996B9B4B8BEDBFFF4F4F404EBD0020006023C92C0388F5FB854503C5763" + $"48D8DF48895B004137A9FFB9B9B904017E03FF360003FFFF000200040200FFBC" + $"34FFFF9C0003FF000002010202BB36AEBA1760B80FFF392B574ACCFB48652300" + $"000001FFFF00000132054003FFFFC9020014020026FF1C0500050002030605B8" + $"12A5BE03E13DE784B8021049F79F49EDD800F1F1F136D9DDF48A9996B9B4B8BE" + $"DBFFF4F4F404EBD0020006023C92C0388F5FB854503C576348D8DF48895B0041" + $"37A9FFB9B9B904017E03FF360003FFFF0003FF9C0003FF000002010202BB36AE" + $"BA1760B80FFF392B574ACCFB48652300000001FFFF0000013205400500030052" + $"7502001002F8006BFF003E04006C050003900000020310020000006E00650400" + $"6B05000359463503C3A414039D816035000000000204BFC1B55A36B55AC3BEB5" + $"5AC6FABC92C6FAB895C6FAC08FBFC1C3CBC3BEC3CB36C3CBB889BC92B889C08F" + $"B889B895066EFFFFF30FFFFEFFFFBFBFFEFFFEFFFEFFFFFFFFBFFFEFDFFBFBFF" + $"FF0FC5DBB8B5C713BA9FC4CAB706C10FB579C307B5D4C0A226BFC1B55AC031B5" + $"5ABDBCB55ABA69B7BA36B63AB8F9B94EB89CBD9BB84EBB7EB8C7BE9AB924BF76" + $"B8E0BEE7B9DEC11FBCDFC332BB37C278BEC4C405C2D7C31AC0FAC3FCC2C5C2C0" + $"C22EC2B7C279C2BDC22EC2B7C226C240C2BDC240C2BDC206C2AFC199C2DAC1D0" + $"C2CB44C2EEC0D4C2DCC114C2E5C051C2CEBF42C312BFB0C2B4BF3DC312BF35BF" + $"1FBDECC312BE23C312BDB8C2EDBD57C2DEBD86C2E2BD53C2DBBD3EC2C2BD3EC2" + $"C2BD31C2B4BD12C2A4BD26C2AABD12C2A4BCA4C27EBC31C254BC31C254BC27C2" + $"51BC11C24FBC1DC250BC10C24BBC0FC241BC10C245BC0FC241BBF5C219BBF5C2" + $"19BBD2C1DDBB68C194BBADC1A4BB70C16EBB58C128BB62C14EBB4BC101BB4BC0" + $"ACBB47C0D7BB4BC0ACBB4EC087BB4EC087BB51C054BB4DBFEEBB54C021BB9DBF" + $"92BBA9BEBFBBAFBF35BBA9BEBFBBA7BE92BBA7BE92BBA7BE52BBBBBE2FBBA8BE" + $"4BBBBBBE2FBBC6BE20BBC6BE20BC00BDD4BBE2BCFBBC5BBD3DBBE2BCFBBAB7BC" + $"58BA8ABC43BA90BC45BA8ABC43BA8ABC42BA8ABC42BA88BBEABA22BB78BA6ABB" + $"ACBA22BB78BA12BB6AB9F8BB48B9ADBAEFB9ADBAEFB97DBAC3B927BAB0B94FBA" + $"B5B924BAA9B91ABA9AB920BAA0B984BA6DB9DEB996B9B5B9F9B9F6B95DBA36B8" + $"EFBA0AB91FBA5EB8C4BA78B85EBA83B89EBACCB83CBB60B7E7BB18B813BB60B7" + $"E7BB6AB7E1BBFEB787BBFEB787BC65B749BCB6B683BC93B6F5BCF5B689BD58B6" + $"64BD2CB67BBDA8B695BE6CB6503C29BE6FB64FBE88B64FBE7CB64FBE62B6A0BE" + $"E1B72FBE88B70EBEE4B733BEE8B73DBEE5B73ABED8B743BEC5B749BE74B7A5BE" + $"74B7A5BE63B7DCBE8FB848BE74B816BE90B84CBE91B852BE91B852BE93B85BBE" + $"9CB87ABE97B86BBE93B883BE8CB88DBE8CB88DBE39B8EFBD91B98FBDB6B906BD" + $"91B98FBD8DB9A0BD8DB9A0BD64B9D7BD7EBA54BD59BA19BD7ABA5CBD76BA62BD" + $"76BA62BD56BA9FBD4DBB2BBD38BAE6BD5FBB6FBDCCBBCEBD98BBA1BDFFBBF9BE" + $"73BC3FBE33BC27BEBEBC5CBF5DBC42BF11BC55BF5DBC42BFAB37BFAB37BFD0BC" + $"1DC01CBC0FBFF4BC0BC045BC13C083BC4EC066BC35C04DBCD9C0F3BDDAC0B6BD" + $"63C0F3BDDAC107BDF8C102BDF2C0BABE83C126BF90C0B1BF1CC126BF90C133BF" + $"9CC133BF9CC130C00EC1C4C0F8C135C0D6C258C11CC347C059C2EEC0C8C347C0" + $"59C36BC02EC388C018C388C018C3A7C003C3DABFC4C3C9BFE8C3EEBF9FC408BF" + $"51C3EFBF73C408BF51C408BF52C408BF52C40CBF99C49540C450BFCEC4EBBFB3" + $"C541BF0FC51FBF56C541BF0FC551BEF24EBEEA4EBEEAC57FBEA3C568BDFCC59E" + $"BE46C53ABDBAC4A2BDDEC4E0BDA9C4A2BDCABDB5C4A4BD31C4A4BD31C4A7BD1D" + $"C4E8BCDEC4D2BCF5C4E8BCDEC4FEBCC8C516BCB3C516BCB3C544BC8BC582BC1F" + $"C570BC5DC58BBC04C587BBBBC586BBD9C587BBBBC587BBA8C587BB9DC587BB9D" + $"C58CBB604EBAF9C584BB23C589BADAC5C1BA5DC5B0BAA1C5C3BA61C5CBBA6FC5" + $"CBBA6FC5D8BA8EC60EBAD3C5EDBAB3C609BB1EC648BBAAC626BB67C669BBEAC6" + $"6EBC7AC66FBC33C66EBC7AC671BCC3C671BCC3C673BCE9C66DBD36C677BD10C6" + $"64BD5AC649BD9BC652BD78C633BDEBC62BBE91C62DBE3F50BED5C67FBF2BC648" + $"BF16C750BD0F0006BC11B6E3BC11B6E3BC32B6AFBC54B637BC46B674BB81B6AA" + $"BA2BB801BAC4B747BA87B7E2BB2FB77DBADDB7AFBB59B764BBAAB733BB82B74D" + $"BBCBB71CBC11B6E3BBFBB707BC11B6E3001DBACDBFD1BACDBFD1BAEBBFA9BB29" + $"BF5BBB12BF89BB43BF24BB34BE92BB34BECFBB34BE4DBB5FBDE9BB34BE25BB80" + $"BDBEBBAABD5FBBA0BD98BB47BD29BA81BCBEBAE5BCF3BA67BCB1BA1FBC8332BC" + $"9FBA0FBC5BBA0DBC02BA1FBC28B9FDBBE5B9B9BBB6B9D0BBD0B99BBB91B95FBB" + $"44B983BB66B93DBB23B8EFBB2CB91BBB15B8C0BB47B8BEBADEB8C2BAFFB8B1BB" + $"13B89FBB7EB8A7BB48B8C8BB7EB8C5BBB2B8C2BB89B8C6BBDBB8C8BC2DB8C7BC" + $"04B8CFBC40B8BABC48B8CBBC48B8B3BC4BB8A9BC4EB8AEBC4DB8A0BC57B893BC" + $"4BB898BC55B881BC4AB88EBD0FB88DBD00B893BD49B8E0BDE7B88DBE02B90DBE" + $"03B922BE74B906BE4BB946BEA7B985BF00B972BEC1B996BF3EB9A240B997BF80" + $"B9AFC010BA16C05EB9DBC02DBA24C0B3BA75C126BA3CC0E7BA8FC143BAC0C17F" + $"BAA9C15FBAD7C19FBAFBC17BBAE3C19DBADDC13ABAD6C0A5BAD2C0EFBADBC060" + $"BACDBFD1BAEAC012BACDBFD10009BD5DB5D6BD5DB5D6BD81B5EDBDB1B60DBD83" + $"B605BDCFB613BE19B600BE01B619BE55B5C3BEF5B5C3BEB0B5EFBF2CB5A1BFA3" + $"B55D3FB55DBFCEB55DC00CB58CBFE9B577C03CB5A6C08FB566C063B574BF39B5" + $"40BCA7B60DBDDCB57ABCEBB623BD5DB5D6BD29B600BD5DB5D60005BF34B6E9BF" + $"34B6E9BF55B6EBBF99B6F5BF7BB702BFCEB6E0BF87B67CBF9BB693BF77B66DBF" + $"3CB663BF2FB623BF44B68FBF34B6E9BF6AB6CBBF34B6E90004BA06B87FBA06B8" + $"7FBA0BB867B9EEB84FB9FDB85FB982B8E4B8EFBA35B92CB987B97D31BA06B87F" + $"B974B8EFBA06B87F0066C6F6BC24C6F6BC24C6DBBB09C6002FC68AB9F1C5B9B8" + $"7BC4FFB79BC563B805C4CEB768C463B708C49AB736C452B6FBC427B6EFC43FB6" + $"E2C411B6FAC407B6C1C401B6D0C315B60FC0D0B56EC1F8B59AC0FAB58AC15EB5" + $"9CC130B589C170B5A1C178B5C9C19FB5BDC12CB5E2C08AB5C9C0D7B5C3C059B5" + $"CDC03FB60DC042B5DDC03DB629C03FB676C036B65CC04AB694C0B6B65EC0A7B6" + $"6FC0CDB643C0F1B619C0B0B607C108B64AC0D0B6B0C13AB6D5C0B5B6A7C066B6" + $"89C083B67CC063B689C05CB6AFC05EB6AAC053B6C0C030B6CFC043B6CABFE5B6" + $"E4BF94B741BFB0B6EFBF87B768BF5CB790BF88B781BF3BB79CBEF6B7B2BF17B7" + $"A3BECCB7C7BEF9B81BBEE9B7FFBF05B82FBF19B86CBF02B860BF59B891BFBEB7" + $"E0BFA3B801BFE0B7B3C038B7ADC008B7BCC04FB7A6C084B776C06CB76EC0D3B7" + $"95C0F7B83EC0EBB7F4C10FB83FC135B80FC100B801C10CB7E5C0ECB76F43B7AA" + $"C125B795C15EB7FCC14DB7BCC170B83CC1C1B820C18DB860C1CFB813C1FCB7D4" + $"C1E1B7BCC213B7E9C1F1B838C2142DC206B84BC23AB83CC222B842C259B83447" + $"B841C26CB835C2AEB84FC2F9B856C2D5B854C320B859C346B898C335B87AC376" + $"B8E6C249B8C5C258B8C6C227B8C2C1E1B8CFC202B8C1C1C8B8DAC199B8EFC1B6" + $"2FC178B8E5C13EB8B5C15AB8C7C117B89BC0C4B872C0EFB885C07EB853BFE2B8" + $"4EC02EB83FBFBFB855BF7FB87BBF9BB866BF5BB896BF10B8A4BF34B87FBEDBB8" + $"DBBE6DB941BEB5B91FBE50B94FBE19B976BE2DB95BBE03B990BDF5B9D1BE03B9" + $"B4BDE8B9EDBDE2BA1DBDCAB9FBBDF7BA3BBDEFBA76BDFEBA52BDD9BAA9BDC9BB" + $"27BD9EBAEEBDF235BE71BBBBBE39BB93BEAFBBE9BF40BBD1BEFBBBE5BF82BBC1" + $"C009BB9CBFC4BBA0C05EBB95C0DCBC03C0A1BBCDC104BC26C0F4BC67C113BC34" + $"C0D3BC9EC103BD00C0E8BCC7C11FBD3644BDA3C13CBD6CC166BDB9C182BDE8C1" + $"76BDCFC18DBE03C16DBE2CC17ABE15C152BE5DC13EBECEC13EBE97C13EBF0DC1" + $"85BF4CC15CBF24C1A9BF71C1A4BFE2C1A7BFB3C1A0C025C1DAC087C1B3C053C2" + $"15C08AC289C06CC253C086C2CBC04DC319BFDDC2E8C00EC346BFB0C386BF66C3" + $"77BFAAC396BF12C3FEBEBFC3C1BEF2C435BE8FC430BE0CC42DBE4DC432BDC0C4" + $"33BD21C428BD6CC43BBCDDC4AABC77C47EBCA7C4CDBC55C511BC03C505BC34C5" + $"19BBE7C512BBA8C51236C512BB92C51035C51ABB70C4F7BB29C47EBB2BC489BB" + $"5DC475BB0EC48ABB01C476BB13C4A1BAECC477BACDC48ABAD7C441BAACC3F0BA" + $"66C418BA9AC3CCBA36C390B9CAC3ADBA00C389B9BDC36CB93AC341B92DC39AB9" + $"4AC3E1B9A2C3C3B981C419B9DFC46DBA6AC44ABA1FC46EBA73C496BA89C48EBA" + $"85C4AFBA9CC4EABAA8C4CABAABC533BAA1C559BA1F4EBA60C56231C4E8B9AEC5" + $"1DB9DAC4CAB996C49EB958C4B6B975C48EB946C473B90DC466B92AC492B8F9C4" + $"BCB91CC49FB90BC4DFB932C52BB951C505B9444EB95EC5B4B96CC591B952C5C9" + $"B97BC5E7B9B8C5D8B9A1C5FEB9DCC626BA26C614BA00C644BA60C694BA9EC64D" + $"BA88C667BAE8C6B6BB83C693BB3FC6DEBBD3C6E3BC7BC6E4BC22C6E2BCCEC6D0" + $"BD78C6F4BD2BC6A6BDCCC69FBE9AC69FBE3EC6A0BE95C6A4BE88C6A2BE8DC6A1" + $"3DC6A0BEC0C696BEBEC6B2BEC3C6D5BE01C6D2BE15C6F5BD65C6F6BC24C700BC" + $"C3C6F6BC240008C301B7D8C301B7D8C2E6B7DBC2D4B7A3C2E8B7AEC2B1B792C2" + $"69B7B7C288B7A7C204B7EEC236B742C221B77BC239B73AC285B743C279B746C2" + $"A6B740C2F2B74EC2D7B730C30BB76AC33CB7A1C31FB787C35FB7C1C301B7D8C3" + $"1CB7D4C301B7D80004BB52C203BB52C203BB44C20CBB26C221BB37C21DBB54C2" + $"48BBB9C290BB86C26EBB91C265BB52C203BB84C226BB52C2030005C4F3BE2DC4" + $"F3BE2DC4D3BE57C48BBEA4C4A5BE75C46EBEDAC47DBF52C47DBF18C4B6BF37C4" + $"EDBEB8C4D0BEEBC502BE92C4F3BE2DC531BE48C4F3BE2D0009C212C326C212C3" + $"26C17FC38FBFF6C344C09DC332BFD0C348BF8DC36ABFABC350BF66C38BBF1FC3" + $"86BF56C386BEAFC386BDCDC386BE3EC386BEC1C3CBC0BFC3BABFC3C3DCC137C3" + $"A9C221C367C1AEC38DC229C364C261C32AC280C337C248C322C212C326C229C3" + $"31C212C32602024E284E2C542E4C324C30482E020250304C3252344C364E344C" + $"34020258B81858305C32543654324E300202583456365836563C5A3854380204" + $"BCD5C1F1BACDC36FBEA5C09BC4A6BFA6C360BE3CC67FC1B0C064C5ACC23AC444" + $"BE90C715B827C85CB961C9B8B682C68B0204C3CFC072C309BF45C521C2733DC6" + $"5EC0DDC4E4BC10C7F6B880C76BB8DAC831B841C6E2BC6FC2E2B880C5CDBE42C1" + $"890622AEAAE6BAEAAAAF9A0EB9E4C6DEB9CB50B985C672BA12C5CDBAABC593BA" + $"C54DBAB8C4BBBAEBC3C8BAABC37CBA85C0CBBC88BE0E36B8E6B993B8DA2EB874" + $"B92DB970B7AFBCD5BA52BCEEB87ABE3AB7AEBD61B7AEBF12B7AEBF852EBF92BA" + $"78C0D2BB91C49BBB6AC51ABC10C51ABBB6C51ABC5AC49BBCB6C21DBCC3BF9FBD" + $"CEBFCBC18BC1FCC323BF00C13E46C33BC196C43B46C3DBC12AC484C02BC493C0" + $"11C3EFC091C36FBE20C2BDBF40BC50C14ABBF74EBB8451BBE8C66EBA9EC6B906" + $"11AAAAA8BA02BBEFB9ECBB49BAB8BCC8BB9DBCE1BE4EBB70C018BC7BC0A5BE07" + $"BF73BE6044BF9FBF6CBDDBC06CBD0245BCAEC1C9BC2FBF40BAABBE0EBB04BF0D" + $"BB04BD82BB04BD14BAB8BCAEBA38060BBAAA2A31C653BA0BC6C4BB84C645BB2B" + $"C69FBBB6C614BBAFC5A0BBDDC309BC1DC163BCAEC0CABB70BFE5BAD7C0CABADE" + $"C36FBAE3C5C5060AAAAB0ABE46C125BF99C130BF80454448C1FCC394C21DC361" + $"C1C3C3F0C11EC447C052C493C02BC3EFC105C389BE67470605EA02BB37BAC5BB" + $"D6BA12B9ACB887B8E0B8ADB90BB869B887B939B920B96C0A05C16BBB91C473BB" + $"91C4D9BC36C4A6BCC3C16BBCE906078A2EBD4F2EBDB5B814BEA7BF402EBF4DBA" + $"45BE0EBB04BEA7BB2BBD8ABAE2BD29BA520607AA3A39B8EDBE012F3CB99F3EB9" + $"93BEDAB920BF99B946BE34B77BBFF5B7C239B74804032EBCE932BDCEBB2BBD34" + $"BB12BECDBB51BF4DBA380208CA68BA5FCB40BD02CA39B9D0CA0EB6BCCAF3B7AE" + $"C947B5E955B58A55B58A55B58AC843B841C8FFB76C53B953C718B814C6E62FC7" + $"4BB728C5A02FC601B84EC2703DC645BCB6C5ACBC1DC711BD82C89DBEE7C79E40" + $"C89DBEE7060ABFFB0FC6F1C097C625BFCBC6F1BFCB5A345C3758315A2E5A305A" + $"2C582A562E582C54305230C691B854C691B920C691B7DAC53ABAAB4FB9ACC501" + $"BB1EC46EBBF7C4ADBB9DC3FABCB6C56DBE0EC38EBE4EC64DBDF00207C86FBE74" + $"C86FBE74C7A3BDA8C78ABB44C80ABB28C71835C633BBB7C6D4BBEFC5DABB98C5" + $"01BB44C509BB79C4F1BAEDC3F4BC43C43EBBD1C4B4BC83C6FF38C63336C7CB3A" + $"C83CBE9AC5B3BF8DC908BD020202C6392F4DB9F9C66CBAC54DBBE24FBB16C46E" + $"BAEB0202C969B7CEC967B915CAFFB9E1543654324E300202583456365836C876" + $"BD8FC969BB84C7AABBF70604EAC2BD4448C110C32EC07EC361C17DC33CC0E4C3" + $"7BC1E30205C5ACB861C65E31C52BB75D50204E224E20C3E1BA5FC37BB94FC455" + $"BB91C606BD02C493BCB6C722BE0DCB1BBDA8CB97BDE4C79EBBF702044EB44BC4" + $"D4284EB5174BB9B9C408B8BAC43DBAD755BD4F4BBBD057BD4FC744BB77C818BC" + $"3B51BAC502034E28C4A1B847C53AB61650BA52C592B8A0C690BBAEC4E0BAAB59" + $"BD8FC4E0B7A104032EB77BC612B63CC969B50AC7AAB777CB35BCA9C99C04032E" + $"B965B906B953B7D4B8D3B86D31B748BAB82C0406BE0BB689BE67B5FDC04BB607" + $"BF7FB5F0C11CB689C1D7B65BC1A2B73BC2A3B85448B77BC33CB801C31EB70849" + $"B609C32304032EBDA8B887BEB4B76EBE10B775BFEC2BC059B8AD04032E28BE01" + $"B3FFC0D8B4253FB3D2C288B5BDC2FB0406FE0BC79EBB2BC8F5BBAAC840BB47C9" + $"EBBC2ECB26BDDBCB05BD14CB40BE74CA4FC07ECA99BFCACA0EC117C9CFC27CCA" + $"0EC1E3C998C30058C3890A04C6FFBB11C856BC50C8BCBCC3C889BD4F040CAAFA" + $"BABC6FC74BC091C699C303C5CDC3DAC54DC440C4CEC44EC382C1EAC303C237C3" + $"4DC1D5C2EEC05EC402C149C3DFBF754BBD87C460BD47C4F9BE46C513BE2D4DBE" + $"98C58BBDBAC5870608BAAABFECC1FC4448C1FCC394C21DC361C1C3C3F0C11EC4" + $"47C052C493C02BC3EFC105C389BED3C2BD0A04C369BC83C6D738C666BD1CC35B" + $"BD350A03B46542B4BE41B4BE420605AF03B6AFC969B63CC9F1B72FC8D0BE4155" + $"B821CA8DC11CC6FAC42DC5C5C55FC53ABE67C91DC1E8C7EAB86DCB260604EEBF" + $"CBBB51C1FCBBD0C13EBC43C29FBB6EC18BBAAB43BB1EC145BAF2C09ABB4B0A04" + $"30303050505050302C0A0001021240BAB100000000000040B755C6CCDBBE9979" + $"01178300040A0101020240BAB100000000000040B755C6CCDBBE99790A020103" + $"0240BAB100000000000040B755C6CCDBBE99790A030A05060708090A0C0D040B" + $"0240BAB100000000000040B755C6CCDBBE99790A110113124084473AE65BBB05" + $"39406A8F40BD7AC6ED3001178400040A00012712401CCA0000000000004031D6" + $"BEA7B4C5651401178100040A150112024046733A7FB6BABB05401508437714C3" + $"BB840A3B012E0A401CC92E6813AE6813401CC9C0B293C4BE831DFF0A11011412" + $"402E52374883B74883402E5243A84DC5C35901178200040A16011502402E5237" + $"4883B74883402E5243A84DC5C3590A17011602402E52374883B74883402E5243" + $"A84DC5C3590A18011702402E52374883B74883402E5243A84DC5C3590A170118" + $"02402E52374883B74883402E5243A84DC5C3590A1801190240268F36C1F1B73C" + $"D33FA9DD442903C369B00A1B011A02402E52374883B74883402E5243A84DC5C3" + $"590A2C011B02402E52374883B74883402E5243A84DC5C3590A11011C12402E52" + $"374883B74883402E5243A84DC5C35901178100040A20011D124018A13C2DD3BC" + $"6F843F776243040C3B35B701178400040A28011D024018A13C481BBC6F843FA1" + $"3143AD0CC180CE0A27011E024018A13C3669BC6F843F850B43AD0CBDA6830A26" + $"03212022023F94B53C057BBC14913F373546DE4E423FFC0A2B012202401CCA00" + $"0000000000401CCAC64FB846E7CF0A2B01230241774E3E9634BD97FB40685AC6" + $"A141CA33220A0001291240075E0000000000004029CCC0DC88C59D5C01178100" + $"040A2E0124123E3A7739EBE1BA96D53D1E7BC874E948577D01178400040A3601" + $"24023DE4F539818BBA43313CC9E6C7EFC848861D0A350125023DE4F539818BBA" + $"43313CC9E6C7EFC848861D0A340126023DE4F539D28CBA43313D0A55C7EFC848" + $"5E9A0A000128123FEA4E3675EFB6F2733F44E3440366C2D26601178200040A00" + $"012A12402E52374883B74883402E5241C554C5CE0401178100040A00012B1240" + $"1CCA000000000000401CCAC0623FC4B46E01178100040A00012C124031960000" + $"00000000401CCAC3AEA8C4B46E01178100040A3A012D02401CCA000000000000" + $"401CCAC0E971C4B46E0A3C012F0A401CCA000000000000401CCAC0E971C4B46E" + $"1DFF0A3B01300A3FEC520000000000003C6A0B41051D473FEE1DFF0A3E013102" + $"40FA05000000000000417C4D3E5061C9E26E0A3F013212401CCA000000000000" + $"401CCAC0E971C42D3C01178200040A4001330A401CCA000000000000401CCAC0" + $"E971C4B46E1DFF0A420134023600000000000000003600004890004AE8000A42" + $"0134023600000000000000003600004870004AE8000A43013402360000000000" + $"0000003600004850004AF8000A43013402360000000000000000360000487000" + $"4AF8000A430134023600000000000000003600004890004AF8000A4401340236" + $"0000000000000000360000485000466000" +}; + +resource(201, "kActionBack") #'VICN' array { + $"6E636966070500020006023B2FA63AC896BCCBD33D335A4A6980490D0B00ACE2" + $"FFFF6ECDFF0200060236D841374D6EBD38553CC6154AA9DF4607770026A8EBFF" + $"0694DE020006023729EA388C8FBC29073AAE614A51AF4A729A00035E8CFF0585" + $"C70200060238E8A83647FBBA8E2D3D42294AF6F7496CE200047BB8FF0694DE02" + $"0006023957BB3923F0BC39AC3C5E604AA873475AB1000592DAFD67CBFF04FF49" + $"080A092E3C3A53424C484F5047503A4435442B3A300A072E3C3A533A48484F48" + $"403A3A3A300A043A303A3A4435442B0A033A483A53424C0A044840484F504750" + $"3A0A043A3A4435503A48400803C125B7D43AB99FB8D3BE0E08023A3A4840070A" + $"0001001001178400040A010101000A020102000A030103000A040104000A0501" + $"05000A06020607100117812004" +}; + +resource(202, "kActionForward") #'VICN' array { + $"6E636966050500020006023B2FA63AC896BCCBD33D335A4A6980490D0B00ACE2" + $"FFFF6ECDFF0200060239AF2C39E19BBC89D83C68DC4AFA1247CFA20006A5F7FF" + $"069EEC02000602B8EEBEB986C33C7FB4BC13FB46FA0C49FBDD000592DAFD3EBD" + $"FF04FF49050A0A2E423A483A524847503F432A3A2F3A3136302E350A0748473A" + $"2F3A392E352E423A483A520A043A2F4847503F432A0A042E3536303A313A3908" + $"0536B9932FBB773B3A3BB939C0E5B755050A0001001001178400040A01010100" + $"0A020102000A030103000A040104100117812004" +}; + +resource(203, "kActionForward2") #'VICN' array { + $"6E63696607050002000602396DF23A056B3B94FCBB09F547CD484AAB5F0090D9" + $"FFFF6ECDFF02000602B5E5793878A83E023B3B2AC34893F147CC490060C8FFFF" + $"44BEFD02000602B6F90538542D3C2FCA3ACA9249A8634A929A00034F77FF0468" + $"9C020006023AC62E392AB63B6BA0BCFF0B49DAAA4AAE4500046FA7FF2BA5E502" + $"0006023A1CB834EBA6383E33BD3E8D499DE44A647800ACE2FFFD6DCCFF04FF49" + $"080A09523A48533F503C532E4C2E3E3A363A2B482E0A07523A485348473C533C" + $"43483A482E0A04482E483A3A363A2B0A03484748533F500A043C433C532E4C2E" + $"3E0A04483A3A362E3E3C430802BD8FB795C2BDB8E008023C43B8C7BF0D070A00" + $"01001001178400040A040101000A020102000A030103000A010104000A050105" + $"000A06020607100117812004" +}; + +resource(204, "kActionStop") #'VICN' array { + $"6E63696606050102000602393D323BB602BEA28F3C4CC64B601449439F00FF3E" + $"3EFFF70606020006033B27153C10C7BDF9893D088A4B2F634882C500C805057D" + $"9D0404FFB4040403750303020006023B06EB3B5469BC08EB3BB31F4AB15D4478" + $"7700FF9090FFFF757504FF440A0A0C2E4C364E3C4742524A5440434A3842353C" + $"BE1B36312E2E38400A0636313C3C40434A54504E402E0A03364E3E483C470A04" + $"483FC712BADE4A3840430A042E2E3631402E382C0A0442354A38C712BADE4AB9" + $"EC0A0E2E4C364EBEB6C37042524A54504E483FC712BADE4AB9EC4335402E382C" + $"2E2E384008022FB8A0BCC3B80708033C3C42364ABA120802BCA9BFF3B8D3C47B" + $"060A0001061001178400040A020101000A03020302000A010100000A04020504" + $"000A0503070809100117810004" +}; + +resource(206, "kActionGoStart") #'VICN' array { + $"6E63696605050002001602B53D77BAF2653D77A7B7CFFC460F874A2D7700F0FF" + $"D50200160236FB2637191CBC57873C40E14B0E7149D82700A2FFBB0200060239" + $"FB483A6DC2BDAC713D02DB4B384746086300EA0606FFD005050200060238B0C7" + $"397C47BD2A963C71B74A82C348B2B700FF4444FFF106060A0A06304942525044" + $"50383E32303D0A09303B3049324A32C0723643364C4252424036320A04503650" + $"44425242400A082D3E3535434645455439462C35312B3C0A062B3C2D3E353543" + $"46454535310A0445455439462C35310A0432C072324A364C36430A043A453A4A" + $"3E4C3EC2630A044645464A484848430A044C3F4C444E424E3D070A0001001001" + $"178400040A010101000A020102000A000406070809000A000103100117840004" + $"0A030105000A04010400" +}; + +resource(205, "kActionGo") archive BBitmap { + "_frame" = rect { 0.0, 0.0, 15.0, 15.0 }, + "_cspace" = 8200, + "_bmflags" = 0, + "_rowbytes" = 64, + "_data" = array { + $"0000000000000000000000000000000000000000000000000000000000000000" + $"0000000000000000000000000000000000000000000000000000000000000000" + $"0000000000000000000000C1000000DE00000061000000070000000100000000" + $"0000000000000000000000000000000000000000000000000000000000000000" + $"0000000000000000000000FF9BD4A5FF48634EFF000000C00000003500000004" + $"0000000000000000000000000000000000000000000000000000000000000000" + $"0000000000000000000000FFB6F9C2FFB3F9C0FF84B68EFF263426FD0000009B" + $"0000001600000002000000000000000000000000000000000000000000000000" + $"0000000000000000000000FFB6F9C2FF7EF995FFA3F9B3FFAAE9B6FF69906FFF" + $"000000E200000064000000070000000100000000000000000000000000000000" + $"0000000000000000000000FFB6F9C2FF6AFB85FF6AFB85FF83FA98FFA4F5B2FF" + $"7CC88AFF325C3BFF000000C00000003500000004000000000000000000000000" + $"0000000000000000000000FFB6F9C2FF5DFC7BFF5DFC7BFF5DFC7BFF5DFB7CFF" + $"60ED7AFF54D76DFF389A48FF002A14FD0000009B000000160000000200000000" + $"0000000000000000000000FFB5F8C1FF59FC77FF59FC77FF59FC77FF59FC77FF" + $"59FC77FF55F674FF40DB5CFF2BBC48FF1C752BFF000000D00000002000000003" + $"0000000000000000000000FFA8F4B5FF2EFA54FF2EFA54FF2EFA54FF2EFA54FF" + $"2EFA54FF2BF350FF22D542FF1CB638FF157027FF000000D00000003D00000009" + $"0000000000000000000000FF92ECA2FF15F940FF15F940FF15F940FF15F740FF" + $"15DE35FF00BE2EFF008922FF002600FD0000009B000000430000002000000003" + $"0000000000000000000000FF7EE591FF15F83DFF15F83DFF1CEA3DFF00C732FF" + $"009E22FF004815FF000000BF000000560000002D0000000F0000000200000000" + $"0000000000000000000000FF6DDF81FF1CF340FF2BDA4AFF1CB638FF006D1CFF" + $"000000E200000074000000360000001900000004000000000000000000000000" + $"0000000000000000000000FF5DDA75FF44D25FFF229338FF002600FD0000009B" + $"0000004300000023000000070000000100000000000000000000000000000000" + $"0000000000000000000000FF46B65CFF1C5222FF000000BF000000560000002D" + $"0000000F00000002000000000000000000000000000000000000000000000000" + $"0000000000000000000000C2000000DE00000074000000360000001900000004" + $"0000000000000000000000000000000000000000000000000000000000000000" + $"0000000000000000000000030000001F00000020000000080000000100000000" + $"0000000000000000000000000000000000000000000000000000000000000000" + } +}; + + diff --git a/src/apps/webpositive/autocompletion/AutoCompleter.cpp b/src/apps/webpositive/autocompletion/AutoCompleter.cpp new file mode 100644 index 0000000000..09060512f6 --- /dev/null +++ b/src/apps/webpositive/autocompletion/AutoCompleter.cpp @@ -0,0 +1,253 @@ +/* + * Copyright 2002-2006, project beam (http://sourceforge.net/projects/beam). + * All rights reserved. Distributed under the terms of the MIT License. + * + * Authors: + * Oliver Tappe <beam@hirschkaefer.de> + */ + +#include "AutoCompleter.h" + +#include <Looper.h> +#include <Message.h> + +#include "AutoCompleterDefaultImpl.h" + + +// #pragma mark - DefaultPatternSelector + + +class DefaultPatternSelector : public BAutoCompleter::PatternSelector { +public: + virtual void SelectPatternBounds(const BString& text, int32 caretPos, + int32* start, int32* length); +}; + + +void +DefaultPatternSelector::SelectPatternBounds(const BString& text, + int32 caretPos, int32* start, int32* length) +{ + if (!start || !length) + return; + *start = 0; + *length = text.Length(); +} + + +// #pragma mark - CompletionStyle + + +BAutoCompleter::CompletionStyle::CompletionStyle(EditView* editView, + ChoiceModel* choiceModel, ChoiceView* choiceView, + PatternSelector* patternSelector) + : + fEditView(editView), + fPatternSelector(patternSelector ? patternSelector + : new DefaultPatternSelector()), + fChoiceModel(choiceModel), + fChoiceView(choiceView) +{ +} + + +BAutoCompleter::CompletionStyle::~CompletionStyle() +{ + delete fEditView; + delete fChoiceModel; + delete fChoiceView; + delete fPatternSelector; +} + + +void +BAutoCompleter::CompletionStyle::SetEditView(EditView* view) +{ + delete fEditView; + fEditView = view; +} + + +void +BAutoCompleter::CompletionStyle::SetPatternSelector( + PatternSelector* selector) +{ + delete fPatternSelector; + fPatternSelector = selector; +} + + +void +BAutoCompleter::CompletionStyle::SetChoiceModel(ChoiceModel* model) +{ + delete fChoiceModel; + fChoiceModel = model; +} + + +void +BAutoCompleter::CompletionStyle::SetChoiceView(ChoiceView* view) +{ + delete fChoiceView; + fChoiceView = view; +} + + +// #pragma mark - BAutoCompleter + + +BAutoCompleter::BAutoCompleter(CompletionStyle* completionStyle) + : + fCompletionStyle(completionStyle) +{ +} + + +BAutoCompleter::BAutoCompleter(EditView* editView, ChoiceModel* choiceModel, + ChoiceView* choiceView, PatternSelector* patternSelector) + : + fCompletionStyle(new BDefaultCompletionStyle(editView, choiceModel, + choiceView, patternSelector)) +{ +} + + +BAutoCompleter::~BAutoCompleter() +{ + delete fCompletionStyle; +} + + +bool +BAutoCompleter::Select(int32 index) +{ + if (fCompletionStyle) + return fCompletionStyle->Select(index); + else + return false; +} + + +bool +BAutoCompleter::SelectNext(bool wrap) +{ + if (fCompletionStyle) + return fCompletionStyle->SelectNext(wrap); + else + return false; +} + + +bool +BAutoCompleter::SelectPrevious(bool wrap) +{ + if (fCompletionStyle) + return fCompletionStyle->SelectPrevious(wrap); + else + return false; +} + + +bool +BAutoCompleter::IsChoiceSelected() const +{ + if (fCompletionStyle) + return fCompletionStyle->IsChoiceSelected(); + else + return false; +} + + +int32 +BAutoCompleter::CountChoices() const +{ + if (fCompletionStyle && fCompletionStyle->GetChoiceModel()) + return fCompletionStyle->GetChoiceModel()->CountChoices(); + else + return 0; +} + + +int32 +BAutoCompleter::CountVisibleChoices() const +{ + if (fCompletionStyle && fCompletionStyle->GetChoiceView()) + return fCompletionStyle->GetChoiceView()->CountVisibleChoices(); + else + return 0; +} + + +int32 +BAutoCompleter::SelectedChoiceIndex() const +{ + if (fCompletionStyle) + return fCompletionStyle->SelectedChoiceIndex(); + else + return -1; +} + + +void +BAutoCompleter::ApplyChoice(bool hideChoices) +{ + if (fCompletionStyle) + fCompletionStyle->ApplyChoice(hideChoices); +} + + +void +BAutoCompleter::CancelChoice() +{ + if (fCompletionStyle) + fCompletionStyle->CancelChoice(); +} + + +void +BAutoCompleter::EditViewStateChanged(bool updateChoices) +{ + if (fCompletionStyle) + fCompletionStyle->EditViewStateChanged(updateChoices); +} + + +void +BAutoCompleter::SetEditView(EditView* view) +{ + if (fCompletionStyle) + fCompletionStyle->SetEditView(view); +} + + +void +BAutoCompleter::SetPatternSelector(PatternSelector* selector) +{ + if (fCompletionStyle) + fCompletionStyle->SetPatternSelector(selector); +} + + +void +BAutoCompleter::SetChoiceModel(ChoiceModel* model) +{ + if (fCompletionStyle) + fCompletionStyle->SetChoiceModel(model); +} + + +void +BAutoCompleter::SetChoiceView(ChoiceView* view) +{ + if (fCompletionStyle) + fCompletionStyle->SetChoiceView(view); +} + + +void +BAutoCompleter::SetCompletionStyle(CompletionStyle* style) +{ + delete fCompletionStyle; + fCompletionStyle = style; +} + diff --git a/src/apps/webpositive/autocompletion/AutoCompleter.h b/src/apps/webpositive/autocompletion/AutoCompleter.h new file mode 100644 index 0000000000..dab8268db2 --- /dev/null +++ b/src/apps/webpositive/autocompletion/AutoCompleter.h @@ -0,0 +1,161 @@ +/* + * Copyright 2002-2006, project beam (http://sourceforge.net/projects/beam). + * All rights reserved. Distributed under the terms of the MIT License. + * + * Authors: + * Oliver Tappe <beam@hirschkaefer.de> + */ +#ifndef _AUTO_COMPLETER_H +#define _AUTO_COMPLETER_H + +#include <MessageFilter.h> + +#include <Rect.h> +#include <String.h> + +class BAutoCompleter { +public: + class Choice { + public: + Choice(const BString& choiceText, + const BString& displayText, int32 matchPos, + int32 matchLen) + : + fText(choiceText), + fDisplayText(displayText), + fMatchPos(matchPos), + fMatchLen(matchLen) + { + } + virtual ~Choice() {} + const BString& Text() const { return fText; } + const BString& DisplayText() const { return fDisplayText; } + int32 MatchPos() const { return fMatchPos; } + int32 MatchLen() const { return fMatchLen; } + + private: + BString fText; + BString fDisplayText; + int32 fMatchPos; + int32 fMatchLen; + }; + + class EditView { + public: + virtual ~EditView() {} + + virtual BRect GetAdjustmentFrame() = 0; + virtual void GetEditViewState(BString& text, + int32* caretPos) = 0; + virtual void SetEditViewState(const BString& text, + int32 caretPos, + int32 selectionLength = 0) = 0; + }; + + class PatternSelector { + public: + virtual ~PatternSelector() {} + + virtual void SelectPatternBounds(const BString& text, + int32 caretPos, int32* start, + int32* length) = 0; + }; + + class ChoiceModel { + public: + + virtual ~ChoiceModel() {} + + virtual void FetchChoicesFor(const BString& pattern) = 0; + + virtual int32 CountChoices() const = 0; + virtual const Choice* ChoiceAt(int32 index) const = 0; + }; + + class CompletionStyle; + class ChoiceView { + public: + virtual ~ChoiceView() {} + + virtual void SelectChoiceAt(int32 index) = 0; + virtual void ShowChoices( + BAutoCompleter::CompletionStyle* completer) + = 0; + virtual void HideChoices() = 0; + virtual bool ChoicesAreShown() = 0; + virtual int32 CountVisibleChoices() const = 0; + }; + + class CompletionStyle { + public: + CompletionStyle(EditView* editView, + ChoiceModel* choiceModel, + ChoiceView* choiceView, + PatternSelector* patternSelector); + virtual ~CompletionStyle(); + + virtual bool Select(int32 index) = 0; + virtual bool SelectNext(bool wrap = false) = 0; + virtual bool SelectPrevious(bool wrap = false) = 0; + virtual bool IsChoiceSelected() const = 0; + virtual int32 SelectedChoiceIndex() const = 0; + + virtual void ApplyChoice(bool hideChoices = true) = 0; + virtual void CancelChoice() = 0; + + virtual void EditViewStateChanged(bool updateChoices) = 0; + + void SetEditView(EditView* view); + void SetPatternSelector(PatternSelector* selector); + void SetChoiceModel(ChoiceModel* model); + void SetChoiceView(ChoiceView* view); + + EditView* GetEditView() { return fEditView; } + PatternSelector* GetPatternSelector() + { return fPatternSelector; } + ChoiceModel* GetChoiceModel() { return fChoiceModel; } + ChoiceView* GetChoiceView() { return fChoiceView; } + + protected: + EditView* fEditView; + PatternSelector* fPatternSelector; + ChoiceModel* fChoiceModel; + ChoiceView* fChoiceView; + }; + +protected: + BAutoCompleter( + CompletionStyle* completionStyle = NULL); + BAutoCompleter(EditView* editView, + ChoiceModel* choiceModel, + ChoiceView* choiceView, + PatternSelector* patternSelector); + virtual ~BAutoCompleter(); + + void EditViewStateChanged( + bool updateChoices = true); + + bool Select(int32 index); + bool SelectNext(bool wrap = false); + bool SelectPrevious(bool wrap = false); + bool IsChoiceSelected() const; + int32 CountChoices() const; + int32 CountVisibleChoices() const; + int32 SelectedChoiceIndex() const; + + void ApplyChoice(bool hideChoices = true); + void CancelChoice(); + + void SetEditView(EditView* view); + void SetPatternSelector(PatternSelector* selector); + void SetChoiceModel(ChoiceModel* model); + void SetChoiceView(ChoiceView* view); + + void SetCompletionStyle(CompletionStyle* style); + +private: + CompletionStyle* fCompletionStyle; +}; + + +#endif // _AUTO_COMPLETER_H diff --git a/src/apps/webpositive/autocompletion/AutoCompleterDefaultImpl.cpp b/src/apps/webpositive/autocompletion/AutoCompleterDefaultImpl.cpp new file mode 100644 index 0000000000..0808239ef3 --- /dev/null +++ b/src/apps/webpositive/autocompletion/AutoCompleterDefaultImpl.cpp @@ -0,0 +1,467 @@ +/* + * Copyright 2002-2006, project beam (http://sourceforge.net/projects/beam). + * All rights reserved. Distributed under the terms of the MIT License. + * + * Authors: + * Oliver Tappe <beam@hirschkaefer.de> + */ + +#include "AutoCompleterDefaultImpl.h" + +#include <ListView.h> +#include <Screen.h> +#include <ScrollView.h> +#include <Window.h> + + +// #pragma mark - BDefaultPatternSelector + + +void +BDefaultPatternSelector::SelectPatternBounds(const BString& text, + int32 caretPos, int32* start, int32* length) +{ + if (!start || !length) + return; + *start = 0; + *length = text.Length(); +} + + +// #pragma mark - BDefaultCompletionStyle + + +BDefaultCompletionStyle::BDefaultCompletionStyle( + BAutoCompleter::EditView* editView, + BAutoCompleter::ChoiceModel* choiceModel, + BAutoCompleter::ChoiceView* choiceView, + BAutoCompleter::PatternSelector* patternSelector) + : + CompletionStyle(editView, choiceModel, choiceView, patternSelector), + fSelectedIndex(-1), + fPatternStartPos(0), + fPatternLength(0), + fIgnoreEditViewStateChanges(false) +{ +} + + +BDefaultCompletionStyle::~BDefaultCompletionStyle() +{ +} + + +bool +BDefaultCompletionStyle::Select(int32 index) +{ + if (!fChoiceView || !fChoiceModel || index == fSelectedIndex + || index < -1 || index >= fChoiceModel->CountChoices()) { + return false; + } + + fSelectedIndex = index; + fChoiceView->SelectChoiceAt(index); + return true; +} + + +bool +BDefaultCompletionStyle::SelectNext(bool wrap) +{ + if (!fChoiceModel || fChoiceModel->CountChoices() == 0) + return false; + + int32 newIndex = fSelectedIndex + 1; + if (newIndex >= fChoiceModel->CountChoices()) { + if (wrap) + newIndex = 0; + else + newIndex = fSelectedIndex; + } + return Select(newIndex); +} + + +bool +BDefaultCompletionStyle::SelectPrevious(bool wrap) +{ + if (!fChoiceModel || fChoiceModel->CountChoices() == 0) + return false; + + int32 newIndex = fSelectedIndex - 1; + if (newIndex < 0) { + if (wrap) + newIndex = fChoiceModel->CountChoices() - 1; + else + newIndex = 0; + } + return Select(newIndex); +} + + +bool +BDefaultCompletionStyle::IsChoiceSelected() const +{ + return fSelectedIndex >= 0; +} + + +int32 +BDefaultCompletionStyle::SelectedChoiceIndex() const +{ + return fSelectedIndex; +} + + +void +BDefaultCompletionStyle::ApplyChoice(bool hideChoices) +{ + if (!fChoiceModel || !fChoiceView || !fEditView || fSelectedIndex < 0) + return; + + BString completedText(fFullEnteredText); + completedText.Remove(fPatternStartPos, fPatternLength); + const BString& choiceStr = fChoiceModel->ChoiceAt(fSelectedIndex)->Text(); + completedText.Insert(choiceStr, fPatternStartPos); + + fIgnoreEditViewStateChanges = true; + + fFullEnteredText = completedText; + fPatternLength = choiceStr.Length(); + fEditView->SetEditViewState(completedText, + fPatternStartPos + choiceStr.Length()); + + if (hideChoices) { + fChoiceView->HideChoices(); + Select(-1); + } + + fIgnoreEditViewStateChanges = false; +} + + +void +BDefaultCompletionStyle::CancelChoice() +{ + if (!fChoiceView || !fEditView) + return; + if (fChoiceView->ChoicesAreShown()) { + fIgnoreEditViewStateChanges = true; + + fEditView->SetEditViewState(fFullEnteredText, + fPatternStartPos + fPatternLength); + fChoiceView->HideChoices(); + Select(-1); + + fIgnoreEditViewStateChanges = false; + } +} + +void +BDefaultCompletionStyle::EditViewStateChanged(bool updateChoices) +{ + if (fIgnoreEditViewStateChanges || !fChoiceModel || !fChoiceView + || !fEditView) { + return; + } + + BString text; + int32 caretPos; + fEditView->GetEditViewState(text, &caretPos); + if (fFullEnteredText == text) + return; + + fFullEnteredText = text; + + if (!updateChoices) + return; + + fPatternSelector->SelectPatternBounds(text, caretPos, &fPatternStartPos, + &fPatternLength); + BString pattern(text.String() + fPatternStartPos, fPatternLength); + fChoiceModel->FetchChoicesFor(pattern); + + Select(-1); + // show a single choice only if it doesn't match the pattern exactly: + if (fChoiceModel->CountChoices() > 1 || (fChoiceModel->CountChoices() == 1 + && pattern.ICompare(fChoiceModel->ChoiceAt(0)->Text())) != 0) { + fChoiceView->ShowChoices(this); + fChoiceView->SelectChoiceAt(fSelectedIndex); + } else + fChoiceView->HideChoices(); +} + + +// #pragma mark - BDefaultChoiceView::ListView + + +static const int32 MSG_INVOKED = 'invk'; + + +BDefaultChoiceView::ListView::ListView( + BAutoCompleter::CompletionStyle* completer) + : + BListView(BRect(0, 0, 100, 100), "ChoiceViewList"), + fCompleter(completer) +{ + // we need to check if user clicks outside of window-bounds: + SetEventMask(B_POINTER_EVENTS); +} + + +void +BDefaultChoiceView::ListView::AttachedToWindow() +{ + SetTarget(this); + SetInvocationMessage(new BMessage(MSG_INVOKED)); + BListView::AttachedToWindow(); +} + + +void +BDefaultChoiceView::ListView::SelectionChanged() +{ + fCompleter->Select(CurrentSelection(0)); +} + + +void +BDefaultChoiceView::ListView::MessageReceived(BMessage* message) +{ + switch(message->what) { + case MSG_INVOKED: + fCompleter->ApplyChoice(); + break; + default: + BListView::MessageReceived(message); + } +} + + +void +BDefaultChoiceView::ListView::MouseDown(BPoint point) +{ + if (!Window()->Frame().Contains(ConvertToScreen(point))) + // click outside of window, so we close it: + Window()->Quit(); + else + BListView::MouseDown(point); +} + + +// #pragma mark - BDefaultChoiceView::ListItem + + +BDefaultChoiceView::ListItem::ListItem(const BAutoCompleter::Choice* choice) + : + BListItem() +{ + fPreText = choice->DisplayText(); + if (choice->MatchLen() > 0) { + fPreText.MoveInto(fMatchText, choice->MatchPos(), choice->MatchLen()); + fPreText.MoveInto(fPostText, choice->MatchPos(), fPreText.Length()); + } +} + + +void +BDefaultChoiceView::ListItem::DrawItem(BView* owner, BRect frame, + bool complete) +{ + rgb_color textColor; + rgb_color nonMatchTextColor; + rgb_color backColor; + rgb_color matchColor; + if (IsSelected()) { + textColor = ui_color(B_MENU_SELECTED_ITEM_TEXT_COLOR); + backColor = ui_color(B_MENU_SELECTED_BACKGROUND_COLOR); + } else { + textColor = ui_color(B_DOCUMENT_TEXT_COLOR); + backColor = ui_color(B_DOCUMENT_BACKGROUND_COLOR); + } + matchColor = tint_color(backColor, (B_NO_TINT + B_DARKEN_1_TINT) / 2); + nonMatchTextColor = tint_color(backColor, 1.7); + + BFont font; + font_height fontHeight; + owner->GetFont(&font); + font.GetHeight(&fontHeight); + float xPos = frame.left + 1; + float yPos = frame.top + fontHeight.ascent; + float w; + if (fPreText.Length()) { + w = owner->StringWidth(fPreText.String()); + owner->SetLowColor(backColor); + owner->FillRect(BRect(xPos, frame.top, xPos + w - 1, frame.bottom), + B_SOLID_LOW); + owner->SetHighColor(nonMatchTextColor); + owner->DrawString(fPreText.String(), BPoint(xPos, yPos)); + xPos += w; + } + if (fMatchText.Length()) { + w = owner->StringWidth(fMatchText.String()); + owner->SetLowColor(matchColor); + owner->FillRect(BRect(xPos, frame.top, xPos + w - 1, frame.bottom), + B_SOLID_LOW); + owner->SetHighColor(textColor); + owner->DrawString(fMatchText.String(), BPoint(xPos, yPos)); + xPos += w; + } + if (fPostText.Length()) { + w = owner->StringWidth(fPostText.String()); + owner->SetLowColor(backColor); + owner->FillRect(BRect(xPos, frame.top, xPos + w - 1, frame.bottom), + B_SOLID_LOW); + owner->SetHighColor(nonMatchTextColor); + owner->DrawString(fPostText.String(), BPoint(xPos, yPos)); + } +} + + +// #pragma mark - BDefaultChoiceView + + +BDefaultChoiceView::BDefaultChoiceView() + : + fWindow(NULL), + fListView(NULL), + fMaxVisibleChoices(8) +{ + +} + + +BDefaultChoiceView::~BDefaultChoiceView() +{ + HideChoices(); +} + + +void +BDefaultChoiceView::SelectChoiceAt(int32 index) +{ + if (fListView && fListView->LockLooper()) { + if (index < 0) + fListView->DeselectAll(); + else { + fListView->Select(index); + fListView->ScrollToSelection(); + } + fListView->UnlockLooper(); + } +} + + +void +BDefaultChoiceView::ShowChoices(BAutoCompleter::CompletionStyle* completer) +{ + if (!completer) + return; + + HideChoices(); + + BAutoCompleter::ChoiceModel* choiceModel = completer->GetChoiceModel(); + BAutoCompleter::EditView* editView = completer->GetEditView(); + + if (!editView || !choiceModel || choiceModel->CountChoices() == 0) + return; + + fListView = new ListView(completer); + int32 count = choiceModel->CountChoices(); + for(int32 i = 0; i<count; ++i) { + fListView->AddItem( + new ListItem(choiceModel->ChoiceAt(i)) + ); + } + + BScrollView *scrollView = NULL; + if (count > fMaxVisibleChoices) { + scrollView = new BScrollView("", fListView, B_FOLLOW_NONE, 0, false, true, B_NO_BORDER); + } + + fWindow = new BWindow(BRect(0, 0, 100, 100), "", B_BORDERED_WINDOW_LOOK, + B_NORMAL_WINDOW_FEEL, B_NOT_MOVABLE | B_WILL_ACCEPT_FIRST_CLICK + | B_AVOID_FOCUS | B_ASYNCHRONOUS_CONTROLS); + if (scrollView != NULL) + fWindow->AddChild(scrollView); + else + fWindow->AddChild(fListView); + + int32 visibleCount = min_c(count, fMaxVisibleChoices); + float listHeight = fListView->ItemFrame(visibleCount - 1).bottom + 1; + + BRect pvRect = editView->GetAdjustmentFrame(); + BRect listRect = pvRect; + listRect.bottom = listRect.top + listHeight - 1; + BRect screenRect = BScreen().Frame(); + if (listRect.bottom + 1 + listHeight <= screenRect.bottom) + listRect.OffsetTo(pvRect.left, pvRect.bottom + 1); + else + listRect.OffsetTo(pvRect.left, pvRect.top - listHeight); + + if (scrollView != NULL) { + // Moving here to cut off the scrollbar top + scrollView->MoveTo(0, -1); + // Adding the 1 and 2 to cut-off the scroll-bar top, right and bottom + scrollView->ResizeTo(listRect.Width() + 1, listRect.Height() + 2); + // Move here to compensate for the above + fListView->MoveTo(0, 1); + fListView->ResizeTo(listRect.Width() - B_V_SCROLL_BAR_WIDTH, listRect.Height()); + } else { + fListView->MoveTo(0, 0); + fListView->ResizeTo(listRect.Width(), listRect.Height()); + } + fWindow->MoveTo(listRect.left, listRect.top); + fWindow->ResizeTo(listRect.Width(), listRect.Height()); + fWindow->Show(); +} + + +void +BDefaultChoiceView::HideChoices() +{ + if (fWindow && fWindow->Lock()) { + fWindow->Quit(); + fWindow = NULL; + fListView = NULL; + } +} + + +bool +BDefaultChoiceView::ChoicesAreShown() +{ + return (fWindow != NULL); +} + + +int32 +BDefaultChoiceView::CountVisibleChoices() const +{ + if (fListView) + return min_c(fMaxVisibleChoices, fListView->CountItems()); + else + return 0; +} + + +void +BDefaultChoiceView::SetMaxVisibleChoices(int32 choices) +{ + if (choices < 1) + choices = 1; + if (choices == fMaxVisibleChoices) + return; + + fMaxVisibleChoices = choices; + + // TODO: Update live? +} + + +int32 +BDefaultChoiceView::MaxVisibleChoices() const +{ + return fMaxVisibleChoices; +} + diff --git a/src/apps/webpositive/autocompletion/AutoCompleterDefaultImpl.h b/src/apps/webpositive/autocompletion/AutoCompleterDefaultImpl.h new file mode 100644 index 0000000000..6c3e50e8f5 --- /dev/null +++ b/src/apps/webpositive/autocompletion/AutoCompleterDefaultImpl.h @@ -0,0 +1,100 @@ +/* + * Copyright 2002-2006, project beam (http://sourceforge.net/projects/beam). + * All rights reserved. Distributed under the terms of the MIT License. + * + * Authors: + * Oliver Tappe <beam@hirschkaefer.de> + */ +#ifndef _AUTO_COMPLETER_DEFAULT_IMPL_H +#define _AUTO_COMPLETER_DEFAULT_IMPL_H + +#include <ListView.h> +#include <String.h> + +#include "AutoCompleter.h" + +class BDefaultPatternSelector : public BAutoCompleter::PatternSelector { +public: + virtual void SelectPatternBounds(const BString& text, + int32 caretPos, int32* start, + int32* length); +}; + + +class BDefaultCompletionStyle : public BAutoCompleter::CompletionStyle { +public: + BDefaultCompletionStyle( + BAutoCompleter::EditView* editView, + BAutoCompleter::ChoiceModel* choiceModel, + BAutoCompleter::ChoiceView* choiceView, + BAutoCompleter::PatternSelector* + patternSelector); + virtual ~BDefaultCompletionStyle(); + + virtual bool Select(int32 index); + virtual bool SelectNext(bool wrap = false); + virtual bool SelectPrevious(bool wrap = false); + virtual bool IsChoiceSelected() const; + virtual int32 SelectedChoiceIndex() const; + + virtual void ApplyChoice(bool hideChoices = true); + virtual void CancelChoice(); + + virtual void EditViewStateChanged(bool updateChoices); + +private: + BString fFullEnteredText; + int32 fSelectedIndex; + int32 fPatternStartPos; + int32 fPatternLength; + bool fIgnoreEditViewStateChanges; +}; + + +class BDefaultChoiceView : public BAutoCompleter::ChoiceView { +protected: + class ListView : public BListView { + public: + ListView( + BAutoCompleter::CompletionStyle* completer); + virtual void SelectionChanged(); + virtual void MessageReceived(BMessage* msg); + virtual void MouseDown(BPoint point); + virtual void AttachedToWindow(); + + private: + BAutoCompleter::CompletionStyle* fCompleter; + }; + + class ListItem : public BListItem { + public: + ListItem(const BAutoCompleter::Choice* choice); + virtual void DrawItem(BView* owner, BRect frame, + bool complete = false); + private: + BString fPreText; + BString fMatchText; + BString fPostText; + }; + +public: + BDefaultChoiceView(); + virtual ~BDefaultChoiceView(); + + virtual void SelectChoiceAt(int32 index); + virtual void ShowChoices( + BAutoCompleter::CompletionStyle* completer); + virtual void HideChoices(); + virtual bool ChoicesAreShown(); + virtual int32 CountVisibleChoices() const; + + void SetMaxVisibleChoices(int32 choices); + int32 MaxVisibleChoices() const; + +private: + BWindow* fWindow; + ListView* fListView; + int32 fMaxVisibleChoices; +}; + +#endif // _AUTO_COMPLETER_DEFAULT_IMPL_H diff --git a/src/apps/webpositive/autocompletion/TextViewCompleter.cpp b/src/apps/webpositive/autocompletion/TextViewCompleter.cpp new file mode 100644 index 0000000000..48f7bcb4da --- /dev/null +++ b/src/apps/webpositive/autocompletion/TextViewCompleter.cpp @@ -0,0 +1,169 @@ +/* + * Copyright 2002-2006, project beam (http://sourceforge.net/projects/beam). + * All rights reserved. Distributed under the terms of the MIT License. + * + * Authors: + * Oliver Tappe <beam@hirschkaefer.de> + */ + +#include "TextViewCompleter.h" + +#include <Looper.h> +#include <TextControl.h> +#include <stdio.h> + +#include "AutoCompleterDefaultImpl.h" + + +// #pragma mark - TextViewWrapper + + +TextViewCompleter::TextViewWrapper::TextViewWrapper(BTextView* textView) + : + fTextView(textView) +{ +} + + +void +TextViewCompleter::TextViewWrapper::GetEditViewState(BString& text, + int32* caretPos) +{ + if (fTextView && fTextView->LockLooper()) { + text = fTextView->Text(); + if (caretPos) { + int32 end; + fTextView->GetSelection(caretPos, &end); + } + fTextView->UnlockLooper(); + } +} + + +void +TextViewCompleter::TextViewWrapper::SetEditViewState(const BString& text, + int32 caretPos, int32 selectionLength) +{ + if (fTextView && fTextView->LockLooper()) { + fTextView->SetText(text.String(), text.Length()); + fTextView->Select(caretPos, caretPos + selectionLength); + fTextView->ScrollToSelection(); + fTextView->UnlockLooper(); + } +} + + +BRect +TextViewCompleter::TextViewWrapper::GetAdjustmentFrame() +{ + BRect frame = fTextView->Bounds(); + frame = fTextView->ConvertToScreen(frame); + frame.InsetBy(0, -3); + return frame; +} + + +// #pragma mark - + + +TextViewCompleter::TextViewCompleter(BTextView* textView, ChoiceModel* model, + PatternSelector* patternSelector) + : + BAutoCompleter(new TextViewWrapper(textView), model, + new BDefaultChoiceView(), patternSelector), + BMessageFilter(B_KEY_DOWN), + fTextView(textView), + fModificationsReported(false) +{ + fTextView->AddFilter(this); +} + + +TextViewCompleter::~TextViewCompleter() +{ + fTextView->RemoveFilter(this); +} + + +void +TextViewCompleter::SetModificationsReported(bool reported) +{ + fModificationsReported = reported; +} + + +void +TextViewCompleter::TextModified(bool updateChoices) +{ + EditViewStateChanged(updateChoices); +} + + +filter_result +TextViewCompleter::Filter(BMessage* message, BHandler** target) +{ + const char* bytes; + int32 modifiers; + if ((!target || message->FindString("bytes", &bytes) != B_OK + || message->FindInt32("modifiers", &modifiers) != B_OK) + || (modifiers & (B_CONTROL_KEY | B_COMMAND_KEY | B_OPTION_KEY + | B_SHIFT_KEY)) != 0) { + return B_DISPATCH_MESSAGE; + } + + switch (bytes[0]) { + case B_UP_ARROW: + SelectPrevious(); + // Insert the current choice into the text view, so the user can + // continue typing. This will not trigger another evaluation, since + // we don't invoke EditViewStateChanged(). + ApplyChoice(false); + return B_SKIP_MESSAGE; + case B_DOWN_ARROW: + SelectNext(); + // See above. + ApplyChoice(false); + return B_SKIP_MESSAGE; + case B_PAGE_UP: + { + int32 index = SelectedChoiceIndex() - CountVisibleChoices(); + index = max_c(index, 0); + Select(index); + ApplyChoice(false); + return B_SKIP_MESSAGE; + } + case B_PAGE_DOWN: + { + int32 index = SelectedChoiceIndex() + CountVisibleChoices(); + index = min_c(index, CountChoices() - 1); + Select(index); + ApplyChoice(false); + return B_SKIP_MESSAGE; + } + + case B_ESCAPE: + CancelChoice(); + return B_DISPATCH_MESSAGE; + case B_RETURN: + if (IsChoiceSelected()) { + ApplyChoice(); + EditViewStateChanged(); + } else + CancelChoice(); + return B_DISPATCH_MESSAGE; + case B_TAB: { + // make sure that the choices-view is closed when tabbing out: + CancelChoice(); + return B_DISPATCH_MESSAGE; + } + default: + if (!fModificationsReported) { + // dispatch message to textview manually... + Looper()->DispatchMessage(message, *target); + // ...and propagate the new state to the auto-completer: + EditViewStateChanged(); + return B_SKIP_MESSAGE; + } + return B_DISPATCH_MESSAGE; + } +} diff --git a/src/apps/webpositive/autocompletion/TextViewCompleter.h b/src/apps/webpositive/autocompletion/TextViewCompleter.h new file mode 100644 index 0000000000..1a2955f6d7 --- /dev/null +++ b/src/apps/webpositive/autocompletion/TextViewCompleter.h @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2006, project beam (http://sourceforge.net/projects/beam). + * All rights reserved. Distributed under the terms of the MIT License. + * + * Authors: + * Oliver Tappe <beam@hirschkaefer.de> + */ +#ifndef TEXT_CONTROL_COMPLETER_H +#define TEXT_CONTROL_COMPLETER_H + +#include <MessageFilter.h> + +#include "AutoCompleter.h" + + +class BTextView; + +class TextViewCompleter : protected BAutoCompleter, public BMessageFilter { +public: + TextViewCompleter(BTextView* textView, + ChoiceModel* choiceModel = NULL, + PatternSelector* patternSelector = NULL); + virtual ~TextViewCompleter(); + + void SetModificationsReported(bool reported); + void TextModified(bool updateChoices); + +private: + virtual filter_result Filter(BMessage* message, BHandler** target); + + class TextViewWrapper : public EditView { + public: + TextViewWrapper(BTextView* textView); + virtual BRect GetAdjustmentFrame(); + virtual void GetEditViewState(BString& text, + int32* caretPos); + virtual void SetEditViewState(const BString& text, + int32 caretPos, int32 selectionLength = 0); + private: + BTextView* fTextView; + }; + +private: + BTextView* fTextView; + bool fModificationsReported; +}; + +#endif // TEXT_CONTROL_COMPLETER_H diff --git a/src/apps/webpositive/support/AutoLocker.h b/src/apps/webpositive/support/AutoLocker.h new file mode 100644 index 0000000000..9baa7aa7a5 --- /dev/null +++ b/src/apps/webpositive/support/AutoLocker.h @@ -0,0 +1,178 @@ +/* + * Copyright 2005-2007, Ingo Weinhold, bonefish@users.sf.net. + * All rights reserved. Distributed under the terms of the MIT License. + */ +#ifndef _AUTO_LOCKER_H +#define _AUTO_LOCKER_H + + +#include <stddef.h> + + +namespace BPrivate { + +// AutoLockerStandardLocking +template<typename Lockable> +class AutoLockerStandardLocking { +public: + inline bool Lock(Lockable* lockable) + { + return lockable->Lock(); + } + + inline void Unlock(Lockable* lockable) + { + lockable->Unlock(); + } +}; + +// AutoLockerReadLocking +template<typename Lockable> +class AutoLockerReadLocking { +public: + inline bool Lock(Lockable* lockable) + { + return lockable->ReadLock(); + } + + inline void Unlock(Lockable* lockable) + { + lockable->ReadUnlock(); + } +}; + +// AutoLockerWriteLocking +template<typename Lockable> +class AutoLockerWriteLocking { +public: + inline bool Lock(Lockable* lockable) + { + return lockable->WriteLock(); + } + + inline void Unlock(Lockable* lockable) + { + lockable->WriteUnlock(); + } +}; + +// AutoLocker +template<typename Lockable, + typename Locking = AutoLockerStandardLocking<Lockable> > +class AutoLocker { +private: + typedef AutoLocker<Lockable, Locking> ThisClass; +public: + inline AutoLocker() + : + fLockable(NULL), + fLocked(false) + { + } + + inline AutoLocker(const Locking& locking) + : + fLockable(NULL), + fLocking(locking), + fLocked(false) + { + } + + inline AutoLocker(Lockable* lockable, bool alreadyLocked = false, + bool lockIfNotLocked = true) + : + fLockable(lockable), + fLocked(fLockable && alreadyLocked) + { + if (!alreadyLocked && lockIfNotLocked) + Lock(); + } + + inline AutoLocker(Lockable& lockable, bool alreadyLocked = false, + bool lockIfNotLocked = true) + : + fLockable(&lockable), + fLocked(fLockable && alreadyLocked) + { + if (!alreadyLocked && lockIfNotLocked) + Lock(); + } + + inline ~AutoLocker() + { + Unlock(); + } + + inline void SetTo(Lockable* lockable, bool alreadyLocked, + bool lockIfNotLocked = true) + { + Unlock(); + fLockable = lockable; + fLocked = (lockable && alreadyLocked); + if (!alreadyLocked && lockIfNotLocked) + Lock(); + } + + inline void SetTo(Lockable& lockable, bool alreadyLocked, + bool lockIfNotLocked = true) + { + SetTo(&lockable, alreadyLocked, lockIfNotLocked); + } + + inline void Unset() + { + Unlock(); + Detach(); + } + + inline bool Lock() + { + if (fLockable && !fLocked) + fLocked = fLocking.Lock(fLockable); + return fLocked; + } + + inline void Unlock() + { + if (fLockable && fLocked) { + fLocking.Unlock(fLockable); + fLocked = false; + } + } + + inline void Detach() + { + fLockable = NULL; + fLocked = false; + } + + inline AutoLocker<Lockable, Locking>& operator=(Lockable* lockable) + { + SetTo(lockable); + return *this; + } + + inline AutoLocker<Lockable, Locking>& operator=(Lockable& lockable) + { + SetTo(&lockable); + return *this; + } + + inline bool IsLocked() const { return fLocked; } + + inline operator bool() const { return fLocked; } + +protected: + Lockable* fLockable; + Locking fLocking; + bool fLocked; +}; + + +} // namespace BPrivate + +using BPrivate::AutoLocker; +using BPrivate::AutoLockerReadLocking; +using BPrivate::AutoLockerWriteLocking; + +#endif // _AUTO_LOCKER_H diff --git a/src/apps/webpositive/support/BaseURL.cpp b/src/apps/webpositive/support/BaseURL.cpp new file mode 100644 index 0000000000..4d2f47cd29 --- /dev/null +++ b/src/apps/webpositive/support/BaseURL.cpp @@ -0,0 +1,18 @@ +/* + * Copyright 2010 Stephan Aßmus <superstippi@gmx.de> + * All rights reserved. Distributed under the terms of the MIT License. + */ + +#include "BaseURL.h" + + +BString +baseURL(const BString string) +{ + int32 baseURLStart = string.FindFirst("://") + 3; + int32 baseURLEnd = string.FindFirst("/", baseURLStart + 1); + BString result; + result.SetTo(string.String() + baseURLStart, baseURLEnd - baseURLStart); + return result; +} + diff --git a/src/apps/webpositive/support/BaseURL.h b/src/apps/webpositive/support/BaseURL.h new file mode 100644 index 0000000000..b4589ef58f --- /dev/null +++ b/src/apps/webpositive/support/BaseURL.h @@ -0,0 +1,14 @@ +/* + * Copyright 2010 Stephan Aßmus <superstippi@gmx.de> + * All rights reserved. Distributed under the terms of the MIT License. + */ +#ifndef BASE_URL_H +#define BASE_URL_H + +#include <String.h> + + +BString baseURL(const BString string); + + +#endif // BASE_URL_H diff --git a/src/apps/webpositive/support/BitmapButton.cpp b/src/apps/webpositive/support/BitmapButton.cpp new file mode 100644 index 0000000000..009079969c --- /dev/null +++ b/src/apps/webpositive/support/BitmapButton.cpp @@ -0,0 +1,119 @@ +/* + * Copyright 2010 Stephan Aßmus <superstippi@gmx.de>. All rights reserved. + * Distributed under the terms of the MIT License. + */ + +#include "BitmapButton.h" + +#include <string.h> + +#include <Bitmap.h> +#include <ControlLook.h> +#include <TranslationUtils.h> + + +static const float kFrameInset = 2; + + +BitmapButton::BitmapButton(const char* resourceName, BMessage* message) + : + BButton("", message), + fBitmap(BTranslationUtils::GetBitmap(resourceName)), + fBackgroundMode(BUTTON_BACKGROUND) +{ +} + + +BitmapButton::BitmapButton(const uint8* bits, uint32 width, uint32 height, + color_space format, BMessage* message) + : + BButton("", message), + fBitmap(new BBitmap(BRect(0, 0, width - 1, height - 1), 0, format)), + fBackgroundMode(BUTTON_BACKGROUND) +{ + memcpy(fBitmap->Bits(), bits, fBitmap->BitsLength()); +} + + +BitmapButton::~BitmapButton() +{ + delete fBitmap; +} + + +BSize +BitmapButton::MinSize() +{ + BSize min(0, 0); + if (fBitmap) { + min.width = fBitmap->Bounds().Width(); + min.height = fBitmap->Bounds().Height(); + } + min.width += kFrameInset * 2; + min.height += kFrameInset * 2; + return min; +} + + +BSize +BitmapButton::MaxSize() +{ + return BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED); +} + + +BSize +BitmapButton::PreferredSize() +{ + return MinSize(); +} + + +void +BitmapButton::Draw(BRect updateRect) +{ + BRect bounds(Bounds()); + rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); + uint32 flags = be_control_look->Flags(this); + + if (fBackgroundMode == BUTTON_BACKGROUND || Value() == B_CONTROL_ON) { + be_control_look->DrawButtonBackground(this, bounds, updateRect, base, + flags); + } else { + SetHighColor(tint_color(base, B_DARKEN_2_TINT)); + StrokeLine(bounds.LeftBottom(), bounds.RightBottom()); + bounds.bottom--; + be_control_look->DrawMenuBarBackground(this, bounds, updateRect, base, + flags); + } + + if (fBitmap == NULL) + return; + + SetDrawingMode(B_OP_ALPHA); + + if (!IsEnabled()) { + SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY); + SetHighColor(0, 0, 0, 120); + } + + BRect bitmapBounds(fBitmap->Bounds()); + BPoint bitmapLocation( + floorf((bounds.left + bounds.right + - (bitmapBounds.left + bitmapBounds.right)) / 2 + 0.5f), + floorf((bounds.top + bounds.bottom + - (bitmapBounds.top + bitmapBounds.bottom)) / 2 + 0.5f)); + + DrawBitmap(fBitmap, bitmapLocation); +} + + +void +BitmapButton::SetBackgroundMode(uint32 mode) +{ + if (fBackgroundMode != mode) { + fBackgroundMode = mode; + Invalidate(); + } +} + diff --git a/src/apps/webpositive/support/BitmapButton.h b/src/apps/webpositive/support/BitmapButton.h new file mode 100644 index 0000000000..432adcb34e --- /dev/null +++ b/src/apps/webpositive/support/BitmapButton.h @@ -0,0 +1,44 @@ +/* + * Copyright 2010 Stephan Aßmus <superstippi@gmx.de>. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef BITMAP_BUTTON_H +#define BITMAP_BUTTON_H + +#include <Button.h> + + +class BBitmap; + + +class BitmapButton : public BButton { +public: + enum { + BUTTON_BACKGROUND = 0, + MENUBAR_BACKGROUND, + }; + + BitmapButton(const char* resourceName, + BMessage* message); + + BitmapButton(const uint8* bits, uint32 width, + uint32 height, color_space format, + BMessage* message); + + virtual ~BitmapButton(); + + virtual BSize MinSize(); + virtual BSize MaxSize(); + virtual BSize PreferredSize(); + + virtual void Draw(BRect updateRect); + + void SetBackgroundMode(uint32 mode); + +private: + BBitmap* fBitmap; + uint32 fBackgroundMode; +}; + + +#endif // BITMAP_BUTTON_H diff --git a/src/apps/webpositive/support/DateTime.cpp b/src/apps/webpositive/support/DateTime.cpp new file mode 100644 index 0000000000..19030db00a --- /dev/null +++ b/src/apps/webpositive/support/DateTime.cpp @@ -0,0 +1,1473 @@ +/* + * Copyright 2007-2010, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Julun <host.haiku@gmx.de> + * Stephan Aßmus <superstippi@gmx.de> + */ + +#include "DateTime.h" + + +#include <time.h> +#include <sys/time.h> + +#include <Message.h> + + +namespace BPrivate { + + +const int32 kSecondsPerMinute = 60; + +const int32 kHoursPerDay = 24; +const int32 kMinutesPerDay = 1440; +const int32 kSecondsPerDay = 86400; +const int32 kMillisecondsPerDay = 86400000; + +const bigtime_t kMicrosecondsPerSecond = 1000000LL; +const bigtime_t kMicrosecondsPerMinute = 60000000LL; +const bigtime_t kMicrosecondsPerHour = 3600000000LL; +const bigtime_t kMicrosecondsPerDay = 86400000000LL; + + +/*! + Constructs a new BTime object. Asked for its time representation, it will + return 0 for Hour(), Minute(), Second() etc. This can represent midnight, + but be aware IsValid() will return false. +*/ +BTime::BTime() + : + fMicroseconds(-1) +{ +} + + +/*! + Constructs a new BTime object as a copy of \c other. +*/ +BTime::BTime(const BTime& other) + : + fMicroseconds(other.fMicroseconds) +{ +} + + +/*! + Constructs a BTime object with \c hour \c minute, \c second, \c microsecond. + + \c hour must be between 0 and 23, \c minute and \c second must be between + 0 and 59 and \c microsecond should be in the range of 0 and 999999. If the + specified time is invalid, the time is not set and IsValid() returns false. +*/ +BTime::BTime(int32 hour, int32 minute, int32 second, int32 microsecond) + : + fMicroseconds(-1) +{ + _SetTime(hour, minute, second, microsecond); +} + + +/*! + Constructs a new BTime object from the provided BMessage archive. +*/ +BTime::BTime(const BMessage* archive) + : + fMicroseconds(-1) +{ + if (archive == NULL) + return; + archive->FindInt64("mircoseconds", &fMicroseconds); +} + + +/*! + Empty destructor. +*/ +BTime::~BTime() +{ +} + + +/*! + Archives the BTime object into the provided BMessage object. + @returns \c B_OK if all went well. + \c B_BAD_VALUE, if the message is \c NULL. + \c other error codes, depending on failure to append + fields to the message. +*/ +status_t +BTime::Archive(BMessage* into) const +{ + if (into == NULL) + return B_BAD_VALUE; + return into->AddInt64("mircoseconds", fMicroseconds); +} + + +/*! + Returns true if the time is valid, otherwise false. A valid time can be + BTime(23, 59, 59, 999999) while BTime(24, 00, 01) would be invalid. +*/ +bool +BTime::IsValid() const +{ + return fMicroseconds > -1 && fMicroseconds < kMicrosecondsPerDay; +} + + +/*! + This is an overloaded member function, provided for convenience. +*/ +bool +BTime::IsValid(const BTime& time) const +{ + return time.fMicroseconds > -1 && time.fMicroseconds < kMicrosecondsPerDay; +} + + +/*! + This is an overloaded member function, provided for convenience. +*/ +bool +BTime::IsValid(int32 hour, int32 minute, int32 second, int32 microsecond) const +{ + return BTime(hour, minute, second, microsecond).IsValid(); +} + + +/*! + Returns the current time as reported by the system depending on the given + time_type \c type. +*/ +BTime +BTime::CurrentTime(time_type type) +{ + struct timeval tv; + if (gettimeofday(&tv, NULL) != 0) { + // gettimeofday failed? + time(&tv.tv_sec); + } + + struct tm result; + struct tm* timeinfo; + if (type == B_GMT_TIME) + timeinfo = gmtime_r(&tv.tv_sec, &result); + else + timeinfo = localtime_r(&tv.tv_sec, &result); + + int32 sec = timeinfo->tm_sec; + return BTime(timeinfo->tm_hour, timeinfo->tm_min, (sec > 59) ? 59 : sec, + tv.tv_usec); +} + + +/*! + Returns a copy of the current BTime object. +*/ +BTime +BTime::Time() const +{ + return *this; +} + + +/*! + This is an overloaded member function, provided for convenience. Set the + current BTime object to the passed BTime \c time object. +*/ +bool +BTime::SetTime(const BTime& time) +{ + fMicroseconds = time.fMicroseconds; + return IsValid(); +} + + +/*! + Set the time to \c hour \c minute, \c second and \c microsecond. + + \c hour must be between 0 and 23, \c minute and \c second must be between + 0 and 59 and \c microsecond should be in the range of 0 and 999999. Returns + true if the time is valid; otherwise false. If the specified time is + invalid, the time is not set and the function returns false. +*/ +bool +BTime::SetTime(int32 hour, int32 minute, int32 second, int32 microsecond) +{ + return _SetTime(hour, minute, second, microsecond); +} + + + +/*! + Adds \c hours to the current time. If the passed value is negativ it will + become earlier. Note: The time will wrap if it passes midnight. +*/ +BTime& +BTime::AddHours(int32 hours) +{ + return _AddMicroseconds(bigtime_t(hours % kHoursPerDay) + * kMicrosecondsPerHour); +} + + +/*! + Adds \c minutes to the current time. If the passed value is negativ it will + become earlier. Note: The time will wrap if it passes midnight. +*/ +BTime& +BTime::AddMinutes(int32 minutes) +{ + return _AddMicroseconds(bigtime_t(minutes % kMinutesPerDay) + * kMicrosecondsPerMinute); +} + + +/*! + Adds \c seconds to the current time. If the passed value is negativ it will + become earlier. Note: The time will wrap if it passes midnight. +*/ +BTime& +BTime::AddSeconds(int32 seconds) +{ + return _AddMicroseconds(bigtime_t(seconds % kSecondsPerDay) + * kMicrosecondsPerSecond); +} + + +/*! + Adds \c milliseconds to the current time. If the passed value is negativ it + will become earlier. Note: The time will wrap if it passes midnight. +*/ +BTime& +BTime::AddMilliseconds(int32 milliseconds) +{ + return _AddMicroseconds(bigtime_t(milliseconds % kMillisecondsPerDay) + * 1000); +} + + +/*! + Adds \c microseconds to the current time. If the passed value is negativ it + will become earlier. Note: The time will wrap if it passes midnight. +*/ +BTime& +BTime::AddMicroseconds(int32 microseconds) +{ + return _AddMicroseconds(microseconds); +} + + +/*! + Returns the hour fragment of the time. +*/ +int32 +BTime::Hour() const +{ + return int32(_Microseconds() / kMicrosecondsPerHour); +} + + +/*! + Returns the minute fragment of the time. +*/ +int32 +BTime::Minute() const +{ + return int32(((_Microseconds() % kMicrosecondsPerHour)) / kMicrosecondsPerMinute); +} + + +/*! + Returns the second fragment of the time. +*/ +int32 +BTime::Second() const +{ + return int32(_Microseconds() / kMicrosecondsPerSecond) % kSecondsPerMinute; +} + + +/*! + Returns the millisecond fragment of the time. +*/ +int32 +BTime::Millisecond() const +{ + + return Microsecond() / 1000; +} + + +/*! + Returns the microsecond fragment of the time. +*/ +int32 +BTime::Microsecond() const +{ + return int32(_Microseconds() % 1000000); +} + + +bigtime_t +BTime::_Microseconds() const +{ + return fMicroseconds == -1 ? 0 : fMicroseconds; +} + + +/*! + Returns the difference between this time and the given BTime \c time based + on the passed diff_type \c type. If \c time is earlier the return value will + be negativ. + + The return value then can be hours, minutes, seconds, milliseconds or + microseconds while its range will always be between -86400000000 and + 86400000000 depending on diff_type \c type. +*/ +bigtime_t +BTime::Difference(const BTime& time, diff_type type) const +{ + bigtime_t diff = time._Microseconds() - _Microseconds(); + switch (type) { + case B_HOURS_DIFF: { + diff /= kMicrosecondsPerHour; + } break; + case B_MINUTES_DIFF: { + diff /= kMicrosecondsPerMinute; + } break; + case B_SECONDS_DIFF: { + diff /= kMicrosecondsPerSecond; + } break; + case B_MILLISECONDS_DIFF: { + diff /= 1000; + } break; + case B_MICROSECONDS_DIFF: + default: break; + } + return diff; +} + + +/*! + Returns true if this time is different from \c time, otherwise false. +*/ +bool +BTime::operator!=(const BTime& time) const +{ + return fMicroseconds != time.fMicroseconds; +} + + +/*! + Returns true if this time is equal to \c time, otherwise false. +*/ +bool +BTime::operator==(const BTime& time) const +{ + return fMicroseconds == time.fMicroseconds; +} + + +/*! + Returns true if this time is earlier than \c time, otherwise false. +*/ +bool +BTime::operator<(const BTime& time) const +{ + return fMicroseconds < time.fMicroseconds; +} + + +/*! + Returns true if this time is earlier than or equal to \c time, otherwise false. +*/ +bool +BTime::operator<=(const BTime& time) const +{ + return fMicroseconds <= time.fMicroseconds; +} + + +/*! + Returns true if this time is later than \c time, otherwise false. +*/ +bool +BTime::operator>(const BTime& time) const +{ + return fMicroseconds > time.fMicroseconds; +} + + +/*! + Returns true if this time is later than or equal to \c time, otherwise false. +*/ +bool +BTime::operator>=(const BTime& time) const +{ + return fMicroseconds >= time.fMicroseconds; +} + + +BTime& +BTime::_AddMicroseconds(bigtime_t microseconds) +{ + bigtime_t count = 0; + if (microseconds < 0) { + count = ((kMicrosecondsPerDay - microseconds) / kMicrosecondsPerDay) * + kMicrosecondsPerDay; + } + fMicroseconds = (_Microseconds() + microseconds + count) % kMicrosecondsPerDay; + return *this; +} + + +bool +BTime::_SetTime(bigtime_t hour, bigtime_t minute, bigtime_t second, + bigtime_t microsecond) +{ + fMicroseconds = hour * kMicrosecondsPerHour + + minute * kMicrosecondsPerMinute + + second * kMicrosecondsPerSecond + + microsecond; + + bool isValid = IsValid(); + if (!isValid) + fMicroseconds = -1; + + return isValid; +} + + +// #pragma mark - BDate + + +/*! + Constructs a new BDate object. IsValid() will return false. +*/ +BDate::BDate() + : + fDay(-1), + fYear(0), + fMonth(-1) +{ +} + + +/*! + Constructs a new BDate object as a copy of \c other. +*/ +BDate::BDate(const BDate& other) + : + fDay(other.fDay), + fYear(other.fYear), + fMonth(other.fMonth) +{ +} + + +/*! + Constructs a BDate object with \c year \c month and \c day. + + Please note that a date before 1.1.4713 BC, a date with year 0 and a date + between 4.10.1582 and 15.10.1582 are considered invalid. If the specified + date is invalid, the date is not set and IsValid() returns false. Also note + that every passed year will be interpreted as is. + +*/ +BDate::BDate(int32 year, int32 month, int32 day) +{ + _SetDate(year, month, day); +} + + +/*! + Constructs a new BDate object from the provided archive. +*/ +BDate::BDate(const BMessage* archive) + : + fDay(-1), + fYear(0), + fMonth(-1) +{ + if (archive == NULL) + return; + archive->FindInt32("day", &fDay); + archive->FindInt32("year", &fYear); + archive->FindInt32("month", &fMonth); +} + + +/*! + Empty destructor. +*/ +BDate::~BDate() +{ +} + + +/*! + Archives the BDate object into the provided BMessage object. + @returns \c B_OK if all went well. + \c B_BAD_VALUE, if the message is \c NULL. + \c other error codes, depending on failure to append + fields to the message. +*/ +status_t +BDate::Archive(BMessage* into) const +{ + if (into == NULL) + return B_BAD_VALUE; + status_t ret = into->AddInt32("day", fDay); + if (ret == B_OK) + ret = into->AddInt32("year", fYear); + if (ret == B_OK) + ret = into->AddInt32("month", fMonth); + return ret; +} + + +/*! + Returns true if the date is valid, otherwise false. + + Please note that a date before 1.1.4713 BC, a date with year 0 and a date + between 4.10.1582 and 15.10.1582 are considered invalid. +*/ +bool +BDate::IsValid() const +{ + return IsValid(fYear, fMonth, fDay); +} + + +/*! + This is an overloaded member function, provided for convenience. +*/ +bool +BDate::IsValid(const BDate& date) const +{ + return IsValid(date.fYear, date.fMonth, date.fDay); +} + + +/*! + This is an overloaded member function, provided for convenience. +*/ +bool +BDate::IsValid(int32 year, int32 month, int32 day) const +{ + // no year 0 in Julian and nothing before 1.1.4713 BC + if (year == 0 || year < -4713) + return false; + + if (month < 1 || month > 12) + return false; + + if (day < 1 || day > _DaysInMonth(year, month)) + return false; + + // 'missing' days between switch julian - gregorian + if (year == 1582 && month == 10 && day > 4 && day < 15) + return false; + + return true; +} + + +/*! + Returns the current date as reported by the system depending on the given + time_type \c type. +*/ +BDate +BDate::CurrentDate(time_type type) +{ + time_t timer; + struct tm result; + struct tm* timeinfo; + + time(&timer); + + if (type == B_GMT_TIME) + timeinfo = gmtime_r(&timer, &result); + else + timeinfo = localtime_r(&timer, &result); + + return BDate(timeinfo->tm_year + 1900, timeinfo->tm_mon +1, timeinfo->tm_mday); +} + + +/*! + Returns a copy of the current BTime object. +*/ +BDate +BDate::Date() const +{ + return *this; +} + + +/*! + This is an overloaded member function, provided for convenience. +*/ +bool +BDate::SetDate(const BDate& date) +{ + return _SetDate(date.fYear, date.fMonth, date.fDay); +} + + +/*! + Set the date to \c year \c month and \c day. + + Returns true if the date is valid; otherwise false. If the specified date is + invalid, the date is not set and the function returns false. +*/ +bool +BDate::SetDate(int32 year, int32 month, int32 day) +{ + return _SetDate(year, month, day); +} + + +/*! + This function sets the given \c year, \c month and \c day to the + representative values of this date. The pointers can be NULL. If the date is + invalid, the values will be set to -1 for \c month and \c day, the \c year + will be set to 0. +*/ +void +BDate::GetDate(int32* year, int32* month, int32* day) +{ + if (year) + *year = fYear; + + if (month) + *month = fMonth; + + if (day) + *day = fDay; +} + + +/*! + Adds \c days to the current date. If the passed value is negativ it will + become earlier. If the current date is invalid, the \c days are not added. +*/ +void +BDate::AddDays(int32 days) +{ + if (IsValid()) + *this = JulianDayToDate(DateToJulianDay() + days); +} + + +/*! + Adds \c years to the current date. If the passed value is negativ it will + become earlier. If the current date is invalid, the \c years are not added. + The day/ month combination will be adjusted if it does not exist in the + resulting year, so this function will then return the latest valid date. +*/ +void +BDate::AddYears(int32 years) +{ + if (IsValid()) { + const int32 tmp = fYear; + fYear += years; + + if ((tmp > 0 && fYear <= 0) || (tmp < 0 && fYear >= 0)) + fYear += (years > 0) ? +1 : -1; + + fDay = min_c(fDay, _DaysInMonth(fYear, fMonth)); + } +} + + +/*! + Adds \c months to the current date. If the passed value is negativ it will + become earlier. If the current date is invalid, the \c months are not added. + The day/ month combination will be adjusted if it does not exist in the + resulting year, so this function will then return the latest valid date. +*/ +void +BDate::AddMonths(int32 months) +{ + if (IsValid()) { + const int32 tmp = fYear; + fYear += months / 12; + fMonth += months % 12; + + if (fMonth > 12) { + fYear++; + fMonth -= 12; + } else if (fMonth < 1) { + fYear--; + fMonth += 12; + } + + if ((tmp > 0 && fYear <= 0) || (tmp < 0 && fYear >= 0)) + fYear += (months > 0) ? +1 : -1; + + // 'missing' days between switch julian - gregorian + if (fYear == 1582 && fMonth == 10 && fDay > 4 && fDay < 15) + fDay = (months > 0) ? 15 : 4; + + fDay = min_c(fDay, DaysInMonth()); + } +} + + +/*! + Returns the day fragment of the date. The return value will be in the range + of 1 to 31, in case the date is invalid it will be -1. +*/ +int32 +BDate::Day() const +{ + return fDay; +} + + +/*! + Returns the year fragment of the date. If the date is invalid, the function + returns 0. +*/ +int32 +BDate::Year() const +{ + return fYear; +} + + +/*! + Returns the month fragment of the date. The return value will be in the + range of 1 to 12, in case the date is invalid it will be -1. +*/ +int32 +BDate::Month() const +{ + return fMonth; +} + + +/*! + Returns the difference in days between this date and the given BDate \c date. + If \c date is earlier the return value will be negativ. If the calculation + is done with an invalid date, the result is undefined. +*/ +int32 +BDate::Difference(const BDate& date) const +{ + return date.DateToJulianDay() - DateToJulianDay(); +} + + +/*! + Returns the week number of the date, if the date is invalid it will return + B_ERROR. Please note that this function does only work within the Gregorian + calendar, thus a date before 15.10.1582 will return B_ERROR. +*/ +int32 +BDate::WeekNumber() const +{ + /* + This algorithm is taken from: + Frequently Asked Questions about Calendars + Version 2.8 Claus Tøndering 15 December 2005 + + Note: it will work only within the Gregorian Calendar + */ + + if (!IsValid() || fYear < 1582 + || (fYear == 1582 && fMonth < 10) + || (fYear == 1582 && fMonth == 10 && fDay < 15)) + return int32(B_ERROR); + + int32 a; + int32 b; + int32 s; + int32 e; + int32 f; + + if (fMonth > 0 && fMonth < 3) { + a = fYear - 1; + b = (a / 4) - (a / 100) + (a / 400); + int32 c = ((a - 1) / 4) - ((a - 1) / 100) + ((a -1) / 400); + s = b - c; + e = 0; + f = fDay - 1 + 31 * (fMonth - 1); + } else if (fMonth >= 3 && fMonth <= 12) { + a = fYear; + b = (a / 4) - (a / 100) + (a / 400); + int32 c = ((a - 1) / 4) - ((a - 1) / 100) + ((a -1) / 400); + s = b - c; + e = s + 1; + f = fDay + ((153 * (fMonth - 3) + 2) / 5) + 58 + s; + } else + return int32(B_ERROR); + + int32 g = (a + b) % 7; + int32 d = (f + g - e) % 7; + int32 n = f + 3 - d; + + int32 weekNumber; + if (n < 0) + weekNumber = 53 - (g -s) / 5; + else if (n > 364 + s) + weekNumber = 1; + else + weekNumber = n / 7 + 1; + + return weekNumber; +} + + +/*! + Returns the day of the week in the range of 1 to 7, while 1 stands for + monday. If the date is invalid, the function will return B_ERROR. +*/ +int32 +BDate::DayOfWeek() const +{ + // http://en.wikipedia.org/wiki/Julian_day#Calculation + return IsValid() ? (DateToJulianDay() % 7) + 1 : int32(B_ERROR); +} + + +/*! + Returns the day of the year in the range of 1 to 365 (366 in leap years). If + the date is invalid, the function will return B_ERROR. +*/ +int32 +BDate::DayOfYear() const +{ + if (!IsValid()) + return int32(B_ERROR); + + return DateToJulianDay() - _DateToJulianDay(fYear, 1, 1) + 1; +} + + +/*! + Returns true if the passed \c year is a leap year, otherwise false. If the + \c year passed is before 4713 BC, the result is undefined. +*/ +bool +BDate::IsLeapYear(int32 year) const +{ + if (year < 1582) { + if (year < 0) + year++; + return (year % 4) == 0; + } + return (year % 400 == 0) || (year % 4 == 0 && year % 100 != 0); +} + + +/*! + Returns the number of days in the year of the current date. If the date is + valid it will return 365 or 366, otherwise B_ERROR; +*/ +int32 +BDate::DaysInYear() const +{ + if (!IsValid()) + return int32(B_ERROR); + + return IsLeapYear(fYear) ? 366 : 365; +} + + +/*! + Returns the number of days in the month of the current date. If the date is + valid it will return 28 up to 31, otherwise B_ERROR; +*/ +int32 +BDate::DaysInMonth() const +{ + if (!IsValid()) + return int32(B_ERROR); + + return _DaysInMonth(fYear, fMonth); +} + + +/*! + Returns the short day name of this object. +*/ +BString +BDate::ShortDayName() const +{ + return ShortDayName(DayOfWeek()); +} + + +/*! + Returns the short day name in case of an valid day, otherwise an empty + string. The passed \c day must be in the range of 1 to 7 while 1 stands for + monday. +*/ +/*static*/ BString +BDate::ShortDayName(int32 day) +{ + if (day < 1 || day > 7) + return BString(); + + tm tm_struct; + memset(&tm_struct, 0, sizeof(tm)); + tm_struct.tm_wday = day == 7 ? 0 : day; + + char buffer[256]; + strftime(buffer, sizeof(buffer), "%a", &tm_struct); + + return BString(buffer); +} + + +/*! + Returns the short month name of this object. +*/ +BString +BDate::ShortMonthName() const +{ + return ShortMonthName(Month()); +} + + +/*! + Returns the short month name in case of an valid month, otherwise an empty + string. The passed \c month must be in the range of 1 to 12. +*/ +/*static*/ BString +BDate::ShortMonthName(int32 month) +{ + if (month < 1 || month > 12) + return BString(); + + tm tm_struct; + memset(&tm_struct, 0, sizeof(tm)); + tm_struct.tm_mon = month - 1; + + char buffer[256]; + strftime(buffer, sizeof(buffer), "%b", &tm_struct); + + return BString(buffer); +} + + +/*! + Returns the long day name of this object's week day. +*/ +BString +BDate::LongDayName() const +{ + return LongDayName(DayOfWeek()); +} + + +/*! + Returns the long day name in case of an valid day, otherwise an empty + string. The passed \c day must be in the range of 1 to 7 while 1 stands for + monday. +*/ +/*static*/ BString +BDate::LongDayName(int32 day) +{ + if (day < 1 || day > 7) + return BString(); + + tm tm_struct; + memset(&tm_struct, 0, sizeof(tm)); + tm_struct.tm_wday = day == 7 ? 0 : day; + + char buffer[256]; + strftime(buffer, sizeof(buffer), "%A", &tm_struct); + + return BString(buffer); +} + + +/*! + Returns the long month name of this object's month. +*/ +BString +BDate::LongMonthName() const +{ + return LongMonthName(Month()); +} + + +/*! + Returns the long month name in case of an valid month, otherwise an empty + string. The passed \c month must be in the range of 1 to 12. +*/ +/*static*/ BString +BDate::LongMonthName(int32 month) +{ + if (month < 1 || month > 12) + return BString(); + + tm tm_struct; + memset(&tm_struct, 0, sizeof(tm)); + tm_struct.tm_mon = month - 1; + + char buffer[256]; + strftime(buffer, sizeof(buffer), "%B", &tm_struct); + + return BString(buffer); +} + + +/*! + Converts the date to Julian day. If your date is invalid, the function will + return B_ERROR. +*/ +int32 +BDate::DateToJulianDay() const +{ + return _DateToJulianDay(fYear, fMonth, fDay); +} + + +/* + Converts the passed \c julianDay to an BDate. If the \c julianDay is negativ, + the function will return an invalid date. Because of the switch from Julian + calendar to Gregorian calendar the 4.10.1582 is followed by the 15.10.1582. +*/ +BDate +BDate::JulianDayToDate(int32 julianDay) +{ + BDate date; + const int32 kGregorianCalendarStart = 2299161; + if (julianDay >= kGregorianCalendarStart) { + // http://en.wikipedia.org/wiki/Julian_day#Gregorian_calendar_from_Julian_day_number + int32 j = julianDay + 32044; + int32 dg = j % 146097; + int32 c = (dg / 36524 + 1) * 3 / 4; + int32 dc = dg - c * 36524; + int32 db = dc % 1461; + int32 a = (db / 365 + 1) * 3 / 4; + int32 da = db - a * 365; + int32 m = (da * 5 + 308) / 153 - 2; + date.fYear = ((j / 146097) * 400 + c * 100 + (dc / 1461) * 4 + a) - 4800 + + (m + 2) / 12; + date.fMonth = (m + 2) % 12 + 1; + date.fDay = int32((da - (m + 4) * 153 / 5 + 122) + 1.5); + } else if (julianDay >= 0) { + // http://en.wikipedia.org/wiki/Julian_day#Calculation + julianDay += 32082; + int32 d = (4 * julianDay + 3) / 1461; + int32 e = julianDay - (1461 * d) / 4; + int32 m = ((5 * e) + 2) / 153; + date.fDay = e - (153 * m + 2) / 5 + 1; + date.fMonth = m + 3 - 12 * (m / 10); + int32 year = d - 4800 + (m / 10); + if (year <= 0) + year--; + date.fYear = year; + } + return date; +} + + +/*! + Returns true if this date is different from \c date, otherwise false. +*/ +bool +BDate::operator!=(const BDate& date) const +{ + return DateToJulianDay() != date.DateToJulianDay(); +} + + +/*! + Returns true if this date is equal to \c date, otherwise false. +*/ +bool +BDate::operator==(const BDate& date) const +{ + return DateToJulianDay() == date.DateToJulianDay(); +} + + +/*! + Returns true if this date is earlier than \c date, otherwise false. +*/ +bool +BDate::operator<(const BDate& date) const +{ + return DateToJulianDay() < date.DateToJulianDay(); +} + + +/*! + Returns true if this date is earlier than or equal to \c date, otherwise false. +*/ +bool +BDate::operator<=(const BDate& date) const +{ + return DateToJulianDay() <= date.DateToJulianDay(); +} + + +/*! + Returns true if this date is later than \c date, otherwise false. +*/ +bool +BDate::operator>(const BDate& date) const +{ + return DateToJulianDay() > date.DateToJulianDay(); +} + + +/*! + Returns true if this date is later than or equal to \c date, otherwise false. +*/ +bool +BDate::operator>=(const BDate& date) const +{ + return DateToJulianDay() >= date.DateToJulianDay(); +} + + +bool +BDate::_SetDate(int32 year, int32 month, int32 day) +{ + fDay = -1; + fYear = 0; + fMonth = -1; + + bool valid = IsValid(year, month, day); + if (valid) { + fDay = day; + fYear = year; + fMonth = month; + } + + return valid; +} + + +int32 +BDate::_DaysInMonth(int32 year, int32 month) const +{ + if (month == 2 && IsLeapYear(year)) + return 29; + + const int32 daysInMonth[12] = + {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + + return daysInMonth[month -1]; +} + + +int32 +BDate::_DateToJulianDay(int32 _year, int32 month, int32 day) const +{ + if (IsValid(_year, month, day)) { + int32 year = _year; + if (year < 0) year++; + + int32 a = (14 - month) / 12; + int32 y = year + 4800 - a; + int32 m = month + (12 * a) - 3; + + // http://en.wikipedia.org/wiki/Julian_day#Calculation + if (year > 1582 + || (year == 1582 && month > 10) + || (year == 1582 && month == 10 && day >= 15)) { + return day + (((153 * m) + 2) / 5) + (365 * y) + (y / 4) - + (y / 100) + (y / 400) - 32045; + } else if (year < 1582 + || (year == 1582 && month < 10) + || (year == 1582 && month == 10 && day <= 4)) { + return day + (((153 * m) + 2) / 5) + (365 * y) + (y / 4) - 32083; + } + } + + // http://en.wikipedia.org/wiki/Gregorian_calendar: + // The last day of the Julian calendar was Thursday October 4, 1582 + // and this was followed by the first day of the Gregorian calendar, + // Friday October 15, 1582 (the cycle of weekdays was not affected). + return int32(B_ERROR); +} + + +// #pragma mark - BDateTime + + +/*! + Constructs a new BDateTime object. IsValid() will return false. +*/ +BDateTime::BDateTime() + : fDate(), + fTime() +{ +} + + +/*! + Constructs a BDateTime object with \c date and \c time. The return value + of IsValid() depends on the validity of the passed objects. +*/ +BDateTime::BDateTime(const BDate& date, const BTime& time) + : fDate(date), + fTime(time) +{ +} + + +/*! + Constructs a new BDateTime object. IsValid() will return false. +*/ +BDateTime::BDateTime(const BMessage* archive) + : fDate(archive), + fTime(archive) +{ +} + + +/*! + Empty destructor. +*/ +BDateTime::~BDateTime() +{ +} + + +/*! + Archives the BDateTime object into the provided BMessage object. + @returns \c B_OK if all went well. + \c B_BAD_VALUE, if the message is \c NULL. + \c other error codes, depending on failure to append + fields to the message. +*/ +status_t +BDateTime::Archive(BMessage* into) const +{ + status_t ret = fDate.Archive(into); + if (ret == B_OK) + ret = fTime.Archive(into); + return ret; +} + + +/*! + Returns true if the date time is valid, otherwise false. +*/ +bool +BDateTime::IsValid() const +{ + return fDate.IsValid() && fTime.IsValid(); +} + + +/*! + Returns the current date and time as reported by the system depending on the + given time_type \c type. +*/ +BDateTime +BDateTime::CurrentDateTime(time_type type) +{ + return BDateTime(BDate::CurrentDate(type), BTime::CurrentTime(type)); +} + + +/*! + Sets the current date and time of this object to \c date and \c time. +*/ +void +BDateTime::SetDateTime(const BDate& date, const BTime& time) +{ + fDate = date; + fTime = time; +} + + +/*! + Returns the current date of this object. +*/ +BDate& +BDateTime::Date() +{ + return fDate; +} + + +/*! + Returns the current date of this object. +*/ +const BDate& +BDateTime::Date() const +{ + return fDate; +} + + +/*! + Set the current date of this object to \c date. +*/ +void +BDateTime::SetDate(const BDate& date) +{ + fDate = date; +} + + +/*! + Returns the current time of this object. +*/ +BTime& +BDateTime::Time() +{ + return fTime; +} + + +/*! + Returns the current time of this object. +*/ +const BTime& +BDateTime::Time() const +{ + return fTime; +} + + +/*! + Sets the current time of this object to \c time. +*/ +void +BDateTime::SetTime(const BTime& time) +{ + fTime = time; +} + + +/*! + Returns the current date and time converted to seconds since + 1.1.1970 - 00:00:00. If the current date is before 1.1.1970 the function + returns -1; +*/ +int32 +BDateTime::Time_t() const +{ + BDate date(1970, 1, 1); + if (date.Difference(fDate) < 0) + return -1; + + tm tm_struct; + + tm_struct.tm_hour = fTime.Hour(); + tm_struct.tm_min = fTime.Minute(); + tm_struct.tm_sec = fTime.Second(); + + tm_struct.tm_year = fDate.Year() - 1900; + tm_struct.tm_mon = fDate.Month() - 1; + tm_struct.tm_mday = fDate.Day(); + + // set less 0 as we wan't use it + tm_struct.tm_isdst = -1; + + // return secs_since_jan1_1970 or -1 on error + return int32(mktime(&tm_struct)); +} + + +/*! + Sets the current date and time converted from seconds since + 1.1.1970 - 00:00:00. +*/ +void +BDateTime::SetTime_t(uint32 seconds) +{ + BTime time; + time.AddSeconds(seconds % kSecondsPerDay); + fTime.SetTime(time); + + BDate date(1970, 1, 1); + date.AddDays(seconds / kSecondsPerDay); + fDate.SetDate(date); +} + + +/*! + Returns true if this datetime is different from \c dateTime, otherwise false. +*/ +bool +BDateTime::operator!=(const BDateTime& dateTime) const +{ + return fTime != dateTime.fTime && fDate != dateTime.fDate; +} + + +/*! + Returns true if this datetime is equal to \c dateTime, otherwise false. +*/ +bool +BDateTime::operator==(const BDateTime& dateTime) const +{ + return fTime == dateTime.fTime && fDate == dateTime.fDate; +} + + +/*! + Returns true if this datetime is earlier than \c dateTime, otherwise false. +*/ +bool +BDateTime::operator<(const BDateTime& dateTime) const +{ + if (fDate < dateTime.fDate) + return true; + if (fDate == dateTime.fDate) + return fTime < dateTime.fTime; + return false; +} + + +/*! + Returns true if this datetime is earlier than or equal to \c dateTime, + otherwise false. +*/ +bool +BDateTime::operator<=(const BDateTime& dateTime) const +{ + if (fDate < dateTime.fDate) + return true; + if (fDate == dateTime.fDate) + return fTime <= dateTime.fTime; + return false; +} + + +/*! + Returns true if this datetime is later than \c dateTime, otherwise false. +*/ +bool +BDateTime::operator>(const BDateTime& dateTime) const +{ + if (fDate > dateTime.fDate) + return true; + if (fDate == dateTime.fDate) + return fTime > dateTime.fTime; + return false; +} + + +/*! + Returns true if this datetime is later than or equal to \c dateTime, + otherwise false. +*/ +bool +BDateTime::operator>=(const BDateTime& dateTime) const +{ + if (fDate > dateTime.fDate) + return true; + if (fDate == dateTime.fDate) + return fTime >= dateTime.fTime; + return false; +} + + +} //namespace BPrivate diff --git a/src/apps/webpositive/support/DateTime.h b/src/apps/webpositive/support/DateTime.h new file mode 100644 index 0000000000..93f76399d6 --- /dev/null +++ b/src/apps/webpositive/support/DateTime.h @@ -0,0 +1,225 @@ +/* + * Copyright 2007-2010, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _DATE_TIME_H_ +#define _DATE_TIME_H_ + + +#include <String.h> + + +class BMessage; + + +namespace BPrivate { + + +enum time_type { + B_GMT_TIME, + B_LOCAL_TIME +}; + + +enum diff_type { + B_HOURS_DIFF, + B_MINUTES_DIFF, + B_SECONDS_DIFF, + B_MILLISECONDS_DIFF, + B_MICROSECONDS_DIFF +}; + + +class BTime { +public: + BTime(); + BTime(const BTime& other); + BTime(int32 hour, int32 minute, int32 second, + int32 microsecond = 0); + BTime(const BMessage* archive); + ~BTime(); + + status_t Archive(BMessage* into) const; + + bool IsValid() const; + bool IsValid(const BTime& time) const; + bool IsValid(int32 hour, int32 minute, int32 second, + int32 microsecond = 0) const; + + static BTime CurrentTime(time_type type); + + BTime Time() const; + bool SetTime(const BTime& time); + bool SetTime(int32 hour, int32 minute, int32 second, + int32 microsecond = 0); + + BTime& AddHours(int32 hours); + BTime& AddMinutes(int32 minutes); + BTime& AddSeconds(int32 seconds); + BTime& AddMilliseconds(int32 milliseconds); + BTime& AddMicroseconds(int32 microseconds); + + int32 Hour() const; + int32 Minute() const; + int32 Second() const; + int32 Millisecond() const; + int32 Microsecond() const; + bigtime_t Difference(const BTime& time, + diff_type type) const; + + bool operator!=(const BTime& time) const; + bool operator==(const BTime& time) const; + + bool operator<(const BTime& time) const; + bool operator<=(const BTime& time) const; + + bool operator>(const BTime& time) const; + bool operator>=(const BTime& time) const; + +private: + bigtime_t _Microseconds() const; + BTime& _AddMicroseconds(bigtime_t microseconds); + bool _SetTime(bigtime_t hour, bigtime_t minute, + bigtime_t second, bigtime_t microsecond); + +private: + bigtime_t fMicroseconds; +}; + + +class BDate { +public: + BDate(); + BDate(const BDate& other); + BDate(int32 year, int32 month, int32 day); + BDate(const BMessage* archive); + ~BDate(); + + status_t Archive(BMessage* into) const; + + bool IsValid() const; + bool IsValid(const BDate& date) const; + bool IsValid(int32 year, int32 month, + int32 day) const; + + static BDate CurrentDate(time_type type); + + BDate Date() const; + bool SetDate(const BDate& date); + + bool SetDate(int32 year, int32 month, int32 day); + void GetDate(int32* year, int32* month, int32* day); + + void AddDays(int32 days); + void AddYears(int32 years); + void AddMonths(int32 months); + + int32 Day() const; + int32 Year() const; + int32 Month() const; + int32 Difference(const BDate& date) const; + + int32 DayOfWeek() const; + int32 DayOfYear() const; + + int32 WeekNumber() const; + bool IsLeapYear(int32 year) const; + + int32 DaysInYear() const; + int32 DaysInMonth() const; + + BString ShortDayName() const; + static BString ShortDayName(int32 day); + + BString ShortMonthName() const; + static BString ShortMonthName(int32 month); + + BString LongDayName() const; + static BString LongDayName(int32 day); + + BString LongMonthName() const; + static BString LongMonthName(int32 month); + + int32 DateToJulianDay() const; + static BDate JulianDayToDate(int32 julianDay); + + bool operator!=(const BDate& date) const; + bool operator==(const BDate& date) const; + + bool operator<(const BDate& date) const; + bool operator<=(const BDate& date) const; + + bool operator>(const BDate& date) const; + bool operator>=(const BDate& date) const; + +private: + int32 _DaysInMonth(int32 year, int32 month) const; + bool _SetDate(int32 year, int32 month, int32 day); + int32 _DateToJulianDay(int32 year, int32 month, + int32 day) const; + +private: + int32 fDay; + int32 fYear; + int32 fMonth; +}; + + +class BDateTime { +public: + BDateTime(); + BDateTime(const BDate &date, const BTime &time); + BDateTime(const BMessage* archive); + ~BDateTime(); + + status_t Archive(BMessage* into) const; + + bool IsValid() const; + + static BDateTime CurrentDateTime(time_type type); + void SetDateTime(const BDate &date, const BTime &time); + + BDate& Date(); + const BDate& Date() const; + void SetDate(const BDate &date); + + BTime& Time(); + const BTime& Time() const; + void SetTime(const BTime &time); + + int32 Time_t() const; + void SetTime_t(uint32 seconds); + + bool operator!=(const BDateTime& dateTime) const; + bool operator==(const BDateTime& dateTime) const; + + bool operator<(const BDateTime& dateTime) const; + bool operator<=(const BDateTime& dateTime) const; + + bool operator>(const BDateTime& dateTime) const; + bool operator>=(const BDateTime& dateTime) const; + +private: + BDate fDate; + BTime fTime; +}; + + +} // namespace BPrivate + + +using BPrivate::time_type; +using BPrivate::B_GMT_TIME; +using BPrivate::B_LOCAL_TIME; +using BPrivate::diff_type; +using BPrivate::B_HOURS_DIFF; +using BPrivate::B_MINUTES_DIFF; +using BPrivate::B_SECONDS_DIFF; +using BPrivate::B_MILLISECONDS_DIFF; +using BPrivate::B_MICROSECONDS_DIFF; +using BPrivate::BTime; +using BPrivate::BDate; +using BPrivate::BDateTime; + + +#endif // _DATE_TIME_H_ diff --git a/src/apps/webpositive/support/FontSelectionView.cpp b/src/apps/webpositive/support/FontSelectionView.cpp new file mode 100644 index 0000000000..d0df01baa1 --- /dev/null +++ b/src/apps/webpositive/support/FontSelectionView.cpp @@ -0,0 +1,527 @@ +/* + * Copyright 2001-2010, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Mark Hogben + * DarkWyrm <bpmagic@columbus.rr.com> + * Axel Dörfler, axeld@pinc-software.de + * Philippe Saint-Pierre, stpere@gmail.com + * Stephan Aßmus <superstippi@gmx.de> + */ + +#include "FontSelectionView.h" + +#include <Box.h> +#include <Catalog.h> +#include <Locale.h> +#include <Looper.h> +#include <MenuField.h> +#include <MenuItem.h> +#include <PopUpMenu.h> +#include <String.h> +#include <StringView.h> +#include <LayoutItem.h> +#include <GroupLayoutBuilder.h> + +#include <stdio.h> + +#undef B_TRANSLATION_CONTEXT +#define B_TRANSLATION_CONTEXT "Font Selection view" + + +static const float kMinSize = 8.0; +static const float kMaxSize = 18.0; + +static const int32 kMsgSetFamily = 'fmly'; +static const int32 kMsgSetStyle = 'styl'; +static const int32 kMsgSetSize = 'size'; + + +// #pragma mark - + + +FontSelectionView::FontSelectionView(const char* name, const char* label, + bool separateStyles, const BFont* currentFont) + : + BHandler(name), + fMessage(NULL), + fTarget(NULL) +{ + if (currentFont == NULL) + fCurrentFont = _DefaultFont(); + else + fCurrentFont = *currentFont; + + fSavedFont = fCurrentFont; + + fSizesMenu = new BPopUpMenu("size menu"); + fFontsMenu = new BPopUpMenu("font menu"); + + // font menu + fFontsMenuField = new BMenuField("fonts", label, fFontsMenu, NULL); + fFontsMenuField->SetFont(be_bold_font); + + // styles menu, if desired + if (separateStyles) { + fStylesMenu = new BPopUpMenu("styles menu"); + fStylesMenuField = new BMenuField("styles", B_TRANSLATE("Style:"), + fStylesMenu, NULL); + } else { + fStylesMenu = NULL; + fStylesMenuField = NULL; + } + + // size menu + fSizesMenuField = new BMenuField("size", B_TRANSLATE("Size:"), fSizesMenu, + NULL); + fSizesMenuField->SetAlignment(B_ALIGN_RIGHT); + + // preview + fPreviewText = new BStringView("preview text", + B_TRANSLATE_COMMENT("The quick brown fox jumps over the lazy dog.", + "Don't translate this literally ! Use a phrase showing all " + "chars from A to Z.")); + + fPreviewText->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, + B_SIZE_UNLIMITED)); + fPreviewText->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), + 1.65)); + fPreviewText->SetAlignment(B_ALIGN_RIGHT); + _UpdateFontPreview(); +} + + +FontSelectionView::~FontSelectionView() +{ + // Some controls may not have been attached... + if (!fPreviewText->Window()) + delete fPreviewText; + if (!fSizesMenuField->Window()) + delete fSizesMenuField; + if (fStylesMenuField && !fStylesMenuField->Window()) + delete fStylesMenuField; + if (!fFontsMenuField->Window()) + delete fFontsMenuField; + + delete fMessage; +} + + +void +FontSelectionView::AttachedToLooper() +{ + _BuildSizesMenu(); + UpdateFontsMenu(); +} + + +void +FontSelectionView::MessageReceived(BMessage* message) +{ + switch (message->what) { + case kMsgSetSize: + { + int32 size; + if (message->FindInt32("size", &size) != B_OK + || size == fCurrentFont.Size()) + break; + + fCurrentFont.SetSize(size); + _UpdateFontPreview(); + _Invoke(); + break; + } + + case kMsgSetFamily: + { + const char* family; + if (message->FindString("family", &family) != B_OK) + break; + + font_style style; + fCurrentFont.GetFamilyAndStyle(NULL, &style); + + BMenuItem* familyItem = fFontsMenu->FindItem(family); + if (familyItem != NULL) { + _SelectCurrentFont(false); + + BMenuItem* styleItem; + if (fStylesMenuField != NULL) + styleItem = fStylesMenuField->Menu()->FindMarked(); + else { + styleItem = familyItem->Submenu()->FindItem(style); + if (styleItem == NULL) + styleItem = familyItem->Submenu()->ItemAt(0); + } + + if (styleItem != NULL) { + styleItem->SetMarked(true); + fCurrentFont.SetFamilyAndStyle(family, styleItem->Label()); + _UpdateFontPreview(); + } + if (fStylesMenuField != NULL) + _AddStylesToMenu(fCurrentFont, fStylesMenuField->Menu()); + } + + _Invoke(); + break; + } + + case kMsgSetStyle: + { + const char* family; + const char* style; + if (message->FindString("family", &family) != B_OK + || message->FindString("style", &style) != B_OK) + break; + + BMenuItem *familyItem = fFontsMenu->FindItem(family); + if (!familyItem) + break; + + _SelectCurrentFont(false); + familyItem->SetMarked(true); + + fCurrentFont.SetFamilyAndStyle(family, style); + _UpdateFontPreview(); + _Invoke(); + break; + } + + default: + BHandler::MessageReceived(message); + } +} + + +void +FontSelectionView::SetMessage(BMessage* message) +{ + delete fMessage; + fMessage = message; +} + + +void +FontSelectionView::SetTarget(BHandler* target) +{ + fTarget = target; +} + + +// #pragma mark - + + +void +FontSelectionView::SetFont(const BFont& font, float size) +{ + BFont resizedFont(font); + resizedFont.SetSize(size); + SetFont(resizedFont); +} + + +void +FontSelectionView::SetFont(const BFont& font) +{ + if (font == fCurrentFont && font == fSavedFont) + return; + + _SelectCurrentFont(false); + fSavedFont = fCurrentFont = font; + _UpdateFontPreview(); + + _SelectCurrentFont(true); + _SelectCurrentSize(true); +} + + +void +FontSelectionView::SetSize(float size) +{ + SetFont(fCurrentFont, size); +} + + +const BFont& +FontSelectionView::Font() const +{ + return fCurrentFont; +} + + +void +FontSelectionView::SetDefaults() +{ + BFont defaultFont = _DefaultFont(); + if (defaultFont == fCurrentFont) + return; + + _SelectCurrentFont(false); + + fCurrentFont = defaultFont; + _UpdateFontPreview(); + + _SelectCurrentFont(true); + _SelectCurrentSize(true); +} + + +void +FontSelectionView::Revert() +{ + if (!IsRevertable()) + return; + + _SelectCurrentFont(false); + + fCurrentFont = fSavedFont; + _UpdateFontPreview(); + + _SelectCurrentFont(true); + _SelectCurrentSize(true); +} + + +bool +FontSelectionView::IsDefaultable() +{ + return fCurrentFont != _DefaultFont(); +} + + +bool +FontSelectionView::IsRevertable() +{ + return fCurrentFont != fSavedFont; +} + + +void +FontSelectionView::UpdateFontsMenu() +{ + int32 numFamilies = count_font_families(); + + fFontsMenu->RemoveItems(0, fFontsMenu->CountItems(), true); + + BFont font = fCurrentFont; + + font_family currentFamily; + font_style currentStyle; + font.GetFamilyAndStyle(¤tFamily, ¤tStyle); + + for (int32 i = 0; i < numFamilies; i++) { + font_family family; + uint32 flags; + if (get_font_family(i, &family, &flags) != B_OK) + continue; + + // if we're setting the fixed font, we only want to show fixed fonts + if (!strcmp(Name(), "fixed") && (flags & B_IS_FIXED) == 0) + continue; + + font.SetFamilyAndFace(family, B_REGULAR_FACE); + + BMessage* message = new BMessage(kMsgSetFamily); + message->AddString("family", family); + message->AddString("name", Name()); + + BMenuItem* familyItem; + if (fStylesMenuField != NULL) { + familyItem = new BMenuItem(family, message); + } else { + // Each family item has a submenu with all styles for that font. + BMenu* stylesMenu = new BMenu(family); + _AddStylesToMenu(font, stylesMenu); + familyItem = new BMenuItem(stylesMenu, message); + } + + familyItem->SetMarked(strcmp(family, currentFamily) == 0); + fFontsMenu->AddItem(familyItem); + familyItem->SetTarget(this); + } + + // Separate styles menu for only the current font. + if (fStylesMenuField != NULL) + _AddStylesToMenu(fCurrentFont, fStylesMenuField->Menu()); +} + + +// #pragma mark - private + + +BLayoutItem* +FontSelectionView::CreateSizesLabelLayoutItem() +{ + return fSizesMenuField->CreateLabelLayoutItem(); +} + + +BLayoutItem* +FontSelectionView::CreateSizesMenuBarLayoutItem() +{ + return fSizesMenuField->CreateMenuBarLayoutItem(); +} + + +BLayoutItem* +FontSelectionView::CreateFontsLabelLayoutItem() +{ + return fFontsMenuField->CreateLabelLayoutItem(); +} + + +BLayoutItem* +FontSelectionView::CreateFontsMenuBarLayoutItem() +{ + return fFontsMenuField->CreateMenuBarLayoutItem(); +} + + +BLayoutItem* +FontSelectionView::CreateStylesLabelLayoutItem() +{ + if (fStylesMenuField) + return fStylesMenuField->CreateLabelLayoutItem(); + return NULL; +} + + +BLayoutItem* +FontSelectionView::CreateStylesMenuBarLayoutItem() +{ + if (fStylesMenuField) + return fStylesMenuField->CreateMenuBarLayoutItem(); + return NULL; +} + + +BView* +FontSelectionView::PreviewBox() const +{ + return fPreviewText; +} + + +// #pragma mark - private + + +void +FontSelectionView::_Invoke() +{ + if (fTarget != NULL && fTarget->Looper() != NULL && fMessage != NULL) { + BMessage message(*fMessage); + fTarget->Looper()->PostMessage(&message, fTarget); + } +} + + +BFont +FontSelectionView::_DefaultFont() const +{ + if (strcmp(Name(), "bold") == 0) + return *be_bold_font; + if (strcmp(Name(), "fixed") == 0) + return *be_fixed_font; + else + return *be_plain_font; +} + + +void +FontSelectionView::_SelectCurrentFont(bool select) +{ + font_family family; + font_style style; + fCurrentFont.GetFamilyAndStyle(&family, &style); + + BMenuItem *item = fFontsMenu->FindItem(family); + if (item != NULL) { + item->SetMarked(select); + + if (item->Submenu() != NULL) { + item = item->Submenu()->FindItem(style); + if (item != NULL) + item->SetMarked(select); + } + } +} + + +void +FontSelectionView::_SelectCurrentSize(bool select) +{ + char label[16]; + snprintf(label, sizeof(label), "%ld", (int32)fCurrentFont.Size()); + + BMenuItem* item = fSizesMenu->FindItem(label); + if (item != NULL) + item->SetMarked(select); +} + + +void +FontSelectionView::_UpdateFontPreview() +{ + fPreviewText->SetFont(&fCurrentFont); +} + + +void +FontSelectionView::_BuildSizesMenu() +{ + const int32 sizes[] = {7, 8, 9, 10, 11, 12, 13, 14, 18, 21, 24, 0}; + + // build size menu + for (int32 i = 0; sizes[i]; i++) { + int32 size = sizes[i]; + if (size < kMinSize || size > kMaxSize) + continue; + + char label[32]; + snprintf(label, sizeof(label), "%ld", size); + + BMessage* message = new BMessage(kMsgSetSize); + message->AddInt32("size", size); + message->AddString("name", Name()); + + BMenuItem* item = new BMenuItem(label, message); + if (size == fCurrentFont.Size()) + item->SetMarked(true); + + fSizesMenu->AddItem(item); + item->SetTarget(this); + } +} + + +void +FontSelectionView::_AddStylesToMenu(const BFont& font, BMenu* stylesMenu) const +{ + stylesMenu->RemoveItems(0, stylesMenu->CountItems(), true); + stylesMenu->SetRadioMode(true); + + font_family family; + font_style style; + font.GetFamilyAndStyle(&family, &style); + BString currentStyle(style); + + int32 numStyles = count_font_styles(family); + + for (int32 j = 0; j < numStyles; j++) { + if (get_font_style(family, j, &style) != B_OK) + continue; + + BMessage* message = new BMessage(kMsgSetStyle); + message->AddString("family", (char*)family); + message->AddString("style", (char*)style); + + BMenuItem* item = new BMenuItem(style, message); + item->SetMarked(currentStyle == style); + + stylesMenu->AddItem(item); + item->SetTarget(this); + } +} + diff --git a/src/apps/webpositive/support/FontSelectionView.h b/src/apps/webpositive/support/FontSelectionView.h new file mode 100644 index 0000000000..8ee630b448 --- /dev/null +++ b/src/apps/webpositive/support/FontSelectionView.h @@ -0,0 +1,91 @@ +/* + * Copyright 2001-2010, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Mark Hogben + * DarkWyrm <bpmagic@columbus.rr.com> + * Axel Dörfler, axeld@pinc-software.de + * Philippe Saint-Pierre, stpere@gmail.com + * Stephan Aßmus <superstippi@gmx.de> + */ +#ifndef FONT_SELECTION_VIEW_H +#define FONT_SELECTION_VIEW_H + + +#include <Font.h> +#include <Handler.h> + +class BLayoutItem; +class BBox; +class BMenu; +class BMenuField; +class BPopUpMenu; +class BStringView; +class BView; + + +class FontSelectionView : public BHandler { +public: + FontSelectionView(const char* name, + const char* label, bool separateStyles, + const BFont* font = NULL); + virtual ~FontSelectionView(); + + void AttachedToLooper(); + virtual void MessageReceived(BMessage* message); + + void SetMessage(BMessage* message); + void SetTarget(BHandler* target); + + void SetFont(const BFont& font, float size); + void SetFont(const BFont& font); + void SetSize(float size); + const BFont& Font() const; + + void SetDefaults(); + void Revert(); + bool IsDefaultable(); + bool IsRevertable(); + + void UpdateFontsMenu(); + + BLayoutItem* CreateSizesLabelLayoutItem(); + BLayoutItem* CreateSizesMenuBarLayoutItem(); + + BLayoutItem* CreateFontsLabelLayoutItem(); + BLayoutItem* CreateFontsMenuBarLayoutItem(); + + BLayoutItem* CreateStylesLabelLayoutItem(); + BLayoutItem* CreateStylesMenuBarLayoutItem(); + + BView* PreviewBox() const; + +private: + void _Invoke(); + + BFont _DefaultFont() const; + void _SelectCurrentFont(bool select); + void _SelectCurrentSize(bool select); + void _UpdateFontPreview(); + void _BuildSizesMenu(); + void _AddStylesToMenu(const BFont& font, + BMenu* menu) const; + +protected: + BMenuField* fFontsMenuField; + BMenuField* fStylesMenuField; + BMenuField* fSizesMenuField; + BPopUpMenu* fFontsMenu; + BPopUpMenu* fStylesMenu; + BPopUpMenu* fSizesMenu; + BStringView* fPreviewText; + + BFont fSavedFont; + BFont fCurrentFont; + + BMessage* fMessage; + BHandler* fTarget; +}; + +#endif // FONT_SELECTION_VIEW_H diff --git a/src/apps/webpositive/support/HashKeys.h b/src/apps/webpositive/support/HashKeys.h new file mode 100644 index 0000000000..3480b56492 --- /dev/null +++ b/src/apps/webpositive/support/HashKeys.h @@ -0,0 +1,124 @@ +/* + * Copyright 2010, Stephan Aßmus <superstippi@gmx.de>. All rights reserved. + * Copyright 2004-2007, Ingo Weinhold <ingo_weinhold@gmx.de>. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef HASH_KEYS_H +#define HASH_KEYS_H + +#include <String.h> + + +namespace BPrivate { + +#if 0 +// TODO: Move here from HashMap.h and adapt all clients. +// HashKey32 +template<typename Value> +struct HashKey32 { + HashKey32() {} + HashKey32(const Value& value) : value(value) {} + + uint32 GetHashCode() const + { + return (uint32)value; + } + + HashKey32<Value> operator=(const HashKey32<Value>& other) + { + value = other.value; + return *this; + } + + bool operator==(const HashKey32<Value>& other) const + { + return (value == other.value); + } + + bool operator!=(const HashKey32<Value>& other) const + { + return (value != other.value); + } + + Value value; +}; + + +// HashKey64 +template<typename Value> +struct HashKey64 { + HashKey64() {} + HashKey64(const Value& value) : value(value) {} + + uint32 GetHashCode() const + { + uint64 v = (uint64)value; + return (uint32)(v >> 32) ^ (uint32)v; + } + + HashKey64<Value> operator=(const HashKey64<Value>& other) + { + value = other.value; + return *this; + } + + bool operator==(const HashKey64<Value>& other) const + { + return (value == other.value); + } + + bool operator!=(const HashKey64<Value>& other) const + { + return (value != other.value); + } + + Value value; +}; +#endif + + +struct HashKeyString { + HashKeyString() {} + HashKeyString(const BString& value) : value(value) {} + HashKeyString(const char* string) : value(string) {} + + uint32 GetHashCode() const + { + // from the Dragon Book: a slightly modified hashpjw() + uint32 hash = 0; + const char* string = value.String(); + if (string != NULL) { + for (; *string; string++) { + uint32 g = hash & 0xf0000000; + if (g != 0) + hash ^= g >> 24; + hash = (hash << 4) + *string; + } + } + return hash; + } + + HashKeyString operator=(const HashKeyString& other) + { + value = other.value; + return *this; + } + + bool operator==(const HashKeyString& other) const + { + return (value == other.value); + } + + bool operator!=(const HashKeyString& other) const + { + return (value != other.value); + } + + BString value; +}; + +} // namespace BPrivate + +using BPrivate::HashKeyString; + +#endif // HASH_KEYS_H diff --git a/src/apps/webpositive/support/HashMap.h b/src/apps/webpositive/support/HashMap.h new file mode 100644 index 0000000000..36b321f2b8 --- /dev/null +++ b/src/apps/webpositive/support/HashMap.h @@ -0,0 +1,481 @@ +// HashMap.h +// +// Copyright (c) 2004-2007, Ingo Weinhold (bonefish@cs.tu-berlin.de) +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +// Except as contained in this notice, the name of a copyright holder shall +// not be used in advertising or otherwise to promote the sale, use or other +// dealings in this Software without prior written authorization of the +// copyright holder. + +#ifndef HASH_MAP_H +#define HASH_MAP_H + + +#include <Locker.h> + +#include "AutoLocker.h" +#include "OpenHashTable.h" + + +namespace BPrivate { + +// HashMapElement +template<typename Key, typename Value> +class HashMapElement : public OpenHashElement { +private: + typedef HashMapElement<Key, Value> Element; +public: + + HashMapElement() : OpenHashElement(), fKey(), fValue() + { + fNext = -1; + } + + inline uint32 Hash() const + { + return fKey.GetHashCode(); + } + + inline bool operator==(const OpenHashElement &_element) const + { + const Element &element = static_cast<const Element&>(_element); + return (fKey == element.fKey); + } + + inline void Adopt(Element &element) + { + fKey = element.fKey; + fValue = element.fValue; + } + + Key fKey; + Value fValue; +}; + +// HashMap +template<typename Key, typename Value> +class HashMap { +public: + class Entry { + public: + Entry() {} + Entry(const Key& key, Value value) : key(key), value(value) {} + + Key key; + Value value; + }; + + class Iterator { + private: + typedef HashMapElement<Key, Value> Element; + public: + Iterator(const Iterator& other) + : + fMap(other.fMap), + fIndex(other.fIndex), + fElement(other.fElement), + fLastElement(other.fElement) + { + } + + bool HasNext() const + { + return fElement; + } + + Entry Next() + { + if (!fElement) + return Entry(); + Entry result(fElement->fKey, fElement->fValue); + _FindNext(); + return result; + } + + Value* NextValue() + { + if (fElement == NULL) + return NULL; + + Value* value = &fElement->fValue; + _FindNext(); + return value; + } + + Entry Remove() + { + if (!fLastElement) + return Entry(); + Entry result(fLastElement->fKey, fLastElement->fValue); + fMap->fTable.Remove(fLastElement, true); + fLastElement = NULL; + return result; + } + + Iterator& operator=(const Iterator& other) + { + fMap = other.fMap; + fIndex = other.fIndex; + fElement = other.fElement; + fLastElement = other.fLastElement; + return *this; + } + + private: + Iterator(const HashMap<Key, Value>* map) + : + fMap(const_cast<HashMap<Key, Value>*>(map)), + fIndex(0), + fElement(NULL), + fLastElement(NULL) + { + // find first + _FindNext(); + } + + void _FindNext() + { + fLastElement = fElement; + if (fElement && fElement->fNext >= 0) { + fElement = fMap->fTable.ElementAt(fElement->fNext); + return; + } + fElement = NULL; + int32 arraySize = fMap->fTable.ArraySize(); + for (; !fElement && fIndex < arraySize; fIndex++) + fElement = fMap->fTable.FindFirst(fIndex); + } + + private: + friend class HashMap<Key, Value>; + + HashMap<Key, Value>* fMap; + int32 fIndex; + Element* fElement; + Element* fLastElement; + }; + + HashMap(); + ~HashMap(); + + status_t InitCheck() const; + + status_t Put(const Key& key, Value value); + Value Remove(const Key& key); + void Clear(); + Value Get(const Key& key) const; + bool Get(const Key& key, Value*& _value) const; + + bool ContainsKey(const Key& key) const; + + int32 Size() const; + + Iterator GetIterator() const; + +protected: + typedef HashMapElement<Key, Value> Element; + friend class Iterator; + +private: + Element *_FindElement(const Key& key) const; + +protected: + OpenHashElementArray<Element> fElementArray; + OpenHashTable<Element, OpenHashElementArray<Element> > fTable; +}; + +// SynchronizedHashMap +template<typename Key, typename Value> +class SynchronizedHashMap : public BLocker { +public: + typedef struct HashMap<Key, Value>::Entry Entry; + typedef struct HashMap<Key, Value>::Iterator Iterator; + + SynchronizedHashMap() : BLocker("synchronized hash map") {} + ~SynchronizedHashMap() { Lock(); } + + status_t InitCheck() const + { + return fMap.InitCheck(); + } + + status_t Put(const Key& key, Value value) + { + MapLocker locker(this); + if (!locker.IsLocked()) + return B_ERROR; + return fMap.Put(key, value); + } + + Value Remove(const Key& key) + { + MapLocker locker(this); + if (!locker.IsLocked()) + return Value(); + return fMap.Remove(key); + } + + void Clear() + { + MapLocker locker(this); + return fMap.Clear(); + } + + Value Get(const Key& key) const + { + const BLocker* lock = this; + MapLocker locker(const_cast<BLocker*>(lock)); + if (!locker.IsLocked()) + return Value(); + return fMap.Get(key); + } + + bool ContainsKey(const Key& key) const + { + const BLocker* lock = this; + MapLocker locker(const_cast<BLocker*>(lock)); + if (!locker.IsLocked()) + return false; + return fMap.ContainsKey(key); + } + + int32 Size() const + { + const BLocker* lock = this; + MapLocker locker(const_cast<BLocker*>(lock)); + return fMap.Size(); + } + + Iterator GetIterator() + { + return fMap.GetIterator(); + } + + // for debugging only + const HashMap<Key, Value>& GetUnsynchronizedMap() const { return fMap; } + HashMap<Key, Value>& GetUnsynchronizedMap() { return fMap; } + +protected: + typedef AutoLocker<BLocker> MapLocker; + + HashMap<Key, Value> fMap; +}; + +// HashKey32 +template<typename Value> +struct HashKey32 { + HashKey32() {} + HashKey32(const Value& value) : value(value) {} + + uint32 GetHashCode() const + { + return (uint32)value; + } + + HashKey32<Value> operator=(const HashKey32<Value>& other) + { + value = other.value; + return *this; + } + + bool operator==(const HashKey32<Value>& other) const + { + return (value == other.value); + } + + bool operator!=(const HashKey32<Value>& other) const + { + return (value != other.value); + } + + Value value; +}; + + +// HashKey64 +template<typename Value> +struct HashKey64 { + HashKey64() {} + HashKey64(const Value& value) : value(value) {} + + uint32 GetHashCode() const + { + uint64 v = (uint64)value; + return (uint32)(v >> 32) ^ (uint32)v; + } + + HashKey64<Value> operator=(const HashKey64<Value>& other) + { + value = other.value; + return *this; + } + + bool operator==(const HashKey64<Value>& other) const + { + return (value == other.value); + } + + bool operator!=(const HashKey64<Value>& other) const + { + return (value != other.value); + } + + Value value; +}; + + +// HashMap + +// constructor +template<typename Key, typename Value> +HashMap<Key, Value>::HashMap() + : + fElementArray(1000), + fTable(1000, &fElementArray) +{ +} + +// destructor +template<typename Key, typename Value> +HashMap<Key, Value>::~HashMap() +{ +} + +// InitCheck +template<typename Key, typename Value> +status_t +HashMap<Key, Value>::InitCheck() const +{ + return (fTable.InitCheck() && fElementArray.InitCheck() + ? B_OK : B_NO_MEMORY); +} + +// Put +template<typename Key, typename Value> +status_t +HashMap<Key, Value>::Put(const Key& key, Value value) +{ + Element* element = _FindElement(key); + if (element) { + // already contains the key: just set the new value + element->fValue = value; + return B_OK; + } + // does not contain the key yet: add an element + element = fTable.Add(key.GetHashCode()); + if (!element) + return B_NO_MEMORY; + element->fKey = key; + element->fValue = value; + return B_OK; +} + +// Remove +template<typename Key, typename Value> +Value +HashMap<Key, Value>::Remove(const Key& key) +{ + Value value = Value(); + if (Element* element = _FindElement(key)) { + value = element->fValue; + fTable.Remove(element); + } + return value; +} + +// Clear +template<typename Key, typename Value> +void +HashMap<Key, Value>::Clear() +{ + fTable.RemoveAll(); +} + +// Get +template<typename Key, typename Value> +Value +HashMap<Key, Value>::Get(const Key& key) const +{ + if (Element* element = _FindElement(key)) + return element->fValue; + return Value(); +} + +// Get +template<typename Key, typename Value> +bool +HashMap<Key, Value>::Get(const Key& key, Value*& _value) const +{ + if (Element* element = _FindElement(key)) { + _value = &element->fValue; + return true; + } + + return false; +} + +// ContainsKey +template<typename Key, typename Value> +bool +HashMap<Key, Value>::ContainsKey(const Key& key) const +{ + return _FindElement(key); +} + +// Size +template<typename Key, typename Value> +int32 +HashMap<Key, Value>::Size() const +{ + return fTable.CountElements(); +} + +// GetIterator +template<typename Key, typename Value> +struct HashMap<Key, Value>::Iterator +HashMap<Key, Value>::GetIterator() const +{ + return Iterator(this); +} + +// _FindElement +template<typename Key, typename Value> +struct HashMap<Key, Value>::Element * +HashMap<Key, Value>::_FindElement(const Key& key) const +{ + Element* element = fTable.FindFirst(key.GetHashCode()); + while (element && element->fKey != key) { + if (element->fNext >= 0) + element = fTable.ElementAt(element->fNext); + else + element = NULL; + } + return element; +} + +} // namespace BPrivate + +using BPrivate::HashMap; +using BPrivate::HashKey32; +using BPrivate::HashKey64; +using BPrivate::SynchronizedHashMap; + +#endif // HASH_MAP_H diff --git a/src/apps/webpositive/support/HashSet.h b/src/apps/webpositive/support/HashSet.h new file mode 100644 index 0000000000..ee7be71288 --- /dev/null +++ b/src/apps/webpositive/support/HashSet.h @@ -0,0 +1,342 @@ +// HashSet.h +// +// Copyright (c) 2004, Ingo Weinhold (bonefish@cs.tu-berlin.de) +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +// Except as contained in this notice, the name of a copyright holder shall +// not be used in advertising or otherwise to promote the sale, use or other +// dealings in this Software without prior written authorization of the +// copyright holder. + +#ifndef HASH_SET_H +#define HASH_SET_H + +#include <Locker.h> + +#include "AutoLocker.h" +#include "OpenHashTable.h" + + +namespace BPrivate { + +// HashSetElement +template<typename Key> +class HashSetElement : public OpenHashElement { +private: + typedef HashSetElement<Key> Element; +public: + + HashSetElement() : OpenHashElement(), fKey() + { + fNext = -1; + } + + inline uint32 Hash() const + { + return fKey.GetHashCode(); + } + + inline bool operator==(const OpenHashElement &_element) const + { + const Element &element = static_cast<const Element&>(_element); + return (fKey == element.fKey); + } + + inline void Adopt(Element &element) + { + fKey = element.fKey; + } + + Key fKey; +}; + +// HashSet +template<typename Key> +class HashSet { +public: + class Iterator { + private: + typedef HashSetElement<Key> Element; + public: + Iterator(const Iterator& other) + : fSet(other.fSet), + fIndex(other.fIndex), + fElement(other.fElement), + fLastElement(other.fElement) + { + } + + bool HasNext() const + { + return fElement; + } + + Key Next() + { + if (!fElement) + return Key(); + Key result(fElement->fKey); + _FindNext(); + return result; + } + + bool Remove() + { + if (!fLastElement) + return false; + fSet->fTable.Remove(fLastElement); + fLastElement = NULL; + return true; + } + + Iterator& operator=(const Iterator& other) + { + fSet = other.fSet; + fIndex = other.fIndex; + fElement = other.fElement; + fLastElement = other.fLastElement; + return *this; + } + + private: + Iterator(HashSet<Key>* map) + : fSet(map), + fIndex(0), + fElement(NULL), + fLastElement(NULL) + { + // find first + _FindNext(); + } + + void _FindNext() + { + fLastElement = fElement; + if (fElement && fElement->fNext >= 0) { + fElement = fSet->fTable.ElementAt(fElement->fNext); + return; + } + fElement = NULL; + int32 arraySize = fSet->fTable.ArraySize(); + for (; !fElement && fIndex < arraySize; fIndex++) + fElement = fSet->fTable.FindFirst(fIndex); + } + + private: + friend class HashSet<Key>; + + HashSet<Key>* fSet; + int32 fIndex; + Element* fElement; + Element* fLastElement; + }; + + HashSet(); + ~HashSet(); + + status_t InitCheck() const; + + status_t Add(const Key& key); + bool Remove(const Key& key); + void Clear(); + bool Contains(const Key& key) const; + + int32 Size() const; + bool IsEmpty() const { return Size() == 0; } + + Iterator GetIterator(); + +protected: + typedef HashSetElement<Key> Element; + friend class Iterator; + +private: + Element *_FindElement(const Key& key) const; + +protected: + OpenHashElementArray<Element> fElementArray; + OpenHashTable<Element, OpenHashElementArray<Element> > fTable; +}; + +// SynchronizedHashSet +template<typename Key> +class SynchronizedHashSet : public BLocker { +public: + typedef struct HashSet<Key>::Iterator Iterator; + + SynchronizedHashSet() : BLocker("synchronized hash set") {} + ~SynchronizedHashSet() { Lock(); } + + status_t InitCheck() const + { + return fSet.InitCheck(); + } + + status_t Add(const Key& key) + { + MapLocker locker(this); + if (!locker.IsLocked()) + return B_ERROR; + return fSet.Add(key); + } + + bool Remove(const Key& key) + { + MapLocker locker(this); + if (!locker.IsLocked()) + return false; + return fSet.Remove(key); + } + + bool Contains(const Key& key) const + { + const BLocker* lock = this; + MapLocker locker(const_cast<BLocker*>(lock)); + if (!locker.IsLocked()) + return false; + return fSet.Contains(key); + } + + int32 Size() const + { + const BLocker* lock = this; + MapLocker locker(const_cast<BLocker*>(lock)); + return fSet.Size(); + } + + Iterator GetIterator() + { + return fSet.GetIterator(); + } + + // for debugging only + const HashSet<Key>& GetUnsynchronizedSet() const { return fSet; } + HashSet<Key>& GetUnsynchronizedSet() { return fSet; } + +protected: + typedef AutoLocker<BLocker> MapLocker; + + HashSet<Key> fSet; +}; + +// HashSet + +// constructor +template<typename Key> +HashSet<Key>::HashSet() + : fElementArray(1000), + fTable(1000, &fElementArray) +{ +} + +// destructor +template<typename Key> +HashSet<Key>::~HashSet() +{ +} + +// InitCheck +template<typename Key> +status_t +HashSet<Key>::InitCheck() const +{ + return (fTable.InitCheck() && fElementArray.InitCheck() + ? B_OK : B_NO_MEMORY); +} + +// Add +template<typename Key> +status_t +HashSet<Key>::Add(const Key& key) +{ + if (Contains(key)) + return B_OK; + Element* element = fTable.Add(key.GetHashCode()); + if (!element) + return B_NO_MEMORY; + element->fKey = key; + return B_OK; +} + +// Remove +template<typename Key> +bool +HashSet<Key>::Remove(const Key& key) +{ + if (Element* element = _FindElement(key)) { + fTable.Remove(element); + return true; + } + return false; +} + +// Clear +template<typename Key> +void +HashSet<Key>::Clear() +{ + fTable.RemoveAll(); +} + +// Contains +template<typename Key> +bool +HashSet<Key>::Contains(const Key& key) const +{ + return _FindElement(key); +} + +// Size +template<typename Key> +int32 +HashSet<Key>::Size() const +{ + return fTable.CountElements(); +} + +// GetIterator +template<typename Key> +struct HashSet<Key>::Iterator +HashSet<Key>::GetIterator() +{ + return Iterator(this); +} + +// _FindElement +template<typename Key> +struct HashSet<Key>::Element * +HashSet<Key>::_FindElement(const Key& key) const +{ + Element* element = fTable.FindFirst(key.GetHashCode()); + while (element && element->fKey != key) { + if (element->fNext >= 0) + element = fTable.ElementAt(element->fNext); + else + element = NULL; + } + return element; +} + +} // namespace BPrivate + +using BPrivate::HashSet; +using BPrivate::SynchronizedHashSet; + +#endif // HASH_SET_H diff --git a/src/apps/webpositive/support/IconButton.cpp b/src/apps/webpositive/support/IconButton.cpp new file mode 100644 index 0000000000..5cd2ede604 --- /dev/null +++ b/src/apps/webpositive/support/IconButton.cpp @@ -0,0 +1,929 @@ +/* + * Copyright 2006-2010, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus <superstippi@gmx.de> + */ + +// NOTE: this file is a duplicate of the version in Icon-O-Matic/generic +// it should be placed into a common folder for generic useful stuff + +#include "IconButton.h" + +#include <new> +#include <stdio.h> + +#include <Application.h> +#include <Bitmap.h> +#include <Control.h> +#include <ControlLook.h> +#include <Entry.h> +#include <Looper.h> +#include <Message.h> +#include <Mime.h> +#include <Path.h> +#include <Region.h> +#include <Resources.h> +#include <Roster.h> +#include <TranslationUtils.h> +#include <Window.h> +#include "IconUtils.h" + +using std::nothrow; + +// constructor +IconButton::IconButton(const char* name, uint32 id, const char* label, + BMessage* message, BHandler* target) + : BView(name, B_WILL_DRAW), + BInvoker(message, target), + fButtonState(STATE_ENABLED), + fID(id), + fNormalBitmap(NULL), + fDisabledBitmap(NULL), + fClickedBitmap(NULL), + fDisabledClickedBitmap(NULL), + fLabel(label), + fTargetCache(target) +{ + SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + SetViewColor(B_TRANSPARENT_32_BIT); +} + +// destructor +IconButton::~IconButton() +{ + _DeleteBitmaps(); +} + +// MessageReceived +void +IconButton::MessageReceived(BMessage* message) +{ + switch (message->what) { + default: + BView::MessageReceived(message); + break; + } +} + +// AttachedToWindow +void +IconButton::AttachedToWindow() +{ + rgb_color background = B_TRANSPARENT_COLOR; + if (BView* parent = Parent()) { + background = parent->ViewColor(); + if (background == B_TRANSPARENT_COLOR) + background = parent->LowColor(); + } + if (background == B_TRANSPARENT_COLOR) + background = ui_color(B_PANEL_BACKGROUND_COLOR); + SetLowColor(background); + + SetTarget(fTargetCache); + if (!Target()) + SetTarget(Window()); +} + +// Draw +void +IconButton::Draw(BRect area) +{ + rgb_color background = LowColor(); + + BRect r(Bounds()); + + if (be_control_look != NULL) { + uint32 flags = 0; + BBitmap* bitmap = fNormalBitmap; + if (!IsEnabled()) { + flags |= BControlLook::B_DISABLED; + bitmap = fDisabledBitmap; + } + if (_HasFlags(STATE_PRESSED) || _HasFlags(STATE_FORCE_PRESSED)) + flags |= BControlLook::B_ACTIVATED; + + if (DrawBorder()) { + be_control_look->DrawButtonFrame(this, r, area, background, + background, flags); + be_control_look->DrawButtonBackground(this, r, area, background, + flags); + } else { + SetHighColor(background); + FillRect(r); + } + + if (bitmap && bitmap->IsValid()) { + float x = r.left + floorf((r.Width() + - bitmap->Bounds().Width()) / 2.0 + 0.5); + float y = r.top + floorf((r.Height() + - bitmap->Bounds().Height()) / 2.0 + 0.5); + BPoint point(x, y); + if (_HasFlags(STATE_PRESSED) || _HasFlags(STATE_FORCE_PRESSED)) + point += BPoint(1.0, 1.0); + if (bitmap->ColorSpace() == B_RGBA32 + || bitmap->ColorSpace() == B_RGBA32_BIG) { + SetDrawingMode(B_OP_ALPHA); + SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); + } + DrawBitmap(bitmap, point); + } + return; + } + + rgb_color lightShadow, shadow, darkShadow, light; + BBitmap* bitmap = fNormalBitmap; + // adjust colors and bitmap according to flags + if (IsEnabled()) { + lightShadow = tint_color(background, B_DARKEN_1_TINT); + shadow = tint_color(background, B_DARKEN_2_TINT); + darkShadow = tint_color(background, B_DARKEN_4_TINT); + light = tint_color(background, B_LIGHTEN_MAX_TINT); + SetHighColor(0, 0, 0, 255); + } else { + lightShadow = tint_color(background, 1.11); + shadow = tint_color(background, B_DARKEN_1_TINT); + darkShadow = tint_color(background, B_DARKEN_2_TINT); + light = tint_color(background, B_LIGHTEN_2_TINT); + bitmap = fDisabledBitmap; + SetHighColor(tint_color(background, B_DISABLED_LABEL_TINT)); + } + if (_HasFlags(STATE_PRESSED) || _HasFlags(STATE_FORCE_PRESSED)) { + if (IsEnabled()) { +// background = tint_color(background, B_DARKEN_2_TINT); +// background = tint_color(background, B_LIGHTEN_1_TINT); + background = tint_color(background, B_DARKEN_1_TINT); + bitmap = fClickedBitmap; + } else { +// background = tint_color(background, B_DARKEN_1_TINT); +// background = tint_color(background, (B_NO_TINT + B_LIGHTEN_1_TINT) / 2.0); + background = tint_color(background, (B_NO_TINT + B_DARKEN_1_TINT) / 2.0); + bitmap = fDisabledClickedBitmap; + } + // background + SetLowColor(background); + r.InsetBy(2.0, 2.0); + StrokeLine(r.LeftBottom(), r.LeftTop(), B_SOLID_LOW); + StrokeLine(r.LeftTop(), r.RightTop(), B_SOLID_LOW); + r.InsetBy(-2.0, -2.0); + } + // draw frame only if tracking + if (DrawBorder()) { + if (_HasFlags(STATE_PRESSED) || _HasFlags(STATE_FORCE_PRESSED)) + DrawPressedBorder(r, background, shadow, darkShadow, lightShadow, light); + else + DrawNormalBorder(r, background, shadow, darkShadow, lightShadow, light); + r.InsetBy(2.0, 2.0); + } else + _DrawFrame(r, background, background, background, background); + float width = Bounds().Width(); + float height = Bounds().Height(); + // bitmap + BRegion originalClippingRegion; + if (bitmap && bitmap->IsValid()) { + float x = floorf((width - bitmap->Bounds().Width()) / 2.0 + 0.5); + float y = floorf((height - bitmap->Bounds().Height()) / 2.0 + 0.5); + BPoint point(x, y); + if (_HasFlags(STATE_PRESSED) || _HasFlags(STATE_FORCE_PRESSED)) + point += BPoint(1.0, 1.0); + if (bitmap->ColorSpace() == B_RGBA32 || bitmap->ColorSpace() == B_RGBA32_BIG) { + FillRect(r, B_SOLID_LOW); + SetDrawingMode(B_OP_ALPHA); + SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); + } + DrawBitmap(bitmap, point); + // constrain clipping region + BRegion region= originalClippingRegion; + GetClippingRegion(®ion); + region.Exclude(bitmap->Bounds().OffsetByCopy(point)); + ConstrainClippingRegion(®ion); + } + // background + SetDrawingMode(B_OP_COPY); + FillRect(r, B_SOLID_LOW); + ConstrainClippingRegion(NULL); + // label + if (fLabel.CountChars() > 0) { + SetDrawingMode(B_OP_COPY); + font_height fh; + GetFontHeight(&fh); + float y = Bounds().bottom - 4.0; + y -= fh.descent; + float x = (width - StringWidth(fLabel.String())) / 2.0; + DrawString(fLabel.String(), BPoint(x, y)); + } +} + +// MouseDown +void +IconButton::MouseDown(BPoint where) +{ + if (!IsValid()) + return; + + if (_HasFlags(STATE_ENABLED)/* && !_HasFlags(STATE_FORCE_PRESSED)*/) { + if (Bounds().Contains(where)) { + SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); + _AddFlags(STATE_PRESSED | STATE_TRACKING); + } else { + _ClearFlags(STATE_PRESSED | STATE_TRACKING); + } + } +} + +// MouseUp +void +IconButton::MouseUp(BPoint where) +{ + if (!IsValid()) + return; + +// if (!_HasFlags(STATE_FORCE_PRESSED)) { + if (_HasFlags(STATE_ENABLED) && _HasFlags(STATE_PRESSED) && Bounds().Contains(where)) + Invoke(); + else if (Bounds().Contains(where)) + _AddFlags(STATE_INSIDE); + _ClearFlags(STATE_PRESSED | STATE_TRACKING); +// } +} + +// MouseMoved +void +IconButton::MouseMoved(BPoint where, uint32 transit, const BMessage* message) +{ + if (!IsValid()) + return; + + uint32 buttons = 0; + Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons); + // catch a mouse up event that we might have missed + if (!buttons && _HasFlags(STATE_PRESSED)) { + MouseUp(where); + return; + } + if (buttons && !_HasFlags(STATE_TRACKING)) + return; + if ((transit == B_INSIDE_VIEW || transit == B_ENTERED_VIEW) + && _HasFlags(STATE_ENABLED)) + _AddFlags(STATE_INSIDE); + else + _ClearFlags(STATE_INSIDE); + if (_HasFlags(STATE_TRACKING)) { + if (Bounds().Contains(where)) + _AddFlags(STATE_PRESSED); + else + _ClearFlags(STATE_PRESSED); + } +} + +#define MIN_SPACE 15.0 + +// GetPreferredSize +void +IconButton::GetPreferredSize(float* width, float* height) +{ + float minWidth = 0.0; + float minHeight = 0.0; + if (IsValid()) { + minWidth += fNormalBitmap->Bounds().IntegerWidth() + 1.0; + minHeight += fNormalBitmap->Bounds().IntegerHeight() + 1.0; + } + if (minWidth < MIN_SPACE) + minWidth = MIN_SPACE; + if (minHeight < MIN_SPACE) + minHeight = MIN_SPACE; + + float hPadding = max_c(6.0, ceilf(minHeight / 4.0)); + float vPadding = max_c(6.0, ceilf(minWidth / 4.0)); + + if (fLabel.CountChars() > 0) { + font_height fh; + GetFontHeight(&fh); + minHeight += ceilf(fh.ascent + fh.descent) + vPadding; + minWidth += StringWidth(fLabel.String()) + vPadding; + } + + if (width) + *width = minWidth + hPadding; + if (height) + *height = minHeight + vPadding; +} + +// MinSize +BSize +IconButton::MinSize() +{ + BSize size; + GetPreferredSize(&size.width, &size.height); + return size; +} + +// MaxSize +BSize +IconButton::MaxSize() +{ + return MinSize(); +} + +// Invoke +status_t +IconButton::Invoke(BMessage* message) +{ + if (!message) + message = Message(); + if (message) { + BMessage clone(*message); + clone.AddInt64("be:when", system_time()); + clone.AddPointer("be:source", (BView*)this); + clone.AddInt32("be:value", Value()); + clone.AddInt32("id", ID()); + return BInvoker::Invoke(&clone); + } + return BInvoker::Invoke(message); +} + +// SetPressed +void +IconButton::SetPressed(bool pressed) +{ + if (pressed) + _AddFlags(STATE_FORCE_PRESSED); + else + _ClearFlags(STATE_FORCE_PRESSED); +} + +// IsPressed +bool +IconButton::IsPressed() const +{ + return _HasFlags(STATE_FORCE_PRESSED); +} + +status_t +IconButton::SetIcon(int32 resourceID) +{ + app_info info; + status_t status = be_app->GetAppInfo(&info); + if (status != B_OK) + return status; + + BResources resources(&info.ref); + status = resources.InitCheck(); + if (status != B_OK) + return status; + + size_t size; + const void* data = resources.LoadResource(B_VECTOR_ICON_TYPE, resourceID, + &size); + if (data != NULL) { + BBitmap bitmap(BRect(0, 0, 31, 31), B_BITMAP_NO_SERVER_LINK, B_RGBA32); + status = bitmap.InitCheck(); + if (status != B_OK) + return status; + status = BIconUtils::GetVectorIcon(reinterpret_cast<const uint8*>(data), + size, &bitmap); + if (status != B_OK) + return status; + return SetIcon(&bitmap); + } +// const void* data = resources.LoadResource(B_BITMAP_TYPE, resourceID, &size); + return B_ERROR; +} + +// SetIcon +status_t +IconButton::SetIcon(const char* pathToBitmap) +{ + if (pathToBitmap == NULL) + return B_BAD_VALUE; + + status_t status = B_BAD_VALUE; + BBitmap* fileBitmap = NULL; + // try to load bitmap from either relative or absolute path + BEntry entry(pathToBitmap, true); + if (!entry.Exists()) { + app_info info; + status = be_app->GetAppInfo(&info); + if (status == B_OK) { + BEntry app_entry(&info.ref, true); + BPath path; + app_entry.GetPath(&path); + status = path.InitCheck(); + if (status == B_OK) { + status = path.GetParent(&path); + if (status == B_OK) { + status = path.Append(pathToBitmap, true); + if (status == B_OK) + fileBitmap = BTranslationUtils::GetBitmap(path.Path()); + else + printf("IconButton::SetIcon() - path.Append() failed: %s\n", strerror(status)); + } else + printf("IconButton::SetIcon() - path.GetParent() failed: %s\n", strerror(status)); + } else + printf("IconButton::SetIcon() - path.InitCheck() failed: %s\n", strerror(status)); + } else + printf("IconButton::SetIcon() - be_app->GetAppInfo() failed: %s\n", strerror(status)); + } else + fileBitmap = BTranslationUtils::GetBitmap(pathToBitmap); + if (fileBitmap) { + status = _MakeBitmaps(fileBitmap); + delete fileBitmap; + } else + status = B_ERROR; + return status; +} + +// SetIcon +status_t +IconButton::SetIcon(const BBitmap* bitmap) +{ + if (bitmap && bitmap->ColorSpace() == B_CMAP8) { + status_t status = bitmap->InitCheck(); + if (status >= B_OK) { + if (BBitmap* rgb32Bitmap = _ConvertToRGB32(bitmap)) { + status = _MakeBitmaps(rgb32Bitmap); + delete rgb32Bitmap; + } else + status = B_NO_MEMORY; + } + return status; + } else + return _MakeBitmaps(bitmap); +} + +// SetIcon +status_t +IconButton::SetIcon(const BMimeType* fileType, bool small) +{ + status_t status = fileType ? fileType->InitCheck() : B_BAD_VALUE; + if (status >= B_OK) { + BBitmap* mimeBitmap = new(nothrow) BBitmap(BRect(0.0, 0.0, 15.0, 15.0), B_CMAP8); + if (mimeBitmap && mimeBitmap->IsValid()) { + status = fileType->GetIcon(mimeBitmap, small ? B_MINI_ICON : B_LARGE_ICON); + if (status >= B_OK) { + if (BBitmap* bitmap = _ConvertToRGB32(mimeBitmap)) { + status = _MakeBitmaps(bitmap); + delete bitmap; + } else + printf("IconButton::SetIcon() - B_RGB32 bitmap is not valid\n"); + } else + printf("IconButton::SetIcon() - fileType->GetIcon() failed: %s\n", strerror(status)); + } else + printf("IconButton::SetIcon() - B_CMAP8 bitmap is not valid\n"); + delete mimeBitmap; + } else + printf("IconButton::SetIcon() - fileType is not valid: %s\n", strerror(status)); + return status; +} + +// SetIcon +status_t +IconButton::SetIcon(const unsigned char* bitsFromQuickRes, + uint32 width, uint32 height, color_space format, bool convertToBW) +{ + status_t status = B_BAD_VALUE; + if (bitsFromQuickRes && width > 0 && height > 0) { + BBitmap* quickResBitmap = new(nothrow) BBitmap(BRect(0.0, 0.0, width - 1.0, height - 1.0), format); + status = quickResBitmap ? quickResBitmap->InitCheck() : B_ERROR; + if (status >= B_OK) { + // It doesn't look right to copy BitsLength() bytes, but bitmaps + // exported from QuickRes still contain their padding, so it is alright. + memcpy(quickResBitmap->Bits(), bitsFromQuickRes, quickResBitmap->BitsLength()); + if (format != B_RGB32 && format != B_RGBA32 && format != B_RGB32_BIG && format != B_RGBA32_BIG) { + // colorspace needs conversion + BBitmap* bitmap = new(nothrow) BBitmap(quickResBitmap->Bounds(), B_RGB32, true); + if (bitmap && bitmap->IsValid()) { + BView* helper = new BView(bitmap->Bounds(), "helper", + B_FOLLOW_NONE, B_WILL_DRAW); + if (bitmap->Lock()) { + bitmap->AddChild(helper); + helper->SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + helper->FillRect(helper->Bounds()); + helper->SetDrawingMode(B_OP_OVER); + helper->DrawBitmap(quickResBitmap, BPoint(0.0, 0.0)); + helper->Sync(); + bitmap->Unlock(); + } + status = _MakeBitmaps(bitmap); + } else + printf("IconButton::SetIcon() - B_RGB32 bitmap is not valid\n"); + delete bitmap; + } else { + // native colorspace (32 bits) + if (convertToBW) { + // convert to gray scale icon + uint8* bits = (uint8*)quickResBitmap->Bits(); + uint32 bpr = quickResBitmap->BytesPerRow(); + for (uint32 y = 0; y < height; y++) { + uint8* handle = bits; + uint8 gray; + for (uint32 x = 0; x < width; x++) { + gray = uint8((116 * handle[0] + 600 * handle[1] + 308 * handle[2]) / 1024); + handle[0] = gray; + handle[1] = gray; + handle[2] = gray; + handle += 4; + } + bits += bpr; + } + } + status = _MakeBitmaps(quickResBitmap); + } + } else + printf("IconButton::SetIcon() - error allocating bitmap: %s\n", strerror(status)); + delete quickResBitmap; + } + return status; +} + +// ClearIcon +void +IconButton::ClearIcon() +{ + _DeleteBitmaps(); + _Update(); +} + +void +IconButton::TrimIcon(bool keepAspect) +{ + if (fNormalBitmap == NULL) + return; + + uint8* bits = (uint8*)fNormalBitmap->Bits(); + uint32 bpr = fNormalBitmap->BytesPerRow(); + uint32 width = fNormalBitmap->Bounds().IntegerWidth() + 1; + uint32 height = fNormalBitmap->Bounds().IntegerHeight() + 1; + BRect trimmed(LONG_MAX, LONG_MAX, LONG_MIN, LONG_MIN); + for (uint32 y = 0; y < height; y++) { + uint8* b = bits + 3; + bool rowHasAlpha = false; + for (uint32 x = 0; x < width; x++) { + if (*b) { + rowHasAlpha = true; + if (x < trimmed.left) + trimmed.left = x; + if (x > trimmed.right) + trimmed.right = x; + } + b += 4; + } + if (rowHasAlpha) { + if (y < trimmed.top) + trimmed.top = y; + if (y > trimmed.bottom) + trimmed.bottom = y; + } + bits += bpr; + } + if (!trimmed.IsValid()) + return; + if (keepAspect) { + float minInset = trimmed.left; + minInset = min_c(minInset, trimmed.top); + minInset = min_c(minInset, fNormalBitmap->Bounds().right - trimmed.right); + minInset = min_c(minInset, fNormalBitmap->Bounds().bottom - trimmed.bottom); + trimmed = fNormalBitmap->Bounds().InsetByCopy(minInset, minInset); + } + trimmed = trimmed & fNormalBitmap->Bounds(); + BBitmap trimmedBitmap(trimmed.OffsetToCopy(B_ORIGIN), + B_BITMAP_NO_SERVER_LINK, B_RGBA32); + bits = (uint8*)fNormalBitmap->Bits(); + bits += 4 * (int32)trimmed.left + bpr * (int32)trimmed.top; + uint8* dst = (uint8*)trimmedBitmap.Bits(); + uint32 trimmedWidth = trimmedBitmap.Bounds().IntegerWidth() + 1; + uint32 trimmedHeight = trimmedBitmap.Bounds().IntegerHeight() + 1; + uint32 trimmedBPR = trimmedBitmap.BytesPerRow(); + for (uint32 y = 0; y < trimmedHeight; y++) { + memcpy(dst, bits, trimmedWidth * 4); + dst += trimmedBPR; + bits += bpr; + } + SetIcon(&trimmedBitmap); +} + +// Bitmap +BBitmap* +IconButton::Bitmap() const +{ + BBitmap* bitmap = NULL; + if (fNormalBitmap && fNormalBitmap->IsValid()) { + bitmap = new(nothrow) BBitmap(fNormalBitmap); + if (bitmap->IsValid()) { + // TODO: remove this functionality when we use real transparent bitmaps + uint8* bits = (uint8*)bitmap->Bits(); + uint32 bpr = bitmap->BytesPerRow(); + uint32 width = bitmap->Bounds().IntegerWidth() + 1; + uint32 height = bitmap->Bounds().IntegerHeight() + 1; + color_space format = bitmap->ColorSpace(); + if (format == B_CMAP8) { + // replace gray with magic transparent index + } else if (format == B_RGB32) { + for (uint32 y = 0; y < height; y++) { + uint8* bitsHandle = bits; + for (uint32 x = 0; x < width; x++) { + if (bitsHandle[0] == 216 + && bitsHandle[1] == 216 + && bitsHandle[2] == 216) { + bitsHandle[3] = 0; // make this pixel completely transparent + } + bitsHandle += 4; + } + bits += bpr; + } + } + } else { + delete bitmap; + bitmap = NULL; + } + } + return bitmap; +} + +// DrawBorder +bool +IconButton::DrawBorder() const +{ + return ((IsEnabled() && (_HasFlags(STATE_INSIDE) + || _HasFlags(STATE_TRACKING))) || _HasFlags(STATE_FORCE_PRESSED)); +} + +// DrawNormalBorder +void +IconButton::DrawNormalBorder(BRect r, rgb_color background, + rgb_color shadow, rgb_color darkShadow, + rgb_color lightShadow, rgb_color light) +{ + _DrawFrame(r, shadow, darkShadow, light, lightShadow); +} + +// DrawPressedBorder +void +IconButton::DrawPressedBorder(BRect r, rgb_color background, + rgb_color shadow, rgb_color darkShadow, + rgb_color lightShadow, rgb_color light) +{ + _DrawFrame(r, shadow, light, darkShadow, background); +} + +// IsValid +bool +IconButton::IsValid() const +{ + return (fNormalBitmap && fDisabledBitmap && fClickedBitmap + && fDisabledClickedBitmap + && fNormalBitmap->IsValid() + && fDisabledBitmap->IsValid() + && fClickedBitmap->IsValid() + && fDisabledClickedBitmap->IsValid()); +} + +// Value +int32 +IconButton::Value() const +{ + return _HasFlags(STATE_PRESSED) ? B_CONTROL_ON : B_CONTROL_OFF; +} + +// SetValue +void +IconButton::SetValue(int32 value) +{ + if (value) + _AddFlags(STATE_PRESSED); + else + _ClearFlags(STATE_PRESSED); +} + +// IsEnabled +bool +IconButton::IsEnabled() const +{ + return _HasFlags(STATE_ENABLED) ? B_CONTROL_ON : B_CONTROL_OFF; +} + +// SetEnabled +void +IconButton::SetEnabled(bool enabled) +{ + if (enabled) + _AddFlags(STATE_ENABLED); + else + _ClearFlags(STATE_ENABLED | STATE_TRACKING | STATE_INSIDE); +} + +// _ConvertToRGB32 +BBitmap* +IconButton::_ConvertToRGB32(const BBitmap* bitmap) const +{ + BBitmap* convertedBitmap = new(nothrow) BBitmap(bitmap->Bounds(), + B_BITMAP_ACCEPTS_VIEWS, B_RGBA32); + if (convertedBitmap && convertedBitmap->IsValid()) { + memset(convertedBitmap->Bits(), 0, convertedBitmap->BitsLength()); + BView* helper = new BView(bitmap->Bounds(), "helper", + B_FOLLOW_NONE, B_WILL_DRAW); + if (convertedBitmap->Lock()) { + convertedBitmap->AddChild(helper); + helper->SetDrawingMode(B_OP_OVER); + helper->DrawBitmap(bitmap, BPoint(0.0, 0.0)); + helper->Sync(); + convertedBitmap->Unlock(); + } + } else { + delete convertedBitmap; + convertedBitmap = NULL; + } + return convertedBitmap; +} + +// _MakeBitmaps +status_t +IconButton::_MakeBitmaps(const BBitmap* bitmap) +{ + status_t status = bitmap ? bitmap->InitCheck() : B_BAD_VALUE; + if (status >= B_OK) { + // make our own versions of the bitmap + BRect b(bitmap->Bounds()); + _DeleteBitmaps(); + color_space format = bitmap->ColorSpace(); + fNormalBitmap = new(nothrow) BBitmap(b, format); + fDisabledBitmap = new(nothrow) BBitmap(b, format); + fClickedBitmap = new(nothrow) BBitmap(b, format); + fDisabledClickedBitmap = new(nothrow) BBitmap(b, format); + if (IsValid()) { + // copy bitmaps from file bitmap + uint8* nBits = (uint8*)fNormalBitmap->Bits(); + uint8* dBits = (uint8*)fDisabledBitmap->Bits(); + uint8* cBits = (uint8*)fClickedBitmap->Bits(); + uint8* dcBits = (uint8*)fDisabledClickedBitmap->Bits(); + uint8* fBits = (uint8*)bitmap->Bits(); + int32 nbpr = fNormalBitmap->BytesPerRow(); + int32 fbpr = bitmap->BytesPerRow(); + int32 pixels = b.IntegerWidth() + 1; + int32 lines = b.IntegerHeight() + 1; + // nontransparent version: + if (format == B_RGB32 || format == B_RGB32_BIG) { + // iterate over color components + for (int32 y = 0; y < lines; y++) { + for (int32 x = 0; x < pixels; x++) { + int32 nOffset = 4 * x; + int32 fOffset = 4 * x; + nBits[nOffset + 0] = fBits[fOffset + 0]; + nBits[nOffset + 1] = fBits[fOffset + 1]; + nBits[nOffset + 2] = fBits[fOffset + 2]; + nBits[nOffset + 3] = 255; + // clicked bits are darker (lame method...) + cBits[nOffset + 0] = (uint8)((float)nBits[nOffset + 0] * 0.8); + cBits[nOffset + 1] = (uint8)((float)nBits[nOffset + 1] * 0.8); + cBits[nOffset + 2] = (uint8)((float)nBits[nOffset + 2] * 0.8); + cBits[nOffset + 3] = 255; + // disabled bits have less contrast (lame method...) + uint8 grey = 216; + float dist = (nBits[nOffset + 0] - grey) * 0.4; + dBits[nOffset + 0] = (uint8)(grey + dist); + dist = (nBits[nOffset + 1] - grey) * 0.4; + dBits[nOffset + 1] = (uint8)(grey + dist); + dist = (nBits[nOffset + 2] - grey) * 0.4; + dBits[nOffset + 2] = (uint8)(grey + dist); + dBits[nOffset + 3] = 255; + // disabled bits have less contrast (lame method...) + grey = 188; + dist = (nBits[nOffset + 0] - grey) * 0.4; + dcBits[nOffset + 0] = (uint8)(grey + dist); + dist = (nBits[nOffset + 1] - grey) * 0.4; + dcBits[nOffset + 1] = (uint8)(grey + dist); + dist = (nBits[nOffset + 2] - grey) * 0.4; + dcBits[nOffset + 2] = (uint8)(grey + dist); + dcBits[nOffset + 3] = 255; + } + nBits += nbpr; + dBits += nbpr; + cBits += nbpr; + dcBits += nbpr; + fBits += fbpr; + } + // transparent version: + } else if (format == B_RGBA32 || format == B_RGBA32_BIG) { + // iterate over color components + for (int32 y = 0; y < lines; y++) { + for (int32 x = 0; x < pixels; x++) { + int32 nOffset = 4 * x; + int32 fOffset = 4 * x; + nBits[nOffset + 0] = fBits[fOffset + 0]; + nBits[nOffset + 1] = fBits[fOffset + 1]; + nBits[nOffset + 2] = fBits[fOffset + 2]; + nBits[nOffset + 3] = fBits[fOffset + 3]; + // clicked bits are darker (lame method...) + cBits[nOffset + 0] = (uint8)(nBits[nOffset + 0] * 0.8); + cBits[nOffset + 1] = (uint8)(nBits[nOffset + 1] * 0.8); + cBits[nOffset + 2] = (uint8)(nBits[nOffset + 2] * 0.8); + cBits[nOffset + 3] = fBits[fOffset + 3]; + // disabled bits have less opacity + + uint8 grey = ((uint16)nBits[nOffset + 0] * 10 + + nBits[nOffset + 1] * 60 + + nBits[nOffset + 2] * 30) / 100; + float dist = (nBits[nOffset + 0] - grey) * 0.3; + dBits[nOffset + 0] = (uint8)(grey + dist); + dist = (nBits[nOffset + 1] - grey) * 0.3; + dBits[nOffset + 1] = (uint8)(grey + dist); + dist = (nBits[nOffset + 2] - grey) * 0.3; + dBits[nOffset + 2] = (uint8)(grey + dist); + dBits[nOffset + 3] = (uint8)(fBits[fOffset + 3] * 0.3); + // disabled bits have less contrast (lame method...) + dcBits[nOffset + 0] = (uint8)(dBits[nOffset + 0] * 0.8); + dcBits[nOffset + 1] = (uint8)(dBits[nOffset + 1] * 0.8); + dcBits[nOffset + 2] = (uint8)(dBits[nOffset + 2] * 0.8); + dcBits[nOffset + 3] = (uint8)(fBits[fOffset + 3] * 0.3); + } + nBits += nbpr; + dBits += nbpr; + cBits += nbpr; + dcBits += nbpr; + fBits += fbpr; + } + // unsupported format + } else { + printf("IconButton::_MakeBitmaps() - bitmap has unsupported colorspace\n"); + status = B_MISMATCHED_VALUES; + _DeleteBitmaps(); + } + } else { + printf("IconButton::_MakeBitmaps() - error allocating local bitmaps\n"); + status = B_NO_MEMORY; + _DeleteBitmaps(); + } + } else + printf("IconButton::_MakeBitmaps() - bitmap is not valid\n"); + return status; +} + +// _DeleteBitmaps +void +IconButton::_DeleteBitmaps() +{ + delete fNormalBitmap; + fNormalBitmap = NULL; + delete fDisabledBitmap; + fDisabledBitmap = NULL; + delete fClickedBitmap; + fClickedBitmap = NULL; + delete fDisabledClickedBitmap; + fDisabledClickedBitmap = NULL; +} + +// _Update +void +IconButton::_Update() +{ + if (LockLooper()) { + Invalidate(); + UnlockLooper(); + } +} + +// _AddFlags +void +IconButton::_AddFlags(uint32 flags) +{ + if (!(fButtonState & flags)) { + fButtonState |= flags; + _Update(); + } +} + +// _ClearFlags +void +IconButton::_ClearFlags(uint32 flags) +{ + if (fButtonState & flags) { + fButtonState &= ~flags; + _Update(); + } +} + +// _HasFlags +bool +IconButton::_HasFlags(uint32 flags) const +{ + return (fButtonState & flags); +} + +// _DrawFrame +void +IconButton::_DrawFrame(BRect r, rgb_color col1, rgb_color col2, + rgb_color col3, rgb_color col4) +{ + BeginLineArray(8); + AddLine(BPoint(r.left, r.bottom), BPoint(r.left, r.top), col1); + AddLine(BPoint(r.left + 1.0, r.top), BPoint(r.right, r.top), col1); + AddLine(BPoint(r.right, r.top + 1.0), BPoint(r.right, r.bottom), col2); + AddLine(BPoint(r.right - 1.0, r.bottom), BPoint(r.left + 1.0, r.bottom), col2); + r.InsetBy(1.0, 1.0); + AddLine(BPoint(r.left, r.bottom), BPoint(r.left, r.top), col3); + AddLine(BPoint(r.left + 1.0, r.top), BPoint(r.right, r.top), col3); + AddLine(BPoint(r.right, r.top + 1.0), BPoint(r.right, r.bottom), col4); + AddLine(BPoint(r.right - 1.0, r.bottom), BPoint(r.left + 1.0, r.bottom), col4); + EndLineArray(); +} diff --git a/src/apps/webpositive/support/IconButton.h b/src/apps/webpositive/support/IconButton.h new file mode 100644 index 0000000000..2876dd8cfd --- /dev/null +++ b/src/apps/webpositive/support/IconButton.h @@ -0,0 +1,134 @@ +/* + * Copyright 2006-2010, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus <superstippi@gmx.de> + */ + +/** gui class that loads an image from disk and shows it + as clickable button */ + +// TODO: inherit from BControl? + +// NOTE: this file is a duplicate of the version in Icon-O-Matic/generic +// it should be placed into a common folder for generic useful stuff + +#ifndef ICON_BUTTON_H +#define ICON_BUTTON_H + +#include <Invoker.h> +#include <String.h> +#include <View.h> + +class BBitmap; +class BMimeType; + +class IconButton : public BView, public BInvoker { +public: + IconButton(const char* name, + uint32 id, + const char* label = NULL, + BMessage* message = NULL, + BHandler* target = NULL); + virtual ~IconButton(); + + // BView interface + virtual void MessageReceived(BMessage* message); + virtual void AttachedToWindow(); + virtual void Draw(BRect updateRect); + virtual void MouseDown(BPoint where); + virtual void MouseUp(BPoint where); + virtual void MouseMoved(BPoint where, uint32 transit, + const BMessage* message); + virtual void GetPreferredSize(float* width, + float* height); + virtual BSize MinSize(); + virtual BSize MaxSize(); + + + // BInvoker interface + virtual status_t Invoke(BMessage* message = NULL); + + // IconButton + bool IsValid() const; + + virtual int32 Value() const; + virtual void SetValue(int32 value); + + bool IsEnabled() const; + void SetEnabled(bool enable); + + void SetPressed(bool pressed); + bool IsPressed() const; + uint32 ID() const + { return fID; } + + status_t SetIcon(int32 resourceID); + status_t SetIcon(const char* pathToBitmap); + status_t SetIcon(const BBitmap* bitmap); + status_t SetIcon(const BMimeType* fileType, + bool small = true); + status_t SetIcon(const unsigned char* bitsFromQuickRes, + uint32 width, uint32 height, + color_space format, + bool convertToBW = false); + void ClearIcon(); + void TrimIcon(bool keepAspect = true); + + BBitmap* Bitmap() const; + // caller has to delete the returned bitmap + + virtual bool DrawBorder() const; + virtual void DrawNormalBorder(BRect r, + rgb_color background, + rgb_color shadow, + rgb_color darkShadow, + rgb_color lightShadow, + rgb_color light); + virtual void DrawPressedBorder(BRect r, + rgb_color background, + rgb_color shadow, + rgb_color darkShadow, + rgb_color lightShadow, + rgb_color light); + +protected: + enum { + STATE_NONE = 0x0000, + STATE_TRACKING = 0x0001, + STATE_PRESSED = 0x0002, + STATE_ENABLED = 0x0004, + STATE_INSIDE = 0x0008, + STATE_FORCE_PRESSED = 0x0010, + }; + + void _AddFlags(uint32 flags); + void _ClearFlags(uint32 flags); + bool _HasFlags(uint32 flags) const; + + void _DrawFrame(BRect frame, + rgb_color col1, + rgb_color col2, + rgb_color col3, + rgb_color col4); + +// private: + BBitmap* _ConvertToRGB32(const BBitmap* bitmap) const; + status_t _MakeBitmaps(const BBitmap* bitmap); + void _DeleteBitmaps(); + void _SendMessage() const; + void _Update(); + + uint32 fButtonState; + int32 fID; + BBitmap* fNormalBitmap; + BBitmap* fDisabledBitmap; + BBitmap* fClickedBitmap; + BBitmap* fDisabledClickedBitmap; + BString fLabel; + + BHandler* fTargetCache; +}; + +#endif // ICON_BUTTON_H diff --git a/src/apps/webpositive/support/IconUtils.h b/src/apps/webpositive/support/IconUtils.h new file mode 100644 index 0000000000..fdcfbd946f --- /dev/null +++ b/src/apps/webpositive/support/IconUtils.h @@ -0,0 +1,80 @@ +/* + * Copyright 2006-2008, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _ICON_UTILS_H +#define _ICON_UTILS_H + + +#include <Mime.h> + +class BBitmap; +class BNode; + + +// This class is a little different from many other classes. +// You don't create an instance of it; you just call its various +// static member functions for utility-like operations. +class BIconUtils { + BIconUtils(); + ~BIconUtils(); + BIconUtils(const BIconUtils&); + BIconUtils& operator=(const BIconUtils&); + +public: + + // Utility function to import an icon from the node that + // has either of the provided attribute names. Which icon type + // is preferred (vector, small or large B_CMAP8 icon) depends + // on the colorspace of the provided bitmap. If the colorspace + // is B_CMAP8, B_CMAP8 icons are preferred. In that case, the + // bitmap size must also match the provided icon_size "size"! + static status_t GetIcon(BNode* node, + const char* vectorIconAttrName, + const char* smallIconAttrName, + const char* largeIconAttrName, + icon_size size, BBitmap* result); + + // Utility functions to import a vector icon in "flat icon" + // format from a BNode attribute or from a flat buffer in + // memory into the preallocated BBitmap "result". + // The colorspace of result needs to be B_RGBA32 or at + // least B_RGB32 (though that makes less sense). The icon + // will be scaled from it's "native" size of 64x64 to the + // size of the bitmap, the scale is derived from the bitmap + // width, the bitmap should have square dimension, or the + // icon will be cut off at the bottom (or have room left). + static status_t GetVectorIcon(BNode* node, + const char* attrName, BBitmap* result); + + static status_t GetVectorIcon(const uint8* buffer, + size_t size, BBitmap* result); + + // Utility function to import an "old" BeOS icon in B_CMAP8 + // colorspace from either the small icon attribute or the + // large icon attribute as given in "smallIconAttrName" and + // "largeIconAttrName". Which icon is loaded depends on + // the given "size". + static status_t GetCMAP8Icon(BNode* node, + const char* smallIconAttrName, + const char* largeIconAttrName, + icon_size size, BBitmap* icon); + + // Utility functions to convert from old icon colorspace + // into colorspace of BBitmap "result" (should be B_RGBA32 + // to make any sense). + static status_t ConvertFromCMAP8(BBitmap* source, + BBitmap* result); + static status_t ConvertToCMAP8(BBitmap* source, + BBitmap* result); + + static status_t ConvertFromCMAP8(const uint8* data, + uint32 width, uint32 height, + uint32 bytesPerRow, BBitmap* result); + + static status_t ConvertToCMAP8(const uint8* data, + uint32 width, uint32 height, + uint32 bytesPerRow, BBitmap* result); +}; + +#endif // _ICON_UTILS_H diff --git a/src/apps/webpositive/support/NavMenu.h b/src/apps/webpositive/support/NavMenu.h new file mode 100644 index 0000000000..127d94f255 --- /dev/null +++ b/src/apps/webpositive/support/NavMenu.h @@ -0,0 +1,168 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// NavMenu is a hierarchical menu of volumes, folders, files and queries +// displays icons, uses the SlowMenu API for full interruptability + +#ifndef NAV_MENU_H +#define NAV_MENU_H + + +#include <Messenger.h> +#include <StorageDefs.h> +#include <Entry.h> + +#include "SlowMenu.h" + + +template<class T> class BObjectList; +class BMenuItem; + +namespace BPrivate { + +class Model; +class BContainerWindow; +class ModelMenuItem; +class EntryListBase; + + +class TrackingHookData { + public: + TrackingHookData() + : + fTrackingHook(NULL), + fDragMessage(NULL) + { + } + + bool (*fTrackingHook)(BMenu *, void *); + BMessenger fTarget; + const BMessage *fDragMessage; +}; + + +class BNavMenu : public BSlowMenu { + public: + BNavMenu(const char* title, uint32 message, const BHandler *, + BWindow *parentWindow = NULL, const BObjectList<BString> *list = NULL); + BNavMenu(const char* title, uint32 message, const BMessenger &, + BWindow *parentWindow = NULL, const BObjectList<BString> *list = NULL); + // parentWindow, if specified, will be closed if nav menu item invoked + // with option held down + + virtual ~BNavMenu(); + + virtual void AttachedToWindow(); + virtual void DetachedFromWindow(); + + void SetNavDir(const entry_ref *); + void ForceRebuild(); + bool NeedsToRebuild() const; + // will cause menu to get rebuilt next time it is shown + + virtual void ResetTargets(); + void SetTarget(const BMessenger &); + BMessenger Target(); + + void SetTypesList(const BObjectList<BString> *list); + const BObjectList<BString> *TypesList() const; + + void AddNavDir(const Model *model, uint32 what, BHandler *target, + bool populateSubmenu); + + void AddNavParentDir(const char *name, const Model *model, uint32 what, BHandler *target); + void AddNavParentDir(const Model *model, uint32 what, BHandler *target); + void SetShowParent(bool show); + + static int32 GetMaxMenuWidth(); + + static int CompareFolderNamesFirstOne(const BMenuItem *, const BMenuItem *); + static int CompareOne(const BMenuItem *, const BMenuItem *); + + static ModelMenuItem *NewModelItem(Model *, const BMessage *, const BMessenger &, + bool suppressFolderHierarchy=false, BContainerWindow * = NULL, + const BObjectList<BString> *typeslist = NULL, + TrackingHookData *hook = NULL); + + TrackingHookData *InitTrackingHook(bool (*hookfunction)(BMenu *, void *), + const BMessenger *target, const BMessage *dragMessage); + + protected: + virtual bool StartBuildingItemList(); + virtual bool AddNextItem(); + virtual void DoneBuildingItemList(); + virtual void ClearMenuBuildingState(); + + void BuildVolumeMenu(); + + void AddOneItem(Model *); + void AddRootItemsIfNeeded(); + void AddTrashItem(); + static void SetTrackingHookDeep(BMenu *, bool (*)(BMenu *, void *), void *); + + entry_ref fNavDir; + BMessage fMessage; + BMessenger fMessenger; + BWindow *fParentWindow; + + // menu building state + uint8 fFlags; + BObjectList<BMenuItem> *fItemList; + EntryListBase *fContainer; + bool fIteratingDesktop; + + const BObjectList<BString> *fTypesList; + + TrackingHookData fTrackingHook; +}; + +// Spring Loaded Folder convenience routines +// used in both Tracker and Deskbar +#ifndef _IMPEXP_TRACKER +# define _IMPEXP_TRACKER +#endif +_IMPEXP_TRACKER bool SpringLoadedFolderCompareMessages(const BMessage *incoming, + const BMessage *dragmessage); +_IMPEXP_TRACKER void SpringLoadedFolderSetMenuStates(const BMenu *menu, + const BObjectList<BString> *typeslist); +_IMPEXP_TRACKER void SpringLoadedFolderAddUniqueTypeToList(entry_ref *ref, + BObjectList<BString> *typeslist); +_IMPEXP_TRACKER void SpringLoadedFolderCacheDragData(const BMessage *incoming, + BMessage **, BObjectList<BString> **typeslist); + +} // namespace BPrivate + +using namespace BPrivate; + +#endif // NAV_MENU_H diff --git a/src/apps/webpositive/support/OpenHashTable.h b/src/apps/webpositive/support/OpenHashTable.h new file mode 100644 index 0000000000..437fbed170 --- /dev/null +++ b/src/apps/webpositive/support/OpenHashTable.h @@ -0,0 +1,514 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// bonefish: +// * removed need for exceptions +// * fixed warnings +// * implemented rehashing +// * added RemoveAll() +// TODO: +// * shrinking of element vectors + +// Hash table with open addresssing + +#ifndef __OPEN_HASH_TABLE__ +#define __OPEN_HASH_TABLE__ + +#include <stdlib.h> +#include <new> + +// don't include <Debug.h> +#ifndef ASSERT +# define ASSERT(E) (void)0 +#endif +#ifndef TRESPASS +# define TRESPASS() (void)0 +#endif + +namespace BPrivate { + +template <class Element> +class ElementVector { + // element vector for OpenHashTable needs to implement this + // interface +public: + Element &At(int32 index); + Element *Add(); + int32 IndexOf(const Element &) const; + void Remove(int32 index); +}; + +class OpenHashElement { +public: + uint32 Hash() const; + bool operator==(const OpenHashElement &) const; + void Adopt(OpenHashElement &); + // low overhead copy, original element is in undefined state + // after call (calls Adopt on BString members, etc.) + int32 fNext; +}; + +const uint32 kPrimes [] = { + 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521, 131071, 262139, + 524287, 1048573, 2097143, 4194301, 8388593, 16777213, 33554393, 67108859, + 134217689, 268435399, 536870909, 1073741789, 2147483647, 0 +}; + +template <class Element, class ElementVec = ElementVector<Element> > +class OpenHashTable { +public: + OpenHashTable(int32 minSize, ElementVec *elementVector = 0, + float maxLoadFactor = 0.8); + // it is up to the subclass of OpenHashTable to supply + // elementVector + ~OpenHashTable(); + + bool InitCheck() const; + + void SetElementVector(ElementVec *elementVector); + + Element *FindFirst(uint32 elementHash) const; + Element *Add(uint32 elementHash); + + void Remove(Element *element, bool dontRehash = false); + void RemoveAll(); + + // when calling Add, any outstanding element pointer may become + // invalid; to deal with this, get the element index and restore + // it after the add + int32 ElementIndex(const Element *) const; + Element *ElementAt(int32 index) const; + + int32 ArraySize() const; + int32 VectorSize() const; + int32 CountElements() const; + +protected: + static int32 OptimalSize(int32 minSize); + +private: + bool _RehashIfNeeded(); + bool _Rehash(); + + int32 fArraySize; + int32 fInitialSize; + int32 fElementCount; + int32 *fHashArray; + ElementVec *fElementVector; + float fMaxLoadFactor; +}; + +template <class Element> +class OpenHashElementArray : public ElementVector<Element> { + // this is a straightforward implementation of an element vector + // deleting is handled by linking deleted elements into a free list + // the vector never shrinks +public: + OpenHashElementArray(int32 initialSize); + ~OpenHashElementArray(); + + bool InitCheck() const; + + Element &At(int32 index); + const Element &At(int32 index) const; + Element *Add(const Element &); + Element *Add(); + void Remove(int32 index); + int32 IndexOf(const Element &) const; + int32 Size() const; + +private: + Element *fData; + int32 fSize; + int32 fNextFree; + int32 fNextDeleted; +}; + + +//----------------------------------- + +template<class Element, class ElementVec> +OpenHashTable<Element, ElementVec>::OpenHashTable(int32 minSize, + ElementVec *elementVector, float maxLoadFactor) + : fArraySize(OptimalSize(minSize)), + fInitialSize(fArraySize), + fElementCount(0), + fElementVector(elementVector), + fMaxLoadFactor(maxLoadFactor) +{ + // sanity check the maximal load factor + if (fMaxLoadFactor < 0.5) + fMaxLoadFactor = 0.5; + // allocate and init the array + fHashArray = (int32*)calloc(fArraySize, sizeof(int32)); + if (fHashArray) { + for (int32 index = 0; index < fArraySize; index++) + fHashArray[index] = -1; + } +} + +template<class Element, class ElementVec> +OpenHashTable<Element, ElementVec>::~OpenHashTable() +{ + RemoveAll(); + free(fHashArray); +} + +template<class Element, class ElementVec> +bool +OpenHashTable<Element, ElementVec>::InitCheck() const +{ + return (fHashArray && fElementVector); +} + +template<class Element, class ElementVec> +int32 +OpenHashTable<Element, ElementVec>::OptimalSize(int32 minSize) +{ + for (int32 index = 0; ; index++) + if (!kPrimes[index] || kPrimes[index] >= (uint32)minSize) + return (int32)kPrimes[index]; + + return 0; +} + +template<class Element, class ElementVec> +Element * +OpenHashTable<Element, ElementVec>::FindFirst(uint32 hash) const +{ + ASSERT(fElementVector); + hash %= fArraySize; + if (fHashArray[hash] < 0) + return 0; + + return &fElementVector->At(fHashArray[hash]); +} + +template<class Element, class ElementVec> +int32 +OpenHashTable<Element, ElementVec>::ElementIndex(const Element *element) const +{ + return fElementVector->IndexOf(*element); +} + +template<class Element, class ElementVec> +Element * +OpenHashTable<Element, ElementVec>::ElementAt(int32 index) const +{ + return &fElementVector->At(index); +} + +template<class Element, class ElementVec> +int32 +OpenHashTable<Element, ElementVec>::ArraySize() const +{ + return fArraySize; +} + +template<class Element, class ElementVec> +int32 +OpenHashTable<Element, ElementVec>::VectorSize() const +{ + return fElementVector->Size(); +} + +template<class Element, class ElementVec> +int32 +OpenHashTable<Element, ElementVec>::CountElements() const +{ + return fElementCount; +} + + +template<class Element, class ElementVec> +Element * +OpenHashTable<Element, ElementVec>::Add(uint32 hash) +{ + ASSERT(fElementVector); + _RehashIfNeeded(); + hash %= fArraySize; + Element *result = fElementVector->Add(); + if (result) { + result->fNext = fHashArray[hash]; + fHashArray[hash] = fElementVector->IndexOf(*result); + fElementCount++; + } + return result; +} + +template<class Element, class ElementVec> +void +OpenHashTable<Element, ElementVec>::Remove(Element *element, bool dontRehash) +{ + if (!dontRehash) + _RehashIfNeeded(); + uint32 hash = element->Hash() % fArraySize; + int32 next = fHashArray[hash]; + ASSERT(next >= 0); + + if (&fElementVector->At(next) == element) { + fHashArray[hash] = element->fNext; + fElementVector->Remove(next); + fElementCount--; + return; + } + + for (int32 index = next; index >= 0; ) { + // look for an existing match in table + next = fElementVector->At(index).fNext; + if (next < 0) { + TRESPASS(); + return; + } + + if (&fElementVector->At(next) == element) { + fElementVector->At(index).fNext = element->fNext; + fElementVector->Remove(next); + fElementCount--; + return; + } + index = next; + } +} + +template<class Element, class ElementVec> +void +OpenHashTable<Element, ElementVec>::RemoveAll() +{ + for (int32 i = 0; fElementCount > 0 && i < fArraySize; i++) { + int32 index = fHashArray[i]; + while (index >= 0) { + Element* element = &fElementVector->At(index); + int32 next = element->fNext; + fElementVector->Remove(index); + fElementCount--; + index = next; + } + fHashArray[i] = -1; + } + _RehashIfNeeded(); +} + +template<class Element, class ElementVec> +void +OpenHashTable<Element, ElementVec>::SetElementVector(ElementVec *elementVector) +{ + fElementVector = elementVector; +} + +// _RehashIfNeeded +template<class Element, class ElementVec> +bool +OpenHashTable<Element, ElementVec>::_RehashIfNeeded() +{ + // The load factor range [fMaxLoadFactor / 3, fMaxLoadFactor] is fine, + // I think. After rehashing the load factor will be about + // fMaxLoadFactor * 2 / 3, respectively fMaxLoadFactor / 2. + float loadFactor = (float)fElementCount / (float)fArraySize; + if (loadFactor > fMaxLoadFactor + || (fArraySize > fInitialSize && loadFactor < fMaxLoadFactor / 3)) { + return _Rehash(); + } + return true; +} + +// _Rehash +template<class Element, class ElementVec> +bool +OpenHashTable<Element, ElementVec>::_Rehash() +{ + bool result = true; + int32 newSize = int32(fElementCount * 1.73 * fMaxLoadFactor); + newSize = (fInitialSize > newSize ? fInitialSize : newSize); + if (newSize != fArraySize) { + // allocate a new array + int32 *newHashArray = (int32*)calloc(newSize, sizeof(int32)); + if (newHashArray) { + // init the new hash array + for (int32 index = 0; index < newSize; index++) + newHashArray[index] = -1; + // iterate through all elements and put them into the new + // hash array + for (int i = 0; i < fArraySize; i++) { + int32 index = fHashArray[i]; + while (index >= 0) { + // insert the element in the new array + Element &element = fElementVector->At(index); + int32 next = element.fNext; + uint32 hash = (element.Hash() % newSize); + element.fNext = newHashArray[hash]; + newHashArray[hash] = index; + // next element in old list + index = next; + } + } + // delete the old array and set the new one + free(fHashArray); + fHashArray = newHashArray; + fArraySize = newSize; + } else + result = false; + } + return result; +} + + +template<class Element> +OpenHashElementArray<Element>::OpenHashElementArray(int32 initialSize) + : fSize(initialSize), + fNextFree(0), + fNextDeleted(-1) +{ + fData = (Element*)calloc((size_t)initialSize, sizeof(Element)); +} + +template<class Element> +OpenHashElementArray<Element>::~OpenHashElementArray() +{ + free(fData); +} + +template<class Element> +bool +OpenHashElementArray<Element>::InitCheck() const +{ + return fData; +} + +template<class Element> +Element & +OpenHashElementArray<Element>::At(int32 index) +{ + ASSERT(index < fSize); + return fData[index]; +} + +template<class Element> +const Element & +OpenHashElementArray<Element>::At(int32 index) const +{ + ASSERT(index < fSize); + return fData[index]; +} + +template<class Element> +int32 +OpenHashElementArray<Element>::IndexOf(const Element &element) const +{ + int32 result = &element - fData; + if (result < 0 || result > fSize) + return -1; + + return result; +} + +template<class Element> +int32 +OpenHashElementArray<Element>::Size() const +{ + return fSize; +} + + +template<class Element> +Element * +OpenHashElementArray<Element>::Add(const Element &newElement) +{ + Element *element = Add(); + if (element) + element.Adopt(newElement); + return element; +} + +#if DEBUG +const int32 kGrowChunk = 10; +#else +const int32 kGrowChunk = 1024; +#endif + +template<class Element> +Element * +OpenHashElementArray<Element>::Add() +{ + int32 index = fNextFree; + if (fNextDeleted >= 0) { + index = fNextDeleted; + fNextDeleted = At(index).fNext; + } else if (fNextFree >= fSize - 1) { + int32 newSize = fSize + kGrowChunk; +/* + Element *newData = (Element *)calloc((size_t)newSize , sizeof(Element)); + if (!newData) + return NULL; + memcpy(newData, fData, fSize * sizeof(Element)); + free(fData); +*/ + Element *newData = (Element*)realloc(fData, + (size_t)newSize * sizeof(Element)); + if (!newData) + return NULL; + + fData = newData; + fSize = newSize; + index = fNextFree; + fNextFree++; + } else + fNextFree++; + + new (&At(index)) Element; + // call placement new to initialize the element properly + ASSERT(At(index).fNext == -1); + + return &At(index); +} + +template<class Element> +void +OpenHashElementArray<Element>::Remove(int32 index) +{ + // delete by chaining empty elements in a single linked + // list, reusing the next field + ASSERT(index < fSize); + At(index).~Element(); + // call the destructor explicitly to destroy the element + // properly + At(index).fNext = fNextDeleted; + fNextDeleted = index; +} + +} // namespace BPrivate + +using BPrivate::OpenHashTable; + +#endif // __OPEN_HASH_TABLE__ diff --git a/src/apps/webpositive/support/SettingsMessage.cpp b/src/apps/webpositive/support/SettingsMessage.cpp new file mode 100644 index 0000000000..15dc12394c --- /dev/null +++ b/src/apps/webpositive/support/SettingsMessage.cpp @@ -0,0 +1,514 @@ +/* + * Copyright 2008-2010, Stephan Aßmus <superstippi@gmx.de>. + * Copyright 1998, Eric Shepherd. + * All rights reserved. Distributed under the terms of the Be Sample Code + * license. + */ + +//! Be Newsletter Volume II, Issue 35; September 2, 1998 (Eric Shepherd) + +#include "SettingsMessage.h" + +#include <new> + +#include <Autolock.h> +#include <Entry.h> +#include <File.h> +#include <Messenger.h> +#include <String.h> + + +SettingsMessage::SettingsMessage(directory_which directory, + const char* filename) + : + BMessage('pref'), + fListeners(4) +{ + fStatus = find_directory(directory, &fPath); + + if (fStatus == B_OK) + fStatus = fPath.Append(filename); + + if (fStatus == B_OK) + fStatus = Load(); +} + + +SettingsMessage::~SettingsMessage() +{ + Save(); + + for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) + delete reinterpret_cast<BMessenger*>(fListeners.ItemAtFast(i)); +} + + +status_t +SettingsMessage::InitCheck() const +{ + return fStatus; +} + + +status_t +SettingsMessage::Load() +{ + BAutolock _(this); + + BFile file(fPath.Path(), B_READ_ONLY); + status_t status = file.InitCheck(); + + if (status == B_OK) + status = Unflatten(&file); + + return status; +} + + +status_t +SettingsMessage::Save() const +{ + BAutolock _(const_cast<SettingsMessage*>(this)); + + BFile file(fPath.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); + status_t status = file.InitCheck(); + + if (status == B_OK) + status = Flatten(&file); + + return status; +} + + +bool +SettingsMessage::AddListener(const BMessenger& listener) +{ + BAutolock _(this); + + BMessenger* listenerCopy = new(std::nothrow) BMessenger(listener); + if (listenerCopy && fListeners.AddItem(listenerCopy)) + return true; + delete listenerCopy; + return false; +} + + +void +SettingsMessage::RemoveListener(const BMessenger& listener) +{ + BAutolock _(this); + + for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) { + BMessenger* listenerItem = reinterpret_cast<BMessenger*>( + fListeners.ItemAtFast(i)); + if (*listenerItem == listener) { + fListeners.RemoveItem(i); + delete listenerItem; + return; + } + } +} + + +// #pragma mark - + + +status_t +SettingsMessage::SetValue(const char* name, bool value) +{ + status_t ret = ReplaceBool(name, value); + if (ret != B_OK) + ret = AddBool(name, value); + if (ret == B_OK) + _NotifyValueChanged(name); + return ret; +} + + +status_t +SettingsMessage::SetValue(const char* name, int8 value) +{ + status_t ret = ReplaceInt8(name, value); + if (ret != B_OK) + ret = AddInt8(name, value); + if (ret == B_OK) + _NotifyValueChanged(name); + return ret; +} + + +status_t +SettingsMessage::SetValue(const char* name, int16 value) +{ + status_t ret = ReplaceInt16(name, value); + if (ret != B_OK) + ret = AddInt16(name, value); + if (ret == B_OK) + _NotifyValueChanged(name); + return ret; +} + + +status_t +SettingsMessage::SetValue(const char* name, int32 value) +{ + status_t ret = ReplaceInt32(name, value); + if (ret != B_OK) + ret = AddInt32(name, value); + if (ret == B_OK) + _NotifyValueChanged(name); + return ret; +} + + +status_t +SettingsMessage::SetValue(const char* name, uint32 value) +{ + status_t ret = ReplaceUInt32(name, value); + if (ret != B_OK) + ret = AddUInt32(name, value); + if (ret == B_OK) + _NotifyValueChanged(name); + return ret; +} + + +status_t +SettingsMessage::SetValue(const char* name, int64 value) +{ + status_t ret = ReplaceInt64(name, value); + if (ret != B_OK) + ret = AddInt64(name, value); + if (ret == B_OK) + _NotifyValueChanged(name); + return ret; +} + + +status_t +SettingsMessage::SetValue(const char* name, float value) +{ + status_t ret = ReplaceFloat(name, value); + if (ret != B_OK) + ret = AddFloat(name, value); + if (ret == B_OK) + _NotifyValueChanged(name); + return ret; +} + + +status_t +SettingsMessage::SetValue(const char* name, double value) +{ + status_t ret = ReplaceDouble(name, value); + if (ret != B_OK) + ret = AddDouble(name, value); + if (ret == B_OK) + _NotifyValueChanged(name); + return ret; +} + + +status_t +SettingsMessage::SetValue(const char* name, const char* value) +{ + status_t ret = ReplaceString(name, value); + if (ret != B_OK) + ret = AddString(name, value); + if (ret == B_OK) + _NotifyValueChanged(name); + return ret; +} + + +status_t +SettingsMessage::SetValue(const char* name, const BString& value) +{ + status_t ret = ReplaceString(name, value); + if (ret != B_OK) + ret = AddString(name, value); + if (ret == B_OK) + _NotifyValueChanged(name); + return ret; +} + + +status_t +SettingsMessage::SetValue(const char* name, const BPoint& value) +{ + status_t ret = ReplacePoint(name, value); + if (ret != B_OK) + ret = AddPoint(name, value); + if (ret == B_OK) + _NotifyValueChanged(name); + return ret; +} + + +status_t +SettingsMessage::SetValue(const char* name, const BRect& value) +{ + status_t ret = ReplaceRect(name, value); + if (ret != B_OK) + ret = AddRect(name, value); + if (ret == B_OK) + _NotifyValueChanged(name); + return ret; +} + + +status_t +SettingsMessage::SetValue(const char* name, const entry_ref& value) +{ + status_t ret = ReplaceRef(name, &value); + if (ret != B_OK) + ret = AddRef(name, &value); + if (ret == B_OK) + _NotifyValueChanged(name); + return ret; +} + + +status_t +SettingsMessage::SetValue(const char* name, const BMessage& value) +{ + status_t ret = ReplaceMessage(name, &value); + if (ret != B_OK) + ret = AddMessage(name, &value); + if (ret == B_OK) + _NotifyValueChanged(name); + return ret; +} + + +status_t +SettingsMessage::SetValue(const char* name, const BFlattenable* value) +{ + status_t ret = ReplaceFlat(name, const_cast<BFlattenable*>(value)); + if (ret != B_OK) + ret = AddFlat(name, const_cast<BFlattenable*>(value)); + if (ret == B_OK) + _NotifyValueChanged(name); + return ret; +} + + +status_t +SettingsMessage::SetValue(const char* name, const BFont& value) +{ + font_family family; + font_style style; + value.GetFamilyAndStyle(&family, &style); + + BMessage fontMessage; + status_t ret = fontMessage.AddString("family", family); + if (ret == B_OK) + ret = fontMessage.AddString("style", style); + if (ret == B_OK) + ret = fontMessage.AddFloat("size", value.Size()); + + if (ret == B_OK) { + if (ReplaceMessage(name, &fontMessage) != B_OK) + ret = AddMessage(name, &fontMessage); + } + if (ret == B_OK) + _NotifyValueChanged(name); + return ret; +} + + +// #pragma mark - + + +bool +SettingsMessage::GetValue(const char* name, bool defaultValue) const +{ + bool value; + if (FindBool(name, &value) != B_OK) + return defaultValue; + return value; +} + + +int8 +SettingsMessage::GetValue(const char* name, int8 defaultValue) const +{ + int8 value; + if (FindInt8(name, &value) != B_OK) + return defaultValue; + return value; +} + + +int16 +SettingsMessage::GetValue(const char* name, int16 defaultValue) const +{ + int16 value; + if (FindInt16(name, &value) != B_OK) + return defaultValue; + return value; +} + + +int32 +SettingsMessage::GetValue(const char* name, int32 defaultValue) const +{ + int32 value; + if (FindInt32(name, &value) != B_OK) + return defaultValue; + return value; +} + + +uint32 +SettingsMessage::GetValue(const char* name, uint32 defaultValue) const +{ + uint32 value; + if (FindUInt32(name, &value) != B_OK) + return defaultValue; + return value; +} + + +int64 +SettingsMessage::GetValue(const char* name, int64 defaultValue) const +{ + int64 value; + if (FindInt64(name, &value) != B_OK) + return defaultValue; + return value; +} + + +float +SettingsMessage::GetValue(const char* name, float defaultValue) const +{ + float value; + if (FindFloat(name, &value) != B_OK) + return defaultValue; + return value; +} + + +double +SettingsMessage::GetValue(const char* name, double defaultValue) const +{ + double value; + if (FindDouble(name, &value) != B_OK) + return defaultValue; + return value; +} + + +BString +SettingsMessage::GetValue(const char* name, const BString& defaultValue) const +{ + BString value; + if (FindString(name, &value) != B_OK) + return defaultValue; + return value; +} + + +const char* +SettingsMessage::GetValue(const char* name, const char* defaultValue) const +{ + const char* value; + if (FindString(name, &value) != B_OK) + return defaultValue; + return value; +} + + +BPoint +SettingsMessage::GetValue(const char *name, BPoint defaultValue) const +{ + BPoint value; + if (FindPoint(name, &value) != B_OK) + return defaultValue; + return value; +} + + +BRect +SettingsMessage::GetValue(const char* name, BRect defaultValue) const +{ + BRect value; + if (FindRect(name, &value) != B_OK) + return defaultValue; + return value; +} + + +entry_ref +SettingsMessage::GetValue(const char* name, const entry_ref& defaultValue) const +{ + entry_ref value; + if (FindRef(name, &value) != B_OK) + return defaultValue; + return value; +} + + +BMessage +SettingsMessage::GetValue(const char* name, const BMessage& defaultValue) const +{ + BMessage value; + if (FindMessage(name, &value) != B_OK) + return defaultValue; + return value; +} + + +BFont +SettingsMessage::GetValue(const char* name, const BFont& defaultValue) const +{ + BMessage fontMessage; + if (FindMessage(name, &fontMessage) != B_OK) + return defaultValue; + + const char* family; + const char* style; + float size; + if (fontMessage.FindString("family", &family) != B_OK + || fontMessage.FindString("style", &style) != B_OK + || fontMessage.FindFloat("size", &size) != B_OK) { + return defaultValue; + } + + BFont value; + if (value.SetFamilyAndStyle(family, style) != B_OK) + return defaultValue; + + value.SetSize(size); + + return value; +} + + +// #pragma mark - private + + +void +SettingsMessage::_NotifyValueChanged(const char* name) const +{ + BMessage message(SETTINGS_VALUE_CHANGED); + message.AddString("name", name); + + // Add the value of that name to the notification. + type_code type; + if (GetInfo(name, &type) == B_OK) { + const void* data; + ssize_t numBytes; + if (FindData(name, type, &data, &numBytes) == B_OK) + message.AddData("value", type, data, numBytes); + } + + int32 count = fListeners.CountItems(); + for (int32 i = 0; i < count; i++) { + BMessenger* listener = reinterpret_cast<BMessenger*>( + fListeners.ItemAtFast(i)); + listener->SendMessage(&message); + } +} + diff --git a/src/apps/webpositive/support/SettingsMessage.h b/src/apps/webpositive/support/SettingsMessage.h new file mode 100644 index 0000000000..8f22785788 --- /dev/null +++ b/src/apps/webpositive/support/SettingsMessage.h @@ -0,0 +1,105 @@ +/* + * Copyright 2008-2010 Stephan Aßmus <superstippi@gmx.de>. + * Copyright 1998 Eric Shepherd. + * All rights reserved. Distributed under the terms of the Be Sample Code + * license. + */ +#ifndef SETTINGS_MESSAGE_H +#define SETTINGS_MESSAGE_H + + +#include <FindDirectory.h> +#include <Font.h> +#include <List.h> +#include <Locker.h> +#include <Message.h> +#include <Path.h> + +class BMessenger; +class BString; + + +enum { + SETTINGS_VALUE_CHANGED = '_svc' +}; + + +class SettingsMessage : public BMessage, public BLocker { +public: + SettingsMessage(directory_which directory, + const char* filename); + virtual ~SettingsMessage(); + + + status_t InitCheck() const; + status_t Load(); + status_t Save() const; + + bool AddListener(const BMessenger& listener); + void RemoveListener(const BMessenger& listener); + + status_t SetValue(const char* name, bool value); + status_t SetValue(const char* name, int8 value); + status_t SetValue(const char* name, int16 value); + status_t SetValue(const char* name, int32 value); + status_t SetValue(const char* name, uint32 value); + status_t SetValue(const char* name, int64 value); + status_t SetValue(const char* name, float value); + status_t SetValue(const char* name, double value); + status_t SetValue(const char* name, + const char* value); + status_t SetValue(const char* name, + const BString& value); + status_t SetValue(const char *name, + const BPoint& value); + status_t SetValue(const char* name, const BRect& value); + status_t SetValue(const char* name, + const entry_ref& value); + status_t SetValue(const char* name, + const BMessage& value); + status_t SetValue(const char* name, + const BFlattenable* value); + status_t SetValue(const char* name, + const BFont& value); + + bool GetValue(const char* name, + bool defaultValue) const; + int8 GetValue(const char* name, + int8 defaultValue) const; + int16 GetValue(const char* name, + int16 defaultValue) const; + int32 GetValue(const char* name, + int32 defaultValue) const; + uint32 GetValue(const char* name, + uint32 defaultValue) const; + int64 GetValue(const char* name, + int64 defaultValue) const; + float GetValue(const char* name, + float defaultValue) const; + double GetValue(const char* name, + double defaultValue) const; + const char* GetValue(const char* name, + const char* defaultValue) const; + BString GetValue(const char* name, + const BString& defaultValue) const; + BPoint GetValue(const char *name, + BPoint defaultValue) const; + BRect GetValue(const char* name, + BRect defaultValue) const; + entry_ref GetValue(const char* name, + const entry_ref& defaultValue) const; + BMessage GetValue(const char* name, + const BMessage& defaultValue) const; + BFont GetValue(const char* name, + const BFont& defaultValue) const; + +private: + void _NotifyValueChanged(const char* name) const; + +private: + BPath fPath; + status_t fStatus; + BList fListeners; +}; + +#endif // SETTINGS_MESSAGE_H diff --git a/src/apps/webpositive/support/SlowMenu.h b/src/apps/webpositive/support/SlowMenu.h new file mode 100644 index 0000000000..62e9156b07 --- /dev/null +++ b/src/apps/webpositive/support/SlowMenu.h @@ -0,0 +1,76 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef __SLOW_MENU__ +#define __SLOW_MENU__ + +#include <Menu.h> +#include <MenuItem.h> +#include <Debug.h> + +// SlowMenu is a convenience class that makes it easier to +// use the AddDynamicItem callback to implement a menu that can +// checks periodically between creating new items and quits +// early if needed + +namespace BPrivate { + +class BSlowMenu : public BMenu { + public: + BSlowMenu(const char *title, menu_layout layout = B_ITEMS_IN_COLUMN); + + protected: + virtual bool StartBuildingItemList(); + // set up state to start building the item list + // returns false if setup failed + virtual bool AddNextItem() = 0; + // returns false if done + virtual void DoneBuildingItemList() = 0; + // default version adds items from itemList to menu and deletes + // the list; override to sort items first, etc. + + virtual void ClearMenuBuildingState() = 0; + + protected: + virtual bool AddDynamicItem(add_state state); + // this is the callback from BMenu, you shouldn't need to override this + + bool fMenuBuilt; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif /* __SLOW_MENU__ */ diff --git a/src/apps/webpositive/support/StringForSize.cpp b/src/apps/webpositive/support/StringForSize.cpp new file mode 100644 index 0000000000..ae7831e397 --- /dev/null +++ b/src/apps/webpositive/support/StringForSize.cpp @@ -0,0 +1,43 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ + +#include "StringForSize.h" + +#include <stdio.h> + + +namespace BPrivate { + + +const char* +string_for_size(double size, char* string, size_t stringSize) +{ + double kib = size / 1024.0; + if (kib < 1.0) { + snprintf(string, stringSize, "%d bytes", (int)size); + return string; + } + double mib = kib / 1024.0; + if (mib < 1.0) { + snprintf(string, stringSize, "%3.2f KiB", kib); + return string; + } + double gib = mib / 1024.0; + if (gib < 1.0) { + snprintf(string, stringSize, "%3.2f MiB", mib); + return string; + } + double tib = gib / 1024.0; + if (tib < 1.0) { + snprintf(string, stringSize, "%3.2f GiB", gib); + return string; + } + snprintf(string, stringSize, "%.2f TiB", tib); + return string; +} + + +} // namespace BPrivate + diff --git a/src/apps/webpositive/support/StringForSize.h b/src/apps/webpositive/support/StringForSize.h new file mode 100644 index 0000000000..fa3e13b115 --- /dev/null +++ b/src/apps/webpositive/support/StringForSize.h @@ -0,0 +1,23 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef STRING_FOR_SIZE_H +#define STRING_FOR_SIZE_H + +#include <SupportDefs.h> + + +namespace BPrivate { + + +const char* string_for_size(double size, char* string, size_t stringSize); + + +} // namespace BPrivate + + +using BPrivate::string_for_size; + + +#endif // COLOR_QUANTIZER_H diff --git a/src/apps/webpositive/support/WindowIcon.h b/src/apps/webpositive/support/WindowIcon.h new file mode 100644 index 0000000000..30d1622058 --- /dev/null +++ b/src/apps/webpositive/support/WindowIcon.h @@ -0,0 +1,54 @@ + +const uint32 kWindowIconWidth = 13; +const uint32 kWindowIconHeight = 14; +const color_space kWindowIconFormat = B_RGBA32; + +const unsigned char kWindowIconBits[] = { + 0x78, 0x78, 0x78, 0xff, 0x78, 0x78, 0x78, 0xff, 0x78, 0x78, 0x78, 0xff, 0x78, 0x78, 0x78, 0xff, + 0x78, 0x78, 0x78, 0xff, 0x78, 0x78, 0x78, 0xff, 0x78, 0x78, 0x78, 0xff, 0x78, 0x78, 0x78, 0xff, + 0x78, 0x78, 0x78, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x78, 0x78, 0x78, 0xff, 0x66, 0xff, 0xff, 0xff, 0x66, 0xff, 0xff, 0xff, + 0x66, 0xff, 0xff, 0xff, 0x66, 0xff, 0xff, 0xff, 0x66, 0xff, 0xff, 0xff, 0x66, 0xff, 0xff, 0xff, + 0x66, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x78, 0x78, 0xff, 0x13, 0xaf, 0xff, 0xff, + 0x13, 0xaf, 0xff, 0xff, 0x13, 0xaf, 0xff, 0xff, 0x13, 0xaf, 0xff, 0xff, 0x13, 0xaf, 0xff, 0xff, + 0x13, 0xaf, 0xff, 0xff, 0x13, 0xaf, 0xff, 0xff, 0x78, 0x78, 0x78, 0xff, 0x78, 0x78, 0x78, 0xff, + 0x78, 0x78, 0x78, 0xff, 0x78, 0x78, 0x78, 0xff, 0x78, 0x78, 0x78, 0xff, 0x78, 0x78, 0x78, 0xff, + 0xc8, 0xc8, 0xc8, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0xc8, 0xc8, 0xc8, 0xff, + 0xc8, 0xc8, 0xc8, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0xc8, 0xc8, 0xc8, 0xff, + 0xc8, 0xc8, 0xc8, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x78, 0x78, 0x78, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc8, 0xc8, 0xc8, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x78, 0x78, 0x78, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc8, 0xc8, 0xc8, 0xff, 0x00, 0x00, 0x00, 0xff, 0x78, 0x78, 0x78, 0xff, 0xc8, 0xc8, 0xc8, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0x00, 0x00, 0x00, 0xff, 0x78, 0x78, 0x78, 0xff, + 0xc8, 0xc8, 0xc8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x78, 0x78, 0x78, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc8, 0xc8, 0xc8, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x78, 0x78, 0x78, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc8, 0xc8, 0xc8, 0xff, 0x00, 0x00, 0x00, 0xff, 0x78, 0x78, 0x78, 0xff, 0xc8, 0xc8, 0xc8, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0x00, 0x00, 0x00, 0xff, 0x78, 0x78, 0x78, 0xff, + 0xc8, 0xc8, 0xc8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x78, 0x78, 0x78, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0xc8, 0xc8, 0xc8, 0xff, + 0xc8, 0xc8, 0xc8, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0xc8, 0xc8, 0xc8, 0xff, + 0xc8, 0xc8, 0xc8, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0xc8, 0xc8, 0xc8, 0xff, 0xc8, 0xc8, 0xc8, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff +}; + diff --git a/src/apps/webpositive/svn_revision.cpp b/src/apps/webpositive/svn_revision.cpp new file mode 100644 index 0000000000..489c64cdd4 --- /dev/null +++ b/src/apps/webpositive/svn_revision.cpp @@ -0,0 +1,10 @@ +/* + * Copyright 2006-2009, Ingo Weinhold <ingo_weinhold@gmx.de> + * All rights reserved. Distributed under the terms of the MIT License. + */ + +#include "svn_revision.h" + +const int32 kSVNRevision = + #include "svn_revision" +; diff --git a/src/apps/webpositive/svn_revision.h b/src/apps/webpositive/svn_revision.h new file mode 100644 index 0000000000..fff9e9b122 --- /dev/null +++ b/src/apps/webpositive/svn_revision.h @@ -0,0 +1,15 @@ +/* + * Copyright 2006-2009, Ingo Weinhold <ingo_weinhold@gmx.de> + * All rights reserved. Distributed under the terms of the MIT License. + */ + +#ifndef SVN_REVISION_H +#define SVN_REVISION_H + +#include <SupportDefs.h> + + +extern const int32 kSVNRevision; + + +#endif // SVN_REVISION_H diff --git a/src/apps/webpositive/tabview/TabContainerView.cpp b/src/apps/webpositive/tabview/TabContainerView.cpp new file mode 100644 index 0000000000..52217fa9c7 --- /dev/null +++ b/src/apps/webpositive/tabview/TabContainerView.cpp @@ -0,0 +1,542 @@ +/* + * Copyright (C) 2010 Rene Gollent <rene@gollent.com> + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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 "TabContainerView.h" + +#include <stdio.h> + +#include <Application.h> +#include <AbstractLayoutItem.h> +#include <Bitmap.h> +#include <Button.h> +#include <CardLayout.h> +#include <ControlLook.h> +#include <GroupView.h> +#include <MenuBar.h> +#include <SpaceLayoutItem.h> +#include <Window.h> + +#include "TabView.h" + + +static const float kLeftTabInset = 4; + + +TabContainerView::TabContainerView(Controller* controller) + : + BGroupView(B_HORIZONTAL, 0.0), + fLastMouseEventTab(NULL), + fMouseDown(false), + fClickCount(0), + fSelectedTab(NULL), + fController(controller), + fFirstVisibleTabIndex(0) +{ + SetFlags(Flags() | B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE); + SetViewColor(B_TRANSPARENT_COLOR); + GroupLayout()->SetInsets(kLeftTabInset, 0, 0, 1); + GroupLayout()->AddItem(BSpaceLayoutItem::CreateGlue(), 0.0f); +} + + +TabContainerView::~TabContainerView() +{ +} + + +BSize +TabContainerView::MinSize() +{ + // Eventually, we want to be scrolling if the tabs don't fit. + BSize size(BGroupView::MinSize()); + size.width = 300; + return size; +} + + +void +TabContainerView::MessageReceived(BMessage* message) +{ + switch (message->what) { + default: + BGroupView::MessageReceived(message); + } +} + + +void +TabContainerView::Draw(BRect updateRect) +{ + // Stroke separator line at bottom. + rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); + BRect frame(Bounds()); + SetHighColor(tint_color(base, B_DARKEN_2_TINT)); + StrokeLine(frame.LeftBottom(), frame.RightBottom()); + frame.bottom--; + + // Draw empty area before first tab. + uint32 borders = BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER; + BRect leftFrame(frame.left, frame.top, kLeftTabInset, frame.bottom); + be_control_look->DrawInactiveTab(this, leftFrame, updateRect, base, 0, + borders); + + // Draw all tabs, keeping track of where they end. + BGroupLayout* layout = GroupLayout(); + int32 count = layout->CountItems() - 1; + for (int32 i = 0; i < count; i++) { + TabLayoutItem* item = dynamic_cast<TabLayoutItem*>( + layout->ItemAt(i)); + if (!item || !item->IsVisible()) + continue; + item->Parent()->Draw(updateRect); + frame.left = item->Frame().right + 1; + } + + // Draw empty area after last tab. + be_control_look->DrawInactiveTab(this, frame, updateRect, base, 0, borders); +} + + +void +TabContainerView::MouseDown(BPoint where) +{ + uint32 buttons; + if (Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons) != B_OK) + buttons = B_PRIMARY_MOUSE_BUTTON; + uint32 clicks; + if (Window()->CurrentMessage()->FindInt32("clicks", (int32*)&clicks) != B_OK) + clicks = 1; + fMouseDown = true; + SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); + if (fLastMouseEventTab) + fLastMouseEventTab->MouseDown(where, buttons); + else { + if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0) { + // Middle click outside tabs should always open a new tab. + fClickCount = 2; + } else if (clicks > 1) + fClickCount = fClickCount++; + else + fClickCount = 1; + } +} + + +void +TabContainerView::MouseUp(BPoint where) +{ + fMouseDown = false; + if (fLastMouseEventTab) { + fLastMouseEventTab->MouseUp(where); + fClickCount = 0; + } else if (fClickCount > 1) { + // NOTE: fClickCount is >= 1 only if the first click was outside + // any tab. So even if fLastMouseEventTab has been reset to NULL + // because this tab was removed during mouse down, we wouldn't + // run the "outside tabs" code below. + fController->DoubleClickOutsideTabs(); + fClickCount = 0; + } + // Always check the tab under the mouse again, since we don't update + // it with fMouseDown == true. + _SendFakeMouseMoved(); +} + + +void +TabContainerView::MouseMoved(BPoint where, uint32 transit, + const BMessage* dragMessage) +{ + _MouseMoved(where, transit, dragMessage); +} + + +void +TabContainerView::DoLayout() +{ + BGroupView::DoLayout(); + + _ValidateTabVisibility(); + _SendFakeMouseMoved(); +} + +void +TabContainerView::AddTab(const char* label, int32 index) +{ + TabView* tab; + if (fController) + tab = fController->CreateTabView(); + else + tab = new TabView(); + tab->SetLabel(label); + AddTab(tab, index); +} + + +void +TabContainerView::AddTab(TabView* tab, int32 index) +{ + tab->SetContainerView(this); + + if (index == -1) + index = GroupLayout()->CountItems() - 1; + + bool hasFrames = fController != NULL && fController->HasFrames(); + bool isFirst = index == 0 && hasFrames; + bool isLast = index == GroupLayout()->CountItems() - 1 && hasFrames; + bool isFront = fSelectedTab == NULL; + tab->Update(isFirst, isLast, isFront); + + GroupLayout()->AddItem(index, tab->LayoutItem()); + + if (isFront) + SelectTab(tab); + if (isLast) { + TabLayoutItem* item + = dynamic_cast<TabLayoutItem*>(GroupLayout()->ItemAt(index - 1)); + if (item) + item->Parent()->SetIsLast(false); + } + + SetFirstVisibleTabIndex(MaxFirstVisibleTabIndex()); + _ValidateTabVisibility(); +} + +TabView* +TabContainerView::RemoveTab(int32 index) +{ + TabLayoutItem* item + = dynamic_cast<TabLayoutItem*>(GroupLayout()->RemoveItem(index)); + + if (!item) + return NULL; + + BRect dirty(Bounds()); + dirty.left = item->Frame().left; + TabView* removedTab = item->Parent(); + removedTab->SetContainerView(NULL); + + if (removedTab == fLastMouseEventTab) + fLastMouseEventTab = NULL; + + // Update tabs after or before the removed tab. + bool hasFrames = fController != NULL && fController->HasFrames(); + item = dynamic_cast<TabLayoutItem*>(GroupLayout()->ItemAt(index)); + if (item) { + // This tab is behind the removed tab. + TabView* tab = item->Parent(); + tab->Update(index == 0 && hasFrames, + index == GroupLayout()->CountItems() - 2 && hasFrames, + tab == fSelectedTab); + if (removedTab == fSelectedTab) { + fSelectedTab = NULL; + SelectTab(tab); + } else if (fController && tab == fSelectedTab) + fController->TabSelected(index); + } else { + // The removed tab was the last tab. + item = dynamic_cast<TabLayoutItem*>(GroupLayout()->ItemAt(index - 1)); + if (item) { + TabView* tab = item->Parent(); + tab->Update(index == 0 && hasFrames, + index == GroupLayout()->CountItems() - 2 && hasFrames, + tab == fSelectedTab); + if (removedTab == fSelectedTab) { + fSelectedTab = NULL; + SelectTab(tab); + } + } + } + + Invalidate(dirty); + _ValidateTabVisibility(); + + return removedTab; +} + + +TabView* +TabContainerView::TabAt(int32 index) const +{ + TabLayoutItem* item = dynamic_cast<TabLayoutItem*>( + GroupLayout()->ItemAt(index)); + if (item) + return item->Parent(); + return NULL; +} + + +int32 +TabContainerView::IndexOf(TabView* tab) const +{ + return GroupLayout()->IndexOfItem(tab->LayoutItem()); +} + + +void +TabContainerView::SelectTab(int32 index) +{ + TabView* tab = NULL; + TabLayoutItem* item = dynamic_cast<TabLayoutItem*>( + GroupLayout()->ItemAt(index)); + if (item) + tab = item->Parent(); + + SelectTab(tab); +} + + +void +TabContainerView::SelectTab(TabView* tab) +{ + if (tab == fSelectedTab) + return; + + if (fSelectedTab) + fSelectedTab->SetIsFront(false); + + fSelectedTab = tab; + + if (fSelectedTab) + fSelectedTab->SetIsFront(true); + + if (fController != NULL) { + int32 index = -1; + if (fSelectedTab != NULL) + index = GroupLayout()->IndexOfItem(tab->LayoutItem()); + + if (!tab->LayoutItem()->IsVisible()) { + SetFirstVisibleTabIndex(index); + } + + fController->TabSelected(index); + } +} + + +void +TabContainerView::SetTabLabel(int32 tabIndex, const char* label) +{ + TabLayoutItem* item = dynamic_cast<TabLayoutItem*>( + GroupLayout()->ItemAt(tabIndex)); + if (item == NULL) + return; + + item->Parent()->SetLabel(label); +} + + +void +TabContainerView::SetFirstVisibleTabIndex(int32 index) +{ + if (index < 0) + index = 0; + if (index > MaxFirstVisibleTabIndex()) + index = MaxFirstVisibleTabIndex(); + if (fFirstVisibleTabIndex == index) + return; + + fFirstVisibleTabIndex = index; + + _UpdateTabVisibility(); +} + + +int32 +TabContainerView::FirstVisibleTabIndex() const +{ + return fFirstVisibleTabIndex; +} + + +int32 +TabContainerView::MaxFirstVisibleTabIndex() const +{ + float availableWidth = _AvailableWidthForTabs(); + if (availableWidth < 0) + return 0; + float visibleTabsWidth = 0; + + BGroupLayout* layout = GroupLayout(); + int32 i = layout->CountItems() - 2; + for (; i >= 0; i--) { + TabLayoutItem* item = dynamic_cast<TabLayoutItem*>( + layout->ItemAt(i)); + if (item == NULL) + continue; + + float itemWidth = item->MinSize().width; + if (availableWidth >= visibleTabsWidth + itemWidth) + visibleTabsWidth += itemWidth; + else { + // The tab before this tab is the last one that can be visible. + return i + 1; + } + } + + return 0; +} + + +bool +TabContainerView::CanScrollLeft() const +{ + return fFirstVisibleTabIndex < MaxFirstVisibleTabIndex(); +} + + +bool +TabContainerView::CanScrollRight() const +{ + BGroupLayout* layout = GroupLayout(); + int32 count = layout->CountItems() - 1; + if (count > 0) { + TabLayoutItem* item = dynamic_cast<TabLayoutItem*>( + layout->ItemAt(count - 1)); + return !item->IsVisible(); + } + return false; +} + + +// #pragma mark - + + +TabView* +TabContainerView::_TabAt(const BPoint& where) const +{ + BGroupLayout* layout = GroupLayout(); + int32 count = layout->CountItems() - 1; + for (int32 i = 0; i < count; i++) { + TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(layout->ItemAt(i)); + if (item == NULL || !item->IsVisible()) + continue; + // Account for the fact that the tab frame does not contain the + // visible bottom border. + BRect frame = item->Frame(); + frame.bottom++; + if (frame.Contains(where)) + return item->Parent(); + } + return NULL; +} + + +void +TabContainerView::_MouseMoved(BPoint where, uint32 _transit, + const BMessage* dragMessage) +{ + TabView* tab = _TabAt(where); + if (fMouseDown) { + uint32 transit = tab == fLastMouseEventTab + ? B_INSIDE_VIEW : B_OUTSIDE_VIEW; + if (fLastMouseEventTab) + fLastMouseEventTab->MouseMoved(where, transit, dragMessage); + return; + } + + if (fLastMouseEventTab != NULL && fLastMouseEventTab == tab) + fLastMouseEventTab->MouseMoved(where, B_INSIDE_VIEW, dragMessage); + else { + if (fLastMouseEventTab) + fLastMouseEventTab->MouseMoved(where, B_EXITED_VIEW, dragMessage); + fLastMouseEventTab = tab; + if (fLastMouseEventTab) + fLastMouseEventTab->MouseMoved(where, B_ENTERED_VIEW, dragMessage); + else + fController->SetToolTip("Double-click or middle-click to open new tab."); + } +} + + +void +TabContainerView::_ValidateTabVisibility() +{ + if (fFirstVisibleTabIndex > MaxFirstVisibleTabIndex()) + SetFirstVisibleTabIndex(MaxFirstVisibleTabIndex()); + else + _UpdateTabVisibility(); +} + + +void +TabContainerView::_UpdateTabVisibility() +{ + float availableWidth = _AvailableWidthForTabs(); + if (availableWidth < 0) + return; + float visibleTabsWidth = 0; + + bool canScrollTabsLeft = fFirstVisibleTabIndex > 0; + bool canScrollTabsRight = false; + + BGroupLayout* layout = GroupLayout(); + int32 count = layout->CountItems() - 1; + for (int32 i = 0; i < count; i++) { + TabLayoutItem* item = dynamic_cast<TabLayoutItem*>( + layout->ItemAt(i)); + if (i < fFirstVisibleTabIndex) + item->SetVisible(false); + else { + float itemWidth = item->MinSize().width; + bool visible = availableWidth >= visibleTabsWidth + itemWidth; + item->SetVisible(visible && !canScrollTabsRight); + visibleTabsWidth += itemWidth; + if (!visible) + canScrollTabsRight = true; + } + } + fController->UpdateTabScrollability(canScrollTabsLeft, canScrollTabsRight); +} + + +float +TabContainerView::_AvailableWidthForTabs() const +{ + float width = Bounds().Width() - 10; + // TODO: Don't really know why -10 is needed above. + + float left; + float right; + GroupLayout()->GetInsets(&left, NULL, &right, NULL); + width -= left + right; + + return width; +} + + +void +TabContainerView::_SendFakeMouseMoved() +{ + BPoint where; + uint32 buttons; + GetMouse(&where, &buttons, false); + if (Bounds().Contains(where)) + _MouseMoved(where, B_INSIDE_VIEW, NULL); +} + diff --git a/src/apps/webpositive/tabview/TabContainerView.h b/src/apps/webpositive/tabview/TabContainerView.h new file mode 100644 index 0000000000..a5a7ee04fa --- /dev/null +++ b/src/apps/webpositive/tabview/TabContainerView.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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. + */ +#ifndef TAB_CONTAINER_VIEW_H +#define TAB_CONTAINER_VIEW_H + +#include <GroupView.h> + + +class TabView; + + +class TabContainerView : public BGroupView { +public: + class Controller { + public: + virtual void TabSelected(int32 tabIndex) = 0; + virtual bool HasFrames() = 0; + virtual TabView* CreateTabView() = 0; + virtual void DoubleClickOutsideTabs() = 0; + virtual void UpdateTabScrollability(bool canScrollLeft, + bool canScrollRight) = 0; + virtual void SetToolTip(const BString& text) = 0; + }; + +public: + TabContainerView(Controller* controller); + virtual ~TabContainerView(); + + virtual BSize MinSize(); + + virtual void MessageReceived(BMessage*); + + virtual void Draw(BRect updateRect); + + virtual void MouseDown(BPoint where); + virtual void MouseUp(BPoint where); + virtual void MouseMoved(BPoint where, uint32 transit, + const BMessage* dragMessage); + + virtual void DoLayout(); + + void AddTab(const char* label, int32 index = -1); + void AddTab(TabView* tab, int32 index = -1); + TabView* RemoveTab(int32 index); + TabView* TabAt(int32 index) const; + + int32 IndexOf(TabView* tab) const; + + void SelectTab(int32 tabIndex); + void SelectTab(TabView* tab); + + void SetTabLabel(int32 tabIndex, const char* label); + + void SetFirstVisibleTabIndex(int32 index); + int32 FirstVisibleTabIndex() const; + int32 MaxFirstVisibleTabIndex() const; + + bool CanScrollLeft() const; + bool CanScrollRight() const; + +private: + TabView* _TabAt(const BPoint& where) const; + void _MouseMoved(BPoint where, uint32 transit, + const BMessage* dragMessage); + void _ValidateTabVisibility(); + void _UpdateTabVisibility(); + float _AvailableWidthForTabs() const; + void _SendFakeMouseMoved(); + +private: + TabView* fLastMouseEventTab; + bool fMouseDown; + uint32 fClickCount; + TabView* fSelectedTab; + Controller* fController; + int32 fFirstVisibleTabIndex; +}; + +#endif // TAB_CONTAINER_VIEW_H diff --git a/src/apps/webpositive/tabview/TabManager.cpp b/src/apps/webpositive/tabview/TabManager.cpp new file mode 100644 index 0000000000..f25b67769d --- /dev/null +++ b/src/apps/webpositive/tabview/TabManager.cpp @@ -0,0 +1,940 @@ +/* + * Copyright (C) 2010 Rene Gollent <rene@gollent.com> + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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 "TabManager.h" + +#include <stdio.h> + +#include <Application.h> +#include <AbstractLayoutItem.h> +#include <Bitmap.h> +#include <Button.h> +#include <CardLayout.h> +#include <ControlLook.h> +#include <GroupView.h> +#include <MenuBar.h> +#include <MenuItem.h> +#include <PopUpMenu.h> +#include <Rect.h> +#include <SpaceLayoutItem.h> +#include <Window.h> + +#include "TabContainerView.h" +#include "TabView.h" + + +const static BString kEmptyString; + + +// #pragma mark - Helper classes + + +class TabButton : public BButton { +public: + TabButton(BMessage* message) + : BButton("", message) + { + } + + virtual BSize MinSize() + { + return BSize(12, 12); + } + + virtual BSize MaxSize() + { + return BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED); + } + + virtual BSize PreferredSize() + { + return MinSize(); + } + + virtual void Draw(BRect updateRect) + { + BRect bounds(Bounds()); + rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); + SetHighColor(tint_color(base, B_DARKEN_2_TINT)); + StrokeLine(bounds.LeftBottom(), bounds.RightBottom()); + bounds.bottom--; + uint32 flags = be_control_look->Flags(this); + uint32 borders = BControlLook::B_TOP_BORDER + | BControlLook::B_BOTTOM_BORDER; + be_control_look->DrawInactiveTab(this, bounds, updateRect, base, + 0, borders); + if (IsEnabled()) { + rgb_color button = tint_color(base, 1.07); + be_control_look->DrawButtonBackground(this, bounds, updateRect, + button, flags, 0); + } + + bounds.left = (bounds.left + bounds.right) / 2 - 6; + bounds.top = (bounds.top + bounds.bottom) / 2 - 6; + bounds.right = bounds.left + 12; + bounds.bottom = bounds.top + 12; + DrawSymbol(bounds, updateRect, base); + } + + virtual void DrawSymbol(BRect frame, const BRect& updateRect, + const rgb_color& base) + { + } +}; + + +class ScrollLeftTabButton : public TabButton { +public: + ScrollLeftTabButton(BMessage* message) + : TabButton(message) + { + } + + virtual void DrawSymbol(BRect frame, const BRect& updateRect, + const rgb_color& base) + { + float tint = IsEnabled() ? B_DARKEN_4_TINT : B_DARKEN_1_TINT; + be_control_look->DrawArrowShape(this, frame, updateRect, + base, BControlLook::B_LEFT_ARROW, 0, tint); + } +}; + + +class ScrollRightTabButton : public TabButton { +public: + ScrollRightTabButton(BMessage* message) + : TabButton(message) + { + } + + virtual void DrawSymbol(BRect frame, const BRect& updateRect, + const rgb_color& base) + { + frame.OffsetBy(1, 0); + float tint = IsEnabled() ? B_DARKEN_4_TINT : B_DARKEN_1_TINT; + be_control_look->DrawArrowShape(this, frame, updateRect, + base, BControlLook::B_RIGHT_ARROW, 0, tint); + } +}; + + +class NewTabButton : public TabButton { +public: + NewTabButton(BMessage* message) + : TabButton(message) + { + SetToolTip("New tab (Cmd-T)"); + } + + virtual BSize MinSize() + { + return BSize(18, 12); + } + + virtual void DrawSymbol(BRect frame, const BRect& updateRect, + const rgb_color& base) + { + SetHighColor(tint_color(base, B_DARKEN_4_TINT)); + float inset = 3; + frame.InsetBy(2, 2); + frame.top++; + frame.left++; + FillRoundRect(BRect(frame.left, frame.top + inset, + frame.right, frame.bottom - inset), 1, 1); + FillRoundRect(BRect(frame.left + inset, frame.top, + frame.right - inset, frame.bottom), 1, 1); + } +}; + + +class TabMenuTabButton : public TabButton { +public: + TabMenuTabButton(BMessage* message) + : TabButton(message) + { + } + + virtual BSize MinSize() + { + return BSize(18, 12); + } + + virtual void DrawSymbol(BRect frame, const BRect& updateRect, + const rgb_color& base) + { + be_control_look->DrawArrowShape(this, frame, updateRect, + base, BControlLook::B_DOWN_ARROW, 0, B_DARKEN_4_TINT); + } + + virtual void MouseDown(BPoint point) + { + if (!IsEnabled()) + return; + + // Invoke must be called before setting B_CONTROL_ON + // for the button to stay "down" + Invoke(); + SetValue(B_CONTROL_ON); + } + + virtual void MouseUp(BPoint point) + { + // Do nothing + } + + void MenuClosed() + { + SetValue(B_CONTROL_OFF); + } +}; + + +enum { + MSG_SCROLL_TABS_LEFT = 'stlt', + MSG_SCROLL_TABS_RIGHT = 'strt', + MSG_OPEN_TAB_MENU = 'otmn' +}; + + +class TabContainerGroup : public BGroupView { +public: + TabContainerGroup(TabContainerView* tabContainerView) + : + BGroupView(B_HORIZONTAL, 0.0), + fTabContainerView(tabContainerView), + fScrollLeftTabButton(NULL), + fScrollRightTabButton(NULL), + fTabMenuButton(NULL) + { + } + + virtual void AttachedToWindow() + { + if (fScrollLeftTabButton != NULL) + fScrollLeftTabButton->SetTarget(this); + if (fScrollRightTabButton != NULL) + fScrollRightTabButton->SetTarget(this); + if (fTabMenuButton != NULL) + fTabMenuButton->SetTarget(this); + } + + virtual void MessageReceived(BMessage* message) + { + switch (message->what) { + case MSG_SCROLL_TABS_LEFT: + fTabContainerView->SetFirstVisibleTabIndex( + fTabContainerView->FirstVisibleTabIndex() - 1); + break; + case MSG_SCROLL_TABS_RIGHT: + fTabContainerView->SetFirstVisibleTabIndex( + fTabContainerView->FirstVisibleTabIndex() + 1); + break; + case MSG_OPEN_TAB_MENU: + { + BPopUpMenu* tabMenu = new BPopUpMenu("tab menu", true, false); + int tabCount = fTabContainerView->GetLayout()->CountItems(); + for (int i = 0; i < tabCount; i++) { + TabView* tab = fTabContainerView->TabAt(i); + if (tab) { + BMenuItem* item = new BMenuItem(tab->Label(), NULL); + tabMenu->AddItem(item); + if (tab->IsFront()) + item->SetMarked(true); + } + } + + // Force layout to get the final menu size. InvalidateLayout() + // did not seem to work here. + tabMenu->AttachedToWindow(); + BRect buttonFrame = fTabMenuButton->Frame(); + BRect menuFrame = tabMenu->Frame(); + BPoint openPoint = ConvertToScreen(buttonFrame.LeftBottom()); + // Open with the right side of the menu aligned with the right + // side of the button and a little below. + openPoint.x -= menuFrame.Width() - buttonFrame.Width(); + openPoint.y += 2; + + BMenuItem *selected = tabMenu->Go(openPoint, false, false, + ConvertToScreen(buttonFrame)); + if (selected) { + selected->SetMarked(true); + int32 index = tabMenu->IndexOf(selected); + if (index != B_ERROR) + fTabContainerView->SelectTab(index); + } + fTabMenuButton->MenuClosed(); + delete tabMenu; + + break; + } + default: + BGroupView::MessageReceived(message); + break; + } + } + + void AddScrollLeftButton(TabButton* button) + { + fScrollLeftTabButton = button; + GroupLayout()->AddView(button, 0.0f); + } + + void AddScrollRightButton(TabButton* button) + { + fScrollRightTabButton = button; + GroupLayout()->AddView(button, 0.0f); + } + + void AddTabMenuButton(TabMenuTabButton* button) + { + fTabMenuButton = button; + GroupLayout()->AddView(button, 0.0f); + } + + void EnableScrollButtons(bool canScrollLeft, bool canScrollRight) + { + fScrollLeftTabButton->SetEnabled(canScrollLeft); + fScrollRightTabButton->SetEnabled(canScrollRight); + if (!canScrollLeft && !canScrollRight) { + // hide scroll buttons + } else { + // show scroll buttons + } + } + +private: + TabContainerView* fTabContainerView; + TabButton* fScrollLeftTabButton; + TabButton* fScrollRightTabButton; + TabMenuTabButton* fTabMenuButton; +}; + + +class TabButtonContainer : public BGroupView { +public: + TabButtonContainer() + : + BGroupView(B_HORIZONTAL, 0.0) + { + SetFlags(Flags() | B_WILL_DRAW); + SetViewColor(B_TRANSPARENT_COLOR); + SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + GroupLayout()->SetInsets(0, 6, 0, 0); + } + + virtual void Draw(BRect updateRect) + { + BRect bounds(Bounds()); + rgb_color base = LowColor(); + be_control_look->DrawInactiveTab(this, bounds, updateRect, + base, 0, BControlLook::B_TOP_BORDER); + } +}; + + +class TabManagerController : public TabContainerView::Controller { +public: + TabManagerController(TabManager* manager); + + virtual ~TabManagerController(); + + virtual void TabSelected(int32 index) + { + fManager->SelectTab(index); + } + + virtual bool HasFrames() + { + return false; + } + + virtual TabView* CreateTabView(); + + virtual void DoubleClickOutsideTabs(); + + virtual void UpdateTabScrollability(bool canScrollLeft, + bool canScrollRight) + { + fTabContainerGroup->EnableScrollButtons(canScrollLeft, canScrollRight); + } + + virtual void SetToolTip(const BString& text) + { + if (fCurrentToolTip == text) + return; + fCurrentToolTip = text; + fManager->GetTabContainerView()->HideToolTip(); + fManager->GetTabContainerView()->SetToolTip( + reinterpret_cast<BToolTip*>(NULL)); + fManager->GetTabContainerView()->SetToolTip(fCurrentToolTip.String()); + } + + void CloseTab(int32 index); + + void SetCloseButtonsAvailable(bool available) + { + fCloseButtonsAvailable = available; + } + + bool CloseButtonsAvailable() const + { + return fCloseButtonsAvailable; + } + + void SetDoubleClickOutsideTabsMessage(const BMessage& message, + const BMessenger& target); + + void SetTabContainerGroup(TabContainerGroup* tabContainerGroup) + { + fTabContainerGroup = tabContainerGroup; + } + +private: + TabManager* fManager; + TabContainerGroup* fTabContainerGroup; + bool fCloseButtonsAvailable; + BMessage* fDoubleClickOutsideTabsMessage; + BMessenger fTarget; + BString fCurrentToolTip; +}; + + +// #pragma mark - WebTabView + + +class WebTabView : public TabView { +public: + WebTabView(TabManagerController* controller); + ~WebTabView(); + + virtual BSize MaxSize(); + + virtual void DrawContents(BView* owner, BRect frame, const BRect& updateRect, + bool isFirst, bool isLast, bool isFront); + + virtual void MouseDown(BPoint where, uint32 buttons); + virtual void MouseUp(BPoint where); + virtual void MouseMoved(BPoint where, uint32 transit, + const BMessage* dragMessage); + + void SetIcon(const BBitmap* icon); + +private: + void _DrawCloseButton(BView* owner, BRect& frame, const BRect& updateRect, + bool isFirst, bool isLast, bool isFront); + BRect _CloseRectFrame(BRect frame) const; + +private: + BBitmap* fIcon; + TabManagerController* fController; + bool fOverCloseRect; + bool fClicked; + bool fCloseOnMouseUp; +}; + + +WebTabView::WebTabView(TabManagerController* controller) + : + TabView(), + fIcon(NULL), + fController(controller), + fOverCloseRect(false), + fClicked(false), + fCloseOnMouseUp(false) +{ +} + + +WebTabView::~WebTabView() +{ + delete fIcon; +} + + +static const int kIconSize = 18; +static const int kIconInset = 3; + + +BSize +WebTabView::MaxSize() +{ + // Account for icon. + BSize size(TabView::MaxSize()); + size.height = max_c(size.height, kIconSize + kIconInset * 2); + if (fIcon) + size.width += kIconSize + kIconInset * 2; + // Account for close button. + size.width += size.height; + return size; +} + + +void +WebTabView::DrawContents(BView* owner, BRect frame, const BRect& updateRect, + bool isFirst, bool isLast, bool isFront) +{ + if (fController->CloseButtonsAvailable()) + _DrawCloseButton(owner, frame, updateRect, isFirst, isLast, isFront); + + if (fIcon) { + BRect iconBounds(0, 0, kIconSize - 1, kIconSize - 1); + // clip to icon bounds, if they are smaller + if (iconBounds.Contains(fIcon->Bounds())) + iconBounds = fIcon->Bounds(); + else { + // Try to scale down the icon by an even factor so the + // final size is between 14 and 18 pixel size. If this fails, + // the icon will simply be displayed at 18x18. + float scale = 2; + while ((fIcon->Bounds().Width() + 1) / scale > kIconSize) + scale *= 2; + if ((fIcon->Bounds().Width() + 1) / scale >= kIconSize - 4 + && (fIcon->Bounds().Height() + 1) / scale >= kIconSize - 4 + && (fIcon->Bounds().Height() + 1) / scale <= kIconSize) { + iconBounds.right = (fIcon->Bounds().Width() + 1) / scale - 1; + iconBounds.bottom = (fIcon->Bounds().Height() + 1) / scale - 1; + } + } + BPoint iconPos(frame.left + kIconInset - 1, + frame.top + floorf((frame.Height() - iconBounds.Height()) / 2)); + iconBounds.OffsetTo(iconPos); + owner->SetDrawingMode(B_OP_ALPHA); + owner->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); + owner->DrawBitmap(fIcon, fIcon->Bounds(), iconBounds, + B_FILTER_BITMAP_BILINEAR); + owner->SetDrawingMode(B_OP_COPY); + frame.left = frame.left + kIconSize + kIconInset * 2; + } + + TabView::DrawContents(owner, frame, updateRect, isFirst, isLast, isFront); +} + + +void +WebTabView::MouseDown(BPoint where, uint32 buttons) +{ + if (buttons & B_TERTIARY_MOUSE_BUTTON) { + fCloseOnMouseUp = true; + return; + } + + BRect closeRect = _CloseRectFrame(Frame()); + if (!fController->CloseButtonsAvailable() || !closeRect.Contains(where)) { + TabView::MouseDown(where, buttons); + return; + } + + fClicked = true; + ContainerView()->Invalidate(closeRect); +} + + +void +WebTabView::MouseUp(BPoint where) +{ + if (!fClicked && !fCloseOnMouseUp) { + TabView::MouseUp(where); + return; + } + + if (fCloseOnMouseUp && Frame().Contains(where)) { + fCloseOnMouseUp = false; + fController->CloseTab(ContainerView()->IndexOf(this)); + // Probably this object is toast now, better return here. + return; + } + + fClicked = false; + fCloseOnMouseUp = false; + + if (_CloseRectFrame(Frame()).Contains(where)) + fController->CloseTab(ContainerView()->IndexOf(this)); +} + + +void +WebTabView::MouseMoved(BPoint where, uint32 transit, + const BMessage* dragMessage) +{ + if (fController->CloseButtonsAvailable()) { + BRect closeRect = _CloseRectFrame(Frame()); + bool overCloseRect = closeRect.Contains(where); + if (overCloseRect != fOverCloseRect) { + fOverCloseRect = overCloseRect; + ContainerView()->Invalidate(closeRect); + } + } + + fController->SetToolTip(Label()); + + TabView::MouseMoved(where, transit, dragMessage); +} + + +void +WebTabView::SetIcon(const BBitmap* icon) +{ + delete fIcon; + if (icon) + fIcon = new BBitmap(icon); + else + fIcon = NULL; + LayoutItem()->InvalidateLayout(); +} + + +BRect +WebTabView::_CloseRectFrame(BRect frame) const +{ + frame.left = frame.right - frame.Height(); + return frame; +} + + +void WebTabView::_DrawCloseButton(BView* owner, BRect& frame, + const BRect& updateRect, bool isFirst, bool isLast, bool isFront) +{ + BRect closeRect = _CloseRectFrame(frame); + frame.right = closeRect.left - be_control_look->DefaultLabelSpacing(); + + closeRect.left = (closeRect.left + closeRect.right) / 2 - 3; + closeRect.right = closeRect.left + 6; + closeRect.top = (closeRect.top + closeRect.bottom) / 2 - 3; + closeRect.bottom = closeRect.top + 6; + + rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); + float tint = B_DARKEN_1_TINT; + if (!IsFront()) { + base = tint_color(base, tint); + tint *= 1.02; + } + + if (fOverCloseRect) + tint *= 1.2; + + if (fClicked && fOverCloseRect) { + BRect buttonRect(closeRect.InsetByCopy(-4, -4)); + be_control_look->DrawButtonFrame(owner, buttonRect, updateRect, + base, base, + BControlLook::B_ACTIVATED | BControlLook::B_BLEND_FRAME); + be_control_look->DrawButtonBackground(owner, buttonRect, updateRect, + base, BControlLook::B_ACTIVATED); + tint *= 1.2; + closeRect.OffsetBy(1, 1); + } + + base = tint_color(base, tint); + owner->SetHighColor(base); + owner->SetPenSize(2); + owner->StrokeLine(closeRect.LeftTop(), closeRect.RightBottom()); + owner->StrokeLine(closeRect.LeftBottom(), closeRect.RightTop()); + owner->SetPenSize(1); +} + + +// #pragma mark - TabManagerController + + +TabManagerController::TabManagerController(TabManager* manager) + : + fManager(manager), + fTabContainerGroup(NULL), + fCloseButtonsAvailable(false), + fDoubleClickOutsideTabsMessage(NULL) +{ +} + + +TabManagerController::~TabManagerController() +{ + delete fDoubleClickOutsideTabsMessage; +} + + +TabView* +TabManagerController::CreateTabView() +{ + return new WebTabView(this); +} + + +void +TabManagerController::DoubleClickOutsideTabs() +{ + fTarget.SendMessage(fDoubleClickOutsideTabsMessage); +} + + +void +TabManagerController::CloseTab(int32 index) +{ + fManager->CloseTab(index); +} + + +void +TabManagerController::SetDoubleClickOutsideTabsMessage(const BMessage& message, + const BMessenger& target) +{ + delete fDoubleClickOutsideTabsMessage; + fDoubleClickOutsideTabsMessage = new BMessage(message); + fTarget = target; +} + + +// #pragma mark - TabManager + + +TabManager::TabManager(const BMessenger& target, BMessage* newTabMessage) + : + fController(new TabManagerController(this)), + fTarget(target) +{ + fController->SetDoubleClickOutsideTabsMessage(*newTabMessage, + be_app_messenger); + + fContainerView = new BView("web view container", 0); + fCardLayout = new BCardLayout(); + fContainerView->SetLayout(fCardLayout); + + fTabContainerView = new TabContainerView(fController); + fTabContainerGroup = new TabContainerGroup(fTabContainerView); + fTabContainerGroup->GroupLayout()->SetInsets(0, 3, 0, 0); + + fController->SetTabContainerGroup(fTabContainerGroup); + +#if INTEGRATE_MENU_INTO_TAB_BAR + fMenu = new BMenu("Menu"); + BMenuBar* menuBar = new BMenuBar("Menu bar"); + menuBar->AddItem(fMenu); + TabButtonContainer* menuBarContainer = new TabButtonContainer(); + menuBarContainer->GroupLayout()->AddView(menuBar); + fTabContainerGroup->GroupLayout()->AddView(menuBarContainer, 0.0f); +#endif + + fTabContainerGroup->GroupLayout()->AddView(fTabContainerView); + fTabContainerGroup->AddScrollLeftButton(new ScrollLeftTabButton( + new BMessage(MSG_SCROLL_TABS_LEFT))); + fTabContainerGroup->AddScrollRightButton(new ScrollRightTabButton( + new BMessage(MSG_SCROLL_TABS_RIGHT))); + NewTabButton* newTabButton = new NewTabButton(newTabMessage); + newTabButton->SetTarget(be_app); + fTabContainerGroup->GroupLayout()->AddView(newTabButton, 0.0f); + fTabContainerGroup->AddTabMenuButton(new TabMenuTabButton( + new BMessage(MSG_OPEN_TAB_MENU))); +} + + +TabManager::~TabManager() +{ + delete fController; +} + + +void +TabManager::SetTarget(const BMessenger& target) +{ + fTarget = target; +} + + +const BMessenger& +TabManager::Target() const +{ + return fTarget; +} + + +#if INTEGRATE_MENU_INTO_TAB_BAR +BMenu* +TabManager::Menu() const +{ + return fMenu; +} +#endif + + +BView* +TabManager::TabGroup() const +{ + return fTabContainerGroup; +} + + +BView* +TabManager::GetTabContainerView() const +{ + return fTabContainerView; +} + + +BView* +TabManager::ContainerView() const +{ + return fContainerView; +} + + +BView* +TabManager::ViewForTab(int32 tabIndex) const +{ + BLayoutItem* item = fCardLayout->ItemAt(tabIndex); + if (item != NULL) + return item->View(); + return NULL; +} + + +int32 +TabManager::TabForView(const BView* containedView) const +{ + int32 count = fCardLayout->CountItems(); + for (int32 i = 0; i < count; i++) { + BLayoutItem* item = fCardLayout->ItemAt(i); + if (item->View() == containedView) + return i; + } + return -1; +} + + +bool +TabManager::HasView(const BView* containedView) const +{ + return TabForView(containedView) >= 0; +} + + +void +TabManager::SelectTab(int32 tabIndex) +{ + fCardLayout->SetVisibleItem(tabIndex); + fTabContainerView->SelectTab(tabIndex); + + BMessage message(TAB_CHANGED); + message.AddInt32("tab index", tabIndex); + fTarget.SendMessage(&message); +} + + +void +TabManager::SelectTab(const BView* containedView) +{ + int32 tabIndex = TabForView(containedView); + if (tabIndex > 0) + SelectTab(tabIndex); +} + + +int32 +TabManager::SelectedTabIndex() const +{ + return fCardLayout->VisibleIndex(); +} + + +void +TabManager::CloseTab(int32 tabIndex) +{ + BMessage message(CLOSE_TAB); + message.AddInt32("tab index", tabIndex); + fTarget.SendMessage(&message); +} + + +void +TabManager::AddTab(BView* view, const char* label, int32 index) +{ + fTabContainerView->AddTab(label, index); + fCardLayout->AddView(index, view); +} + + +BView* +TabManager::RemoveTab(int32 index) +{ + // It's important to remove the view first, since + // removing the tab will preliminary mess with the selected tab + // and then item count of card layout and tab container will not + // match yet. + BLayoutItem* item = fCardLayout->RemoveItem(index); + if (item == NULL) + return NULL; + + TabView* tab = fTabContainerView->RemoveTab(index); + delete tab; + + BView* view = item->View(); + delete item; + return view; +} + + +int32 +TabManager::CountTabs() const +{ + return fCardLayout->CountItems(); +} + + +void +TabManager::SetTabLabel(int32 tabIndex, const char* label) +{ + fTabContainerView->SetTabLabel(tabIndex, label); +} + +const BString& +TabManager::TabLabel(int32 tabIndex) +{ + TabView* tab = fTabContainerView->TabAt(tabIndex); + if (tab) + return tab->Label(); + else + return kEmptyString; +} + +void +TabManager::SetTabIcon(const BView* containedView, const BBitmap* icon) +{ + WebTabView* tab = dynamic_cast<WebTabView*>(fTabContainerView->TabAt( + TabForView(containedView))); + if (tab) + tab->SetIcon(icon); +} + + +void +TabManager::SetCloseButtonsAvailable(bool available) +{ + if (available == fController->CloseButtonsAvailable()) + return; + fController->SetCloseButtonsAvailable(available); + fTabContainerView->Invalidate(); +} + + diff --git a/src/apps/webpositive/tabview/TabManager.h b/src/apps/webpositive/tabview/TabManager.h new file mode 100644 index 0000000000..97cd65c898 --- /dev/null +++ b/src/apps/webpositive/tabview/TabManager.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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. + */ + +#ifndef TAB_MANAGER_H +#define TAB_MANAGER_H + +#include <Messenger.h> +#include <TabView.h> + +enum { + TAB_CHANGED = 'tcha', + CLOSE_TAB = 'cltb' +}; + +class BBitmap; +class BCardLayout; +class BGroupView; +class BMenu; +class TabContainerGroup; +class TabContainerView; +class TabManagerController; + +class TabManager { +public: + TabManager(const BMessenger& target, + BMessage* newTabMessage); + virtual ~TabManager(); + + void SetTarget(const BMessenger& target); + const BMessenger& Target() const; + +#if INTEGRATE_MENU_INTO_TAB_BAR + BMenu* Menu() const; +#endif + + BView* TabGroup() const; + BView* GetTabContainerView() const; + BView* ContainerView() const; + + BView* ViewForTab(int32 tabIndex) const; + int32 TabForView(const BView* containedView) const; + bool HasView(const BView* containedView) const; + + void SelectTab(int32 tabIndex); + void SelectTab(const BView* containedView); + int32 SelectedTabIndex() const; + void CloseTab(int32 tabIndex); + + void AddTab(BView* view, const char* label, + int32 index = -1); + BView* RemoveTab(int32 index); + int32 CountTabs() const; + + void SetTabLabel(int32 tabIndex, const char* label); + const BString& TabLabel(int32); + void SetTabIcon(const BView* containedView, + const BBitmap* icon); + void SetCloseButtonsAvailable(bool available); + +private: +#if INTEGRATE_MENU_INTO_TAB_BAR + BMenu* fMenu; +#endif + TabContainerGroup* fTabContainerGroup; + TabContainerView* fTabContainerView; + BView* fContainerView; + BCardLayout* fCardLayout; + TabManagerController* fController; + + BMessenger fTarget; +}; + +#endif // TAB_MANAGER_H diff --git a/src/apps/webpositive/tabview/TabView.cpp b/src/apps/webpositive/tabview/TabView.cpp new file mode 100644 index 0000000000..3bb78ec185 --- /dev/null +++ b/src/apps/webpositive/tabview/TabView.cpp @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2010 Rene Gollent <rene@gollent.com> + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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 "TabView.h" + +#include <stdio.h> + +#include <Application.h> +#include <Bitmap.h> +#include <Button.h> +#include <CardLayout.h> +#include <ControlLook.h> +#include <GroupView.h> +#include <MenuBar.h> +#include <SpaceLayoutItem.h> +#include <Window.h> + +#include "TabContainerView.h" + + +// #pragma mark - TabView + + +TabView::TabView() + : + fContainerView(NULL), + fLayoutItem(new TabLayoutItem(this)), + fLabel() +{ +} + + +TabView::~TabView() +{ + // The layout item is deleted for us by the layout which contains it. + if (!fContainerView) + delete fLayoutItem; +} + + +BSize +TabView::MinSize() +{ + BSize size(MaxSize()); + size.width = 60.0f; + return size; +} + + +BSize +TabView::PreferredSize() +{ + return MaxSize(); +} + + +BSize +TabView::MaxSize() +{ + float extra = be_control_look->DefaultLabelSpacing(); + float labelWidth = 300.0f; + return BSize(labelWidth, _LabelHeight() + extra); +} + + +void +TabView::Draw(BRect updateRect) +{ + BRect frame(fLayoutItem->Frame()); + if (fIsFront) { + // Extend the front tab outward left/right in order to merge + // the frames of adjacent tabs. + if (!fIsFirst) + frame.left--; + if (!fIsLast) + frame.right++; + + frame.bottom++; + } + DrawBackground(fContainerView, frame, updateRect, fIsFirst, fIsLast, + fIsFront); + if (fIsFront) { + frame.top += 3.0f; + if (!fIsFirst) + frame.left++; + if (!fIsLast) + frame.right--; + } else + frame.top += 6.0f; + float spacing = be_control_look->DefaultLabelSpacing(); + frame.InsetBy(spacing, spacing / 2); + DrawContents(fContainerView, frame, updateRect, fIsFirst, fIsLast, + fIsFront); +} + + +void +TabView::DrawBackground(BView* owner, BRect frame, const BRect& updateRect, + bool isFirst, bool isLast, bool isFront) +{ + rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); + uint32 borders = BControlLook::B_TOP_BORDER + | BControlLook::B_BOTTOM_BORDER; + if (isFirst) + borders |= BControlLook::B_LEFT_BORDER; + if (isLast) + borders |= BControlLook::B_RIGHT_BORDER; + if (isFront) { + be_control_look->DrawActiveTab(owner, frame, updateRect, base, + 0, borders); + } else { + be_control_look->DrawInactiveTab(owner, frame, updateRect, base, + 0, borders); + } +} + + +void +TabView::DrawContents(BView* owner, BRect frame, const BRect& updateRect, + bool isFirst, bool isLast, bool isFront) +{ + rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); + be_control_look->DrawLabel(owner, fLabel.String(), frame, updateRect, + base, 0, BAlignment(B_ALIGN_LEFT, B_ALIGN_MIDDLE)); +} + + +void +TabView::MouseDown(BPoint where, uint32 buttons) +{ + fContainerView->SelectTab(this); +} + + +void +TabView::MouseUp(BPoint where) +{ +} + + +void +TabView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage) +{ +} + + +void +TabView::SetIsFront(bool isFront) +{ + Update(fIsFirst, fIsLast, isFront); +} + + +bool +TabView::IsFront() const +{ + return fIsFront; +} + + +void +TabView::SetIsLast(bool isLast) +{ + Update(fIsFirst, isLast, fIsFront); +} + + +void +TabView::Update(bool isFirst, bool isLast, bool isFront) +{ + if (fIsFirst == isFirst && fIsLast == isLast && fIsFront == isFront) + return; + fIsFirst = isFirst; + fIsLast = isLast; + fIsFront = isFront; + + fLayoutItem->InvalidateContainer(); +} + + +void +TabView::SetContainerView(TabContainerView* containerView) +{ + fContainerView = containerView; +} + + +TabContainerView* +TabView::ContainerView() const +{ + return fContainerView; +} + + +BLayoutItem* +TabView::LayoutItem() const +{ + return fLayoutItem; +} + + +void +TabView::SetLabel(const char* label) +{ + if (fLabel == label) + return; + fLabel = label; + fLayoutItem->InvalidateLayout(); +} + + +const BString& +TabView::Label() const +{ + return fLabel; +} + + +BRect +TabView::Frame() const +{ + return fLayoutItem->Frame(); +} + + +float +TabView::_LabelHeight() const +{ + font_height fontHeight; + fContainerView->GetFontHeight(&fontHeight); + return ceilf(fontHeight.ascent) + ceilf(fontHeight.descent); +} + + +// #pragma mark - TabLayoutItem + + +TabLayoutItem::TabLayoutItem(TabView* parent) + : + fParent(parent), + fVisible(true) +{ +} + + +bool +TabLayoutItem::IsVisible() +{ + return fVisible; +} + + +void +TabLayoutItem::SetVisible(bool visible) +{ + if (fVisible == visible) + return; + + fVisible = visible; + + InvalidateContainer(); + fParent->ContainerView()->InvalidateLayout(); +} + + +BRect +TabLayoutItem::Frame() +{ + return fFrame; +} + + +void +TabLayoutItem::SetFrame(BRect frame) +{ + BRect dirty = fFrame; + fFrame = frame; + dirty = dirty | fFrame; + InvalidateContainer(dirty); +} + + +BView* +TabLayoutItem::View() +{ + return NULL; +} + + +BSize +TabLayoutItem::BaseMinSize() +{ + return fParent->MinSize(); +} + + +BSize +TabLayoutItem::BaseMaxSize() +{ + return fParent->MaxSize(); +} + + +BSize +TabLayoutItem::BasePreferredSize() +{ + return fParent->PreferredSize(); +} + + +BAlignment +TabLayoutItem::BaseAlignment() +{ + return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); +} + + +TabView* +TabLayoutItem::Parent() const +{ + return fParent; +} + + +void +TabLayoutItem::InvalidateContainer() +{ + InvalidateContainer(Frame()); +} + + +void +TabLayoutItem::InvalidateContainer(BRect frame) +{ + // Invalidate more than necessary, to help the TabContainerView + // redraw the parts outside any tabs... + frame.bottom++; + frame.right++; + fParent->ContainerView()->Invalidate(frame); +} diff --git a/src/apps/webpositive/tabview/TabView.h b/src/apps/webpositive/tabview/TabView.h new file mode 100644 index 0000000000..9f73c5a994 --- /dev/null +++ b/src/apps/webpositive/tabview/TabView.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> + * + * 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. + */ +#ifndef TAB_VIEW_H +#define TAB_VIEW_H + +#include <AbstractLayoutItem.h> +#include <Rect.h> +#include <String.h> + + +class BMessage; +class BView; +class TabContainerView; +class TabLayoutItem; + + +class TabView { +public: + TabView(); + virtual ~TabView(); + + virtual BSize MinSize(); + virtual BSize PreferredSize(); + virtual BSize MaxSize(); + + void Draw(BRect updateRect); + virtual void DrawBackground(BView* owner, BRect frame, + const BRect& updateRect, bool isFirst, + bool isLast, bool isFront); + virtual void DrawContents(BView* owner, BRect frame, + const BRect& updateRect, bool isFirst, + bool isLast, bool isFront); + + virtual void MouseDown(BPoint where, uint32 buttons); + virtual void MouseUp(BPoint where); + virtual void MouseMoved(BPoint where, uint32 transit, + const BMessage* dragMessage); + + void SetIsFront(bool isFront); + bool IsFront() const; + void SetIsLast(bool isLast); + virtual void Update(bool isFirst, bool isLast, + bool isFront); + + BLayoutItem* LayoutItem() const; + void SetContainerView( + TabContainerView* containerView); + TabContainerView* ContainerView() const; + + void SetLabel(const char* label); + const BString& Label() const; + + BRect Frame() const; + +private: + float _LabelHeight() const; + +private: + TabContainerView* fContainerView; + TabLayoutItem* fLayoutItem; + + BString fLabel; + + bool fIsFirst; + bool fIsLast; + bool fIsFront; +}; + + +class TabLayoutItem : public BAbstractLayoutItem { +public: + TabLayoutItem(TabView* parent); + + virtual bool IsVisible(); + virtual void SetVisible(bool visible); + + virtual BRect Frame(); + virtual void SetFrame(BRect frame); + + virtual BView* View(); + + virtual BSize BaseMinSize(); + virtual BSize BaseMaxSize(); + virtual BSize BasePreferredSize(); + virtual BAlignment BaseAlignment(); + + TabView* Parent() const; + void InvalidateContainer(); + void InvalidateContainer(BRect frame); +private: + TabView* fParent; + BRect fFrame; + bool fVisible; +}; + + + +#endif // TAB_VIEW_H |
