559 lines
20 KiB
C++

// vim: set tabstop=4 shiftwidth=4 expandtab:
/*
Gwenview: an image viewer
Copyright 2008 Aurélien Gâteau <agateau@kde.org>
This program 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 2
of the License, or (at your option) any later version.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA.
*/
// Self
#include "fullscreencontent.h"
// Qt
#include <QAction>
#include <QApplication>
#include <QCheckBox>
#include <QEvent>
#include <QLabel>
#include <QMenu>
#include <QTimer>
#include <QToolButton>
#include <QWidgetAction>
// KF
#include <KActionCollection>
#include <KActionMenu>
#include <KIconLoader>
#include <KLocalizedString>
// Local
#include "gwenview_app_debug.h"
#include <gvcore.h>
#include <lib/document/documentfactory.h>
#include <lib/eventwatcher.h>
#include <lib/fullscreenbar.h>
#include <lib/gwenviewconfig.h>
#include <lib/imagemetainfomodel.h>
#include <lib/shadowfilter.h>
#include <lib/slideshow.h>
#include <lib/stylesheetutils.h>
#include <lib/thumbnailview/thumbnailbarview.h>
namespace Gwenview
{
/**
* A widget which behaves more or less like a QToolBar, but which uses real
* widgets for the toolbar items. We need a real widget to be able to position
* the option menu.
*/
class FullScreenToolBar : public QWidget
{
public:
explicit FullScreenToolBar(QWidget *parent = nullptr)
: QWidget(parent)
, mLayout(new QHBoxLayout(this))
{
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
mLayout->setSpacing(0);
mLayout->setContentsMargins(0, 0, 0, 0);
}
void addAction(QAction *action, Qt::ToolButtonStyle style = Qt::ToolButtonIconOnly)
{
auto button = new QToolButton;
button->setDefaultAction(action);
button->setToolButtonStyle(style);
button->setAutoRaise(true);
const int extent = KIconLoader::SizeMedium;
button->setIconSize(QSize(extent, extent));
mLayout->addWidget(button);
}
void addSeparator()
{
mLayout->addSpacing(QApplication::style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing));
}
void addStretch()
{
mLayout->addStretch();
}
void setDirection(QBoxLayout::Direction direction)
{
mLayout->setDirection(direction);
}
private:
QBoxLayout *const mLayout;
};
FullScreenContent::FullScreenContent(QObject *parent, GvCore *gvCore)
: QObject(parent)
{
mGvCore = gvCore;
mViewPageVisible = false;
}
void FullScreenContent::init(KActionCollection *actionCollection, QWidget *autoHideParentWidget, SlideShow *slideShow)
{
mSlideShow = slideShow;
mActionCollection = actionCollection;
connect(actionCollection->action(QStringLiteral("view")), &QAction::toggled, this, &FullScreenContent::slotViewModeActionToggled);
// mAutoHideContainer
mAutoHideContainer = new FullScreenBar(autoHideParentWidget);
mAutoHideContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
auto layout = new QVBoxLayout(mAutoHideContainer);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
EventWatcher::install(autoHideParentWidget, QEvent::Resize, this, SLOT(adjustSize()));
// mContent
mContent = new QWidget;
mContent->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
mContent->setAutoFillBackground(true);
EventWatcher::install(mContent, QEvent::Show, this, SLOT(updateCurrentUrlWidgets()));
layout->addWidget(mContent);
createOptionsAction();
// mToolBar
mToolBar = new FullScreenToolBar(mContent);
#define addAction(name) mToolBar->addAction(actionCollection->action(name))
addAction(QStringLiteral("toggle_sidebar"));
mToolBar->addSeparator();
addAction(QStringLiteral("browse"));
addAction(QStringLiteral("view"));
mToolBar->addSeparator();
addAction(QStringLiteral("go_previous"));
addAction(QStringLiteral("toggle_slideshow"));
addAction(QStringLiteral("go_next"));
mToolBar->addSeparator();
addAction(QStringLiteral("rotate_left"));
addAction(QStringLiteral("rotate_right"));
#undef addAction
mToolBarShadow = new ShadowFilter(mToolBar);
// mInformationLabel
mInformationLabel = new QLabel;
mInformationLabel->setWordWrap(true);
mInformationLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
// mDocumentCountLabel
mDocumentCountLabel = new QLabel;
mInformationContainer = new QWidget;
const int infoContainerTopMargin = 6;
const int infoContainerBottomMargin = 2;
mInformationContainer->setContentsMargins(6, infoContainerTopMargin, 6, infoContainerBottomMargin);
mInformationContainer->setAutoFillBackground(true);
mInformationContainer->setBackgroundRole(QPalette::Mid);
mInformationContainerShadow = new ShadowFilter(mInformationContainer);
auto hLayout = new QHBoxLayout(mInformationContainer);
hLayout->setContentsMargins(0, 0, 0, 0);
hLayout->addWidget(mInformationLabel);
hLayout->addWidget(mDocumentCountLabel);
// Thumbnail bar
mThumbnailBar = new ThumbnailBarView(mContent);
mThumbnailBar->setThumbnailScaleMode(ThumbnailView::ScaleToSquare);
auto delegate = new ThumbnailBarItemDelegate(mThumbnailBar);
mThumbnailBar->setItemDelegate(delegate);
mThumbnailBar->setSelectionMode(QAbstractItemView::ExtendedSelection);
// Calculate minimum bar height to give mInformationLabel exactly two lines height
const int lineHeight = mInformationLabel->fontMetrics().lineSpacing();
mMinimumThumbnailBarHeight = mToolBar->sizeHint().height() + lineHeight * 2 + infoContainerTopMargin + infoContainerBottomMargin;
// Ensure document count is updated when items added/removed from folder
connect(mThumbnailBar, &ThumbnailBarView::rowsInsertedSignal, this, &FullScreenContent::updateDocumentCountLabel);
connect(mThumbnailBar, &ThumbnailBarView::rowsRemovedSignal, this, &FullScreenContent::updateDocumentCountLabel);
connect(mThumbnailBar, &ThumbnailBarView::indexActivated, this, &FullScreenContent::updateDocumentCountLabel);
// Right bar
mRightToolBar = new FullScreenToolBar(mContent);
mRightToolBar->addAction(mActionCollection->action(QStringLiteral("leave_fullscreen")), Qt::ToolButtonFollowStyle);
mRightToolBar->addAction(mOptionsAction, Qt::ToolButtonFollowStyle);
mRightToolBar->addStretch();
mRightToolBar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
mRightToolBarShadow = new ShadowFilter(mRightToolBar);
updateLayout();
updateContainerAppearance();
}
ThumbnailBarView *FullScreenContent::thumbnailBar() const
{
return mThumbnailBar;
}
void FullScreenContent::setCurrentUrl(const QUrl &url)
{
if (url.isEmpty()) {
mCurrentDocument = Document::Ptr();
} else {
mCurrentDocument = DocumentFactory::instance()->load(url);
connect(mCurrentDocument.data(), &Document::metaInfoUpdated, this, &FullScreenContent::updateCurrentUrlWidgets);
}
updateCurrentUrlWidgets();
// Give the thumbnail view time to update its "current index"
QTimer::singleShot(0, this, &FullScreenContent::updateDocumentCountLabel);
}
void FullScreenContent::updateInformationLabel()
{
if (!mCurrentDocument) {
return;
}
if (!mInformationLabel->isVisible()) {
return;
}
ImageMetaInfoModel *model = mCurrentDocument->metaInfo();
QStringList valueList;
const QStringList fullScreenPreferredMetaInfoKeyList = GwenviewConfig::fullScreenPreferredMetaInfoKeyList();
for (const QString &key : fullScreenPreferredMetaInfoKeyList) {
const QString value = model->getValueForKey(key);
if (!value.isEmpty()) {
valueList << value;
}
}
const QString text = valueList.join(i18nc("@item:intext fullscreen meta info separator", ", "));
mInformationLabel->setText(text);
}
void FullScreenContent::updateCurrentUrlWidgets()
{
updateInformationLabel();
updateMetaInfoDialog();
}
void FullScreenContent::showImageMetaInfoDialog()
{
if (!mImageMetaInfoDialog) {
mImageMetaInfoDialog = new ImageMetaInfoDialog(mInformationLabel);
// Do not let the fullscreen theme propagate to this dialog for now,
// it's already quite complicated to create a theme
mImageMetaInfoDialog->setStyle(QApplication::style());
mImageMetaInfoDialog->setAttribute(Qt::WA_DeleteOnClose, true);
connect(mImageMetaInfoDialog.data(),
&ImageMetaInfoDialog::preferredMetaInfoKeyListChanged,
this,
&FullScreenContent::slotPreferredMetaInfoKeyListChanged);
connect(mImageMetaInfoDialog.data(), &QObject::destroyed, this, &FullScreenContent::slotImageMetaInfoDialogClosed);
}
if (mCurrentDocument) {
mImageMetaInfoDialog->setMetaInfo(mCurrentDocument->metaInfo(), GwenviewConfig::fullScreenPreferredMetaInfoKeyList());
}
mAutoHideContainer->setAutoHidingEnabled(false);
mImageMetaInfoDialog->show();
}
void FullScreenContent::slotPreferredMetaInfoKeyListChanged(const QStringList &list)
{
GwenviewConfig::setFullScreenPreferredMetaInfoKeyList(list);
GwenviewConfig::self()->save();
updateInformationLabel();
}
void FullScreenContent::updateMetaInfoDialog()
{
if (!mImageMetaInfoDialog) {
return;
}
ImageMetaInfoModel *model = mCurrentDocument ? mCurrentDocument->metaInfo() : nullptr;
mImageMetaInfoDialog->setMetaInfo(model, GwenviewConfig::fullScreenPreferredMetaInfoKeyList());
}
static QString formatSlideShowIntervalText(int value)
{
return i18ncp("Slideshow interval in seconds", "%1 sec", "%1 secs", value);
}
void FullScreenContent::updateLayout()
{
delete mContent->layout();
if (GwenviewConfig::showFullScreenThumbnails()) {
mRightToolBar->setDirection(QBoxLayout::TopToBottom);
auto layout = new QHBoxLayout(mContent);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
QVBoxLayout *vLayout;
// First column
vLayout = new QVBoxLayout;
vLayout->addWidget(mToolBar);
vLayout->addWidget(mInformationContainer);
layout->addLayout(vLayout);
// Second column
layout->addSpacing(2);
layout->addWidget(mThumbnailBar);
layout->addSpacing(2);
// Third column
vLayout = new QVBoxLayout;
vLayout->addWidget(mRightToolBar);
layout->addLayout(vLayout);
mInformationContainer->setMaximumWidth(mToolBar->sizeHint().width());
const int barHeight = qMax(GwenviewConfig::fullScreenBarHeight(), mMinimumThumbnailBarHeight);
mThumbnailBar->setFixedHeight(barHeight);
mAutoHideContainer->setFixedHeight(barHeight);
mInformationLabel->setAlignment(Qt::AlignTop);
mDocumentCountLabel->setAlignment(Qt::AlignBottom | Qt::AlignRight);
// Shadows
mToolBarShadow->reset();
mToolBarShadow->setShadow(ShadowFilter::RightEdge, QColor(0, 0, 0, 64));
mToolBarShadow->setShadow(ShadowFilter::BottomEdge, QColor(255, 255, 255, 8));
mInformationContainerShadow->reset();
mInformationContainerShadow->setShadow(ShadowFilter::LeftEdge, QColor(0, 0, 0, 64));
mInformationContainerShadow->setShadow(ShadowFilter::TopEdge, QColor(0, 0, 0, 64));
mInformationContainerShadow->setShadow(ShadowFilter::RightEdge, QColor(0, 0, 0, 128));
mInformationContainerShadow->setShadow(ShadowFilter::BottomEdge, QColor(255, 255, 255, 8));
mRightToolBarShadow->reset();
mRightToolBarShadow->setShadow(ShadowFilter::LeftEdge, QColor(0, 0, 0, 64));
mRightToolBarShadow->setShadow(ShadowFilter::BottomEdge, QColor(255, 255, 255, 8));
} else {
mRightToolBar->setDirection(QBoxLayout::RightToLeft);
auto layout = new QHBoxLayout(mContent);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(mToolBar);
layout->addWidget(mInformationContainer);
layout->addWidget(mRightToolBar);
mInformationContainer->setMaximumWidth(QWIDGETSIZE_MAX);
mAutoHideContainer->setFixedHeight(mToolBar->sizeHint().height());
mInformationLabel->setAlignment(Qt::AlignVCenter);
mDocumentCountLabel->setAlignment(Qt::AlignVCenter | Qt::AlignRight);
// Shadows
mToolBarShadow->reset();
mInformationContainerShadow->reset();
mInformationContainerShadow->setShadow(ShadowFilter::LeftEdge, QColor(0, 0, 0, 64));
mInformationContainerShadow->setShadow(ShadowFilter::RightEdge, QColor(0, 0, 0, 32));
mRightToolBarShadow->reset();
mRightToolBarShadow->setShadow(ShadowFilter::LeftEdge, QColor(255, 255, 255, 8));
}
}
void FullScreenContent::updateContainerAppearance()
{
if (!mContent->window()->isFullScreen() || !mViewPageVisible) {
mAutoHideContainer->setActivated(false);
return;
}
mThumbnailBar->setVisible(GwenviewConfig::showFullScreenThumbnails());
mAutoHideContainer->adjustSize();
mAutoHideContainer->setActivated(true);
}
void FullScreenContent::adjustSize()
{
if (mContent->window()->isFullScreen() && mViewPageVisible) {
mAutoHideContainer->adjustSize();
}
}
void FullScreenContent::createOptionsAction()
{
// We do not use a KActionMenu because:
//
// - It causes the button to show a small down arrow on its right,
// which makes it wider
//
// - We can't control where the menu shows: in no-thumbnail-mode, the
// menu should not be aligned to the right edge of the screen because
// if the mode is changed to thumbnail-mode, we want the option button
// to remain visible
mOptionsAction = new QAction(this);
mOptionsAction->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
mOptionsAction->setText(i18n("Configure"));
mOptionsAction->setToolTip(i18nc("@info:tooltip", "Configure full screen mode"));
QObject::connect(mOptionsAction, &QAction::triggered, this, &FullScreenContent::showOptionsMenu);
}
void FullScreenContent::updateSlideShowIntervalLabel()
{
Q_ASSERT(mConfigWidget);
const int value = mConfigWidget->mSlideShowIntervalSlider->value();
const QString text = formatSlideShowIntervalText(value);
mConfigWidget->mSlideShowIntervalLabel->setText(text);
}
void FullScreenContent::setFullScreenBarHeight(int value)
{
mThumbnailBar->setFixedHeight(value);
mAutoHideContainer->setFixedHeight(value);
GwenviewConfig::setFullScreenBarHeight(value);
}
void FullScreenContent::showOptionsMenu()
{
Q_ASSERT(!mConfigWidget);
mConfigWidget = new FullScreenConfigWidget;
FullScreenConfigWidget *widget = mConfigWidget;
// Put widget in a menu
QMenu menu;
auto action = new QWidgetAction(&menu);
action->setDefaultWidget(widget);
menu.addAction(action);
// Slideshow checkboxes
widget->mSlideShowLoopCheckBox->setChecked(mSlideShow->loopAction()->isChecked());
connect(widget->mSlideShowLoopCheckBox, &QAbstractButton::toggled, mSlideShow->loopAction(), &QAction::trigger);
widget->mSlideShowRandomCheckBox->setChecked(mSlideShow->randomAction()->isChecked());
connect(widget->mSlideShowRandomCheckBox, &QAbstractButton::toggled, mSlideShow->randomAction(), &QAction::trigger);
// Interval slider
widget->mSlideShowIntervalSlider->setValue(int(GwenviewConfig::interval()));
connect(widget->mSlideShowIntervalSlider, &QAbstractSlider::valueChanged, mSlideShow, &SlideShow::setInterval);
connect(widget->mSlideShowIntervalSlider, &QAbstractSlider::valueChanged, this, &FullScreenContent::updateSlideShowIntervalLabel);
// Interval label
QString text = formatSlideShowIntervalText(88);
int width = widget->mSlideShowIntervalLabel->fontMetrics().boundingRect(text).width();
widget->mSlideShowIntervalLabel->setFixedWidth(width);
updateSlideShowIntervalLabel();
// Image information
connect(widget->mConfigureDisplayedInformationButton, &QAbstractButton::clicked, this, &FullScreenContent::showImageMetaInfoDialog);
// Thumbnails
widget->mThumbnailGroupBox->setVisible(mViewPageVisible);
if (mViewPageVisible) {
widget->mShowThumbnailsCheckBox->setChecked(GwenviewConfig::showFullScreenThumbnails());
widget->mHeightSlider->setMinimum(mMinimumThumbnailBarHeight);
widget->mHeightSlider->setValue(mThumbnailBar->height());
connect(widget->mShowThumbnailsCheckBox, &QAbstractButton::toggled, this, &FullScreenContent::slotShowThumbnailsToggled);
connect(widget->mHeightSlider, &QAbstractSlider::valueChanged, this, &FullScreenContent::setFullScreenBarHeight);
}
// Show menu below its button
QPoint pos;
QWidget *button = mOptionsAction->associatedWidgets().constFirst();
Q_ASSERT(button);
qCWarning(GWENVIEW_APP_LOG) << button << button->geometry();
if (QApplication::isRightToLeft()) {
pos = button->mapToGlobal(button->rect().bottomLeft());
} else {
pos = button->mapToGlobal(button->rect().bottomRight());
pos.rx() -= menu.sizeHint().width();
}
qCWarning(GWENVIEW_APP_LOG) << pos;
menu.exec(pos);
}
void FullScreenContent::setFullScreenMode(bool fullScreenMode)
{
Q_UNUSED(fullScreenMode);
updateContainerAppearance();
setupThumbnailBarStyleSheet();
}
void FullScreenContent::setDistractionFreeMode(bool distractionFreeMode)
{
mAutoHideContainer->setEdgeTriggerEnabled(!distractionFreeMode);
}
void FullScreenContent::slotImageMetaInfoDialogClosed()
{
mAutoHideContainer->setAutoHidingEnabled(true);
}
void FullScreenContent::slotShowThumbnailsToggled(bool value)
{
GwenviewConfig::setShowFullScreenThumbnails(value);
GwenviewConfig::self()->save();
mThumbnailBar->setVisible(value);
updateLayout();
mContent->adjustSize();
mAutoHideContainer->adjustSize();
}
void FullScreenContent::slotViewModeActionToggled(bool value)
{
mViewPageVisible = value;
updateContainerAppearance();
}
void FullScreenContent::setupThumbnailBarStyleSheet()
{
const QPalette pal = mGvCore->palette(GvCore::NormalPalette);
const QPalette fsPal = mGvCore->palette(GvCore::FullScreenPalette);
QColor bgColor = fsPal.color(QPalette::Normal, QPalette::Base);
QColor bgSelColor = pal.color(QPalette::Normal, QPalette::Highlight);
QColor bgHovColor = pal.color(QPalette::Normal, QPalette::Highlight);
// Darken the select color a little to suit dark theme of fullscreen mode
bgSelColor.setHsv(bgSelColor.hue(), bgSelColor.saturation(), (bgSelColor.value() * 0.8));
// Calculate hover color based on background color in case it changes (matches ViewMainPage thumbnail bar)
bgHovColor.setHsv(bgHovColor.hue(), (bgHovColor.saturation() / 2), ((bgHovColor.value() + bgColor.value()) / 2));
QString genCss =
"QListView {"
" background-color: %1;"
"}";
genCss = genCss.arg(StyleSheetUtils::rgba(bgColor));
QString itemSelCss =
"QListView::item:selected {"
" background-color: %1;"
"}";
itemSelCss = itemSelCss.arg(StyleSheetUtils::rgba(bgSelColor));
QString itemHovCss =
"QListView::item:hover:!selected {"
" background-color: %1;"
"}";
itemHovCss = itemHovCss.arg(StyleSheetUtils::rgba(bgHovColor));
QString css = genCss + itemSelCss + itemHovCss;
mThumbnailBar->setStyleSheet(css);
}
void FullScreenContent::updateDocumentCountLabel()
{
const int current = mThumbnailBar->currentIndex().row() + 1;
const int total = mThumbnailBar->model()->rowCount();
const QString text = i18nc("@info:status %1 current document index, %2 total documents", "%1 of %2", current, total);
mDocumentCountLabel->setText(text);
}
} // namespace
#include "moc_fullscreencontent.cpp"