diff --git a/NEWS b/NEWS index 94e45b6308..12c6a87a30 100644 --- a/NEWS +++ b/NEWS @@ -1,124 +1,124 @@ digiKam 6.1.0 - Release date: 2019-03-10 ***************************************************************************************************** NEW FEATURES: General : New plugins interface for digiKam and Showfoto named dplugins. General : All export tools become generic plugins and are shared with Showfoto. General : Update internal libpgf to last 07193. General : Add compatiblity with OpenCV version 4. General : MacOS and AppImage bundles are now published with Qt 5.11.3. General : Add new optional configuration option to compile with Faces Engine Neural Network. BQM : Add new advanced settings in resize tool. BQM : All Batch Queue Manager tools become Bqm plugins. Editor : All Image Editor tools become Editor plugins and are shared with Showfoto. Item View : Add sort items by modification date. ***************************************************************************************************** BUGFIXES: 001 ==> 279216 - "Resize image" feature is missing some useful options [patch]. 002 ==> 368779 - Missing translations to Plugin Categories columns in setup page. 003 ==> 165852 - Google Youtube video uploader. 004 ==> 404578 - Links broken on downloadpage for 6.0.0. 005 ==> 402069 - FAQ link on Support page is broken. 006 ==> 404503 - There is a typo in support section of website. 007 ==> 404621 - I can not import the album Google Photo. 008 ==> 404642 - digiKam flatpak: Please include mysql driver (for using an external mysql db). 009 ==> 404690 - There is an unwanted bar in the left screen edge in full screen view. 010 ==> 404736 - Merging tags keeps popping up confirmation dialogs every second. 011 ==> 404737 - digiKam does not compile with opencv 3.4.1. 012 ==> 404735 - F2 should edit tag name when tag name has focus. 013 ==> 404748 - Default Album rename dialog should be larger (or save its size when changed). 014 ==> 304811 - Offer a "stretch histogram" functionality to automatically adjust brightness & gamma. 015 ==> 374464 - Can start print wizzard. 016 ==> 316687 - ImageMagick-6.8.3.9 could not be found. 017 ==> 305137 - wish for integrated(?) clip-generator. 018 ==> 404821 - Presentation offset from full screen when OpenGL transitions selected. 019 ==> 404894 - 6.0.0 x86-64 appimage startup complains about mssing ']' (startup bash script being called). 020 ==> 368262 - Google services tool does not use kaccounts. 021 ==> 376913 - Can't create a new album in picasa/googlephoto. 022 ==> 404896 - Vertical video are displayed horizontally. 023 ==> 404859 - Using Batch Queue Manager to make a JPG copy of the images on completion the "arw" images are no longer visible in the originating album. 024 ==> 404893 - Digikam::DigikamApp::slotSolidDeviceChanged: slotSolidDeviceChanged: messages referring to directory that digikam should not care about. 025 ==> 264296 - Lack of right-click delete of points in Curves [patch]. 026 ==> 404954 - Places, Devices, and Removable Devices no longer show in "Select Target Location". 027 ==> 404962 - List of subfolders: incorrect encoding (spaces -> %20, accents). 028 ==> 402724 - digiKam Settings/Configuration: missing section "Plugins" in Windows/6.0.0B3. 029 ==> 404987 - Ability to select which import/export options are included in menu. 030 ==> 404999 - Inconsistency in facetag font size. 031 ==> 244259 - Last image is displayed twice when Advanced Slideshow with KenBurns effect is run more than once. 032 ==> 405043 - Add volume control to video playback. 033 ==> 401253 - Face detect crashes every time. 034 ==> 405042 - Ability to loop video playback. 035 ==> 405138 - Can not disable webservice plugin. 036 ==> 405137 - Original items visible in Thumbnails view. 037 ==> 400606 - Dead space above thumbnails. 038 ==> 380434 - 5.6.0-pre pkg does not detect filesystem changes. 039 ==> 405250 - Menus gone missing. 040 ==> 388198 - Menu Help -> What's this is not used. 041 ==> 392570 - Missing Option to display complete filename. 042 ==> 375474 - Renaming People Tag Causes Unpredictable Sort Order In People Menu. 043 ==> 398868 - Video upside down in Preview (Thumbnail ok). 044 ==> 380065 - "Open with" menu entry missing [patch]. 045 ==> 405258 - Provide an OpenWith... function to get a specific ImageEditor. 046 ==> 402807 - Progress manager doesn't seem to be involved in the fingerprint scanning (v6.0 beta 3). 047 ==> 278935 - Please make XMP Sidecar filename configurable [patch]. 048 ==> 405231 - Monitor Color Profile is not applied in "Presentation". 049 ==> 405347 - Selecting by aspect ratio: abs function in sqlite lowercase, in mariadb uppercase. 050 ==> 405327 - Position and size of faces display depends on configuration setting. 051 ==> 405234 - Refresh does no work. 052 ==> 403649 - Filesystem changes are not visible in album view [patch]. 053 ==> 400768 - Many different trash cans hard & slow to use. 054 ==> 296864 - SETUP : Create interface for changing physical location or path of album. 055 ==> 397189 - digikam is crashing when adding this photo. 056 ==> 405378 - Missing libz.dll on launch. 057 ==> 401306 - digikam-git r41326 doesn't compile with OpenCV 4 058 ==> 379049 - IPTC and XMP metadata not always read when adding a new album. 059 ==> 404939 - AppImage package: integration with the OS with AppImageLauncher. 060 ==> 405514 - Configure Shortcuts : Shortcuts file contains space in assigned_tags. 061 ==> 405149 - The "File Name" in the thumbnail view is not visible. 062 ==> 405512 - Meta Key Not Useful For Key Modifier In Windows. 063 ==> 405513 - Configure Shortcuts : Defaults Button Does Not Apply To New Schemes. 064 ==> 388334 - Auto Filter In People Tag List Confused With Shortcut Keys. 065 ==> 405518 - digiKam can not be added to the Gnome Dash as favorite. 066 ==> 405342 - Increase slideshow caption font size and keyboard shortcut or button to display/hide it. 067 ==> 405636 - When importing picture not all people tags are present. 068 ==> 379916 - Some people tags are missing. 069 ==> 386967 - digiKam with Adobe Bridge keywords under Windows. 070 ==> 402433 - Google Maps Doesn't Zoom With Mouse Wheel. 071 ==> 396920 - EXIF info not written when files from some cameras (Pentax K3) are edited and saved. 072 ==> 375468 - Cannot turn on Menubar. 073 ==> 387253 - No more menu bar in the Gnome desktop environment. 074 ==> 397405 - Large empty space in Digikams main icon view since ~20180707 appimage. 075 ==> 399237 - Thumbnails view shows an empty dock for thumbnails. 076 ==> 393935 - Segmentation fault during face detection. 077 ==> 388533 - UpdateSchemaFromV7ToV9 fails. 078 ==> 281493 - digiKam fails to install on Windows XP. 079 ==> 405537 - Strange behavior with tag in attached file. 080 ==> 405233 - digikam: symbol lookup error: /tmp/.mount_digika5N9rGH/usr/lib/libQt5XcbQpa.so.5: undefined symbol: FT_Property_Set. 081 ==> 404853 - digikam-6.0.0 faces engine fails to compile on PowerPC. 082 ==> 405625 - digiKam 6.0.0 faces engine fails to compile on PowerPC with AltiVec enabled. 083 ==> 372340 - Tagged face areas on portait (vertical) oriented images are mispositioned. 084 ==> 405743 - Customise Tool tab in editor. 085 ==> 376014 - Moving grouped photos from one folder to another breaks the group. 086 ==> 377782 - Group is lost when moved to another album. 087 ==> 385147 - Moving grouped images into another album removes groups. 088 ==> 342017 - Reverse Geocoding doesn't work. 089 ==> 405174 - Right-click advice menu. Open with another program. 090 ==> 399285 - Would it be possible to drop kio for a better 'open with' functionality across all platforms? 091 ==> 383079 - Open the photo in GIMP under windows. 092 ==> 374356 - "Show item in file explorer" as well as "open with". 093 ==> 208201 - Icons appears and disappears. 094 ==> 202955 - thumbnails in album view are flickering with high frequency. 095 ==> 208201 - Icons appears and disappears. 096 ==> 396933 - Tooltips of thumbnails and albums not readable in light designs. 097 ==> 393777 - Item Tool Tips are unreadable in Windows 10 (white on yellow). 098 ==> 240237 - Not possible to rotate the image. 099 ==> 366446 - Creating new albums fails due to invalid path name. 100 ==> 369235 - Menu bar is not translated. 101 ==> 403132 - Lazy Update not working (or: Stop when I say so). 102 ==> 404797 - digikam-6.0.0-x86-64.appimage: stack trace shortly after startup. -103 ==> - +103 ==> 405789 - Sidebar widths revert to their defaults after switching back from full-screen view. +104 ==> diff --git a/core/libs/widgets/mainview/sidebar.cpp b/core/libs/widgets/mainview/sidebar.cpp index 3bc41d051d..4863eb20f0 100644 --- a/core/libs/widgets/mainview/sidebar.cpp +++ b/core/libs/widgets/mainview/sidebar.cpp @@ -1,1344 +1,1349 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-03-22 * Description : a widget to manage sidebar in GUI. * * Copyright (C) 2005-2006 by Joern Ahrens * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2008-2011 by Marcel Wiesweg * Copyright (C) 2001-2003 by Joseph Wenninger * * 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, 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. * * ============================================================ */ #include "sidebar.h" #include // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Local includes #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN DMultiTabBarFrame::Private { public: QBoxLayout* mainLayout; QList tabs; Qt::Edge position; DMultiTabBar::TextStyle style; }; DMultiTabBarFrame::DMultiTabBarFrame(QWidget* const parent, Qt::Edge pos) : QFrame(parent), d(new Private) { d->position = pos; if (pos == Qt::LeftEdge || pos == Qt::RightEdge) d->mainLayout = new QVBoxLayout(this); else d->mainLayout = new QHBoxLayout(this); d->mainLayout->setContentsMargins(QMargins()); d->mainLayout->setSpacing(0); d->mainLayout->addStretch(); setFrameStyle(NoFrame); setBackgroundRole(QPalette::Window); } DMultiTabBarFrame::~DMultiTabBarFrame() { qDeleteAll(d->tabs); d->tabs.clear(); delete d; } void DMultiTabBarFrame::setStyle(DMultiTabBar::TextStyle style) { d->style = style; for (int i = 0 ; i < d->tabs.count() ; ++i) d->tabs.at(i)->setStyle(d->style); updateGeometry(); } void DMultiTabBarFrame::contentsMousePressEvent(QMouseEvent* e) { e->ignore(); } void DMultiTabBarFrame::mousePressEvent(QMouseEvent* e) { e->ignore(); } DMultiTabBarTab* DMultiTabBarFrame::tab(int id) const { QListIterator it(d->tabs); while (it.hasNext()) { DMultiTabBarTab* const tab = it.next(); if (tab->id() == id) return tab; } return 0; } int DMultiTabBarFrame::appendTab(const QPixmap& pic, int id, const QString& text) { DMultiTabBarTab* const tab = new DMultiTabBarTab(pic, text, id, this, d->position, d->style); d->tabs.append(tab); // Insert before the stretch. d->mainLayout->insertWidget(d->tabs.size()-1, tab); tab->show(); return 0; } void DMultiTabBarFrame::removeTab(int id) { for (int pos = 0 ; pos < d->tabs.count() ; ++pos) { if (d->tabs.at(pos)->id() == id) { // remove & delete the tab delete d->tabs.takeAt(pos); break; } } } void DMultiTabBarFrame::setPosition(Qt::Edge pos) { d->position = pos; for (int i = 0 ; i < d->tabs.count() ; ++i) d->tabs.at(i)->setPosition(d->position); updateGeometry(); } QList* DMultiTabBarFrame::tabs() { return &d->tabs; } // ------------------------------------------------------------------------------------- DMultiTabBarButton::DMultiTabBarButton(const QPixmap& pic, const QString& text, int id, QWidget* const parent) : QPushButton(QIcon(pic), text, parent), m_id(id) { connect(this, SIGNAL(clicked()), this, SLOT(slotClicked())); // we can't see the focus, so don't take focus. #45557 // If keyboard navigation is wanted, then only the bar should take focus, // and arrows could change the focused button; but generally, tabbars don't take focus anyway. setFocusPolicy(Qt::NoFocus); // See RB #128005 setAttribute(Qt::WA_LayoutUsesWidgetRect); } DMultiTabBarButton::~DMultiTabBarButton() { } void DMultiTabBarButton::setText(const QString& text) { QPushButton::setText(text); } void DMultiTabBarButton::slotClicked() { updateGeometry(); emit clicked(m_id); } int DMultiTabBarButton::id() const { return m_id; } void DMultiTabBarButton::hideEvent(QHideEvent* e) { QPushButton::hideEvent(e); DMultiTabBar* const tb = dynamic_cast(parentWidget()); if (tb) tb->updateSeparator(); } void DMultiTabBarButton::showEvent(QShowEvent* e) { QPushButton::showEvent(e); DMultiTabBar* const tb = dynamic_cast(parentWidget()); if (tb) tb->updateSeparator(); } void DMultiTabBarButton::paintEvent(QPaintEvent*) { QStyleOptionButton opt; opt.initFrom(this); opt.icon = icon(); opt.iconSize = iconSize(); // removes the QStyleOptionButton::HasMenu ButtonFeature opt.features = QStyleOptionButton::Flat; QPainter painter(this); style()->drawControl(QStyle::CE_PushButton, &opt, &painter, this); } // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN DMultiTabBarTab::Private { public: Qt::Edge position; DMultiTabBar::TextStyle style; }; DMultiTabBarTab::DMultiTabBarTab(const QPixmap& pic, const QString& text, int id, QWidget* const parent, Qt::Edge pos, DMultiTabBar::TextStyle style) : DMultiTabBarButton(pic, text, id, parent), d(new Private) { d->style = style; d->position = pos; setToolTip(text); setCheckable(true); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); // shrink down to icon only, but prefer to show text if it's there } DMultiTabBarTab::~DMultiTabBarTab() { delete d; } void DMultiTabBarTab::setPosition(Qt::Edge pos) { d->position = pos; updateGeometry(); } void DMultiTabBarTab::setStyle(DMultiTabBar::TextStyle style) { d->style = style; updateGeometry(); } QPixmap DMultiTabBarTab::iconPixmap() const { int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); return icon().pixmap(iconSize); } void DMultiTabBarTab::initStyleOption(QStyleOptionToolButton* opt) const { opt->initFrom(this); // Setup icon.. if (!icon().isNull()) { opt->iconSize = iconPixmap().size(); opt->icon = icon(); } // Should we draw text? if (shouldDrawText()) opt->text = text(); if (underMouse()) opt->state |= QStyle::State_AutoRaise | QStyle::State_MouseOver | QStyle::State_Raised; if (isChecked()) opt->state |= QStyle::State_Sunken | QStyle::State_On; opt->font = font(); opt->toolButtonStyle = shouldDrawText() ? Qt::ToolButtonTextBesideIcon : Qt::ToolButtonIconOnly; opt->subControls = QStyle::SC_ToolButton; } QSize DMultiTabBarTab::sizeHint() const { return computeSizeHint(shouldDrawText()); } QSize DMultiTabBarTab::minimumSizeHint() const { return computeSizeHint(false); } void DMultiTabBarTab::computeMargins(int* hMargin, int* vMargin) const { // Unfortunately, QStyle does not give us enough information to figure out // where to place things, so we try to reverse-engineer it QStyleOptionToolButton opt; initStyleOption(&opt); QPixmap iconPix = iconPixmap(); QSize trialSize = iconPix.size(); QSize expandSize = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, trialSize, this); *hMargin = (expandSize.width() - trialSize.width())/2; *vMargin = (expandSize.height() - trialSize.height())/2; } QSize DMultiTabBarTab::computeSizeHint(bool withText) const { // Compute as horizontal first, then flip around if need be. QStyleOptionToolButton opt; initStyleOption(&opt); int hMargin, vMargin; computeMargins(&hMargin, &vMargin); // Compute interior size, starting from pixmap.. QPixmap iconPix = iconPixmap(); QSize size = iconPix.size(); // Always include text height in computation, to avoid resizing the minor direction // when expanding text.. QSize textSize = fontMetrics().size(0, text()); size.setHeight(qMax(size.height(), textSize.height())); // Pick margins for major/minor direction, depending on orientation int majorMargin = isVertical() ? vMargin : hMargin; int minorMargin = isVertical() ? hMargin : vMargin; size.setWidth (size.width() + 2*majorMargin); size.setHeight(size.height() + 2*minorMargin); if (withText) { // Add enough room for the text, and an extra major margin. size.setWidth(size.width() + textSize.width() + majorMargin); } if (isVertical()) { return QSize(size.height(), size.width()); } return size; } void DMultiTabBarTab::setState(bool newState) { setChecked(newState); updateGeometry(); } void DMultiTabBarTab::setIcon(const QString& icon) { const QIcon i = QIcon::fromTheme(icon); const int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); setIcon(i.pixmap(iconSize)); } void DMultiTabBarTab::setIcon(const QPixmap& icon) { QPushButton::setIcon(icon); } bool DMultiTabBarTab::shouldDrawText() const { return (d->style == DMultiTabBar::AllIconsText) || isChecked(); } bool DMultiTabBarTab::isVertical() const { return (d->position == Qt::RightEdge || d->position == Qt::LeftEdge); } void DMultiTabBarTab::paintEvent(QPaintEvent*) { QPainter painter(this); QStyleOptionToolButton opt; initStyleOption(&opt); // Paint bevel.. if (underMouse() || isChecked()) { opt.text.clear(); opt.icon = QIcon(); style()->drawComplexControl(QStyle::CC_ToolButton, &opt, &painter, this); } int hMargin, vMargin; computeMargins(&hMargin, &vMargin); // We first figure out how much room we have for the text, based on // icon size and margin, try to fit in by eliding, and perhaps // give up on drawing the text entirely if we're too short on room QPixmap icon = iconPixmap(); int textRoom = 0; int iconRoom = 0; QString t; if (shouldDrawText()) { if (isVertical()) { iconRoom = icon.height() + 2*vMargin; textRoom = height() - iconRoom - vMargin; } else { iconRoom = icon.width() + 2*hMargin; textRoom = width() - iconRoom - hMargin; } t = painter.fontMetrics().elidedText(text(), Qt::ElideRight, textRoom); // See whether anything is left. Qt will return either // ... or the ellipsis unicode character, 0x2026 if (t == QLatin1String("...") || t == QChar(0x2026)) { t.clear(); } } // Label time.... Simple case: no text, so just plop down the icon right in the center // We only do this when the button never draws the text, to avoid jumps in icon position // when resizing if (!shouldDrawText()) { style()->drawItemPixmap(&painter, rect(), Qt::AlignCenter | Qt::AlignVCenter, icon); return; } // Now where the icon/text goes depends on text direction and tab position QRect iconArea; QRect labelArea; bool bottomIcon = false; bool rtl = layoutDirection() == Qt::RightToLeft; if (isVertical()) { if (d->position == Qt::LeftEdge && !rtl) bottomIcon = true; if (d->position == Qt::RightEdge && rtl) bottomIcon = true; } if (isVertical()) { if (bottomIcon) { labelArea = QRect(0, vMargin, width(), textRoom); iconArea = QRect(0, vMargin + textRoom, width(), iconRoom); } else { labelArea = QRect(0, iconRoom, width(), textRoom); iconArea = QRect(0, 0, width(), iconRoom); } } else { // Pretty simple --- depends only on RTL/LTR if (rtl) { labelArea = QRect(hMargin, 0, textRoom, height()); iconArea = QRect(hMargin + textRoom, 0, iconRoom, height()); } else { labelArea = QRect(iconRoom, 0, textRoom, height()); iconArea = QRect(0, 0, iconRoom, height()); } } style()->drawItemPixmap(&painter, iconArea, Qt::AlignCenter | Qt::AlignVCenter, icon); if (t.isEmpty()) { return; } QRect labelPaintArea = labelArea; if (isVertical()) { // If we're vertical, we paint to a simple 0,0 origin rect, // and get the transformations to get us in the right place labelPaintArea = QRect(0, 0, labelArea.height(), labelArea.width()); QTransform tr; if (bottomIcon) { tr.translate(labelArea.x(), labelPaintArea.width() + labelArea.y()); tr.rotate(-90); } else { tr.translate(labelPaintArea.height() + labelArea.x(), labelArea.y()); tr.rotate(90); } painter.setTransform(tr); } style()->drawItemText(&painter, labelPaintArea, Qt::AlignLeading | Qt::AlignVCenter, palette(), true, t, QPalette::ButtonText); } // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN DMultiTabBar::Private { public: DMultiTabBarFrame* internal; QBoxLayout* layout; QFrame* btnTabSep; QList buttons; Qt::Edge position; }; DMultiTabBar::DMultiTabBar(Qt::Edge pos, QWidget* const parent) : QWidget(parent), d(new Private) { if (pos == Qt::LeftEdge || pos == Qt::RightEdge) { d->layout = new QVBoxLayout(this); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); } else { d->layout = new QHBoxLayout(this); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); } d->layout->setContentsMargins(QMargins()); d->layout->setSpacing(0); d->internal = new DMultiTabBarFrame(this, pos); setPosition(pos); setStyle(ActiveIconText); d->layout->insertWidget(0, d->internal); d->layout->insertWidget(0, d->btnTabSep = new QFrame(this)); d->btnTabSep->setFixedHeight(4); d->btnTabSep->setFrameStyle(QFrame::Panel | QFrame::Sunken); d->btnTabSep->setLineWidth(2); d->btnTabSep->hide(); updateGeometry(); } DMultiTabBar::~DMultiTabBar() { qDeleteAll(d->buttons); d->buttons.clear(); delete d; } int DMultiTabBar::appendButton(const QPixmap &pic, int id, QMenu *popup, const QString&) { DMultiTabBarButton* const btn = new DMultiTabBarButton(pic, QString(), id, this); // a button with a QMenu can have another size. Make sure the button has always the same size. btn->setFixedWidth(btn->height()); btn->setMenu(popup); d->buttons.append(btn); d->layout->insertWidget(0,btn); btn->show(); d->btnTabSep->show(); return 0; } void DMultiTabBar::updateSeparator() { bool hideSep = true; QListIterator it(d->buttons); while (it.hasNext()) { if (it.next()->isVisibleTo(this)) { hideSep = false; break; } } if (hideSep) d->btnTabSep->hide(); else d->btnTabSep->show(); } int DMultiTabBar::appendTab(const QPixmap& pic, int id, const QString& text) { d->internal->appendTab(pic,id,text); return 0; } DMultiTabBarButton* DMultiTabBar::button(int id) const { QListIterator it(d->buttons); while (it.hasNext()) { DMultiTabBarButton* const button = it.next(); if (button->id() == id) return button; } return 0; } DMultiTabBarTab* DMultiTabBar::tab(int id) const { return d->internal->tab(id); } void DMultiTabBar::removeButton(int id) { for (int pos = 0 ; pos < d->buttons.count() ; ++pos) { if (d->buttons.at(pos)->id() == id) { d->buttons.takeAt(pos)->deleteLater(); break; } } if (d->buttons.count() == 0) d->btnTabSep->hide(); } void DMultiTabBar::removeTab(int id) { d->internal->removeTab(id); } void DMultiTabBar::setTab(int id,bool state) { DMultiTabBarTab* const ttab = tab(id); if (ttab) ttab->setState(state); } bool DMultiTabBar::isTabRaised(int id) const { DMultiTabBarTab* const ttab = tab(id); if (ttab) return ttab->isChecked(); return false; } void DMultiTabBar::setStyle(TextStyle style) { d->internal->setStyle(style); } DMultiTabBar::TextStyle DMultiTabBar::tabStyle() const { return d->internal->d->style; } void DMultiTabBar::setPosition(Qt::Edge pos) { d->position = pos; d->internal->setPosition(pos); } Qt::Edge DMultiTabBar::position() const { return d->position; } void DMultiTabBar::fontChange(const QFont&) { updateGeometry(); } // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN SidebarState { public: SidebarState() : activeWidget(0), size(0) { } SidebarState(QWidget* const w, int size) : activeWidget(w), size(size) { } QWidget* activeWidget; int size; }; // ------------------------------------------------------------------------------------- class Q_DECL_HIDDEN Sidebar::Private { public: explicit Private() : minimizedDefault(false), minimized(false), isMinimized(false), tabs(0), activeTab(-1), dragSwitchId(-1), restoreSize(0), stack(0), splitter(0), dragSwitchTimer(0), appendedTabsStateCache(), optionActiveTabEntry(QLatin1String("ActiveTab")), optionMinimizedEntry(QLatin1String("Minimized")), optionRestoreSizeEntry(QLatin1String("RestoreSize")) { } bool minimizedDefault; bool minimized; bool isMinimized; // Backup of shrinked status before backup(), restored by restore() // NOTE: when sidebar is hidden, only icon bar is affected. If sidebar view is // visible, this one must be shrink and restored accordingly. int tabs; int activeTab; int dragSwitchId; int restoreSize; QStackedWidget* stack; SidebarSplitter* splitter; QTimer* dragSwitchTimer; QHash appendedTabsStateCache; const QString optionActiveTabEntry; const QString optionMinimizedEntry; const QString optionRestoreSizeEntry; }; class Q_DECL_HIDDEN SidebarSplitter::Private { public: QList sidebars; }; // ------------------------------------------------------------------------------------- Sidebar::Sidebar(QWidget* const parent, SidebarSplitter* const sp, Qt::Edge side, bool minimizedDefault) : DMultiTabBar(side, parent), StateSavingObject(this), d(new Private) { d->splitter = sp; d->minimizedDefault = minimizedDefault; d->stack = new QStackedWidget(d->splitter); d->dragSwitchTimer = new QTimer(this); connect(d->dragSwitchTimer, SIGNAL(timeout()), this, SLOT(slotDragSwitchTimer())); d->splitter->d->sidebars << this; setStyle(DMultiTabBar::ActiveIconText); } Sidebar::~Sidebar() { saveState(); if (d->splitter) { d->splitter->d->sidebars.removeAll(this); } delete d; } SidebarSplitter* Sidebar::splitter() const { return d->splitter; } void Sidebar::doLoadState() { KConfigGroup group = getConfigGroup(); int tab = group.readEntry(entryName(d->optionActiveTabEntry), 0); bool minimized = group.readEntry(entryName(d->optionMinimizedEntry), d->minimizedDefault); d->restoreSize = group.readEntry(entryName(d->optionRestoreSizeEntry), -1); // validate if (tab >= d->tabs || tab < 0) { tab = 0; } if (minimized) { d->activeTab = tab; setTab(d->activeTab, false); d->stack->setCurrentIndex(d->activeTab); shrink(); emit signalChangedTab(d->stack->currentWidget()); return; } d->activeTab = -1; clicked(tab); } void Sidebar::doSaveState() { KConfigGroup group = getConfigGroup(); group.writeEntry(entryName(d->optionActiveTabEntry), d->activeTab); group.writeEntry(entryName(d->optionMinimizedEntry), d->minimized); group.writeEntry(entryName(d->optionRestoreSizeEntry), d->minimized ? d->restoreSize : -1); } void Sidebar::backup() { // backup preview state of sidebar view (shrink or not) d->isMinimized = d->minimized; // In all case, shrink sidebar view shrink(); DMultiTabBar::hide(); } void Sidebar::backup(const QList thirdWidgetsToBackup, QList* const sizes) { sizes->clear(); foreach(QWidget* const widget, thirdWidgetsToBackup) { *sizes << d->splitter->size(widget); } backup(); } void Sidebar::restore() { DMultiTabBar::show(); // restore preview state of sidebar view, stored in backup() if (!d->isMinimized) { expand(); } } void Sidebar::restore(const QList thirdWidgetsToRestore, const QList& sizes) { restore(); if (thirdWidgetsToRestore.size() == sizes.size()) { for (int i=0; isplitter->setSize(thirdWidgetsToRestore.at(i), sizes.at(i)); } } } void Sidebar::appendTab(QWidget* const w, const QIcon& pic, const QString& title) { // Store state (but not on initialization) if (isVisible()) { d->appendedTabsStateCache[w] = SidebarState(d->stack->currentWidget(), d->splitter->size(this)); } // Add tab w->setParent(d->stack); DMultiTabBar::appendTab(pic.pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)), d->tabs, title); d->stack->insertWidget(d->tabs, w); tab(d->tabs)->setAcceptDrops(true); tab(d->tabs)->installEventFilter(this); connect(tab(d->tabs), SIGNAL(clicked(int)), this, SLOT(clicked(int))); d->tabs++; } void Sidebar::deleteTab(QWidget* const w) { int tab = d->stack->indexOf(w); if (tab < 0) { return; } bool removingActiveTab = (tab == d->activeTab); if (removingActiveTab) { d->activeTab = -1; } d->stack->removeWidget(d->stack->widget(tab)); // delete widget removeTab(tab); d->tabs--; // restore or reset active tab and width if (!d->minimized) { // restore to state before adding tab // using a hash is simple, but does not handle well multiple add/removal operations at a time SidebarState state = d->appendedTabsStateCache.take(w); if (state.activeWidget) { int tab = d->stack->indexOf(state.activeWidget); if (tab != -1) { switchTabAndStackToTab(tab); emit signalChangedTab(d->stack->currentWidget()); if (state.size == 0) { d->minimized = true; setTab(d->activeTab, false); } d->splitter->setSize(this, state.size); } } else { if (removingActiveTab) { clicked(d->tabs - 1); } d->splitter->setSize(this, -1); } } else { d->restoreSize = -1; } } void Sidebar::clicked(int tab) { if (tab >= d->tabs || tab < 0) { return; } if (tab == d->activeTab) { d->stack->isHidden() ? expand() : shrink(); } else { switchTabAndStackToTab(tab); if (d->minimized) { expand(); } emit signalChangedTab(d->stack->currentWidget()); } } void Sidebar::setActiveTab(QWidget* const w) { int tab = d->stack->indexOf(w); if (tab < 0) { return; } switchTabAndStackToTab(tab); if (d->minimized) { expand(); } emit signalChangedTab(d->stack->currentWidget()); } void Sidebar::activePreviousTab() { int tab = d->stack->indexOf(d->stack->currentWidget()); if (tab == 0) tab = d->tabs-1; else tab--; setActiveTab(d->stack->widget(tab)); } void Sidebar::activeNextTab() { int tab = d->stack->indexOf(d->stack->currentWidget()); if (tab== d->tabs-1) tab = 0; else tab++; setActiveTab(d->stack->widget(tab)); } void Sidebar::switchTabAndStackToTab(int tab) { if (d->activeTab >= 0) { setTab(d->activeTab, false); } d->activeTab = tab; setTab(d->activeTab, true); d->stack->setCurrentIndex(d->activeTab); } QWidget* Sidebar::getActiveTab() const { if (d->splitter) { return d->stack->currentWidget(); } else { return 0; } } void Sidebar::shrink() { d->minimized = true; // store the size that we had. We may later need it when we restore to visible. int currentSize = d->splitter->size(this); if (currentSize) { d->restoreSize = currentSize; } d->stack->hide(); emit signalViewChanged(); } void Sidebar::expand() { d->minimized = false; d->stack->show(); + QTimer::singleShot(0, this, SLOT(slotExpandTimer())); +} + +void Sidebar::slotExpandTimer() +{ // Do not expand to size 0 (only splitter handle visible) // but either to previous size, or the minimum size hint if (d->splitter->size(this) == 0) { setTab(d->activeTab, true); d->splitter->setSize(this, d->restoreSize ? d->restoreSize : -1); } emit signalViewChanged(); } bool Sidebar::isExpanded() const { return !d->minimized; } bool Sidebar::eventFilter(QObject* obj, QEvent* ev) { for (int i = 0 ; i < d->tabs; ++i) { if ( obj == tab(i) ) { if ( ev->type() == QEvent::DragEnter) { QDragEnterEvent* const e = static_cast(ev); enterEvent(e); e->accept(); return false; } else if (ev->type() == QEvent::DragMove) { if (!d->dragSwitchTimer->isActive()) { d->dragSwitchTimer->setSingleShot(true); d->dragSwitchTimer->start(800); d->dragSwitchId = i; } return false; } else if (ev->type() == QEvent::DragLeave) { d->dragSwitchTimer->stop(); QDragLeaveEvent* const e = static_cast(ev); leaveEvent(e); return false; } else if (ev->type() == QEvent::Drop) { d->dragSwitchTimer->stop(); QDropEvent* const e = static_cast(ev); leaveEvent(e); return false; } else { return false; } } } // Else, pass the event on to the parent class return DMultiTabBar::eventFilter(obj, ev); } void Sidebar::slotDragSwitchTimer() { clicked(d->dragSwitchId); } void Sidebar::slotSplitterBtnClicked() { clicked(d->activeTab); } // ----------------------------------------------------------------------------- const QString SidebarSplitter::DEFAULT_CONFIG_KEY = QLatin1String("SplitterState"); SidebarSplitter::SidebarSplitter(QWidget* const parent) : QSplitter(parent), d(new Private) { connect(this, SIGNAL(splitterMoved(int,int)), this, SLOT(slotSplitterMoved(int,int))); } SidebarSplitter::SidebarSplitter(Qt::Orientation orientation, QWidget* const parent) : QSplitter(orientation, parent), d(new Private) { connect(this, SIGNAL(splitterMoved(int,int)), this, SLOT(slotSplitterMoved(int,int))); } SidebarSplitter::~SidebarSplitter() { // retreat cautiously from sidebars that live longer foreach(Sidebar* const sidebar, d->sidebars) { sidebar->d->splitter = 0; } delete d; } void SidebarSplitter::restoreState(KConfigGroup& group) { restoreState(group, DEFAULT_CONFIG_KEY); } void SidebarSplitter::restoreState(KConfigGroup& group, const QString& key) { if (group.hasKey(key)) { QByteArray state; state = group.readEntry(key, state); QSplitter::restoreState(QByteArray::fromBase64(state)); } } void SidebarSplitter::saveState(KConfigGroup& group) { saveState(group, DEFAULT_CONFIG_KEY); } void SidebarSplitter::saveState(KConfigGroup& group, const QString& key) { group.writeEntry(key, QSplitter::saveState().toBase64()); } int SidebarSplitter::size(Sidebar* const bar) const { return size(bar->d->stack); } int SidebarSplitter::size(QWidget* const widget) const { int index = indexOf(widget); if (index == -1) { return -1; } return sizes().at(index); } void SidebarSplitter::setSize(Sidebar* const bar, int size) { setSize(bar->d->stack, size); } void SidebarSplitter::setSize(QWidget* const widget, int size) { int index = indexOf(widget); if (index == -1) { return; } // special case: Use minimum size hint if (size == -1) { if (orientation() == Qt::Horizontal) { size = widget->minimumSizeHint().width(); } if (orientation() == Qt::Vertical) { size = widget->minimumSizeHint().height(); } } QList sizeList = sizes(); sizeList[index] = size; setSizes(sizeList); } void SidebarSplitter::slotSplitterMoved(int pos, int index) { Q_UNUSED(pos); // When the user moves the splitter so that size is 0 (collapsed), // it may be difficult to restore the sidebar as clicking the buttons // has no effect (only hides/shows the splitter handle) // So we want to transform this size-0-sidebar // to a sidebar that is shrunk (d->stack hidden) // and can be restored by clicking a tab bar button // We need to look at the widget between index-1 and index // and the one between index and index+1 QList sizeList = sizes(); // Is there a sidebar with size 0 before index ? if (index > 0 && sizeList.at(index-1) == 0) { QWidget* const w = widget(index-1); foreach(Sidebar* const sidebar, d->sidebars) { if (w == sidebar->d->stack) { if (!sidebar->d->minimized) { sidebar->setTab(sidebar->d->activeTab, false); sidebar->shrink(); } break; } } } // Is there a sidebar with size 0 after index ? if (sizeList.at(index) == 0) { QWidget* const w = widget(index); foreach(Sidebar* const sidebar, d->sidebars) { if (w == sidebar->d->stack) { if (!sidebar->d->minimized) { sidebar->setTab(sidebar->d->activeTab, false); sidebar->shrink(); } break; } } } } } // namespace Digikam diff --git a/core/libs/widgets/mainview/sidebar.h b/core/libs/widgets/mainview/sidebar.h index 9889d14c6b..1188ba0291 100644 --- a/core/libs/widgets/mainview/sidebar.h +++ b/core/libs/widgets/mainview/sidebar.h @@ -1,530 +1,531 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-03-22 * Description : a widget to manage sidebar in GUI. * * Copyright (C) 2005-2006 by Joern Ahrens * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2008-2013 by Marcel Wiesweg * Copyright (C) 2001-2003 by Joseph Wenninger * * 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, 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. * * ============================================================ */ #ifndef DIGIKAM_SIDE_BAR_H #define DIGIKAM_SIDE_BAR_H // Qt includes #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_export.h" #include "statesavingobject.h" namespace Digikam { class DMultiTabBarButton; class DMultiTabBarTab; /** * A Widget for horizontal and vertical tabs. */ class DIGIKAM_EXPORT DMultiTabBar: public QWidget { Q_OBJECT public: /** * The list of available styles for DMultiTabBar */ enum TextStyle { ActiveIconText = 0, /// Always shows icon, only show the text of active tabs. AllIconsText = 2 /// Always shows the text and icons. }; public: explicit DMultiTabBar(Qt::Edge pos, QWidget* const parent=0); virtual ~DMultiTabBar(); /** * append a new button to the button area. The button can later on be accessed with button(ID) * eg for connecting signals to it * @param pic a pixmap for the button * @param id an arbitrary ID value. It will be emitted in the clicked signal for identifying the button * if more than one button is connected to a signals. * @param popup A popup menu which should be displayed if the button is clicked * @param not_used_yet will be used for a popup text in the future */ int appendButton(const QPixmap &pic, int id=-1, QMenu* const popup=0, const QString& not_used_yet=QString()); /** * remove a button with the given ID */ void removeButton(int id); /** * append a new tab to the tab area. It can be accessed later on with tabb(id); * @param pic a bitmap for the tab * @param id an arbitrary ID which can be used later on to identify the tab * @param text if a mode with text is used it will be the tab text, otherwise a mouse over hint */ int appendTab(const QPixmap& pic,int id=-1,const QString& text=QString()); /** * remove a tab with a given ID */ void removeTab(int id); /** * set a tab to "raised" * @param id The ID of the tab to manipulate * @param state true == activated/raised, false == not active */ void setTab(int id, bool state); /** * return the state of a tab, identified by its ID */ bool isTabRaised(int id) const; /** * get a pointer to a button within the button area identified by its ID */ DMultiTabBarButton* button(int id) const; /** * get a pointer to a tab within the tab area, identified by its ID */ DMultiTabBarTab* tab(int id) const; /** * set the real position of the widget. * @param pos if the mode is horizontal, only use top, bottom, if it is vertical use left or right */ void setPosition(Qt::Edge pos); /** * get the tabbar position. * @return position */ Qt::Edge position() const; /** * set the display style of the tabs */ void setStyle(TextStyle style); /** * get the display style of the tabs * @return display style */ TextStyle tabStyle() const; protected: void updateSeparator(); virtual void fontChange(const QFont&); private: friend class DMultiTabBarButton; class Private; Private* const d; }; // ------------------------------------------------------------------------------------- class DIGIKAM_EXPORT DMultiTabBarButton: public QPushButton { Q_OBJECT public: int id() const; virtual ~DMultiTabBarButton(); public Q_SLOTS: void setText(const QString& text); Q_SIGNALS: /** * this is emitted if the button is clicked * @param id the ID identifying the button */ void clicked(int id); protected Q_SLOTS: virtual void slotClicked(); protected: DMultiTabBarButton(const QPixmap& pic, const QString&, int id, QWidget* const parent); virtual void hideEvent(QHideEvent*); virtual void showEvent(QShowEvent*); virtual void paintEvent(QPaintEvent*); private: friend class DMultiTabBar; int m_id; }; // ------------------------------------------------------------------------------------- class DIGIKAM_EXPORT DMultiTabBarTab: public DMultiTabBarButton { Q_OBJECT public: virtual ~DMultiTabBarTab(); virtual QSize sizeHint() const; virtual QSize minimumSizeHint() const; public Q_SLOTS: /** * this is used internally, but can be used by the user. * It the according call of DMultiTabBar is invoked though this modifications will be overwritten */ void setPosition(Qt::Edge); /** * this is used internally, but can be used by the user. * It the according call of DMultiTabBar is invoked though this modifications will be overwritten */ void setStyle(DMultiTabBar::TextStyle); /** * set the active state of the tab * @param state true==active false==not active */ void setState(bool state); void setIcon(const QString&); void setIcon(const QPixmap&); protected: void computeMargins (int* hMargin, int* vMargin) const; QSize computeSizeHint(bool withText) const; bool shouldDrawText() const; bool isVertical() const; QPixmap iconPixmap() const; void initStyleOption(QStyleOptionToolButton* opt) const; friend class DMultiTabBarFrame; /** * This class should never be created except with the appendTab call of DMultiTabBar */ DMultiTabBarTab(const QPixmap& pic, const QString&, int id, QWidget* const parent, Qt::Edge pos, DMultiTabBar::TextStyle style); virtual void paintEvent(QPaintEvent*); private: class Private; Private* const d; }; // ------------------------------------------------------------------------------------- class DMultiTabBarFrame: public QFrame { Q_OBJECT public: explicit DMultiTabBarFrame(QWidget* const parent, Qt::Edge pos); virtual ~DMultiTabBarFrame(); int appendTab(const QPixmap&, int = -1, const QString& = QString()); DMultiTabBarTab* tab(int) const; void removeTab(int); void setPosition(Qt::Edge pos); void setStyle(DMultiTabBar::TextStyle style); void showActiveTabTexts(bool show); QList* tabs(); protected: /** * Reimplemented from QScrollView * in order to ignore all mouseEvents on the viewport, so that the * parent can handle them. */ virtual void contentsMousePressEvent(QMouseEvent*); virtual void mousePressEvent(QMouseEvent*); private: friend class DMultiTabBar; class Private; Private* const d; }; // ------------------------------------------------------------------------------------- class SidebarSplitter; /** * This class handles a sidebar view * * Since this class derives from StateSavingObject, you can call * StateSavingObject#loadState() and StateSavingObject#saveState() * for loading/saving of settings. However, if you use multiple * sidebar instances in your program, you have to remember to either * call QObject#setObjectName(), StateSavingObject#setEntryPrefix() or * StateSavingObject#setConfigGroup() first. */ class DIGIKAM_EXPORT Sidebar : public DMultiTabBar, public StateSavingObject { Q_OBJECT public: /** * Creates a new sidebar * @param parent sidebar's parent * @param sp sets the splitter, which should handle the width. The splitter normally * is part of the main view. Internally, the width of the widget stack can * be changed by a QSplitter. * @param side where the sidebar should be displayed. At the left or right border. Use Qt::LeftEdge or Qt::RightEdge. * @param minimizedDefault hide the sidebar when the program is started the first time. */ explicit Sidebar(QWidget* const parent, SidebarSplitter* const sp, Qt::Edge side = Qt::LeftEdge, bool minimizedDefault=false); virtual ~Sidebar(); SidebarSplitter* splitter() const; /** * Appends a new tab to the sidebar * @param w widget which is activated by this tab * @param pic icon which is shown in this tab * @param title text which is shown it this tab */ void appendTab(QWidget* const w, const QIcon& pic, const QString& title); /** * Deletes a tab from the tabbar */ void deleteTab(QWidget* const w); /** * Activates a tab */ void setActiveTab(QWidget* const w); /** * Activates a next tab from current one. If current one is last, first one is activated. */ void activeNextTab(); /** * Activates a previous tab from current one. If current one is first, last one is activated. */ void activePreviousTab(); /** * Returns the currently activated tab, or 0 if no tab is active */ QWidget* getActiveTab() const; /** * Hides the sidebar (display only the activation buttons) */ void shrink(); /** * redisplays the whole sidebar */ void expand(); /** * hide sidebar and backup minimized state. */ void backup(); /** * Hide sidebar and backup minimized state. * If there are other widgets in this splitter, stores * their sizes in the provided list. */ void backup(const QList thirdWidgetsToBackup, QList* const sizes); /** * show sidebar and restore minimized state. */ void restore(); /** * Show sidebar and restore minimized state. * Restores other widgets' sizes in splitter. */ void restore(const QList thirdWidgetsToRestore, const QList& sizes); /** * return the visible status of current sidebar tab. */ bool isExpanded() const; protected: /** * load the last view state from disk - called by StateSavingObject#loadState() */ void doLoadState(); /** * save the view state to disk - called by StateSavingObject#saveState() */ void doSaveState(); private: bool eventFilter(QObject* o, QEvent* e); void switchTabAndStackToTab(int tab); private Q_SLOTS: /** * Activates a tab */ void clicked(int tab); + void slotExpandTimer(); void slotDragSwitchTimer(); void slotSplitterBtnClicked(); Q_SIGNALS: /** * is emitted, when another tab is activated */ void signalChangedTab(QWidget* w); /** * is emitted, when tab is shrink or expanded */ void signalViewChanged(); private: friend class SidebarSplitter; class Private; Private* const d; }; // ----------------------------------------------------------------------------- class DIGIKAM_EXPORT SidebarSplitter : public QSplitter { Q_OBJECT public: const static QString DEFAULT_CONFIG_KEY; /** * This is a QSplitter with better support for storing its state * in config files, especially if Sidebars are contained in the splitter. */ explicit SidebarSplitter(QWidget* const parent = 0); explicit SidebarSplitter(Qt::Orientation orientation, QWidget* const parent = 0); ~SidebarSplitter(); /** * Saves the splitter state to group, handling minimized sidebars correctly. * DEFAULT_CONFIG_KEY is used for storing the state. */ void saveState(KConfigGroup& group); /** * Saves the splitter state to group, handling minimized sidebars correctly. * This version uses a specified key in the config group. */ void saveState(KConfigGroup& group, const QString& key); /** * Restores the splitter state from group, handling minimized sidebars correctly. * DEFAULT_CONFIG_KEY is used for restoring the state. */ void restoreState(KConfigGroup& group); /** * Restores the splitter state from group, handling minimized sidebars correctly. * This version uses a specified key in the config group. */ void restoreState(KConfigGroup& group, const QString& key); /** * Returns the value of sizes() that corresponds to the given Sidebar or splitter child widget. */ int size(Sidebar* const bar) const; int size(QWidget* const widget) const; /** * Sets the splitter size for the given sidebar or splitter child widget to size. * Special value -1: Sets the minimum size hint of the widget. */ void setSize(Sidebar* const bar, int size); void setSize(QWidget* const widget, int size); void addSplitterCollapserButton(QWidget* const widget); private Q_SLOTS: void slotSplitterMoved(int pos, int index); private: friend class Sidebar; class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_SIDE_BAR_H