diff --git a/libs/widgets/KoToolBox.cpp b/libs/widgets/KoToolBox.cpp --- a/libs/widgets/KoToolBox.cpp +++ b/libs/widgets/KoToolBox.cpp @@ -82,6 +82,7 @@ bool floating; QMap contextIconSizes; QMenu* contextSize; + Qt::Orientation orientation; }; void KoToolBox::Private::addSection(Section *section, const QString &name) @@ -117,7 +118,6 @@ SIGNAL(addedTool(KoToolAction*,KoCanvasController*)), this, SLOT(toolAdded(KoToolAction*,KoCanvasController*))); - QTimer::singleShot(0, this, SLOT(adjustToFit())); } KoToolBox::~KoToolBox() @@ -251,16 +251,9 @@ painter.end(); } -void KoToolBox::resizeEvent(QResizeEvent* event) -{ - QWidget::resizeEvent(event); - if (!d->floating) { - setMinimumSize(layout()->minimumSize()); // This enforces the minimum size on the widget - } -} - void KoToolBox::setOrientation(Qt::Orientation orientation) { + d->orientation = orientation; d->layout->setOrientation(orientation); QTimer::singleShot(0, this, SLOT(update())); foreach(Section* section, d->sections) { @@ -270,7 +263,6 @@ void KoToolBox::setFloating(bool v) { - setMinimumSize(QSize(1,1)); d->floating = v; } @@ -282,20 +274,6 @@ } -void KoToolBox::adjustToFit() -{ - int newWidth = width() - (width() % layout()->minimumSize().width()); - if (newWidth != width() && newWidth >= layout()->minimumSize().width()) { - setMaximumWidth(newWidth); - QTimer::singleShot(0, this, SLOT(resizeUnlock())); - } -} - -void KoToolBox::resizeUnlock() -{ - setMaximumWidth(QWIDGETSIZE_MAX); -} - void KoToolBox::slotContextIconSize() { QAction* action = qobject_cast(sender()); @@ -309,13 +287,11 @@ button->setIconSize(QSize(iconSize, iconSize)); } - foreach(Section *section, d->sections) { + foreach(Section *section, d->sections) { section->setButtonSize(QSize(iconSize + BUTTON_MARGIN, iconSize + BUTTON_MARGIN)); } } - - adjustToFit(); } void KoToolBox::contextMenuEvent(QContextMenuEvent *event) @@ -356,3 +332,9 @@ d->contextSize->exec(event->globalPos()); } +KoToolBoxLayout *KoToolBox::toolBoxLayout() const +{ + return d->layout; +} + +#include "moc_KoToolBoxScrollArea_p.cpp" diff --git a/libs/widgets/KoToolBoxButton.cpp b/libs/widgets/KoToolBoxButton.cpp --- a/libs/widgets/KoToolBoxButton.cpp +++ b/libs/widgets/KoToolBoxButton.cpp @@ -25,6 +25,8 @@ #include #include #include +#include +#include KoToolBoxButton::KoToolBoxButton(KoToolAction *toolAction, QWidget *parent) : QToolButton(parent) diff --git a/libs/widgets/KoToolBoxButton_p.h b/libs/widgets/KoToolBoxButton_p.h --- a/libs/widgets/KoToolBoxButton_p.h +++ b/libs/widgets/KoToolBoxButton_p.h @@ -32,8 +32,7 @@ void setHighlightColor(); private Q_SLOTS: - void setDataFromToolAction(); - + void setDataFromToolAction(); // Generates tooltips. private: KoToolAction *m_toolAction; }; diff --git a/libs/widgets/KoToolBoxDocker.cpp b/libs/widgets/KoToolBoxDocker.cpp --- a/libs/widgets/KoToolBoxDocker.cpp +++ b/libs/widgets/KoToolBoxDocker.cpp @@ -21,45 +21,62 @@ #include "KoToolBoxDocker_p.h" #include "KoToolBox_p.h" -#include +#include "KoToolBoxScrollArea_p.h" +#include "KoDockWidgetTitleBar.h" +#include "KoDockRegistry.h" #include - +#include +#include +#include KoToolBoxDocker::KoToolBoxDocker(KoToolBox *toolBox) : QDockWidget(i18n("Toolbox")) , m_toolBox(toolBox) + , m_scrollArea(new KoToolBoxScrollArea(toolBox, this)) { setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); - setWidget(toolBox); + setWidget(m_scrollArea); - connect(this, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), - this, SLOT(updateToolBoxOrientation(Qt::DockWidgetArea))); - connect(this, SIGNAL(topLevelChanged(bool)), - this, SLOT(updateFloating(bool))); + // create title bar KoDockWidgetTitleBar* titleBar = new KoDockWidgetTitleBar(this); titleBar->setTextVisibilityMode(KoDockWidgetTitleBar::TextCanBeInvisible); titleBar->setToolTip(i18n("Tools")); setTitleBarWidget(titleBar); + + connect(this, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), + this, SLOT(updateToolBoxOrientation(Qt::DockWidgetArea))); + connect(this, SIGNAL(topLevelChanged(bool)), + this, SLOT(updateFloating(bool))); } void KoToolBoxDocker::setCanvas(KoCanvasBase *canvas) { - setEnabled(canvas != 0); + Q_UNUSED(canvas); } void KoToolBoxDocker::unsetCanvas() { - setEnabled(false); +} + +void KoToolBoxDocker::resizeEvent(QResizeEvent *event) +{ + QDockWidget::resizeEvent(event); + if (isFloating()) { + if (m_scrollArea->width() > m_scrollArea->height()) { + m_scrollArea->setOrientation(Qt::Horizontal); + } else { + m_scrollArea->setOrientation(Qt::Vertical); + } + } } void KoToolBoxDocker::updateToolBoxOrientation(Qt::DockWidgetArea area) { if (area == Qt::TopDockWidgetArea || area == Qt::BottomDockWidgetArea) { - m_toolBox->setOrientation(Qt::Horizontal); + m_scrollArea->setOrientation(Qt::Horizontal); } else { - m_toolBox->setOrientation(Qt::Vertical); + m_scrollArea->setOrientation(Qt::Vertical); } - m_toolBox->setFloating(area == Qt::NoDockWidgetArea); } void KoToolBoxDocker::updateFloating(bool v) diff --git a/libs/widgets/KoToolBoxDocker_p.h b/libs/widgets/KoToolBoxDocker_p.h --- a/libs/widgets/KoToolBoxDocker_p.h +++ b/libs/widgets/KoToolBoxDocker_p.h @@ -28,6 +28,7 @@ class KoCanvasBase; class KoToolBox; +class KoToolBoxScrollArea; class KoToolBoxDocker : public QDockWidget, public KoCanvasObserverBase { @@ -40,13 +41,16 @@ virtual void unsetCanvas(); virtual QString observerName() const { return QStringLiteral("KoToolBoxDocker"); } +protected: + void resizeEvent(QResizeEvent *event) override; protected Q_SLOTS: void updateToolBoxOrientation(Qt::DockWidgetArea area); void updateFloating(bool); private: KoToolBox *m_toolBox; + KoToolBoxScrollArea *m_scrollArea; }; #endif // _KO_TOOLBOX_DOCKER_H_ diff --git a/libs/widgets/KoToolBoxLayout_p.h b/libs/widgets/KoToolBoxLayout_p.h --- a/libs/widgets/KoToolBoxLayout_p.h +++ b/libs/widgets/KoToolBoxLayout_p.h @@ -28,6 +28,7 @@ #include #include #include +#include class SectionLayout : public QLayout { @@ -38,26 +39,27 @@ { } - ~SectionLayout() + ~SectionLayout() { qDeleteAll( m_items ); m_items.clear(); } void addButton(QAbstractButton *button, int priority) { addChildWidget(button); + m_priorities.insert(button, priority); int index = 1; - foreach(QWidgetItem *item, m_items) { + foreach (QWidgetItem *item, m_items) { if (m_priorities.value(static_cast(item->widget())) > priority) break; index++; } m_items.insert(index-1, new QWidgetItem(button)); } - QSize sizeHint() const + QSize sizeHint() const { Q_ASSERT(0); return QSize(); @@ -76,7 +78,7 @@ int count() const { return m_items.count(); } - void setGeometry (const QRect &rect) + void setGeometry (const QRect &rect) { int x = 0; int y = 0; @@ -206,29 +208,35 @@ Q_OBJECT public: explicit KoToolBoxLayout(QWidget *parent) - : QLayout(parent), m_orientation(Qt::Vertical), m_currentHeight(0) + : QLayout(parent) + , m_orientation(Qt::Vertical) { setSpacing(6); } + ~KoToolBoxLayout(); QSize sizeHint() const + { + // Prefer showing one row/column by default + const QSize minSize = minimumSize(); + if (!minSize.isValid()) { + return minSize; + } + if (m_orientation == Qt::Vertical) { + return QSize(minSize.width(), minSize.height() + spacing()); + } else { + return QSize(minSize.height() + spacing(), minSize.width()); + } + } + + QSize minimumSize() const { if (m_sections.isEmpty()) return QSize(); QSize oneIcon = static_cast (m_sections[0]->widget())->iconSize(); return oneIcon; } - QSize minimumSize() const - { - QSize s = sizeHint(); - if (m_orientation == Qt::Vertical) { - s.setHeight(m_currentHeight); - } else { - s.setWidth(m_currentHeight); - } - return s; - } void addSection(Section *section) { @@ -249,107 +257,149 @@ Q_ASSERT(0); // don't let anything else be added. (code depends on this!) } - QLayoutItem* itemAt(int i) const + QLayoutItem* itemAt(int i) const { - if (m_sections.count() >= i) - return 0; - return m_sections.at(i); + return m_sections.value(i); } QLayoutItem* takeAt(int i) { return m_sections.takeAt(i); } int count() const { return m_sections.count(); } - void setGeometry (const QRect &rect) + void setGeometry (const QRect &rect) + { + QLayout::setGeometry(rect); + doLayout(rect.size(), true); + } + + bool hasHeightForWidth() const override + { + return m_orientation == Qt::Vertical; + } + + int heightForWidth(int width) const override + { + if (m_orientation == Qt::Vertical) { + const int height = doLayout(QSize(width, 0), false); + return height; + } else { + return -1; + } + } + + /** + * For calculating the width from height by KoToolBoxScrollArea. + * QWidget doesn't actually support trading width for height, so it needs to + * be handled specifically. + */ + int widthForHeight(int height) const + { + if (m_orientation == Qt::Horizontal) { + const int width = doLayout(QSize(0, height), false); + return width; + } else { + return -1; + } + } + + void setOrientation (Qt::Orientation orientation) + { + m_orientation = orientation; + invalidate(); + } + +private: + int doLayout(const QSize &size, bool applyGeometry) const { // nothing to do? - if (m_sections.isEmpty()) - { - m_currentHeight = 0; - return; + if (m_sections.isEmpty()) { + return 0; } // the names of the variables assume a vertical orientation, // but all calculations are done based on the real orientation + const bool isVertical = m_orientation == Qt::Vertical; const QSize iconSize = static_cast (m_sections.first()->widget())->iconSize(); - const int maxWidth = (m_orientation == Qt::Vertical) ? rect.width() : rect.height(); + const int maxWidth = isVertical ? size.width() : size.height(); // using min 1 as width to e.g. protect against div by 0 below - const int iconWidth = qMax(1, (m_orientation == Qt::Vertical) ? iconSize.width() : iconSize.height()); - const int iconHeight = qMax(1, (m_orientation == Qt::Vertical) ? iconSize.height() : iconSize.width()); + const int iconWidth = qMax(1, isVertical ? iconSize.width() : iconSize.height()); + const int iconHeight = qMax(1, isVertical ? iconSize.height() : iconSize.width()); const int maxColumns = qMax(1, (maxWidth / iconWidth)); int x = 0; int y = 0; bool firstSection = true; - foreach (QWidgetItem *wi, m_sections) { - Section *section = static_cast (wi->widget()); - // Since sections can overlap (if a section occupies two rows, and there - // is space on the second row for all of the next section, the next section - // will be placed overlapping with the previous section), it's important that - // later sections will be higher in the widget z-order than previous - // sections, so raise it. - section->raise(); - const int buttonCount = section->visibleButtonCount(); - if (buttonCount == 0) { - // move out of view, not perfect TODO: better solution - section->setGeometry(1000, 1000, 0, 0); - continue; - } + if (!applyGeometry) { + foreach (QWidgetItem *wi, m_sections) { + Section *section = static_cast (wi->widget()); + const int buttonCount = section->visibleButtonCount(); + if (buttonCount == 0) { + continue; + } - // rows needed for the buttons (calculation gets the ceiling value of the plain div) - const int neededRowCount = ((buttonCount-1) / maxColumns) + 1; - const int availableButtonCount = (maxWidth - x + 1) / iconWidth; - - if (firstSection) { - firstSection = false; - } else if (buttonCount > availableButtonCount) { - // start on a new row, set separator - x = 0; - y += iconHeight + spacing(); - const Section::Separators separator = - (m_orientation == Qt::Vertical) ? Section::SeparatorTop : Section::SeparatorLeft; - section->setSeparator( separator ); - } else { - // append to last row, set separators (on first row only to the left side) - const bool isFirstRow = (y == 0); - const Section::Separators separators = - isFirstRow ? - ((m_orientation == Qt::Vertical) ? Section::SeparatorLeft : Section::SeparatorTop) : - (Section::SeparatorTop | Section::SeparatorLeft); - section->setSeparator( separators ); - } + // rows needed for the buttons (calculation gets the ceiling value of the plain div) + const int neededRowCount = ((buttonCount-1) / maxColumns) + 1; - const int usedColumns = qMin(buttonCount, maxColumns); - if (m_orientation == Qt::Vertical) { - section->setGeometry(x, y, - usedColumns * iconWidth, neededRowCount * iconHeight); - } else { - section->setGeometry(y, x, - neededRowCount * iconHeight, usedColumns * iconWidth); + if (firstSection) { + firstSection = false; + } else { + // start on a new row, set separator + x = 0; + y += iconHeight + spacing(); + } + + // advance by the icons in the last row + const int lastRowColumnCount = buttonCount - ((neededRowCount-1) * maxColumns); + x += (lastRowColumnCount * iconWidth) + spacing(); + // advance by all but the last used row + y += (neededRowCount - 1) * iconHeight; } + } else { + foreach (QWidgetItem *wi, m_sections) { + Section *section = static_cast (wi->widget()); + const int buttonCount = section->visibleButtonCount(); + if (buttonCount == 0) { + section->hide(); + continue; + } - // advance by the icons in the last row - const int lastRowColumnCount = buttonCount - ((neededRowCount-1) * maxColumns); - x += (lastRowColumnCount * iconWidth) + spacing(); - // advance by all but the last used row - y += (neededRowCount - 1) * iconHeight; - } + // rows needed for the buttons (calculation gets the ceiling value of the plain div) + const int neededRowCount = ((buttonCount-1) / maxColumns) + 1; - // cache total height (or width), adding the iconHeight for the current row - m_currentHeight = y + iconHeight; - } + if (firstSection) { + firstSection = false; + } else { + // start on a new row, set separator + x = 0; + y += iconHeight + spacing(); + const Section::Separators separator = + isVertical ? Section::SeparatorTop : Section::SeparatorLeft; + section->setSeparator( separator ); + } - void setOrientation (Qt::Orientation orientation) - { - m_orientation = orientation; - invalidate(); + const int usedColumns = qMin(buttonCount, maxColumns); + if (isVertical) { + section->setGeometry(x, y, + usedColumns * iconWidth, neededRowCount * iconHeight); + } else { + section->setGeometry(y, x, + neededRowCount * iconHeight, usedColumns * iconWidth); + } + + // advance by the icons in the last row + const int lastRowColumnCount = buttonCount - ((neededRowCount-1) * maxColumns); + x += (lastRowColumnCount * iconWidth) + spacing(); + // advance by all but the last used row + y += (neededRowCount - 1) * iconHeight; + } + } + + return y + iconHeight; } -private: QList m_sections; Qt::Orientation m_orientation; - mutable int m_currentHeight; }; #endif diff --git a/libs/widgets/KoToolBoxScrollArea_p.h b/libs/widgets/KoToolBoxScrollArea_p.h new file mode 100644 --- /dev/null +++ b/libs/widgets/KoToolBoxScrollArea_p.h @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2018 Alvin Wong + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KO_TOOLBOX_SCROLL_AREA_H +#define KO_TOOLBOX_SCROLL_AREA_H + +#include "KoToolBox_p.h" +#include "KoToolBoxLayout_p.h" + +#include +#include +#include +#include +#include +#include + +class KoToolBoxScrollArea : public QScrollArea +{ + Q_OBJECT +public: + KoToolBoxScrollArea(KoToolBox *toolBox, QWidget *parent) + : QScrollArea(parent) + , m_toolBox(toolBox) + , m_orientation(Qt::Vertical) + , m_scrollPrev(new QToolButton(this)) + , m_scrollNext(new QToolButton(this)) + { + setFrameShape(QFrame::NoFrame); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + m_toolBox->setOrientation(m_orientation); + setWidget(m_toolBox); + + m_scrollPrev->setAutoRepeat(true); + m_scrollPrev->setAutoFillBackground(true); + m_scrollPrev->setFocusPolicy(Qt::NoFocus); + connect(m_scrollPrev, &QToolButton::clicked, this, &KoToolBoxScrollArea::doScrollPrev); + m_scrollNext->setAutoRepeat(true); + m_scrollNext->setAutoFillBackground(true); + m_scrollNext->setFocusPolicy(Qt::NoFocus); + connect(m_scrollNext, &QToolButton::clicked, this, &KoToolBoxScrollArea::doScrollNext); + + QScroller *scroller = KoKineticScroller::createPreconfiguredScroller(this); + if (!scroller) { + QScroller::grabGesture(viewport(), QScroller::MiddleMouseButtonGesture); + QScroller *scroller = QScroller::scroller(viewport()); + QScrollerProperties sp = scroller->scrollerProperties(); + + sp.setScrollMetric(QScrollerProperties::MaximumVelocity, 0.0); + sp.setScrollMetric(QScrollerProperties::OvershootDragResistanceFactor, 0.1); + sp.setScrollMetric(QScrollerProperties::OvershootDragDistanceFactor, 0.1); + sp.setScrollMetric(QScrollerProperties::OvershootScrollDistanceFactor, 0.0); + sp.setScrollMetric(QScrollerProperties::OvershootScrollTime, 0.4); + + scroller->setScrollerProperties(sp); + } + connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChange(QScroller::State))); + } + + void setOrientation(Qt::Orientation orientation) + { + if (orientation == m_orientation) { + return; + } + m_orientation = orientation; + m_toolBox->setOrientation(orientation); + layoutItems(); + } + + Qt::Orientation orientation() const + { + return m_orientation; + } + + QSize minimumSizeHint() const override + { + return m_toolBox->minimumSizeHint(); + } + + QSize sizeHint() const override + { + return m_toolBox->sizeHint(); + } + +public Q_SLOTS: + void slotScrollerStateChange(QScroller::State state){ KoKineticScroller::updateCursor(this, state); } + +protected: + bool event(QEvent *event) override + { + if (event->type() == QEvent::LayoutRequest) { + // LayoutRequest can be triggered by icon changes, so resize the toolbox + layoutItems(); + // The toolbox might have changed the sizeHint and minimumSizeHint + updateGeometry(); + } + return QScrollArea::event(event); + } + + void resizeEvent(QResizeEvent *event) override + { + layoutItems(); + QScrollArea::resizeEvent(event); + updateScrollButtons(); + } + + void wheelEvent(QWheelEvent *event) override + { + if (m_orientation == Qt::Vertical) { + QApplication::sendEvent(verticalScrollBar(), event); + } else { + QApplication::sendEvent(horizontalScrollBar(), event); + } + } + + void scrollContentsBy(int dx, int dy) override + { + QScrollArea::scrollContentsBy(dx, dy); + updateScrollButtons(); + } + +private Q_SLOTS: + void doScrollPrev() + { + if (m_orientation == Qt::Vertical) { + verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepSub); + } else { + horizontalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepSub); + } + } + + void doScrollNext() + { + if (m_orientation == Qt::Vertical) { + verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepAdd); + } else { + horizontalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepAdd); + } + } + +private: + int scrollButtonWidth() const + { + QStyleOption opt; + opt.init(this); + return style()->pixelMetric(QStyle::PM_TabBarScrollButtonWidth, &opt, this); + } + + void layoutItems() + { + const KoToolBoxLayout *l = m_toolBox->toolBoxLayout(); + QSize newSize = viewport()->size(); + if (m_orientation == Qt::Vertical) { + newSize.setHeight(l->heightForWidth(newSize.width())); + } else { + newSize.setWidth(l->widthForHeight(newSize.height())); + } + m_toolBox->resize(newSize); + + updateScrollButtons(); + } + + void updateScrollButtons() + { + // We move the scroll buttons outside the widget rect instead of setting + // the visibility, because setting the visibility triggers a relayout + // of QAbstractScrollArea, which occasionally causes an offset bug when + // QScroller performs the overshoot animation. (Overshoot is done by + // moving the viewport widget, but the viewport position is reset during + // a relayout.) + const int scrollButtonWidth = this->scrollButtonWidth(); + if (m_orientation == Qt::Vertical) { + m_scrollPrev->setArrowType(Qt::UpArrow); + m_scrollPrev->setEnabled(verticalScrollBar()->value() != verticalScrollBar()->minimum()); + if (m_scrollPrev->isEnabled()) { + m_scrollPrev->setGeometry(0, 0, width(), scrollButtonWidth); + } else { + m_scrollPrev->setGeometry(-width(), 0, width(), scrollButtonWidth); + } + m_scrollNext->setArrowType(Qt::DownArrow); + m_scrollNext->setEnabled(verticalScrollBar()->value() != verticalScrollBar()->maximum()); + if (m_scrollNext->isEnabled()) { + m_scrollNext->setGeometry(0, height() - scrollButtonWidth, width(), scrollButtonWidth); + } else { + m_scrollNext->setGeometry(-width(), height() - scrollButtonWidth, width(), scrollButtonWidth); + } + } else { + m_scrollPrev->setArrowType(Qt::LeftArrow); + m_scrollPrev->setEnabled(horizontalScrollBar()->value() != horizontalScrollBar()->minimum()); + if (m_scrollPrev->isEnabled()) { + m_scrollPrev->setGeometry(0, 0, scrollButtonWidth, height()); + } else { + m_scrollPrev->setGeometry(0, -height(), scrollButtonWidth, height()); + } + m_scrollNext->setArrowType(Qt::RightArrow); + m_scrollNext->setEnabled(horizontalScrollBar()->value() != horizontalScrollBar()->maximum()); + if (m_scrollNext->isEnabled()) { + m_scrollNext->setGeometry(width() - scrollButtonWidth, 0, scrollButtonWidth, height()); + } else { + m_scrollNext->setGeometry(width() - scrollButtonWidth, -height(), scrollButtonWidth, height()); + } + } + } + + KoToolBox *m_toolBox; + Qt::Orientation m_orientation; + + QToolButton *m_scrollPrev; + QToolButton *m_scrollNext; +}; + +#endif // KO_TOOLBOX_SCROLL_AREA_H diff --git a/libs/widgets/KoToolBox_p.h b/libs/widgets/KoToolBox_p.h --- a/libs/widgets/KoToolBox_p.h +++ b/libs/widgets/KoToolBox_p.h @@ -31,6 +31,7 @@ class KoCanvasController; class KoShapeLayer; +class KoToolBoxLayout; /** * KoToolBox is a dock widget that can order tools according to type and @@ -78,6 +79,8 @@ void setFloating(bool v); + KoToolBoxLayout *toolBoxLayout() const; + private: /** * Add a button to the toolbox. @@ -95,18 +98,11 @@ /// add a tool post-initialization. The tool will also be activated. void toolAdded(KoToolAction *toolAction, KoCanvasController *canvas); - /// resize the toolbox to show the icons without any gap at the edge - void adjustToFit(); - - /// unlocks the with after adjustToFit - void resizeUnlock(); - /// set the icon size for all the buttons void slotContextIconSize(); protected: void paintEvent(QPaintEvent *event); - void resizeEvent(QResizeEvent* event); void contextMenuEvent(QContextMenuEvent *event); private: diff --git a/libs/widgetutils/CMakeLists.txt b/libs/widgetutils/CMakeLists.txt --- a/libs/widgetutils/CMakeLists.txt +++ b/libs/widgetutils/CMakeLists.txt @@ -10,6 +10,7 @@ KoUpdaterPrivate_p.cpp KoProperties.cpp KoFileDialog.cpp + KoKineticScroller.cpp ) @@ -43,6 +44,7 @@ KoUpdater.h KoProperties.h KoFileDialog.h + KoKineticScroller.h ${CMAKE_CURRENT_BINARY_DIR}/kowidgetutils_export.h DESTINATION ${INCLUDE_INSTALL_DIR}/calligra COMPONENT Devel diff --git a/libs/widgets/KoToolBoxButton_p.h b/libs/widgetutils/KoKineticScroller.h copy from libs/widgets/KoToolBoxButton_p.h copy to libs/widgetutils/KoKineticScroller.h --- a/libs/widgets/KoToolBoxButton_p.h +++ b/libs/widgetutils/KoKineticScroller.h @@ -1,5 +1,6 @@ -/* - * Copyright (c) 2015 Friedrich W. H. Kossebau +/* This file is part of the KDE project + * Copyright (C) 2018 Emmet O'Neill + * Copyright (C) 2018 Eoin O'Neill * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -17,25 +18,22 @@ * Boston, MA 02110-1301, USA. */ -#ifndef _KO_TOOLBOXBUTTON_H_ -#define _KO_TOOLBOXBUTTON_H_ +#ifndef KISKINECTICSCROLLER_H +#define KISKINECTICSCROLLER_H +#include +#include -#include +class QAbstractScrollArea; -class KoToolAction; +/* This is a convenience namespace for setting up global kinetic scrolling + * with consistent settings across various UI elements within Krita. */ -class KoToolBoxButton : public QToolButton -{ - Q_OBJECT -public: - explicit KoToolBoxButton(KoToolAction *toolAction, QWidget * parent); - void setHighlightColor(); +namespace KoKineticScroller { +KOWIDGETUTILS_EXPORT QScroller* createPreconfiguredScroller(QAbstractScrollArea *target); -private Q_SLOTS: - void setDataFromToolAction(); +KOWIDGETUTILS_EXPORT QScroller::ScrollerGestureType getConfiguredGestureType(); -private: - KoToolAction *m_toolAction; -}; +KOWIDGETUTILS_EXPORT void updateCursor(QWidget *source, QScroller::State state); +} -#endif // _KO_TOOLBOXBUTTON_H_ +#endif // KISKINECTICSCROLLER_H diff --git a/libs/widgetutils/KoKineticScroller.cpp b/libs/widgetutils/KoKineticScroller.cpp new file mode 100644 --- /dev/null +++ b/libs/widgetutils/KoKineticScroller.cpp @@ -0,0 +1,122 @@ +/* This file is part of the KDE project + * Copyright (C) 2018 Emmet O'Neill + * Copyright (C) 2018 Eoin O'Neill + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include + +QScroller* KoKineticScroller::createPreconfiguredScroller(QAbstractScrollArea *scrollArea) +{ + KConfigGroup config = KSharedConfig::openConfig()->group("KoKineticScroller"); + int sensitivity = config.readEntry("KineticScrollingSensitivity", 75); + bool enabled = config.readEntry("KineticScrollingEnabled", true); + bool hideScrollBars = config.readEntry("KineticScrollingHideScrollbar", false); + float resistanceCoefficient = config.readEntry("KineticScrollingResistanceCoefficient", 10.0f); + float dragVelocitySmoothFactor = config.readEntry("KineticScrollingDragVelocitySmoothingFactor", 1.0f); + float minimumVelocity = config.readEntry("KineticScrollingMinimumVelocity", 0.0f); + float axisLockThresh = config.readEntry("KineticScrollingAxisLockThreshold", 1.0f); + float maximumClickThroughVelocity = config.readEntry("KineticScrollingMaxClickThroughVelocity", 0.0f); + float flickAccelerationFactor = config.readEntry("KineticScrollingFlickAccelerationFactor", 1.5f); + float overshootDragResistanceFactor = config.readEntry("KineticScrollingOvershotDragResistanceFactor", 0.1f); + float overshootDragDistanceFactor = config.readEntry("KineticScrollingOvershootDragDistanceFactor", 0.3f); + float overshootScrollDistanceFactor = config.readEntry("KineticScrollingOvershootScrollDistanceFactor", 0.1f); + float overshootScrollTime = config.readEntry("KineticScrollingOvershootScrollTime", 0.4f); + QScroller::ScrollerGestureType gestureType = getConfiguredGestureType(); + + if (enabled && scrollArea) { + if (hideScrollBars) { + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); + } + + QAbstractItemView *itemView = qobject_cast(scrollArea); + if (itemView) { + itemView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + } + + QScroller *scroller = QScroller::scroller(scrollArea); + scroller->grabGesture(scrollArea, gestureType); + + QScrollerProperties properties; + + // DragStartDistance seems to be based on meter per second; though it's + // not explicitly documented, other QScroller values are in that metric. + // To start kinetic scrolling, with minimal sensitity, we expect a drag + // of 10 mm, with minimum sensitity any > 0 mm. + const float mm = 0.001f; + const float resistance = 1.0f - (sensitivity / 100.0f); + const float mousePressEventDelay = config.readEntry("KineticScrollingMousePressDelay", 1.0f - 0.75f * resistance); + + properties.setScrollMetric(QScrollerProperties::DragStartDistance, resistance * resistanceCoefficient * mm); + properties.setScrollMetric(QScrollerProperties::DragVelocitySmoothingFactor, dragVelocitySmoothFactor); + properties.setScrollMetric(QScrollerProperties::MinimumVelocity, minimumVelocity); + properties.setScrollMetric(QScrollerProperties::AxisLockThreshold, axisLockThresh); + properties.setScrollMetric(QScrollerProperties::MaximumClickThroughVelocity, maximumClickThroughVelocity); + properties.setScrollMetric(QScrollerProperties::MousePressEventDelay, mousePressEventDelay); + properties.setScrollMetric(QScrollerProperties::AcceleratingFlickSpeedupFactor, flickAccelerationFactor); + + properties.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QScrollerProperties::OvershootAlwaysOn); + properties.setScrollMetric(QScrollerProperties::OvershootDragResistanceFactor, overshootDragResistanceFactor); + properties.setScrollMetric(QScrollerProperties::OvershootDragDistanceFactor, overshootDragDistanceFactor); + properties.setScrollMetric(QScrollerProperties::OvershootScrollDistanceFactor, overshootScrollDistanceFactor); + properties.setScrollMetric(QScrollerProperties::OvershootScrollTime, overshootScrollTime); + + scroller->setScrollerProperties(properties); + + return scroller; + } + + return nullptr; +} + +QScroller::ScrollerGestureType KoKineticScroller::getConfiguredGestureType() +{ + KConfigGroup config = KSharedConfig::openConfig()->group("KoKineticScroller"); + int gesture = config.readEntry("KineticScrollingGesture", 0); + + switch (gesture) { + case 0: { + return QScroller::TouchGesture; + } + case 1: { + return QScroller::LeftMouseButtonGesture; + } + case 2: { + return QScroller::MiddleMouseButtonGesture; + } + case 3: { + return QScroller::RightMouseButtonGesture; + } + default: + return QScroller::MiddleMouseButtonGesture; + } +} + +void KoKineticScroller::updateCursor(QWidget *source, QScroller::State state) +{ + if( state == QScroller::State::Pressed ) { + source->setCursor(Qt::OpenHandCursor); + } else if (state == QScroller::State::Dragging) { + source->setCursor(Qt::ClosedHandCursor); + } else { + source->setCursor(Qt::ArrowCursor); + } +}