From f0fc1cba42b7866f7c4176fb53dba20f9479da15 Mon Sep 17 00:00:00 2001 From: Klaus Basan Date: Wed, 23 Sep 2015 20:01:10 +0200 Subject: [PATCH] refs #452, views upgraded * load indicator when performing long lasting operations * custom menu can be injected (->menu delegate) * resizing based on random elements (subset resized only) --- src/blackgui/loadindicator.cpp | 138 +++++++++++++++ src/blackgui/loadindicator.h | 108 ++++++++++++ src/blackgui/menudelegate.h | 39 +++++ src/blackgui/views/viewbase.cpp | 292 +++++++++++++++++++++++++++----- src/blackgui/views/viewbase.h | 139 ++++++++++----- 5 files changed, 629 insertions(+), 87 deletions(-) create mode 100644 src/blackgui/loadindicator.cpp create mode 100644 src/blackgui/loadindicator.h create mode 100644 src/blackgui/menudelegate.h diff --git a/src/blackgui/loadindicator.cpp b/src/blackgui/loadindicator.cpp new file mode 100644 index 000000000..9aca49a96 --- /dev/null +++ b/src/blackgui/loadindicator.cpp @@ -0,0 +1,138 @@ +/* Copyright (C) 2015 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + * + * Class based on qLed: Copyright (C) 2010 by P. Sereno, http://www.sereno-online.com + */ + +#include "loadindicator.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace BlackGui +{ + CLoadIndicator::CLoadIndicator(int width, int height, QWidget *parent) + : QWidget(parent) + { + this->resize(width, height); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + setFocusPolicy(Qt::NoFocus); + setAutoFillBackground(true); + this->setStyleSheet("background-color: transparent;"); + } + + bool CLoadIndicator::isAnimated() const + { + return (m_timerId != -1); + } + + void CLoadIndicator::setDisplayedWhenStopped(bool state) + { + m_displayedWhenStopped = state; + update(); + } + + bool CLoadIndicator::isDisplayedWhenStopped() const + { + return m_displayedWhenStopped; + } + + void CLoadIndicator::startAnimation() + { + m_angle = 0; + this->show(); + this->setEnabled(true); + if (m_timerId == -1) { m_timerId = startTimer(m_delayMs); } + } + + void CLoadIndicator::stopAnimation() + { + if (m_timerId != -1) { killTimer(m_timerId); } + m_timerId = -1; + this->hide(); + this->setEnabled(false); + update(); + } + + void CLoadIndicator::setAnimationDelay(int delay) + { + if (m_timerId != -1) { killTimer(m_timerId); } + + m_delayMs = delay; + if (m_timerId != -1) { m_timerId = startTimer(m_delayMs); } + } + + void CLoadIndicator::setColor(const QColor &color) + { + m_color = color; + update(); + } + + QSize CLoadIndicator::sizeHint() const + { + return QSize(64, 64); + } + + int CLoadIndicator::heightForWidth(int w) const + { + return w; + } + + void CLoadIndicator::timerEvent(QTimerEvent *event) + { + Q_UNUSED(event); + m_angle = (m_angle + 30) % 360; + update(); + emit updatedAnimation(); + } + + void CLoadIndicator::paintEvent(QPaintEvent *event) + { + Q_UNUSED(event); + QPainter p(this); + this->paint(p); + } + + void CLoadIndicator::paint(QPainter &painter) const + { + if (!m_displayedWhenStopped && !isAnimated()) { return; } + if (!this->isVisible() || !this->isEnabled()) { return; } + + int width = qMin(this->width(), this->height()); + painter.setRenderHint(QPainter::Antialiasing); + + // painter.setBrush(QBrush(QColor(0, 0, 255))); + // painter.drawEllipse(0, 0, width, width); + + int outerRadius = (width - 1) * 0.5; + int innerRadius = (width - 1) * 0.5 * 0.38; + + int capsuleHeight = outerRadius - innerRadius; + int capsuleWidth = (width > 32) ? capsuleHeight * .23 : capsuleHeight * .35; + int capsuleRadius = capsuleWidth / 2; + + for (int i = 0; i < 12; i++) + { + QColor color = m_color; + color.setAlphaF(1.0f - (i / 12.0f)); + painter.setPen(Qt::NoPen); + painter.setBrush(color); + painter.save(); + painter.translate(rect().center()); + painter.rotate(m_angle - i * 30.0f); + painter.drawRoundedRect(-capsuleWidth * 0.5, -(innerRadius + capsuleHeight), capsuleWidth, capsuleHeight, capsuleRadius, capsuleRadius); + painter.restore(); + } + } + +} // ns diff --git a/src/blackgui/loadindicator.h b/src/blackgui/loadindicator.h new file mode 100644 index 000000000..e70329600 --- /dev/null +++ b/src/blackgui/loadindicator.h @@ -0,0 +1,108 @@ +/* Copyright (C) 2015 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + * + * Class based on qLed: Copyright (C) 2010 by P. Sereno, http://www.sereno-online.com + */ + +//! \file + +#ifndef BLACKGUI_LOADINDICATOR_H +#define BLACKGUI_LOADINDICATOR_H + +#include "blackgui/blackguiexport.h" +#include +#include + +namespace BlackGui +{ + /** + * The QProgressIndicator class lets an application display a progress indicator to show that a lengthy task is under way. + * Progress indicators are indeterminate and do nothing more than spin to show that the application is busy. + * \note based on https://github.com/mojocorp/QProgressIndicator under MIT license + */ + class CLoadIndicator : public QWidget + { + Q_OBJECT + + public: + //! Constructor + CLoadIndicator(int width = 64, int height = 64, QWidget *parent = nullptr); + + //! Returns the delay between animation steps. + //! \return The number of milliseconds between animation steps. By default, the animation delay is set to 40 milliseconds. + //! \sa setAnimationDelay + int animationDelay() const { return m_delayMs; } + + //! Returns a Boolean value indicating whether the component is currently animated. + //! \return Animation state. + //! \sa startAnimation stopAnimation + bool isAnimated() const; + + //! Returns a Boolean value indicating whether the receiver shows itself even when it is not animating. + //! \return Return true if the progress indicator shows itself even when it is not animating. By default, it returns false. + //! \sa setDisplayedWhenStopped + bool isDisplayedWhenStopped() const; + + //! Returns the color of the component. + //! \sa setColor + const QColor &color() const { return m_color; } + + //! \copydoc QWidget::sizeHint + virtual QSize sizeHint() const override; + + //! \copydoc QWidget::heightForWidth + virtual int heightForWidth(int w) const override; + + //! Paint to another painter + void paint(QPainter &painter) const; + + signals: + //! Animation has been updated + void updatedAnimation(); + + public slots: + //! Starts the spin animation. + //! \sa stopAnimation isAnimated + void startAnimation(); + + //! Stops the spin animation. + //! \sa startAnimation isAnimated + void stopAnimation(); + + //! Sets the delay between animation steps. + //! Setting the \a delay to a value larger than 40 slows the animation, while setting the \a delay to a smaller value speeds it up. + //! \param delay The delay, in milliseconds. + //! \sa animationDelay + void setAnimationDelay(int delay); + + //! Sets whether the component hides itself when it is not animating. + //! \param state The animation state. Set false to hide the progress indicator when it is not animating; otherwise true. + //! \sa isDisplayedWhenStopped + void setDisplayedWhenStopped(bool state); + + //! Sets the color of the components to the given color. + //! \sa color + void setColor(const QColor &color); + + protected: + //! \copydoc QWidget::timerEvent + virtual void timerEvent(QTimerEvent *event) override; + + //! \copydoc QWidget::paintEvent + virtual void paintEvent(QPaintEvent *event) override; + + private: + int m_angle = 0; + int m_timerId = -1; + int m_delayMs = 1000; + bool m_displayedWhenStopped = false; + QColor m_color = Qt::blue; + }; +} // ns + +#endif diff --git a/src/blackgui/menudelegate.h b/src/blackgui/menudelegate.h new file mode 100644 index 000000000..13db7d042 --- /dev/null +++ b/src/blackgui/menudelegate.h @@ -0,0 +1,39 @@ +/* Copyright (C) 2015 + * swift project Community / Contributors + * + * This file is part of swift Project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +#ifndef BLACKGUI_MENUDELEGATE_H +#define BLACKGUI_MENUDELEGATE_H + +#include +#include + +namespace BlackGui +{ + /*! + * Interface to implement a custom menu + */ + class IMenuDelegate : public QObject + { + Q_OBJECT + + public: + //! Display custom menu + virtual void customMenu(QMenu &menu) const = 0; + + //! Destructor + virtual ~IMenuDelegate() {} + + protected: + //! Constructor + IMenuDelegate(QWidget *parent = nullptr) : QObject(parent) {} + }; + +} // ns + +#endif // guard diff --git a/src/blackgui/views/viewbase.cpp b/src/blackgui/views/viewbase.cpp index 303958e1c..823079373 100644 --- a/src/blackgui/views/viewbase.cpp +++ b/src/blackgui/views/viewbase.cpp @@ -9,7 +9,9 @@ #include "viewbase.h" #include "blackgui/models/allmodels.h" -#include "../guiutility.h" +#include "blackgui/stylesheetutility.h" +#include "blackgui/guiutility.h" +#include "blackcore/blackcorefreefunctions.h" #include #include @@ -17,21 +19,19 @@ #include #include #include +#include +#include +#include using namespace BlackMisc; using namespace BlackGui; using namespace BlackGui::Models; +using namespace BlackGui::Filters; namespace BlackGui { namespace Views { - - void CViewBaseNonTemplate::resizeToContents() - { - this->performResizeToContents(); - } - CViewBaseNonTemplate::CViewBaseNonTemplate(QWidget *parent) : QTableView(parent) { this->setContextMenuPolicy(Qt::CustomContextMenu); @@ -43,22 +43,63 @@ namespace BlackGui this->setWordWrap(true); } - void CViewBaseNonTemplate::setFilterDialog(QDialog *filterDialog) + void CViewBaseNonTemplate::resizeToContents() + { + this->performModeBasedResizeToContent(); + } + + void CViewBaseNonTemplate::setFilterDialog(CFilterDialog *filterDialog) { if (filterDialog) { this->m_withMenuFilter = true; - this->m_filterDialog.reset(filterDialog); - connect(filterDialog, &QDialog::finished, this, &CViewBaseNonTemplate::ps_filterDialogFinished); + this->m_filterWidget = filterDialog; + connect(filterDialog, &CFilterDialog::finished, this, &CViewBaseNonTemplate::ps_filterDialogFinished); } else { - if (!this->m_filterDialog.isNull()) { disconnect(this->m_filterDialog.data()); } + if (this->m_filterWidget) { disconnect(this->m_filterWidget); } this->m_withMenuFilter = false; - this->m_filterDialog.reset(nullptr); + this->m_filterWidget->deleteLater(); + this->m_filterWidget = nullptr; } } + void CViewBaseNonTemplate::setFilterWidget(CFilterWidget *filterWidget) + { + if (this->m_filterWidget) + { + disconnect(this->m_filterWidget); + m_filterWidget = nullptr; + } + + if (filterWidget) + { + this->m_withMenuFilter = false; + this->m_filterWidget = filterWidget; + bool s = connect(filterWidget, &CFilterWidget::changeFilter, this, &CViewBaseNonTemplate::ps_filterWidgetChangedFilter); + Q_ASSERT_X(s, Q_FUNC_INFO, "filter connect"); + s = connect(this, &CViewBaseNonTemplate::rowCountChanged, filterWidget, &CFilterWidget::onRowCountChanged); + Q_ASSERT_X(s, Q_FUNC_INFO, "filter connect"); + Q_UNUSED(s); + } + } + + void CViewBaseNonTemplate::setCustomMenu(IMenuDelegate *menu) + { + this->m_menu = menu; + } + + void CViewBaseNonTemplate::enableLoadIndicator(bool enable) + { + m_enabledLoadIndicator = enable; + } + + bool CViewBaseNonTemplate::isShowingLoadIndicator() const + { + return m_enabledLoadIndicator && m_showingLoadIndicator; + } + QWidget *CViewBaseNonTemplate::mainApplicationWindowWidget() const { return CGuiUtility::mainApplicationWindowWidget(); @@ -66,7 +107,12 @@ namespace BlackGui void CViewBaseNonTemplate::customMenu(QMenu &menu) const { + // delegate? + if (this->m_menu) { this->m_menu->customMenu(menu); } + + // standard menus if (this->m_withMenuItemRefresh) { menu.addAction(BlackMisc::CIcons::refresh16(), "Update", this, SIGNAL(requestUpdate())); } + if (this->m_withMenuItemBackend) { menu.addAction(BlackMisc::CIcons::refresh16(), "Reload from backend", this, SIGNAL(requestNewBackendData())); } if (this->m_withMenuItemClear) { menu.addAction(BlackMisc::CIcons::delete16(), "Clear", this, SLOT(ps_clear())); } if (this->m_withMenuFilter) { @@ -74,6 +120,8 @@ namespace BlackGui menu.addAction(BlackMisc::CIcons::tableSheet16(), "Remove Filter", this, SLOT(ps_removeFilter())); } if (!menu.isEmpty()) { menu.addSeparator(); } + + // resizing menu.addAction(BlackMisc::CIcons::resize16(), "Full resize", this, SLOT(fullResizeToContents())); if (m_rowResizeMode == Interactive) { @@ -96,6 +144,26 @@ namespace BlackGui actionInteractiveResize->setEnabled(enabled); menu.addAction(actionInteractiveResize); connect(actionInteractiveResize, &QAction::toggled, this, &CViewBaseNonTemplate::ps_toggleResizeMode); + + if (m_showingLoadIndicator) + { + // just in case, if this ever will be dangling + menu.addAction(BlackMisc::CIcons::preloader16(), "Hide load indicator", this, SLOT(hideLoadIndicator())); + } + } + + void CViewBaseNonTemplate::paintEvent(QPaintEvent *event) + { + QTableView::paintEvent(event); + // CStyleSheetUtility::useStyleSheetInDerivedWidget(this, QStyle::PE_Widget); + } + + void CViewBaseNonTemplate::allowDragDropValueObjects(bool allowDrag, bool allowDrop) + { + // see model for implementing logic of drag + this->setAcceptDrops(allowDrop); + this->setDragEnabled(allowDrag); + this->setDropIndicatorShown(allowDrop); } int CViewBaseNonTemplate::getHorizontalHeaderFontHeight() const @@ -128,7 +196,7 @@ namespace BlackGui case Interactive: this->rowsResizeModeToInteractive(); break; case Content: this->rowsResizeModeToContent(); break; default: - Q_ASSERT(false); + Q_ASSERT_X(false, Q_FUNC_INFO, "wrong resize mode"); break; } } @@ -141,15 +209,15 @@ namespace BlackGui void CViewBaseNonTemplate::ps_displayFilterDialog() { if (!this->m_withMenuFilter) { return; } - if (!this->m_filterDialog) { return; } - this->m_filterDialog->show(); + if (!this->m_filterWidget) { return; } + this->m_filterWidget->show(); } void CViewBaseNonTemplate::rowsResizeModeToInteractive() { const int height = this->verticalHeader()->minimumSectionSize(); QHeaderView *verticalHeader = this->verticalHeader(); - Q_ASSERT(verticalHeader); + Q_ASSERT_X(verticalHeader, Q_FUNC_INFO, "Missing vertical header"); verticalHeader->setSectionResizeMode(QHeaderView::Interactive); verticalHeader->setDefaultSectionSize(height); this->m_rowResizeMode = Interactive; @@ -163,13 +231,51 @@ namespace BlackGui this->m_rowResizeMode = Content; } - bool CViewBaseNonTemplate::performResizing() const + void CViewBaseNonTemplate::showLoadIndicator(int containerSizeDependent) { + if (!m_enabledLoadIndicator) { return; } + if (this->m_showingLoadIndicator) { return; } + if (containerSizeDependent >= 0) + { + // really with indicator? + if (containerSizeDependent < ResizeSubsetThreshold) { return; } + } + this->m_showingLoadIndicator = true; + emit loadIndicatorVisibilityChanged(this->m_showingLoadIndicator); + // this->setStyleSheet(styleSheet()); + + if (!this->m_loadIndicator) + { + this->m_loadIndicator = new CLoadIndicator(64, 64, this->viewport()); + // connect(this->m_loadIndicator, &CLoadIndicator::updatedAnimation, this, &CViewBaseNonTemplate::ps_updatedIndicator); + + } + QPoint middle = this->geometry().center(); + int w = m_loadIndicator->width(); + int h = m_loadIndicator->height(); + int x = middle.x() - w / 2; + int y = middle.y() - h / 2; + this->m_loadIndicator->setGeometry(x, y, w, h); + this->m_loadIndicator->startAnimation(); + } + + void CViewBaseNonTemplate::hideLoadIndicator() + { + if (!this->m_showingLoadIndicator) { return; } + this->m_showingLoadIndicator = false; + emit loadIndicatorVisibilityChanged(this->m_showingLoadIndicator); + if (!this->m_loadIndicator) { return; } + this->m_loadIndicator->stopAnimation(); + } + + bool CViewBaseNonTemplate::isResizeConditionMet(int containerSize) const + { + if (m_resizeMode == ResizingOnceSubset) { return false; } if (m_resizeMode == ResizingOff) { return false; } if (m_resizeMode == ResizingOnce) { return m_resizeCount < 1; } if (m_resizeMode == ResizingAuto) { - if (reachedResizeThreshold()) { return false; } + if (reachedResizeThreshold(containerSize)) { return false; } if (m_resizeAutoNthTime < 2) { return true; } return (m_resizeCount % m_resizeAutoNthTime) == 0; } @@ -179,8 +285,8 @@ namespace BlackGui void CViewBaseNonTemplate::fullResizeToContents() { m_resizeCount++; - this->resizeColumnsToContents(); - this->resizeRowsToContents(); + this->resizeColumnsToContents(); // columns + this->resizeRowsToContents(); // rows if (m_forceStretchLastColumnWhenResized) { // re-stretch @@ -210,20 +316,65 @@ namespace BlackGui } } - template int CViewBase::updateContainer(const ContainerType &container, bool sort, bool resize) + void CViewBaseNonTemplate::ps_updatedIndicator() { - Q_ASSERT(this->m_model); + this->update(); + } + + template + CViewBase::CViewBase(QWidget *parent, ModelClass *model) : CViewBaseNonTemplate(parent), m_model(model) + { + this->setSortingEnabled(true); + if (model) { this->setModel(this->m_model); } + } + + template + int CViewBase::updateContainer(const ContainerType &container, bool sort, bool resize) + { + Q_ASSERT_X(this->m_model, Q_FUNC_INFO, "Missing model"); + this->showLoadIndicator(container.size()); + bool reallyResize = resize && isResizeConditionMet(container.size()); // do we really perform resizing + bool presize = (m_resizeMode == ResizingOnceSubset) && + this->isEmpty() && // only when no data yet + !reallyResize; // not when we resize later + presize = presize || (this->isEmpty() && resize && !reallyResize); // we presize if we wanted to resize but actually do not because of condition + bool presizeThreshold = presize && container.size() > ResizeSubsetThreshold; // only when size making sense + + // when we will not resize, we might presize + if (presizeThreshold) + { + int presizeRandomElements = container.size() / 100; + this->m_model->update(container.randomElements(presizeRandomElements), false); + this->fullResizeToContents(); + } int c = this->m_model->update(container, sort); - if (resize) { this->resizeToContents(); } + + // resize after real update according to mode + if (presizeThreshold) + { + // currently no furhter actions + } + else if (reallyResize) + { + this->resizeToContents(); + } + else if (presize && !presizeThreshold) + { + // small amount of data not covered before + this->fullResizeToContents(); + } + this->hideLoadIndicator(); return c; } - template BlackMisc::CWorker *CViewBase::updateContainerAsync(const ContainerType &container, bool sort, bool resize) + template + BlackMisc::CWorker *CViewBase::updateContainerAsync(const ContainerType &container, bool sort, bool resize) { Q_UNUSED(sort); ModelClass *model = this->derivedModel(); auto sortColumn = model->getSortColumn(); auto sortOrder = model->getSortOrder(); + this->showLoadIndicator(container.size()); BlackMisc::CWorker *worker = BlackMisc::CWorker::fromTask(this, "ViewSort", [model, container, sortColumn, sortOrder]() { return model->sortContainerByColumn(container, sortColumn, sortOrder); @@ -236,9 +387,10 @@ namespace BlackGui return worker; } - template void CViewBase::updateContainerMaybeAsync(const ContainerType &container, bool sort, bool resize) + template + void CViewBase::updateContainerMaybeAsync(const ContainerType &container, bool sort, bool resize) { - if (container.size() > asyncRowsCountThreshold && sort) + if (container.size() > ASyncRowsCountThreshold && sort) { // larger container with sorting updateContainerAsync(container, sort, resize); @@ -254,7 +406,7 @@ namespace BlackGui { Q_ASSERT(this->m_model); this->m_model->insert(value); - if (resize) { this->performResizeToContents(); } + if (resize) { this->performModeBasedResizeToContent(); } } template @@ -322,6 +474,24 @@ namespace BlackGui this->m_model->setObjectName(modelName); } + template + void CViewBase::takeFilterOwnership(std::unique_ptr > &filter) + { + this->derivedModel()->takeFilterOwnership(filter); + } + + template + void CViewBase::removeFilter() + { + this->derivedModel()->removeFilter(); + } + + template + bool CViewBase::hasFilter() const + { + return derivedModel()->hasFilter(); + } + template void CViewBase::setSortIndicator() { @@ -351,10 +521,17 @@ namespace BlackGui } template - void CViewBase::performResizeToContents() + bool CViewBase::reachedResizeThreshold(int containerSize) const + { + if (containerSize < 0) { return this->rowCount() > m_skipResizeThreshold; } + return containerSize > m_skipResizeThreshold; + } + + template + void CViewBase::performModeBasedResizeToContent() { // small set or large set? - if (this->performResizing()) + if (this->isResizeConditionMet()) { this->fullResizeToContents(); } @@ -375,12 +552,33 @@ namespace BlackGui bool CViewBase::ps_filterDialogFinished(int status) { QDialog::DialogCode statusCode = static_cast(status); - if (statusCode == QDialog::Rejected) + return ps_filterWidgetChangedFilter(statusCode == QDialog::Accepted); + } + + template + bool CViewBase::ps_filterWidgetChangedFilter(bool enabled) + { + if (enabled) { - this->derivedModel()->removeFilter(); - return true; // handled + if (!this->m_filterWidget) + { + this->removeFilter(); + } + else + { + IModelFilterProvider *provider = dynamic_cast*>(this->m_filterWidget); + Q_ASSERT_X(provider, Q_FUNC_INFO, "Filter widget does not provide interface"); + if (!provider) { return false; } + std::unique_ptr> f(provider->createModelFilter()); + this->takeFilterOwnership(f); + } } - return false; + else + { + // no filter + this->removeFilter(); + } + return true; // handled } template @@ -391,22 +589,22 @@ namespace BlackGui // see here for the reason of thess forward instantiations // http://www.parashift.com/c++-faq/separate-template-class-defn-from-decl.html - template class CViewBase; - template class CViewBase; - template class CViewBase; - - template class CViewBase; - template class CViewBase; - template class CViewBase; - - template class CViewBase; - template class CViewBase; - template class CViewBase; - template class CViewBase; - - template class CViewBase; + template class CViewBase; template class CViewBase; + template class CViewBase; + template class CViewBase; + template class CViewBase; + template class CViewBase; + template class CViewBase; template class CViewBase; + template class CViewBase; + template class CViewBase; + template class CViewBase; + template class CViewBase; + template class CViewBase; + template class CViewBase; + template class CViewBase; + template class CViewBase; } // namespace } // namespace diff --git a/src/blackgui/views/viewbase.h b/src/blackgui/views/viewbase.h index 9ead6e484..a85bd1b79 100644 --- a/src/blackgui/views/viewbase.h +++ b/src/blackgui/views/viewbase.h @@ -12,6 +12,11 @@ #ifndef BLACKGUI_VIEWBASE_H #define BLACKGUI_VIEWBASE_H +#include "blackgui/filters/filterdialog.h" +#include "blackgui/filters/filterwidget.h" +#include "blackgui/models/modelfilter.h" +#include "blackgui/menudelegate.h" +#include "blackgui/loadindicator.h" #include "blackgui/blackguiexport.h" #include "blackmisc/icons.h" #include "blackmisc/worker.h" @@ -29,43 +34,51 @@ namespace BlackGui { namespace Views { - //! Non templated base class, allows Q_OBJECT and signals / slots to be used class BLACKGUI_EXPORT CViewBaseNonTemplate : public QTableView { Q_OBJECT - public: + //! Load indicator property allows using in stylesheet + Q_PROPERTY(bool isShowingLoadIndicator READ isShowingLoadIndicator NOTIFY loadIndicatorVisibilityChanged) - //! Resize mode + public: + //! Resize mode, when to resize rows / columns //! \remarks Using own resizing (other than QHeaderView::ResizeMode) enum ResizeMode { - ResizingAuto, //!< always resizing, \sa m_resizeAutoNthTime - ResizingOnce, //!< only one time - ResizingOff //!< never + ResizingAuto, //!< resizing when below threshold, \sa m_resizeAutoNthTime forcing only every n-th update to be resized + ResizingOnce, //!< only one time + ResizingOnceSubset, //!< use a subset of the data to resize + ResizingOff //!< never }; - //! How rows are resizes + //! How rows are resized, makes sense when \sa ResizeMode is \sa ResizingOff enum RowsResizeMode { Interactive, Content }; - //! When (rows count) to use asynchronous updates - static const int asyncRowsCountThreshold = 50; + //! When (row count) to use asynchronous updates + static const int ASyncRowsCountThreshold = 50; + + //! When to use pre-sizing with random elements + static const int ResizeSubsetThreshold = 50; //! Clear data virtual void clear() = 0; + //! Allow to drag and/or drop value objects + virtual void allowDragDropValueObjects(bool allowDrag, bool allowDrop); + //! Resize mode ResizeMode getResizeMode() const { return m_resizeMode; } //! Set resize mode void setResizeMode(ResizeMode mode) { m_resizeMode = mode; } - //! In ResizeAuto mode, how often to update. "1" updates every time, "2" every 2nd time, .. + //! In \sa ResizingAuto mode, how often to update. "1" updates every time, "2" every 2nd time, .. void setAutoResizeFrequency(int updateEveryNthTime) { this->m_resizeAutoNthTime = updateEveryNthTime; } //! Header (horizontal) font @@ -81,15 +94,33 @@ namespace BlackGui QModelIndexList selectedRows() const; //! Filter dialog - void setFilterDialog(QDialog *filterDialog); + void setFilterDialog(BlackGui::Filters::CFilterDialog *filterDialog); + + //! Set filter widget + void setFilterWidget(BlackGui::Filters::CFilterWidget *filterDialog); + + //! Set custom menu if applicable + void setCustomMenu(BlackGui::IMenuDelegate *menu); + + //! Enable loading indicator + void enableLoadIndicator(bool enable); + + //! Showing load indicator + bool isShowingLoadIndicator() const; //! Main application window widget if any QWidget *mainApplicationWindowWidget() const; signals: - //! Ask for new data + //! Ask for new data from currently loaded data void requestUpdate(); + //! Load indicator's visibility has been changed + void loadIndicatorVisibilityChanged(bool visible); + + //! Load data from backend (where it makes sense) + void requestNewBackendData(); + //! Asynchronous update finished void asyncUpdateFinished(); @@ -112,6 +143,12 @@ namespace BlackGui //! Resize mode to content void rowsResizeModeToContent(); + //! Show loading indicator + void showLoadIndicator(int containerSizeDependent = -1); + + //! Hide loading indicator + void hideLoadIndicator(); + protected: //! Constructor CViewBaseNonTemplate(QWidget *parent); @@ -120,34 +157,42 @@ namespace BlackGui //! \remarks override this method to contribute to the menu virtual void customMenu(QMenu &menu) const; + //! \copydoc QTableView::paintEvent + virtual void paintEvent(QPaintEvent *event) override; + //! Perform resizing / non slot method for template - virtual void performResizeToContents() = 0; + virtual void performModeBasedResizeToContent() = 0; //! Helper method with template free signature //! \param variant contains the container //! \param sort - //! \param performResizing - virtual int performUpdateContainer(const BlackMisc::CVariant &variant, bool sort, bool performResizing) = 0; + //! \param resize + virtual int performUpdateContainer(const BlackMisc::CVariant &variant, bool sort, bool resize) = 0; //! Skip resizing because of size? - virtual bool reachedResizeThreshold() const = 0; + virtual bool reachedResizeThreshold(int containerSize = -1) const = 0; //! Resize or skip resize? - virtual bool performResizing() const; + virtual bool isResizeConditionMet(int containerSize = -1) const; //! Init default values void init(); - ResizeMode m_resizeMode = ResizingAuto; //!< mode - RowsResizeMode m_rowResizeMode = Interactive; //!< row resize mode - int m_resizeCount = 0; //!< flag / counter, how many resize activities - int m_skipResizeThreshold = 40; //!< when to skip resize (rows count) - int m_resizeAutoNthTime = 1; //!< with ResizeAuto, resize every n-th time - bool m_forceStretchLastColumnWhenResized = false; //!< a small table might (few columns) might to fail stretching, force again - bool m_withMenuItemClear = false; //!< allow clearing the view via menu - bool m_withMenuItemRefresh = false; //!< allow refreshing the view via menu - bool m_withMenuFilter = false; //!< filter can be opened - QScopedPointer m_filterDialog; //!< filter dialog if any + ResizeMode m_resizeMode = ResizingOnceSubset; //!< mode + RowsResizeMode m_rowResizeMode = Interactive; //!< row resize mode for row height + int m_resizeCount = 0; //!< flag / counter, how many resize activities + int m_skipResizeThreshold = 40; //!< when to skip resize (rows count) + int m_resizeAutoNthTime = 1; //!< with ResizeAuto, resize every n-th time + bool m_forceStretchLastColumnWhenResized = false; //!< a small table might (few columns) might to fail stretching, force again + bool m_withMenuItemClear = false; //!< allow clearing the view via menu + bool m_withMenuItemRefresh = false; //!< allow refreshing the view via menu + bool m_withMenuItemBackend = false; //!< allow to request data from backend + bool m_withMenuFilter = false; //!< filter can be opened + bool m_showingLoadIndicator = false; //!< showing loading indicator + bool m_enabledLoadIndicator = true; //!< loading indicator enabled/disabled + QWidget *m_filterWidget = nullptr; //!< filter widget if any + BlackGui::IMenuDelegate *m_menu = nullptr; //!< custom menu if any + BlackGui::CLoadIndicator *m_loadIndicator = nullptr; //!< load indicator if neeeded protected slots: //! Helper method with template free signature serving as callback from threaded worker @@ -162,6 +207,9 @@ namespace BlackGui //! Filter dialog finished virtual bool ps_filterDialogFinished(int status) = 0; + //! Filter changed in filter widget + virtual bool ps_filterWidgetChangedFilter(bool enabled) = 0; + private slots: //! Custom menu was requested void ps_customMenuRequested(QPoint pos); @@ -169,6 +217,9 @@ namespace BlackGui //! Toggle the resize mode void ps_toggleResizeMode(bool checked); + //! Indicator has been updated + void ps_updatedIndicator(); + //! Clear the model virtual void ps_clear() { this->clear(); } }; @@ -176,10 +227,9 @@ namespace BlackGui //! Base class for views template class CViewBase : public CViewBaseNonTemplate { - public: //! Destructor - virtual ~CViewBase() {} + virtual ~CViewBase() { if (this->m_model) { this->m_model->markDestroyed(); }} //! Model ModelClass *derivedModel() { return this->m_model; } @@ -191,13 +241,13 @@ namespace BlackGui virtual void clear() override { Q_ASSERT(this->m_model); this->m_model->clear(); } //! Update whole container - int updateContainer(const ContainerType &container, bool sort = true, bool performResizing = true); + int updateContainer(const ContainerType &container, bool sort = true, bool resize = true); //! Update whole container in background - BlackMisc::CWorker *updateContainerAsync(const ContainerType &container, bool sort = true, bool performResizing = true); + BlackMisc::CWorker *updateContainerAsync(const ContainerType &container, bool sort = true, bool resize = true); //! Based on size call sync / async update - void updateContainerMaybeAsync(const ContainerType &container, bool sort = true, bool performResizing = true); + void updateContainerMaybeAsync(const ContainerType &container, bool sort = true, bool resize = true); //! Insert void insert(const ObjectType &value, bool resize = true); @@ -226,15 +276,20 @@ namespace BlackGui //! Set own name and the model's name virtual void setObjectName(const QString &name); + //! Set filter and take ownership, any previously set filter will be destroyed + void takeFilterOwnership(std::unique_ptr> &filter); + + //! Removes filter and destroys filter object + void removeFilter(); + + //! Has filter set? + bool hasFilter() const; + protected: ModelClass *m_model = nullptr; //!< corresponding model //! Constructor - CViewBase(QWidget *parent, ModelClass *model = nullptr) : CViewBaseNonTemplate(parent), m_model(model) - { - this->setSortingEnabled(true); - if (model) { this->setModel(this->m_model); } - } + CViewBase(QWidget *parent, ModelClass *model = nullptr); //! Set the search indicator based on model void setSortIndicator(); @@ -243,18 +298,22 @@ namespace BlackGui void standardInit(ModelClass *model = nullptr); //! \copydoc CViewBaseNonTemplate::reachedResizeThreshold - virtual bool reachedResizeThreshold() const override { return this->rowCount() > m_skipResizeThreshold; } + virtual bool reachedResizeThreshold(int containrerSize = -1) const override; //! \copydoc CViewBaseNonTemplate::performResizing - virtual void performResizeToContents() override; + virtual void performModeBasedResizeToContent() override; //! \copydoc CViewBaseNonTemplate::performUpdateContainer - virtual int performUpdateContainer(const BlackMisc::CVariant &variant, bool sort, bool performResizing) override; + virtual int performUpdateContainer(const BlackMisc::CVariant &variant, bool sort, bool resize) override; //! \copydoc CViewBaseNonTemplate::ps_filterDialogFinished //! \remarks Actually a slot, but not defined as such as the template does not support Q_OBJECT virtual bool ps_filterDialogFinished(int status) override; + //! \copydoc CViewBaseNonTemplate::ps_FilterWidgetChangedFilter + //! \remarks Actually a slot, but not defined as such as the template does not support Q_OBJECT + virtual bool ps_filterWidgetChangedFilter(bool enabled) override; + //! \copydoc CViewBaseNonTemplate::ps_removeFilter //! \remarks Actually a slot, but not defined as such as the template does not support Q_OBJECT virtual void ps_removeFilter() override;