Port sidebar to QDockWidget

With this MR, the sidebar can now be (if not locked):
* docked to the left or right side
* undocked and floated as an independent window
* closed with the close button in the header

BUG: 455013
This commit is contained in:
Eugene Popov 2022-12-22 22:50:55 +00:00 committed by Albert Astals Cid
parent 38438b00bf
commit 2fbab13e1f
8 changed files with 192 additions and 3 deletions

View file

@ -79,6 +79,13 @@ public:
*/
virtual bool openNewFilesInTabs() const = 0;
/**
* Returns the sidebar container.
*
* @since 23.04
*/
virtual QWidget *getSideContainer() const = 0;
// SIGNALS
/**
* The signal 'openSourceReference' is emitted whenever the user has triggered a source

View file

@ -1107,6 +1107,11 @@ bool Part::openNewFilesInTabs() const
return Okular::Settings::self()->shellOpenFileInTabs();
}
QWidget *Part::getSideContainer() const
{
return m_sidebar->getSideContainer();
}
bool Part::activateTabIfAlreadyOpenFile() const
{
return Okular::Settings::self()->switchToTabIfOpen();

View file

@ -147,6 +147,7 @@ public:
bool areSourceLocationsShownGraphically() const override;
void setShowSourceLocationsGraphically(bool show) override;
bool openNewFilesInTabs() const override;
QWidget *getSideContainer() const override;
Q_INVOKABLE bool activateTabIfAlreadyOpenFile() const;
void setModified(bool modified) override;

View file

@ -166,6 +166,11 @@ void Sidebar::moveSplitter(int sideWidgetSize)
d->splitter->setSizes(splitterSizeList);
}
QWidget *Sidebar::getSideContainer() const
{
return d->sideContainer;
}
void Sidebar::splitterMoved(int /*pos*/, int index)
{
// if the side panel has been resized, save splitter sizes

View file

@ -33,6 +33,8 @@ public:
void moveSplitter(int sideWidgetSize);
QWidget *getSideContainer() const;
Q_SIGNALS:
void urlsDropped(const QList<QUrl> &urls);

View file

@ -36,6 +36,7 @@
#include <KXMLGUIFactory>
#include <QApplication>
#include <QDBusConnection>
#include <QDockWidget>
#include <QDragMoveEvent>
#include <QFileDialog>
#include <QMenuBar>
@ -62,6 +63,86 @@ static const char *shouldShowToolBarComingFromFullScreen = "shouldShowToolBarCom
static const char *const SESSION_URL_KEY = "Urls";
static const char *const SESSION_TAB_KEY = "ActiveTab";
static constexpr char SIDEBAR_LOCKED_KEY[] = "LockSidebar";
static constexpr char SIDEBAR_VISIBLE_KEY[] = "ShowSidebar";
/**
* Groups sidebar containers in a QDockWidget.
*
* This control groups all the sidebar containers provided by each tab (the Part object),
* allowing the user to dock it to the left and right sides of the window,
* or detach it from the window altogether.
*/
class Sidebar : public QDockWidget
{
Q_OBJECT
public:
explicit Sidebar(QWidget *parent = nullptr)
: QDockWidget(parent)
{
setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
setFeatures(defaultFeatures());
m_stackedWidget = new QStackedWidget;
setWidget(m_stackedWidget);
}
bool isLocked() const
{
return features().testFlag(NoDockWidgetFeatures);
}
void setLocked(bool locked)
{
setFeatures(locked ? NoDockWidgetFeatures : defaultFeatures());
// show titlebar only if not locked
if (locked) {
if (!m_dumbTitleWidget) {
m_dumbTitleWidget = new QWidget;
}
setTitleBarWidget(m_dumbTitleWidget);
} else {
setTitleBarWidget(nullptr);
}
}
int indexOf(QWidget *widget) const
{
return m_stackedWidget->indexOf(widget);
}
void addWidget(QWidget *widget)
{
m_stackedWidget->addWidget(widget);
}
void removeWidget(QWidget *widget)
{
m_stackedWidget->removeWidget(widget);
}
void setCurrentWidget(QWidget *widget)
{
m_stackedWidget->setCurrentWidget(widget);
}
private:
static DockWidgetFeatures defaultFeatures()
{
DockWidgetFeatures dockFeatures = DockWidgetClosable | DockWidgetMovable;
if (!KWindowSystem::isPlatformWayland()) { // TODO : Remove this check when QTBUG-87332 is fixed
dockFeatures |= DockWidgetFloatable;
}
return dockFeatures;
}
QStackedWidget *m_stackedWidget = nullptr;
QWidget *m_dumbTitleWidget = nullptr;
};
Shell::Shell(const QString &serializedOptions)
: KParts::MainWindow()
, m_menuBarWasShown(true)
@ -126,12 +207,27 @@ Shell::Shell(const QString &serializedOptions)
connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &Shell::closeTab);
connect(m_tabWidget->tabBar(), &QTabBar::tabMoved, this, &Shell::moveTabData);
m_sidebar = new Sidebar;
m_sidebar->setObjectName(QStringLiteral("okular_sidebar"));
m_sidebar->setContextMenuPolicy(Qt::ActionsContextMenu);
m_sidebar->setWindowTitle(i18n("Sidebar"));
connect(m_sidebar, &QDockWidget::visibilityChanged, this, [this](bool visible) {
// sync sidebar visibility with the m_showSidebarAction only if welcome screen is hidden
if (m_showSidebarAction && m_centralStackedWidget->currentWidget() != m_welcomeScreen) {
m_showSidebarAction->setChecked(visible);
}
});
addDockWidget(Qt::LeftDockWidgetArea, m_sidebar);
// then, setup our actions
setupActions();
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater);
// and integrate the part's GUI with the shell's
setupGUI(Keys | ToolBar | Save);
// NOTE : apply default sidebar width only after calling setupGUI(...)
resizeDocks({m_sidebar}, {200}, Qt::Horizontal);
m_tabs.append(TabState(firstPart));
m_tabWidget->addTab(firstPart->widget(), QString()); // triggers setActiveTab that calls createGUI( part )
@ -367,11 +463,25 @@ void Shell::readSettings()
m_menuBarWasShown = group.readEntry(shouldShowMenuBarComingFromFullScreen, true);
m_toolBarWasShown = group.readEntry(shouldShowToolBarComingFromFullScreen, true);
}
const KConfigGroup sidebarGroup = KSharedConfig::openConfig()->group("General");
m_sidebar->setVisible(sidebarGroup.readEntry(SIDEBAR_VISIBLE_KEY, true));
m_sidebar->setLocked(sidebarGroup.readEntry(SIDEBAR_LOCKED_KEY, true));
m_showSidebarAction->setChecked(m_sidebar->isVisibleTo(this));
m_lockSidebarAction->setChecked(m_sidebar->isLocked());
}
void Shell::writeSettings()
{
saveRecents();
KConfigGroup sidebarGroup = KSharedConfig::openConfig()->group("General");
sidebarGroup.writeEntry(SIDEBAR_LOCKED_KEY, m_sidebar->isLocked());
// NOTE : Consider whether the m_showSidebarAction is checked, because
// the sidebar can be forcibly hidden if the welcome screen is displayed
sidebarGroup.writeEntry(SIDEBAR_VISIBLE_KEY, m_sidebar->isVisibleTo(this) || m_showSidebarAction->isChecked());
KConfigGroup group = KSharedConfig::openConfig()->group("Desktop Entry");
group.writeEntry("FullScreen", m_fullScreenAction->isChecked());
if (m_fullScreenAction->isChecked()) {
@ -425,6 +535,19 @@ void Shell::setupActions()
m_undoCloseTab->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo")));
m_undoCloseTab->setEnabled(false);
connect(m_undoCloseTab, &QAction::triggered, this, &Shell::undoCloseTab);
m_showSidebarAction = actionCollection()->addAction(QStringLiteral("okular_show_sidebar"));
m_showSidebarAction->setCheckable(true);
m_showSidebarAction->setIcon(QIcon::fromTheme(QStringLiteral("sidebar-show-symbolic")));
m_showSidebarAction->setText(i18n("Show Sidebar"));
connect(m_showSidebarAction, &QAction::triggered, m_sidebar, &Sidebar::setVisible);
m_lockSidebarAction = actionCollection()->addAction(QStringLiteral("okular_lock_sidebar"));
m_lockSidebarAction->setCheckable(true);
m_lockSidebarAction->setIcon(QIcon::fromTheme(QStringLiteral("lock")));
m_lockSidebarAction->setText(i18n("Lock Sidebar"));
connect(m_lockSidebarAction, &QAction::triggered, m_sidebar, &Sidebar::setLocked);
m_sidebar->addAction(m_lockSidebarAction);
}
void Shell::saveProperties(KConfigGroup &group)
@ -673,7 +796,24 @@ bool Shell::queryClose()
void Shell::setActiveTab(int tab)
{
m_tabWidget->setCurrentIndex(tab);
// NOTE : createGUI(...) breaks the visibility of the sidebar, so we need
// to save and restore it
const bool isSidebarVisible = m_sidebar->isVisible();
createGUI(m_tabs[tab].part);
m_sidebar->setVisible(isSidebarVisible);
// dock KPart's sidebar if new and make it current
Okular::ViewerInterface *iPart = qobject_cast<Okular::ViewerInterface *>(m_tabs[tab].part);
Q_ASSERT(iPart);
QWidget *sideContainer = iPart->getSideContainer();
if (m_sidebar->indexOf(sideContainer) == -1) {
m_sidebar->addWidget(sideContainer);
if (m_sidebar->maximumWidth() > sideContainer->maximumWidth()) {
m_sidebar->setMaximumWidth(sideContainer->maximumWidth());
}
}
m_sidebar->setCurrentWidget(sideContainer);
m_printAction->setEnabled(m_tabs[tab].printEnabled);
m_closeAction->setEnabled(m_tabs[tab].closeEnabled);
@ -689,6 +829,13 @@ void Shell::closeTab(int tab)
part->factory()->removeClient(part);
}
part->disconnect();
Okular::ViewerInterface *iPart = qobject_cast<Okular::ViewerInterface *>(m_tabs[tab].part);
Q_ASSERT(iPart);
QWidget *sideContainer = iPart->getSideContainer();
m_sidebar->removeWidget(sideContainer);
connect(part, &QObject::destroyed, sideContainer, &QObject::deleteLater);
part->deleteLater();
m_tabs.removeAt(tab);
m_tabWidget->removeTab(tab);
@ -776,7 +923,7 @@ void Shell::applyOptionsToPart(QObject *part, const QString &serializedOptions)
}
}
void Shell::connectPart(QObject *part)
void Shell::connectPart(const KParts::ReadWritePart *part)
{
// We're abusing the fact we know the part is our part here
connect(this, SIGNAL(moveSplitter(int)), part, SLOT(moveSplitter(int))); // clazy:exclude=old-style-connect
@ -788,6 +935,12 @@ void Shell::connectPart(QObject *part)
// Otherwise the QSize,QSize gets turned into QSize, QSize that is not normalized signals and is slightly slower
connect(part, SIGNAL(fitWindowToPage(QSize,QSize)), this, SLOT(slotFitWindowToPage(QSize,QSize))); // clazy:exclude=old-style-connect
// clang-format on
// since sidebar is now docked to main window, we use another action
// to show/hide it, so we should hide a similar KPart's action
if (QAction *action = part->actionCollection()->action(QStringLiteral("show_leftpanel"))) {
action->setVisible(false);
}
}
void Shell::print()
@ -903,12 +1056,17 @@ void Shell::slotFitWindowToPage(const QSize pageViewSize, const QSize pageSize)
void Shell::hideWelcomeScreen()
{
m_sidebar->setVisible(m_showSidebarAction->isChecked());
m_centralStackedWidget->setCurrentWidget(m_tabWidget);
m_showSidebarAction->setEnabled(true);
}
void Shell::showWelcomeScreen()
{
m_showSidebarAction->setEnabled(false);
m_centralStackedWidget->setCurrentWidget(m_welcomeScreen);
m_sidebar->setVisible(false);
refreshRecentsOnWelcomeScreen();
}
@ -927,4 +1085,6 @@ void Shell::forgetRecentItem(QUrl const &url)
}
}
#include "shell.moc"
/* kate: replace-tabs on; indent-width 4; */

View file

@ -24,6 +24,7 @@
#include "welcomescreen.h"
class Sidebar;
class KRecentFilesAction;
class KToggleAction;
class QTabWidget;
@ -155,7 +156,7 @@ private:
void setupActions();
void openNewTab(const QUrl &url, const QString &serializedOptions);
void applyOptionsToPart(QObject *part, const QString &serializedOptions);
void connectPart(QObject *part);
void connectPart(const KParts::ReadWritePart *part);
int findTabIndex(QObject *sender) const;
int findTabIndex(const QUrl &url) const;
@ -177,6 +178,7 @@ private:
KToggleAction *m_openInTab;
WelcomeScreen *m_welcomeScreen;
QStackedWidget *m_centralStackedWidget;
Sidebar *m_sidebar = nullptr;
struct TabState {
explicit TabState(KParts::ReadWritePart *p)
@ -194,6 +196,8 @@ private:
QAction *m_nextTabAction;
QAction *m_prevTabAction;
QAction *m_undoCloseTab;
QAction *m_showSidebarAction = nullptr;
QAction *m_lockSidebarAction = nullptr;
#ifndef Q_OS_WIN
KActivities::ResourceInstance *m_activityResource;

View file

@ -1,6 +1,6 @@
<?xml version="1.0"?>
<!DOCTYPE gui SYSTEM "kpartgui.dtd">
<gui version="9" name="okular_shell" >
<gui version="10" name="okular_shell" >
<MenuBar>
<Menu name="file" >
<DefineGroup append="open_merge" name="file_open" />
@ -13,6 +13,7 @@
</Menu-->
<Menu name="settings" >
<DefineGroup append="show_merge" name="show_merge" />
<Action name="okular_show_sidebar" append="show_merge" />
<DefineGroup append="configure_merge" name="configure_merge" />
</Menu>
<Merge/>
@ -21,8 +22,12 @@
</Menu>
</MenuBar>
<ToolBar noMerge="1" name="mainToolBar" >
<Action name="okular_show_sidebar" />
<text>Main Toolbar</text>
<!--Action name="file_open_recent" /-->
<!--Action name="file_print" /-->
</ToolBar>
<ActionProperties scheme="Default">
<Action priority="0" name="okular_show_sidebar"/>
</ActionProperties>
</gui>