From 0b45d6ebeab4943fd4ad88311e422f8190ebdf51 Mon Sep 17 00:00:00 2001 From: Oleg Shparber Date: Sun, 24 Mar 2019 22:14:52 -0400 Subject: [PATCH] feat(browser,sidebar,ui): implement real tabs (#1081) Adds actual independent tabs instead of swapping sidebar and webview. --- src/libs/CMakeLists.txt | 1 + src/libs/browser/webview.cpp | 3 +- src/libs/sidebar/CMakeLists.txt | 11 + src/libs/sidebar/container.cpp | 70 ++++ src/libs/sidebar/container.h | 60 ++++ src/libs/sidebar/proxyview.cpp | 73 ++++ src/libs/sidebar/proxyview.h | 54 +++ src/libs/sidebar/view.cpp | 30 ++ src/libs/sidebar/view.h | 44 +++ src/libs/sidebar/viewprovider.cpp | 30 ++ src/libs/sidebar/viewprovider.h | 51 +++ src/libs/ui/CMakeLists.txt | 5 +- src/libs/ui/browsertab.cpp | 223 ++++++++++++ src/libs/ui/browsertab.h | 81 +++++ src/libs/ui/mainwindow.cpp | 501 ++++----------------------- src/libs/ui/mainwindow.h | 35 +- src/libs/ui/mainwindow.ui | 178 +--------- src/libs/ui/searchsidebar.cpp | 334 ++++++++++++++++++ src/libs/ui/searchsidebar.h | 95 +++++ src/libs/ui/sidebarviewprovider.cpp | 45 +++ src/libs/ui/sidebarviewprovider.h | 50 +++ src/libs/ui/widgets/CMakeLists.txt | 1 + src/libs/ui/widgets/layouthelper.cpp | 23 ++ src/libs/ui/widgets/layouthelper.h | 47 +++ src/libs/ui/widgets/searchedit.cpp | 19 +- src/libs/ui/widgets/searchedit.h | 3 - 26 files changed, 1439 insertions(+), 628 deletions(-) create mode 100644 src/libs/sidebar/CMakeLists.txt create mode 100644 src/libs/sidebar/container.cpp create mode 100644 src/libs/sidebar/container.h create mode 100644 src/libs/sidebar/proxyview.cpp create mode 100644 src/libs/sidebar/proxyview.h create mode 100644 src/libs/sidebar/view.cpp create mode 100644 src/libs/sidebar/view.h create mode 100644 src/libs/sidebar/viewprovider.cpp create mode 100644 src/libs/sidebar/viewprovider.h create mode 100644 src/libs/ui/browsertab.cpp create mode 100644 src/libs/ui/browsertab.h create mode 100644 src/libs/ui/searchsidebar.cpp create mode 100644 src/libs/ui/searchsidebar.h create mode 100644 src/libs/ui/sidebarviewprovider.cpp create mode 100644 src/libs/ui/sidebarviewprovider.h create mode 100644 src/libs/ui/widgets/layouthelper.cpp create mode 100644 src/libs/ui/widgets/layouthelper.h diff --git a/src/libs/CMakeLists.txt b/src/libs/CMakeLists.txt index 27232678b..f20ce033d 100644 --- a/src/libs/CMakeLists.txt +++ b/src/libs/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(browser) add_subdirectory(core) add_subdirectory(registry) +add_subdirectory(sidebar) add_subdirectory(ui) add_subdirectory(util) diff --git a/src/libs/browser/webview.cpp b/src/libs/browser/webview.cpp index c716db80a..6c415002a 100644 --- a/src/libs/browser/webview.cpp +++ b/src/libs/browser/webview.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -101,7 +102,7 @@ void WebView::resetZoom() QWebView *WebView::createWindow(QWebPage::WebWindowType type) { Q_UNUSED(type) - return Core::Application::instance()->mainWindow()->createTab()->m_webView; + return Core::Application::instance()->mainWindow()->createTab()->webControl()->m_webView; } void WebView::contextMenuEvent(QContextMenuEvent *event) diff --git a/src/libs/sidebar/CMakeLists.txt b/src/libs/sidebar/CMakeLists.txt new file mode 100644 index 000000000..4332b8fc7 --- /dev/null +++ b/src/libs/sidebar/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library(Sidebar + container.cpp + proxyview.cpp + view.cpp + viewprovider.cpp +) + +target_link_libraries(Sidebar) + +find_package(Qt5 COMPONENTS Widgets REQUIRED) +target_link_libraries(Sidebar Qt5::Widgets) diff --git a/src/libs/sidebar/container.cpp b/src/libs/sidebar/container.cpp new file mode 100644 index 000000000..051650337 --- /dev/null +++ b/src/libs/sidebar/container.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + +#include "container.h" + +#include "view.h" + +#include +#include +#include + +#include +#include +#include + +#include + +using namespace Zeal; +using namespace Zeal::Sidebar; + +Container::Container(QWidget *parent) + : QWidget(parent) +{ + setMinimumWidth(150); + + // Setup splitter. + m_splitter = new QSplitter(); + m_splitter->setOrientation(Qt::Vertical); + connect(m_splitter, &QSplitter::splitterMoved, this, [this]() { + Core::Application::instance()->settings()->tocSplitterState = m_splitter->saveState(); + }); + + // Setup main layout. + auto layout = WidgetUi::LayoutHelper::createBorderlessLayout(); + layout->addWidget(m_splitter); + setLayout(layout); +} + +Container::~Container() +{ + +} + +void Container::addView(View *view) +{ + if (m_views.contains(view)) + return; + + m_views.append(view); + m_splitter->addWidget(view); +} diff --git a/src/libs/sidebar/container.h b/src/libs/sidebar/container.h new file mode 100644 index 000000000..73a56e130 --- /dev/null +++ b/src/libs/sidebar/container.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + +#ifndef ZEAL_SIDEBAR_CONTAINER_H +#define ZEAL_SIDEBAR_CONTAINER_H + +#include + +class QSplitter; + +namespace Zeal { +namespace Sidebar { + +class View; + +// TODO: Implement view groups (alt. naming: tabs, pages) (move splitter into a group?). +class Container : public QWidget +{ + Q_OBJECT +public: + explicit Container(QWidget *parent = nullptr); + ~Container() override; + + void addView(View *view); + +public slots: + +signals: + +private: + Q_DISABLE_COPY(Container) + + QSplitter *m_splitter = nullptr; + + QList m_views; +}; + +} // namespace Sidebar +} // namespace Zeal + +#endif // ZEAL_SIDEBAR_CONTAINER_H diff --git a/src/libs/sidebar/proxyview.cpp b/src/libs/sidebar/proxyview.cpp new file mode 100644 index 000000000..5fbd3b36e --- /dev/null +++ b/src/libs/sidebar/proxyview.cpp @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + +#include "proxyview.h" + +#include "viewprovider.h" + +#include + +#include + +using namespace Zeal; +using namespace Zeal::Sidebar; + +ProxyView::ProxyView(ViewProvider *provider, const QString &id, QWidget *parent) + : View(parent) + , m_viewProvider(provider) + , m_viewId(id) +{ + setLayout(WidgetUi::LayoutHelper::createBorderlessLayout()); + + connect(m_viewProvider, &ViewProvider::viewChanged, this, [this]() { + auto view = m_viewProvider->view(m_viewId); + if (view == nullptr) { + qWarning("ViewProvider returned invalid view!"); + return; + } + + if (m_view == view) + return; + + clearCurrentView(); + layout()->addWidget(view); + view->show(); + m_view = view; + }); +} + +ProxyView::~ProxyView() +{ + clearCurrentView(); +} + +void ProxyView::clearCurrentView() +{ + // Unparent the view, because we don't own it. + QLayout *l = layout(); + if (l->isEmpty()) + return; + + m_view->hide(); + l->removeWidget(m_view); + m_view->setParent(nullptr); +} diff --git a/src/libs/sidebar/proxyview.h b/src/libs/sidebar/proxyview.h new file mode 100644 index 000000000..2f2bac84d --- /dev/null +++ b/src/libs/sidebar/proxyview.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + +#ifndef ZEAL_SIDEBAR_PROXYVIEW_H +#define ZEAL_SIDEBAR_PROXYVIEW_H + +#include "view.h" + +namespace Zeal { +namespace Sidebar { + +class ViewProvider; + +class ProxyView final : public View +{ + Q_OBJECT +public: + explicit ProxyView(ViewProvider *provider, const QString &id = QString(), QWidget *parent = nullptr); + ~ProxyView() override; + +private: + Q_DISABLE_COPY(ProxyView) + + void clearCurrentView(); + + ViewProvider *m_viewProvider = nullptr; + QString m_viewId; + + View *m_view = nullptr; +}; + +} // namespace Sidebar +} // namespace Zeal + +#endif // ZEAL_SIDEBAR_PROXYVIEW_H diff --git a/src/libs/sidebar/view.cpp b/src/libs/sidebar/view.cpp new file mode 100644 index 000000000..b6ad0652c --- /dev/null +++ b/src/libs/sidebar/view.cpp @@ -0,0 +1,30 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + +#include "view.h" + +using namespace Zeal::Sidebar; + +View::View(QWidget *parent) + : QWidget(parent) +{ +} diff --git a/src/libs/sidebar/view.h b/src/libs/sidebar/view.h new file mode 100644 index 000000000..d94771fa5 --- /dev/null +++ b/src/libs/sidebar/view.h @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + +#ifndef ZEAL_SIDEBAR_VIEW_H +#define ZEAL_SIDEBAR_VIEW_H + +#include + +namespace Zeal { +namespace Sidebar { + +class View : public QWidget +{ + Q_OBJECT +public: + explicit View(QWidget *parent = nullptr); + +private: + Q_DISABLE_COPY(View) +}; + +} // namespace Sidebar +} // namespace Zeal + +#endif // ZEAL_SIDEBAR_VIEW_H diff --git a/src/libs/sidebar/viewprovider.cpp b/src/libs/sidebar/viewprovider.cpp new file mode 100644 index 000000000..7ec528a54 --- /dev/null +++ b/src/libs/sidebar/viewprovider.cpp @@ -0,0 +1,30 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + +#include "viewprovider.h" + +using namespace Zeal::Sidebar; + +ViewProvider::ViewProvider(QObject *parent) + : QObject(parent) +{ +} diff --git a/src/libs/sidebar/viewprovider.h b/src/libs/sidebar/viewprovider.h new file mode 100644 index 000000000..2ff509db3 --- /dev/null +++ b/src/libs/sidebar/viewprovider.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + +#ifndef ZEAL_SIDEBAR_VIEWPROVIDER_H +#define ZEAL_SIDEBAR_VIEWPROVIDER_H + +#include + +namespace Zeal { +namespace Sidebar { + +class View; + +class ViewProvider : public QObject +{ + Q_OBJECT +public: + explicit ViewProvider(QObject *parent = nullptr); + + virtual View *view(const QString &id = QString()) const = 0; + +signals: + void viewChanged(); + +private: + Q_DISABLE_COPY(ViewProvider) +}; + +} // namespace Sidebar +} // namespace Zeal + +#endif // ZEAL_SIDEBAR_VIEWPROVIDER_H diff --git a/src/libs/ui/CMakeLists.txt b/src/libs/ui/CMakeLists.txt index ac6cb96fd..8f4b078e1 100644 --- a/src/libs/ui/CMakeLists.txt +++ b/src/libs/ui/CMakeLists.txt @@ -10,16 +10,19 @@ set(Ui_FORMS add_library(Ui STATIC aboutdialog.cpp + browsertab.cpp docsetlistitemdelegate.cpp docsetsdialog.cpp mainwindow.cpp progressitemdelegate.cpp searchitemdelegate.cpp + searchsidebar.cpp settingsdialog.cpp + sidebarviewprovider.cpp ${Ui_FORMS} # For Qt Creator. ) -target_link_libraries(Ui Browser QxtGlobalShortcut Widgets Registry) +target_link_libraries(Ui Browser Sidebar QxtGlobalShortcut Widgets Registry) find_package(Qt5 COMPONENTS WebKitWidgets REQUIRED) target_link_libraries(Ui Qt5::WebKitWidgets) diff --git a/src/libs/ui/browsertab.cpp b/src/libs/ui/browsertab.cpp new file mode 100644 index 000000000..59a78f5e8 --- /dev/null +++ b/src/libs/ui/browsertab.cpp @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + +#include "browsertab.h" + +#include "searchsidebar.h" +#include "widgets/layouthelper.h" +#include "widgets/toolbarframe.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace Zeal; +using namespace Zeal::WidgetUi; + +namespace { +const char WelcomePageUrl[] = "qrc:///browser/welcome.html"; +const char WelcomePageNoAdUrl[] = "qrc:///browser/welcome-noad.html"; +} // namespace + +BrowserTab::BrowserTab(QWidget *parent) + : QWidget(parent) +{ + // Setup WebControl. + m_webControl = new Browser::WebControl(this); + connect(m_webControl, &Browser::WebControl::titleChanged, this, &BrowserTab::titleChanged); + connect(m_webControl, &Browser::WebControl::urlChanged, this, [this](const QUrl &url) { + const QString name = docsetName(url); + // TODO: Check if changed. + emit iconChanged(docsetIcon(name)); + + Registry::Docset *docset = Core::Application::instance()->docsetRegistry()->docset(name); + if (docset) { + m_searchSidebar->pageTocModel()->setResults(docset->relatedLinks(url)); + m_webControl->setJavaScriptEnabled(docset->isJavaScriptEnabled()); + } else { + m_searchSidebar->pageTocModel()->setResults(); + // Always enable JS outside of docsets. + m_webControl->setJavaScriptEnabled(true); + } + + m_backButton->setEnabled(m_webControl->canGoBack()); + m_forwardButton->setEnabled(m_webControl->canGoForward()); + }); + + // Setup navigation toolbar. + m_backButton = new QToolButton(); + m_backButton->setAutoRaise(true); + m_backButton->setIcon(qApp->style()->standardIcon(QStyle::SP_ArrowBack)); + m_backButton->setStyleSheet(QStringLiteral("QToolButton::menu-indicator { image: none; }")); + m_backButton->setText(QStringLiteral("←")); + m_backButton->setToolTip(tr("Go back one page")); + + auto backMenu = new QMenu(m_backButton); + connect(backMenu, &QMenu::aboutToShow, this, [this, backMenu]() { + backMenu->clear(); + QWebHistory *history = m_webControl->history(); + QList items = history->backItems(10); + for (auto it = items.crbegin(); it != items.crend(); ++it) { + const QIcon icon = docsetIcon(docsetName(it->url())); + const QWebHistoryItem item = *it; + backMenu->addAction(icon, it->title(), this, [=](bool) { history->goToItem(item); }); + } + }); + m_backButton->setMenu(backMenu); + + connect(m_backButton, &QToolButton::clicked, m_webControl, &Browser::WebControl::back); + + m_forwardButton = new QToolButton(); + m_forwardButton->setAutoRaise(true); + m_forwardButton->setIcon(qApp->style()->standardIcon(QStyle::SP_ArrowForward)); + m_forwardButton->setStyleSheet(QStringLiteral("QToolButton::menu-indicator { image: none; }")); + m_forwardButton->setText(QStringLiteral("→")); + m_forwardButton->setToolTip(tr("Go forward one page")); + + auto forwardMenu = new QMenu(m_forwardButton); + connect(forwardMenu, &QMenu::aboutToShow, this, [this, forwardMenu]() { + forwardMenu->clear(); + QWebHistory *history = m_webControl->history(); + const auto forwardItems = history->forwardItems(10); + for (const QWebHistoryItem &item: forwardItems) { + const QIcon icon = docsetIcon(docsetName(item.url())); + forwardMenu->addAction(icon, item.title(), this, [=](bool) { history->goToItem(item); }); + } + }); + m_forwardButton->setMenu(forwardMenu); + + connect(m_forwardButton, &QToolButton::clicked, m_webControl, &Browser::WebControl::forward); + + auto label = new QLabel("Test"); + label->setAlignment(Qt::AlignCenter); + connect(m_webControl, &Browser::WebControl::titleChanged, label, &QLabel::setText); + + auto toolBarLayout = new QHBoxLayout(); + toolBarLayout->setContentsMargins(4, 0, 4, 0); + toolBarLayout->setSpacing(4); + + toolBarLayout->addWidget(m_backButton); + toolBarLayout->addWidget(m_forwardButton); + toolBarLayout->addWidget(label, 1); + + auto toolBarFrame = new ToolBarFrame(); + toolBarFrame->setLayout(toolBarLayout); + + // Setup main layout. + auto layout = LayoutHelper::createBorderlessLayout(); + layout->addWidget(toolBarFrame); + layout->addWidget(m_webControl); + setLayout(layout); + + auto registry = Core::Application::instance()->docsetRegistry(); + using Registry::DocsetRegistry; + connect(registry, &DocsetRegistry::docsetAboutToBeUnloaded, this, [this](const QString &name) { + if (docsetName(m_webControl->url()) != name) + return; + + // TODO: Add custom 'Page has been removed' page. + navigateToStartPage(); + // TODO: Cleanup history. + }); +} + +BrowserTab *BrowserTab::clone(QWidget *parent) const +{ + auto tab = new BrowserTab(parent); + + if (m_searchSidebar) { + tab->m_searchSidebar = m_searchSidebar->clone(); + connect(tab->m_searchSidebar, &SearchSidebar::navigationRequested, + tab->m_webControl, &Browser::WebControl::load); + } + + tab->m_webControl->restoreHistory(m_webControl->saveHistory()); + tab->m_webControl->setZoomLevel(m_webControl->zoomLevel()); + + return tab; +} + +BrowserTab::~BrowserTab() +{ + if (m_searchSidebar) { + // The sidebar is not in this widget's hierarchy, so direct delete is not safe. + m_searchSidebar->deleteLater(); + } +} + +Browser::WebControl *BrowserTab::webControl() const +{ + return m_webControl; +} + +SearchSidebar *BrowserTab::searchSidebar() +{ + if (m_searchSidebar == nullptr) { + // Create SearchSidebar managed by this tab. + m_searchSidebar = new SearchSidebar(); + connect(m_searchSidebar, &SearchSidebar::navigationRequested, + m_webControl, &Browser::WebControl::load); + } + + return m_searchSidebar; +} + +void BrowserTab::navigateToStartPage() +{ + if (Core::Application::instance()->settings()->isAdDisabled) { + m_webControl->load(QUrl(WelcomePageNoAdUrl)); + } else { + m_webControl->load(QUrl(WelcomePageUrl)); + } +} + +void BrowserTab::search(const Registry::SearchQuery &query) +{ + if (query.isEmpty()) + return; + + m_searchSidebar->search(query); +} + +QString BrowserTab::docsetName(const QUrl &url) const +{ + const QRegExp docsetRegex(QStringLiteral("/([^/]+)[.]docset")); + return docsetRegex.indexIn(url.path()) != -1 ? docsetRegex.cap(1) : QString(); +} + +QIcon BrowserTab::docsetIcon(const QString &docsetName) const +{ + Registry::Docset *docset = Core::Application::instance()->docsetRegistry()->docset(docsetName); + return docset ? docset->icon() : QIcon(QStringLiteral(":/icons/logo/icon.png")); +} diff --git a/src/libs/ui/browsertab.h b/src/libs/ui/browsertab.h new file mode 100644 index 000000000..46389aabc --- /dev/null +++ b/src/libs/ui/browsertab.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + +#ifndef ZEAL_WIDGETUI_BROWSERTAB_H +#define ZEAL_WIDGETUI_BROWSERTAB_H + +#include + +#include +#include + +class QToolButton; + +namespace Zeal { + +namespace Browser { +class WebControl; +} // namespace Browser + +namespace Registry { +class SearchQuery; +} //namespace Registry + +namespace WidgetUi { + +class SearchSidebar; + +class BrowserTab : public QWidget +{ + Q_OBJECT +public: + explicit BrowserTab(QWidget *parent = nullptr); + BrowserTab *clone(QWidget *parent = nullptr) const; + ~BrowserTab() override; + + Browser::WebControl *webControl() const; + SearchSidebar *searchSidebar(); // TODO: const + +public slots: + void navigateToStartPage(); + void search(const Registry::SearchQuery &query); + +signals: + void iconChanged(const QIcon &icon); + void titleChanged(const QString &title); + +private: + Q_DISABLE_COPY(BrowserTab) + QString docsetName(const QUrl &url) const; + QIcon docsetIcon(const QString &docsetName) const; + + // Widgets. + SearchSidebar *m_searchSidebar = nullptr; + Browser::WebControl *m_webControl = nullptr; + QToolButton *m_backButton = nullptr; + QToolButton *m_forwardButton = nullptr; +}; + +} // namespace WidgetUi +} // namespace Zeal + +#endif // ZEAL_WIDGETUI_BROWSERTAB_H diff --git a/src/libs/ui/mainwindow.cpp b/src/libs/ui/mainwindow.cpp index e1dc5563e..3e09db4ff 100644 --- a/src/libs/ui/mainwindow.cpp +++ b/src/libs/ui/mainwindow.cpp @@ -25,9 +25,11 @@ #include "ui_mainwindow.h" #include "aboutdialog.h" +#include "browsertab.h" #include "docsetsdialog.h" -#include "searchitemdelegate.h" +#include "searchsidebar.h" #include "settingsdialog.h" +#include "sidebarviewprovider.h" #include #include @@ -36,9 +38,10 @@ #include #include #include -#include #include #include +#include +#include #include #include @@ -46,11 +49,9 @@ #include #include #include -#include #include #include #include -#include #include #include @@ -58,93 +59,16 @@ using namespace Zeal; using namespace Zeal::WidgetUi; namespace { -const char WelcomePageUrl[] = "qrc:///browser/welcome.html"; -const char WelcomePageNoAdUrl[] = "qrc:///browser/welcome-noad.html"; const char DarkModeCssUrl[] = ":/browser/assets/css/darkmode.css"; const char HighlightOnNavigateCssUrl[] = ":/browser/assets/css/highlight.css"; } -namespace Zeal { -namespace WidgetUi { - -struct TabState -{ - explicit TabState() - { - searchModel = new Registry::SearchModel(); - tocModel = new Registry::SearchModel(); - - widget = new Browser::WebControl(); - } - - TabState(const TabState &other) - : searchQuery(other.searchQuery) - , selections(other.selections) - , expansions(other.expansions) - , searchScrollPosition(other.searchScrollPosition) - , tocScrollPosition(other.tocScrollPosition) - { - searchModel = other.searchModel->clone(); - tocModel = other.tocModel->clone(); - - widget = new Browser::WebControl(); - restoreHistory(other.saveHistory()); - } - - TabState &operator=(const TabState &) = delete; - - ~TabState() - { - delete searchModel; - delete tocModel; - - widget->deleteLater(); - } - - void restoreHistory(const QByteArray &array) const - { - widget->restoreHistory(array); - } - - QByteArray saveHistory() const - { - return widget->saveHistory(); - } - - void goToStartPage() - { - if (Core::Application::instance()->settings()->isAdDisabled) { - widget->load(QUrl(WelcomePageNoAdUrl)); - } else { - widget->load(QUrl(WelcomePageUrl)); - } - } - - QString searchQuery; - - // Content/Search results tree view state - Registry::SearchModel *searchModel = nullptr; - QModelIndexList selections; - QModelIndexList expansions; - int searchScrollPosition = 0; - - // TOC list view state - Registry::SearchModel *tocModel = nullptr; - int tocScrollPosition = 0; - - Browser::WebControl *widget = nullptr; -}; - -} // namespace WidgetUi -} // namespace Zeal - MainWindow::MainWindow(Core::Application *app, QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), m_application(app), m_settings(app->settings()), - m_globalShortcut(new QxtGlobalShortcut(m_settings->showShortcut, this)), - m_openDocsetTimer(new QTimer(this)) + m_globalShortcut(new QxtGlobalShortcut(m_settings->showShortcut, this)) { ui->setupUi(this); @@ -156,19 +80,16 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : // Setup application wide shortcuts. // Focus search bar. QShortcut *shortcut = new QShortcut(QStringLiteral("Ctrl+K"), this); - connect(shortcut, &QShortcut::activated, - ui->lineEdit, static_cast(&SearchEdit::setFocus)); + connect(shortcut, &QShortcut::activated, this, [this]() { currentTab()->searchSidebar()->focusSearchEdit(); }); shortcut = new QShortcut(QStringLiteral("Ctrl+L"), this); - connect(shortcut, &QShortcut::activated, - ui->lineEdit, static_cast(&SearchEdit::setFocus)); + connect(shortcut, &QShortcut::activated, this, [this]() { currentTab()->searchSidebar()->focusSearchEdit(); }); // Duplicate current tab. shortcut = new QShortcut(QStringLiteral("Ctrl+Alt+T"), this); connect(shortcut, &QShortcut::activated, this, [this]() { duplicateTab(m_tabBar->currentIndex()); }); restoreGeometry(m_settings->windowGeometry); - ui->splitter->restoreState(m_settings->verticalSplitterGeometry); // Menu // File @@ -188,7 +109,7 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : // Edit ui->actionFind->setShortcut(QKeySequence::Find); connect(ui->actionFind, &QAction::triggered, this, [this]() { - currentTab()->activateSearchBar(); + currentTab()->webControl()->activateSearchBar(); }); if (QKeySequence(QKeySequence::Preferences).isEmpty()) { @@ -204,24 +125,22 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : m_globalShortcut->setEnabled(true); }); - ui->actionBack->setIcon(qApp->style()->standardIcon(QStyle::SP_ArrowBack)); ui->actionBack->setShortcut(QKeySequence::Back); - connect(ui->actionBack, &QAction::triggered, this, [this]() { currentTab()->back(); }); + connect(ui->actionBack, &QAction::triggered, this, [this]() { currentTab()->webControl()->back(); }); addAction(ui->actionBack); - ui->actionForward->setIcon(qApp->style()->standardIcon(QStyle::SP_ArrowForward)); ui->actionForward->setShortcut(QKeySequence::Forward); - connect(ui->actionForward, &QAction::triggered, this, [this]() { currentTab()->forward(); }); + connect(ui->actionForward, &QAction::triggered, this, [this]() { currentTab()->webControl()->forward(); }); addAction(ui->actionForward); shortcut = new QShortcut(QKeySequence::ZoomIn, this); - connect(shortcut, &QShortcut::activated, this, [this]() { currentTab()->zoomIn(); }); + connect(shortcut, &QShortcut::activated, this, [this]() { currentTab()->webControl()->zoomIn(); }); shortcut = new QShortcut(QStringLiteral("Ctrl+="), this); - connect(shortcut, &QShortcut::activated, this, [this]() { currentTab()->zoomIn(); }); + connect(shortcut, &QShortcut::activated, this, [this]() { currentTab()->webControl()->zoomIn(); }); shortcut = new QShortcut(QKeySequence::ZoomOut, this); - connect(shortcut, &QShortcut::activated, this, [this]() { currentTab()->zoomOut(); }); + connect(shortcut, &QShortcut::activated, this, [this]() { currentTab()->webControl()->zoomOut(); }); shortcut = new QShortcut(QStringLiteral("Ctrl+0"), this); - connect(shortcut, &QShortcut::activated, this, [this]() { currentTab()->resetZoom(); }); + connect(shortcut, &QShortcut::activated, this, [this]() { currentTab()->webControl()->resetZoom(); }); // Tools Menu connect(ui->actionDocsets, &QAction::triggered, this, [this]() { @@ -266,53 +185,18 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : } }); - m_backMenu = new QMenu(ui->backButton); - connect(m_backMenu, &QMenu::aboutToShow, this, [this]() { - m_backMenu->clear(); - QWebHistory *history = currentTab()->history(); - QList items = history->backItems(10); - for (auto it = items.crbegin(); it != items.crend(); ++it) { - const QIcon icon = docsetIcon(docsetName(it->url())); - const QWebHistoryItem item = *it; - m_backMenu->addAction(icon, it->title(), [=](bool) { history->goToItem(item); }); - } - }); - ui->backButton->setDefaultAction(ui->actionBack); - ui->backButton->setMenu(m_backMenu); - - m_forwardMenu = new QMenu(ui->forwardButton); - connect(m_forwardMenu, &QMenu::aboutToShow, this, [this]() { - m_forwardMenu->clear(); - QWebHistory *history = currentTab()->history(); - const auto forwardItems = history->forwardItems(10); - for (const QWebHistoryItem &item: forwardItems) { - const QIcon icon = docsetIcon(docsetName(item.url())); - m_forwardMenu->addAction(icon, item.title(), [=](bool) { history->goToItem(item); }); - } - }); - ui->forwardButton->setDefaultAction(ui->actionForward); - ui->forwardButton->setMenu(m_forwardMenu); - - // Set default stretch factor. - ui->splitter->setStretchFactor(1, 2); - - // treeView and lineEdit - ui->lineEdit->setTreeView(ui->treeView); - ui->lineEdit->setFocus(); - setupSearchBoxCompletions(); - auto delegate = new SearchItemDelegate(ui->treeView); - delegate->setDecorationRoles({Registry::ItemDataRole::DocsetIconRole, Qt::DecorationRole}); - connect(ui->lineEdit, &QLineEdit::textChanged, this, [delegate](const QString &text) { - delegate->setHighlight(Registry::SearchQuery::fromString(text).query()); - }); - ui->treeView->setItemDelegate(delegate); + // Setup sidebar. + auto m_sbViewProvider = new SidebarViewProvider(this); + auto sbView = new Sidebar::ProxyView(m_sbViewProvider, QStringLiteral("index")); - ui->tocListView->setItemDelegate(new SearchItemDelegate(ui->tocListView)); - connect(ui->tocSplitter, &QSplitter::splitterMoved, this, [this]() { - m_settings->tocSplitterState = ui->tocSplitter->saveState(); - }); + auto sb = new Sidebar::Container(); + sb->addView(sbView); + // Setup splitter. + ui->splitter->insertWidget(0, sb); + ui->splitter->restoreState(m_settings->verticalSplitterGeometry); + // Setup web bridge. m_webBridge = new Browser::WebBridge(this); connect(m_webBridge, &Browser::WebBridge::actionTriggered, this, [this](const QString &action) { // TODO: In the future connect directly to the ActionManager. @@ -325,69 +209,6 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : createTab(); - connect(ui->treeView, &QTreeView::clicked, this, &MainWindow::openDocset); - connect(ui->tocListView, &QListView::clicked, this, &MainWindow::openDocset); - connect(ui->treeView, &QTreeView::activated, this, &MainWindow::openDocset); - connect(ui->tocListView, &QListView::activated, this, &MainWindow::openDocset); - - connect(m_application->docsetRegistry(), &Registry::DocsetRegistry::searchCompleted, - this, [this](const QList &results) { - currentTabState()->searchModel->setResults(results); - }); - - connect(m_application->docsetRegistry(), &Registry::DocsetRegistry::docsetAboutToBeUnloaded, - this, [this](const QString &name) { - for (TabState *tabState : qAsConst(m_tabStates)) { - if (tabState == currentTabState()) { - // Disable updates because removeSearchResultWithName can - // call {begin,end}RemoveRows multiple times, and cause - // degradation of UI responsiveness. - ui->treeView->setUpdatesEnabled(false); - tabState->searchModel->removeSearchResultWithName(name); - ui->treeView->setUpdatesEnabled(true); - } else { - tabState->searchModel->removeSearchResultWithName(name); - } - - if (docsetName(tabState->widget->url()) == name) { - tabState->tocModel->setResults(); - // TODO: Add custom 'Page has been removed' page. - tabState->goToStartPage(); - } - - // TODO: Cleanup history - } - - setupSearchBoxCompletions(); - }); - - connect(m_application->docsetRegistry(), &Registry::DocsetRegistry::docsetLoaded, - this, [this](const QString &) { - setupSearchBoxCompletions(); - }); - - connect(ui->lineEdit, &QLineEdit::textChanged, this, [this](const QString &text) { - if (text == currentTabState()->searchQuery) - return; - - currentTabState()->searchQuery = text; - m_application->docsetRegistry()->search(text); - }); - - // Setup delayed navigation to a page until user makes a pause in typing a search query. - m_openDocsetTimer->setInterval(400); - m_openDocsetTimer->setSingleShot(true); - connect(m_openDocsetTimer, &QTimer::timeout, this, [this]() { - QModelIndex index = m_openDocsetTimer->property("index").toModelIndex(); - if (!index.isValid()) - return; - - openDocset(index); - - // Get focus back. - ui->lineEdit->setFocus(Qt::MouseFocusReason); - }); - ui->actionNewTab->setShortcut(QKeySequence::AddTab); connect(ui->actionNewTab, &QAction::triggered, this, [this]() { createTab(); }); addAction(ui->actionNewTab); @@ -396,16 +217,6 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : createTab(); }); - // Save expanded items - connect(ui->treeView, &QTreeView::expanded, this, [this](const QModelIndex &index) { - if (currentTabState()->expansions.indexOf(index) == -1) - currentTabState()->expansions.append(index); - }); - - connect(ui->treeView, &QTreeView::collapsed, this, [this](const QModelIndex &index) { - currentTabState()->expansions.removeOne(index); - }); - #ifdef Q_OS_WIN32 ui->actionCloseTab->setShortcut(QKeySequence(Qt::Key_W + Qt::CTRL)); #else @@ -429,11 +240,6 @@ MainWindow::MainWindow(Core::Application *app, QWidget *parent) : m_tabBar->setCurrentIndex((m_tabBar->currentIndex() - 1 + m_tabBar->count()) % m_tabBar->count()); }); -#ifdef Q_OS_MACOS - ui->treeView->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->tocListView->setAttribute(Qt::WA_MacShowFocusRect, false); -#endif - connect(m_settings, &Core::Settings::updated, this, &MainWindow::applySettings); applySettings(); @@ -446,185 +252,68 @@ MainWindow::~MainWindow() m_settings->verticalSplitterGeometry = ui->splitter->saveState(); m_settings->windowGeometry = saveGeometry(); - // Delete the UI first, because it depends on tab states. delete ui; - qDeleteAll(m_tabStates); } void MainWindow::search(const Registry::SearchQuery &query) { - if (query.isEmpty()) - return; - - ui->lineEdit->setText(query.toString()); - emit ui->treeView->activated(ui->treeView->currentIndex()); -} - -void MainWindow::openDocset(const QModelIndex &index) -{ - const QVariant url = index.data(Registry::ItemDataRole::UrlRole); - if (url.isNull()) - return; - - currentTab()->load(url.toUrl()); - currentTab()->setFocus(); -} - -QString MainWindow::docsetName(const QUrl &url) const -{ - const QRegExp docsetRegex(QStringLiteral("/([^/]+)[.]docset")); - return docsetRegex.indexIn(url.path()) != -1 ? docsetRegex.cap(1) : QString(); -} - -QIcon MainWindow::docsetIcon(const QString &docsetName) const -{ - Registry::Docset *docset = m_application->docsetRegistry()->docset(docsetName); - return docset ? docset->icon() : QIcon(QStringLiteral(":/icons/logo/icon.png")); -} - -void MainWindow::queryCompleted() -{ - m_openDocsetTimer->stop(); - - syncTreeView(); - - ui->treeView->setCurrentIndex(currentTabState()->searchModel->index(0, 0, QModelIndex())); - - m_openDocsetTimer->setProperty("index", ui->treeView->currentIndex()); - m_openDocsetTimer->start(); + currentTab()->search(query); } void MainWindow::closeTab(int index) { - if (index == -1) + if (index == -1) { index = m_tabBar->currentIndex(); + } if (index == -1) return; - TabState *state = m_tabStates.takeAt(index); - ui->webViewStack->removeWidget(state->widget); + BrowserTab *tab = tabAt(index); + ui->webViewStack->removeWidget(tab); + tab->deleteLater(); // Handle the tab bar last to avoid currentChanged signal coming too early. m_tabBar->removeTab(index); - delete state; - - if (m_tabStates.isEmpty()) + if (ui->webViewStack->count() == 0) { createTab(); + } } void MainWindow::moveTab(int from, int to) { - m_tabStates.swap(from, to); - const QSignalBlocker blocker(ui->webViewStack); QWidget *w = ui->webViewStack->widget(from); ui->webViewStack->removeWidget(w); ui->webViewStack->insertWidget(to, w); } -Browser::WebControl *MainWindow::createTab(int index) +BrowserTab *MainWindow::createTab() { - if (m_settings->openNewTabAfterActive) - index = m_tabBar->currentIndex() + 1; - else if (index == -1) - index = m_tabStates.size(); - - auto newState = new TabState(); - newState->widget->setWebBridgeObject("zAppBridge", m_webBridge); - newState->goToStartPage(); - - m_tabStates.insert(index, newState); - ui->webViewStack->insertWidget(index, newState->widget); - m_tabBar->insertTab(index, tr("Loading...")); - m_tabBar->setCurrentIndex(index); - - ui->lineEdit->setFocus(); - - return newState->widget; + auto tab = new BrowserTab(); + tab->navigateToStartPage(); + addTab(tab); + return tab; } void MainWindow::duplicateTab(int index) { - if (index < 0 || index >= m_tabStates.size()) + BrowserTab *tab = tabAt(index); + if (tab == nullptr) return; - auto tabState = m_tabStates.at(index); - syncTabState(tabState); - - auto newState = new TabState(*tabState); - newState->widget->setWebBridgeObject("zAppBridge", m_webBridge); - - ++index; - m_tabStates.insert(index, newState); - ui->webViewStack->insertWidget(index, newState->widget); - m_tabBar->insertTab(index, newState->widget->title()); - m_tabBar->setCurrentIndex(index); -} - -void MainWindow::syncTreeView() -{ - QItemSelectionModel *oldSelectionModel = ui->treeView->selectionModel(); - - TabState *tabState = currentTabState(); - if (tabState->searchQuery.isEmpty()) { - ui->treeView->setModel(m_application->docsetRegistry()->model()); - ui->treeView->setRootIsDecorated(true); - } else { - ui->treeView->setModel(tabState->searchModel); - ui->treeView->setRootIsDecorated(false); - } - - // TODO: Remove once QTBUG-49966 is addressed. - QItemSelectionModel *newSelectionModel = ui->treeView->selectionModel(); - if (oldSelectionModel && newSelectionModel != oldSelectionModel) { - oldSelectionModel->deleteLater(); - } - - ui->treeView->reset(); + // Add a duplicate next to the `index`. + addTab(tab->clone(), index + 1); } -void MainWindow::syncToc() +void MainWindow::addTab(BrowserTab *tab, int index) { - if (!currentTabState()->tocModel->isEmpty()) { - ui->tocListView->show(); - ui->tocSplitter->restoreState(m_settings->tocSplitterState); - } else { - ui->tocListView->hide(); - } - -} - -TabState *MainWindow::currentTabState() const -{ - return m_tabStates.at(m_tabBar->currentIndex()); -} - -Browser::WebControl *MainWindow::currentTab() const -{ - return qobject_cast(ui->webViewStack->currentWidget()); -} - -void MainWindow::attachTab(TabState *tabState) -{ - using Registry::SearchModel; - connect(tabState->searchModel, &SearchModel::updated, this, &MainWindow::queryCompleted); - connect(tabState->tocModel, &SearchModel::updated, this, &MainWindow::syncToc); - - connect(tabState->widget, &Browser::WebControl::urlChanged, this, [this, tabState](const QUrl &url) { - const QString name = docsetName(url); - m_tabBar->setTabIcon(m_tabBar->currentIndex(), docsetIcon(name)); - - Registry::Docset *docset = m_application->docsetRegistry()->docset(name); - if (docset) { - tabState->tocModel->setResults(docset->relatedLinks(url)); - tabState->widget->setJavaScriptEnabled(docset->isJavaScriptEnabled()); - } - ui->actionBack->setEnabled(tabState->widget->canGoBack()); - ui->actionForward->setEnabled(tabState->widget->canGoForward()); + connect(tab, &BrowserTab::iconChanged, this, [this, tab](const QIcon &icon) { + const int index = ui->webViewStack->indexOf(tab); + Q_ASSERT(m_tabBar->tabData(index).value() == tab); + m_tabBar->setTabIcon(index, icon); }); - - connect(tabState->widget, &Browser::WebControl::titleChanged, this, [this](const QString &title) { + connect(tab, &BrowserTab::titleChanged, this, [this, tab](const QString &title) { if (title.isEmpty()) return; @@ -633,55 +322,35 @@ void MainWindow::attachTab(TabState *tabState) #else setWindowTitle(QStringLiteral("%1 - Zeal Portable").arg(title)); #endif - m_tabBar->setTabText(m_tabBar->currentIndex(), title); - m_tabBar->setTabToolTip(m_tabBar->currentIndex(), title); + const int index = ui->webViewStack->indexOf(tab); + Q_ASSERT(m_tabBar->tabData(index).value() == tab); + m_tabBar->setTabText(index, title); + m_tabBar->setTabToolTip(index, title); }); - ui->lineEdit->setText(tabState->searchQuery); - ui->tocListView->setModel(tabState->tocModel); - - syncTreeView(); - syncToc(); - - // Bring back the selections and expansions - ui->treeView->blockSignals(true); - for (const QModelIndex &selection: qAsConst(tabState->selections)) { - ui->treeView->selectionModel()->select(selection, QItemSelectionModel::Select); - } + tab->webControl()->setWebBridgeObject("zAppBridge", m_webBridge); + tab->searchSidebar()->focusSearchEdit(); - for (const QModelIndex &expandedIndex: qAsConst(tabState->expansions)) { - ui->treeView->expand(expandedIndex); + if (index == -1) { + index = m_settings->openNewTabAfterActive + ? m_tabBar->currentIndex() + 1 + : ui->webViewStack->count(); } - ui->treeView->blockSignals(false); - - ui->actionBack->setEnabled(tabState->widget->canGoBack()); - ui->actionForward->setEnabled(tabState->widget->canGoForward()); - - ui->treeView->verticalScrollBar()->setValue(tabState->searchScrollPosition); - ui->tocListView->verticalScrollBar()->setValue(tabState->tocScrollPosition); + ui->webViewStack->insertWidget(index, tab); + m_tabBar->insertTab(index, tr("Loading...")); + m_tabBar->setCurrentIndex(index); + m_tabBar->setTabData(index, QVariant::fromValue(tab)); } -void MainWindow::detachTab(TabState *tabState) +BrowserTab *MainWindow::currentTab() const { - tabState->searchModel->disconnect(this); - tabState->tocModel->disconnect(this); - tabState->widget->disconnect(this); + return tabAt(m_tabBar->currentIndex()); } -// Sets up the search box autocompletions. -void MainWindow::setupSearchBoxCompletions() +BrowserTab *MainWindow::tabAt(int index) const { - QStringList completions; - const auto docsets = m_application->docsetRegistry()->docsets(); - for (const Registry::Docset * const docset: docsets) { - if (docset->keywords().isEmpty()) - continue; - - completions << docset->keywords().constFirst() + QLatin1Char(':'); - } - - ui->lineEdit->setCompletions(completions); + return qobject_cast(ui->webViewStack->widget(index)); } void MainWindow::setupTabBar() @@ -694,32 +363,24 @@ void MainWindow::setupTabBar() m_tabBar->setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab); m_tabBar->setExpanding(false); m_tabBar->setUsesScrollButtons(true); - m_tabBar->setDrawBase(false); m_tabBar->setDocumentMode(true); m_tabBar->setElideMode(Qt::ElideRight); m_tabBar->setStyleSheet(QStringLiteral("QTabBar::tab { width: 150px; }")); m_tabBar->setMovable(true); connect(m_tabBar, &QTabBar::currentChanged, this, [this](int index) { - static const char PreviousTabState[] = "previousTabState"; - if (index == -1) return; - // Save previous tab state. Using 'void *' to avoid Q_DECLARE_METATYPE. - TabState *previousTabState - = static_cast(m_tabBar->property(PreviousTabState).value()); - if (m_tabStates.contains(previousTabState)) { - syncTabState(previousTabState); - detachTab(previousTabState); - } - - // Load current tab state - TabState *tabState = m_tabStates.at(index); - m_tabBar->setProperty(PreviousTabState, qVariantFromValue(static_cast(tabState))); - attachTab(tabState); + BrowserTab *tab = tabAt(index); +#ifndef PORTABLE_BUILD + setWindowTitle(QStringLiteral("%1 - Zeal").arg(tab->webControl()->title())); +#else + setWindowTitle(QStringLiteral("%1 - Zeal Portable").arg(tab->webControl()->title())); +#endif ui->webViewStack->setCurrentIndex(index); + emit currentTabChanged(); }); connect(m_tabBar, &QTabBar::tabCloseRequested, this, &MainWindow::closeTab); connect(m_tabBar, &QTabBar::tabMoved, this, &MainWindow::moveTab); @@ -744,8 +405,7 @@ void MainWindow::setupTabBar() addAction(action); } - auto layout = static_cast(ui->navigationBar->layout()); - layout->insertWidget(2, m_tabBar, 0, Qt::AlignBottom); + ui->centralWidgetLayout->insertWidget(0, m_tabBar); } void MainWindow::createTrayIcon() @@ -790,13 +450,6 @@ void MainWindow::removeTrayIcon() delete trayIconMenu; } -void MainWindow::syncTabState(TabState *tabState) -{ - tabState->selections = ui->treeView->selectionModel()->selectedIndexes(); - tabState->searchScrollPosition = ui->treeView->verticalScrollBar()->value(); - tabState->tocScrollPosition = ui->tocListView->verticalScrollBar()->value(); -} - void MainWindow::bringToFront() { show(); @@ -804,7 +457,7 @@ void MainWindow::bringToFront() raise(); activateWindow(); - ui->lineEdit->setFocus(); + currentTab()->searchSidebar()->focusSearchEdit(); } void MainWindow::changeEvent(QEvent *event) @@ -856,12 +509,10 @@ void MainWindow::keyPressEvent(QKeyEvent *keyEvent) { switch (keyEvent->key()) { case Qt::Key_Escape: - ui->lineEdit->setFocus(); - ui->lineEdit->clearQuery(); + currentTab()->searchSidebar()->focusSearchEdit(true); break; case Qt::Key_Question: - ui->lineEdit->setFocus(); - ui->lineEdit->selectQuery(); + currentTab()->searchSidebar()->focusSearchEdit(); break; default: QMainWindow::keyPressEvent(keyEvent); diff --git a/src/libs/ui/mainwindow.h b/src/libs/ui/mainwindow.h index f8433b415..6d6d59c61 100644 --- a/src/libs/ui/mainwindow.h +++ b/src/libs/ui/mainwindow.h @@ -28,10 +28,8 @@ class QxtGlobalShortcut; -class QModelIndex; class QSystemTrayIcon; class QTabBar; -class QTimer; namespace Zeal { @@ -55,7 +53,8 @@ namespace Ui { class MainWindow; } // namespace Ui -struct TabState; +class BrowserTab; +class SidebarViewProvider; class MainWindow : public QMainWindow { @@ -66,11 +65,14 @@ class MainWindow : public QMainWindow void search(const Registry::SearchQuery &query); void bringToFront(); - Browser::WebControl *createTab(int index = -1); + BrowserTab *createTab(); public slots: void toggleWindow(); +signals: + void currentTabChanged(); + protected: void changeEvent(QEvent *event) override; void closeEvent(QCloseEvent *event) override; @@ -79,33 +81,21 @@ public slots: private slots: void applySettings(); - void openDocset(const QModelIndex &index); - void queryCompleted(); void closeTab(int index = -1); void moveTab(int from, int to); void duplicateTab(int index); private: - void syncTreeView(); - void syncToc(); - void setupSearchBoxCompletions(); void setupTabBar(); - TabState *currentTabState() const; - Browser::WebControl *currentTab() const; - - void attachTab(TabState *tabState); - void detachTab(TabState *tabState); - - QString docsetName(const QUrl &url) const; - QIcon docsetIcon(const QString &docsetName) const; + void addTab(BrowserTab *tab, int index = -1); + BrowserTab *currentTab() const; + BrowserTab *tabAt(int index) const; void createTrayIcon(); void removeTrayIcon(); - void syncTabState(TabState *tabState); - - QList m_tabStates; + void syncTabState(BrowserTab *tab); Ui::MainWindow *ui = nullptr; Core::Application *m_application = nullptr; @@ -120,9 +110,10 @@ private slots: QTabBar *m_tabBar = nullptr; - QSystemTrayIcon *m_trayIcon = nullptr; + friend class SidebarViewProvider; + SidebarViewProvider *m_sbViewProvider = nullptr; - QTimer *m_openDocsetTimer = nullptr; + QSystemTrayIcon *m_trayIcon = nullptr; }; } // namespace WidgetUi diff --git a/src/libs/ui/mainwindow.ui b/src/libs/ui/mainwindow.ui index b2e0239f1..922938d49 100644 --- a/src/libs/ui/mainwindow.ui +++ b/src/libs/ui/mainwindow.ui @@ -38,158 +38,16 @@ false - - - - 0 - - - - - - - - - 4 - - - 4 - - - 0 - - - 4 - - - 0 - - - - - Enter your query - - - true - - - - - - - - - - - 150 - 0 - - - - Qt::Vertical - - - - QFrame::NoFrame - - - true - - - false - - - - - QFrame::NoFrame - - - Qt::ScrollBarAlwaysOff - - - true - - - - - - 0 - - - - - 4 - - - 4 - - - 0 - - - 4 - - - 0 - - - - - Go back one page - - - QToolButton::menu-indicator { image: none; } - - - - - - true - - - - - - - Go forward one page - - - QToolButton::menu-indicator { image: none; } - - - - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - 600 + 400 0 @@ -249,7 +107,8 @@ - + + .. &Quit @@ -257,7 +116,8 @@ - + + .. Prefere&nces @@ -265,7 +125,8 @@ - + + .. About &Zeal @@ -276,7 +137,8 @@ false - + + .. &Back @@ -287,7 +149,8 @@ false - + + .. &Forward @@ -295,7 +158,8 @@ - + + .. New &Tab @@ -328,7 +192,8 @@ - + + .. &Find @@ -341,19 +206,6 @@ - - - SearchEdit - QLineEdit -
ui/widgets/searchedit.h
-
- - ToolBarFrame - QWidget -
ui/widgets/toolbarframe.h
- 1 -
-
diff --git a/src/libs/ui/searchsidebar.cpp b/src/libs/ui/searchsidebar.cpp new file mode 100644 index 000000000..b2ac59233 --- /dev/null +++ b/src/libs/ui/searchsidebar.cpp @@ -0,0 +1,334 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + +#include "searchsidebar.h" + +#include "searchitemdelegate.h" +#include "widgets/layouthelper.h" +#include "widgets/searchedit.h" +#include "widgets/toolbarframe.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Zeal; +using namespace Zeal::WidgetUi; + +SearchSidebar::SearchSidebar(QWidget *parent) + : SearchSidebar(nullptr, parent) +{ +} + +SearchSidebar::SearchSidebar(const SearchSidebar *other, QWidget *parent) + : Sidebar::View(parent) +{ + // Setup search result view. + m_treeView = new QTreeView(); + m_treeView->setFrameShape(QFrame::NoFrame); + m_treeView->setHeaderHidden(true); + m_treeView->setUniformRowHeights(true); +#ifdef Q_OS_MACOS + m_searchResultView->setAttribute(Qt::WA_MacShowFocusRect, false); +#endif + + // Save expanded items. + connect(m_treeView, &QTreeView::expanded, this, [this](const QModelIndex &index) { + if (m_expandedIndexList.indexOf(index) == -1) { + m_expandedIndexList.append(index); + } + }); + connect(m_treeView, &QTreeView::collapsed, this, [this](const QModelIndex &index) { + m_expandedIndexList.removeOne(index); + }); + + auto delegate = new SearchItemDelegate(m_treeView); + delegate->setDecorationRoles({Registry::ItemDataRole::DocsetIconRole, Qt::DecorationRole}); + m_treeView->setItemDelegate(delegate); + + connect(m_treeView, &QTreeView::activated, this, &SearchSidebar::indexActivated); + connect(m_treeView, &QTreeView::clicked, this, &SearchSidebar::indexActivated); + + // Setup page TOC view. + // TODO: Move to a separate Sidebar View. + m_pageTocView = new QListView(); + m_pageTocView->setFrameShape(QFrame::NoFrame); + m_pageTocView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_pageTocView->setItemDelegate(new SearchItemDelegate(m_pageTocView)); + m_pageTocView->setUniformItemSizes(true); + m_pageTocView->setVisible(false); +#ifdef Q_OS_MACOS + m_pageTocView->setAttribute(Qt::WA_MacShowFocusRect, false); +#endif + + m_pageTocModel = new Registry::SearchModel(this); + connect(m_pageTocModel, &Registry::SearchModel::updated, this, [this]() { + if (m_pageTocModel->isEmpty()) { + m_pageTocView->hide(); + } else { + m_pageTocView->show(); + m_splitter->restoreState(Core::Application::instance()->settings()->tocSplitterState); + } + }); + m_pageTocView->setModel(m_pageTocModel); + + connect(m_pageTocView, &QListView::activated, this, &SearchSidebar::indexActivated); + connect(m_pageTocView, &QListView::clicked, this, &SearchSidebar::indexActivated); + + // Setup search input box. + m_searchEdit = new SearchEdit(); + m_searchEdit->installEventFilter(this); + setupSearchBoxCompletions(); + + // Connect delegate first to make cloning work. + connect(m_searchEdit, &QLineEdit::textChanged, this, [delegate](const QString &text) { + delegate->setHighlight(Registry::SearchQuery::fromString(text).query()); + }); + + // Clone state if `other` is provided. + if (other) { + if (other->m_searchEdit->text().isEmpty()) { + m_searchModel = new Registry::SearchModel(this); + + m_treeView->setRootIsDecorated(true); + m_treeView->setModel(Core::Application::instance()->docsetRegistry()->model()); + + for (const QModelIndex &index: other->m_expandedIndexList) { + m_treeView->expand(index); + } + } else { + m_searchEdit->setText(other->m_searchEdit->text()); + + m_searchModel = other->m_searchModel->clone(this); + m_treeView->setRootIsDecorated(false); + m_treeView->setModel(m_searchModel); + } + + for (const QModelIndex &index: other->m_treeView->selectionModel()->selectedIndexes()) { + m_treeView->selectionModel()->select(index, QItemSelectionModel::Select); + } + + // Cannot update position until layout geomentry is calculated, so set it in showEvent(). + m_pendingVerticalPosition = other->m_treeView->verticalScrollBar()->value(); + } else { + m_searchModel = new Registry::SearchModel(this); + + m_treeView->setRootIsDecorated(true); + m_treeView->setModel(Core::Application::instance()->docsetRegistry()->model()); + } + + connect(m_searchEdit, &QLineEdit::textChanged, this, [this](const QString &text) { + QItemSelectionModel *oldSelectionModel = m_treeView->selectionModel(); + + if (text.isEmpty()) { + m_treeView->setModel(Core::Application::instance()->docsetRegistry()->model()); + m_treeView->setRootIsDecorated(true); + } else { + m_treeView->setModel(m_searchModel); + m_treeView->setRootIsDecorated(false); + } + + // TODO: Remove once QTBUG-49966 is addressed. + QItemSelectionModel *newSelectionModel = m_treeView->selectionModel(); + if (oldSelectionModel && newSelectionModel != oldSelectionModel) { + oldSelectionModel->deleteLater(); + } + + m_treeView->reset(); + + Core::Application::instance()->docsetRegistry()->search(text); + }); + + auto toolBarLayout = new QVBoxLayout(); + toolBarLayout->setContentsMargins(4, 0, 4, 0); + toolBarLayout->setSpacing(4); + + toolBarLayout->addWidget(m_searchEdit); + + auto toolBarFrame = new ToolBarFrame(); + toolBarFrame->setLayout(toolBarLayout); + + // Setup splitter. + m_splitter = new QSplitter(); + m_splitter->setOrientation(Qt::Vertical); + m_splitter->addWidget(m_treeView); + m_splitter->addWidget(m_pageTocView); + connect(m_splitter, &QSplitter::splitterMoved, this, [this]() { + Core::Application::instance()->settings()->tocSplitterState = m_splitter->saveState(); + }); + + // Setup main layout. + auto layout = LayoutHelper::createBorderlessLayout(); + layout->addWidget(toolBarFrame); + layout->addWidget(m_splitter); + setLayout(layout); + + // Setup delayed navigation to a page until user makes a pause in typing a search query. + m_delayedNavigationTimer = new QTimer(this); + m_delayedNavigationTimer->setInterval(400); + m_delayedNavigationTimer->setSingleShot(true); + connect(m_delayedNavigationTimer, &QTimer::timeout, this, [this]() { + QModelIndex index = m_delayedNavigationTimer->property("index").toModelIndex(); + if (!index.isValid()) + return; + + indexActivated(index); + + // Get focus back. + m_searchEdit->setFocus(Qt::MouseFocusReason); + }); + + // Setup Docset Registry. + auto registry = Core::Application::instance()->docsetRegistry(); + using Registry::DocsetRegistry; + connect(registry, &DocsetRegistry::searchCompleted, + this, [this](const QList &results) { + if (!isVisible()) + return; + + m_searchModel->setResults(results); + }); + + connect(registry, &DocsetRegistry::docsetAboutToBeUnloaded, this, [this](const QString &name) { + if (isVisible()) { + // Disable updates because removeSearchResultWithName can + // call {begin,end}RemoveRows multiple times, and cause + // degradation of UI responsiveness. + m_treeView->setUpdatesEnabled(false); + m_searchModel->removeSearchResultWithName(name); + m_treeView->setUpdatesEnabled(true); + } else { + m_searchModel->removeSearchResultWithName(name); + } + + setupSearchBoxCompletions(); + }); + + connect(registry, &DocsetRegistry::docsetLoaded, this, [this](const QString &) { + setupSearchBoxCompletions(); + }); +} + +SearchSidebar *SearchSidebar::clone(QWidget *parent) const +{ + return new SearchSidebar(this, parent); +} + +Registry::SearchModel *SearchSidebar::pageTocModel() const +{ + return m_pageTocModel; +} + +void SearchSidebar::focusSearchEdit(bool clear) +{ + if (!isVisible()) { + m_pendingSearchEditFocus = true; + return; + } + + m_searchEdit->setFocus(); + + if (clear) { + m_searchEdit->clearQuery(); + } +} + +void SearchSidebar::search(const Registry::SearchQuery &query) +{ + // TODO: Pass Registry::SearchQuery, converting to QString is dumb at this point. + m_searchEdit->setText(query.toString()); +} + +void SearchSidebar::indexActivated(const QModelIndex &index) +{ + const QVariant url = index.data(Registry::ItemDataRole::UrlRole); + if (url.isNull()) + return; + + emit navigationRequested(url.toUrl()); +} + +void SearchSidebar::setupSearchBoxCompletions() +{ + QStringList completions; + const auto docsets = Core::Application::instance()->docsetRegistry()->docsets(); + for (const Registry::Docset *docset: docsets) { + const QStringList keywords = docset->keywords(); + if (keywords.isEmpty()) + continue; + + completions << keywords.constFirst() + QLatin1Char(':'); + } + + if (completions.isEmpty()) + return; + + m_searchEdit->setCompletions(completions); +} + +bool SearchSidebar::eventFilter(QObject *object, QEvent *event) +{ + if (object == m_searchEdit && event->type() == QEvent::KeyPress) { + auto e = static_cast(event); + switch (e->key()) { + case Qt::Key_Return: + case Qt::Key_Down: + case Qt::Key_Up: + case Qt::Key_PageDown: + case Qt::Key_PageUp: + QCoreApplication::sendEvent(m_treeView, event); + break; + } + } + + return Sidebar::View::eventFilter(object, event); +} + +void SearchSidebar::showEvent(QShowEvent *event) +{ + Q_UNUSED(event) + + if (m_pendingVerticalPosition > 0) { + m_treeView->verticalScrollBar()->setValue(m_pendingVerticalPosition); + m_pendingVerticalPosition = 0; + } + + if (m_pendingSearchEditFocus) { + m_searchEdit->setFocus(); + m_pendingSearchEditFocus = false; + } +} diff --git a/src/libs/ui/searchsidebar.h b/src/libs/ui/searchsidebar.h new file mode 100644 index 000000000..780aebb59 --- /dev/null +++ b/src/libs/ui/searchsidebar.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + +#ifndef ZEAL_WIDGETUI_SEARCHSIDEBAR_H +#define ZEAL_WIDGETUI_SEARCHSIDEBAR_H + +#include + +#include +#include + +class QSplitter; +class QListView; +class QTimer; +class QTreeView; + +namespace Zeal { + +namespace Registry { +class SearchModel; +class SearchQuery; +} // namespace Registry + +namespace WidgetUi { + +class SearchEdit; + +class SearchSidebar final : public Sidebar::View +{ + Q_OBJECT +public: + explicit SearchSidebar(QWidget *parent = nullptr); + SearchSidebar *clone(QWidget *parent = nullptr) const; + + Registry::SearchModel *pageTocModel() const; + +signals: + void navigationRequested(const QUrl &url); + +public slots: + void focusSearchEdit(bool clear = false); + void search(const Registry::SearchQuery &query); + +private slots: + void indexActivated(const QModelIndex &index); + void setupSearchBoxCompletions(); + +protected: + bool eventFilter(QObject *object, QEvent *event) override; + void showEvent(QShowEvent *event) override; + +private: + Q_DISABLE_COPY(SearchSidebar) + explicit SearchSidebar(const SearchSidebar *other, QWidget *parent = nullptr); + + SearchEdit *m_searchEdit = nullptr; + bool m_pendingSearchEditFocus = false; + + // Index and search results tree view state. + QTreeView *m_treeView = nullptr; + QModelIndexList m_expandedIndexList; + int m_pendingVerticalPosition = 0; + Registry::SearchModel *m_searchModel = nullptr; + + // TOC list view state. + QListView *m_pageTocView = nullptr; + Registry::SearchModel *m_pageTocModel = nullptr; + + QSplitter *m_splitter = nullptr; + QTimer *m_delayedNavigationTimer = nullptr; +}; + +} // namespace WidgetUi +} // namespace Zeal + +#endif // ZEAL_WIDGETUI_SEARCHSIDEBAR_H diff --git a/src/libs/ui/sidebarviewprovider.cpp b/src/libs/ui/sidebarviewprovider.cpp new file mode 100644 index 000000000..ed6b67ecb --- /dev/null +++ b/src/libs/ui/sidebarviewprovider.cpp @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + +#include "sidebarviewprovider.h" + +#include "browsertab.h" +#include "mainwindow.h" +#include "searchsidebar.h" + +using namespace Zeal; +using namespace Zeal::WidgetUi; + +SidebarViewProvider::SidebarViewProvider(MainWindow *mainWindow) + : Sidebar::ViewProvider(mainWindow) + , m_mainWindow(mainWindow) +{ + connect(m_mainWindow, &MainWindow::currentTabChanged, this, &SidebarViewProvider::viewChanged, Qt::QueuedConnection); +} + +Sidebar::View *SidebarViewProvider::view(const QString &id) const +{ + if (id == QLatin1String("index")) + return m_mainWindow->currentTab()->searchSidebar(); + + return nullptr; +} diff --git a/src/libs/ui/sidebarviewprovider.h b/src/libs/ui/sidebarviewprovider.h new file mode 100644 index 000000000..65af00783 --- /dev/null +++ b/src/libs/ui/sidebarviewprovider.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + +#ifndef ZEAL_WIDGETUI_SIDEBARVIEWPROVIDER_H +#define ZEAL_WIDGETUI_SIDEBARVIEWPROVIDER_H + +#include + +namespace Zeal { +namespace WidgetUi { + +class MainWindow; + +class SidebarViewProvider : public Sidebar::ViewProvider +{ + Q_OBJECT +public: + explicit SidebarViewProvider(MainWindow *mainWindow); + + Sidebar::View *view(const QString &id = QString()) const override; + +private: + Q_DISABLE_COPY(SidebarViewProvider) + + MainWindow *m_mainWindow = nullptr; +}; + +} // namespace Sidebar +} // namespace Zeal + +#endif // ZEAL_WIDGETUI_SIDEBARVIEWPROVIDER_H diff --git a/src/libs/ui/widgets/CMakeLists.txt b/src/libs/ui/widgets/CMakeLists.txt index a2971786f..e29d288f6 100644 --- a/src/libs/ui/widgets/CMakeLists.txt +++ b/src/libs/ui/widgets/CMakeLists.txt @@ -1,4 +1,5 @@ add_library(Widgets STATIC + layouthelper.cpp searchedit.cpp shortcutedit.cpp toolbarframe.cpp diff --git a/src/libs/ui/widgets/layouthelper.cpp b/src/libs/ui/widgets/layouthelper.cpp new file mode 100644 index 000000000..495d816e2 --- /dev/null +++ b/src/libs/ui/widgets/layouthelper.cpp @@ -0,0 +1,23 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + +#include "layouthelper.h" diff --git a/src/libs/ui/widgets/layouthelper.h b/src/libs/ui/widgets/layouthelper.h new file mode 100644 index 000000000..2df9ffd87 --- /dev/null +++ b/src/libs/ui/widgets/layouthelper.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Oleg Shparber +** Contact: https://go.zealdocs.org/l/contact +** +** This file is part of Zeal. +** +** Zeal is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** Zeal is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Zeal. If not, see . +** +****************************************************************************/ + +#ifndef ZEAL_WIDGETUI_LAYOUTHELPER_H +#define ZEAL_WIDGETUI_LAYOUTHELPER_H + +#include + +namespace Zeal { +namespace WidgetUi { + +namespace LayoutHelper { + +template +Layout *createBorderlessLayout() { + static_assert(std::is_base_of::value, "Layout must derive from QLayout"); + auto layout = new Layout(); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + return layout; +}; + +} // namespace LayoutHelper + +} // namespace WidgetUi +} // namespace Zeal + +#endif // ZEAL_WIDGETUI_LAYOUTHELPER_H diff --git a/src/libs/ui/widgets/searchedit.cpp b/src/libs/ui/widgets/searchedit.cpp index ffaf6dcce..328d2b7de 100644 --- a/src/libs/ui/widgets/searchedit.cpp +++ b/src/libs/ui/widgets/searchedit.cpp @@ -37,6 +37,9 @@ using namespace Zeal::WidgetUi; SearchEdit::SearchEdit(QWidget *parent) : QLineEdit(parent) { + setClearButtonEnabled(true); + setPlaceholderText(tr("Search")); + m_completionLabel = new QLabel(this); m_completionLabel->setObjectName(QStringLiteral("completer")); m_completionLabel->setStyleSheet(QStringLiteral("QLabel#completer { color: gray; }")); @@ -45,12 +48,6 @@ SearchEdit::SearchEdit(QWidget *parent) : connect(this, &SearchEdit::textChanged, this, &SearchEdit::showCompletions); } -void SearchEdit::setTreeView(QTreeView *view) -{ - m_treeView = view; - m_focusing = false; -} - // Makes the line edit use autocompletions. void SearchEdit::setCompletions(const QStringList &completions) { @@ -108,13 +105,6 @@ void SearchEdit::keyPressEvent(QKeyEvent *event) clearQuery(); event->accept(); break; - case Qt::Key_Return: - case Qt::Key_Down: - case Qt::Key_Up: - case Qt::Key_PageDown: - case Qt::Key_PageUp: - QCoreApplication::sendEvent(m_treeView, event); - break; default: QLineEdit::keyPressEvent(event); break; @@ -132,6 +122,9 @@ void SearchEdit::mousePressEvent(QMouseEvent *event) void SearchEdit::showCompletions(const QString &newValue) { + if (!isVisible()) + return; + const int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); const int textWidth = fontMetrics().width(newValue); diff --git a/src/libs/ui/widgets/searchedit.h b/src/libs/ui/widgets/searchedit.h index 679e72d58..454aef4cb 100644 --- a/src/libs/ui/widgets/searchedit.h +++ b/src/libs/ui/widgets/searchedit.h @@ -29,7 +29,6 @@ class QCompleter; class QEvent; class QLabel; -class QTreeView; namespace Zeal { namespace WidgetUi { @@ -40,7 +39,6 @@ class SearchEdit : public QLineEdit public: explicit SearchEdit(QWidget *parent = nullptr); - void setTreeView(QTreeView *view); void clearQuery(); void selectQuery(); void setCompletions(const QStringList &completions); @@ -59,7 +57,6 @@ private slots: int queryStart() const; QCompleter *m_prefixCompleter = nullptr; - QTreeView *m_treeView = nullptr; QLabel *m_completionLabel = nullptr; bool m_focusing = false; };