refs #560, allow to load / save from view/model

* added load/save functions
* changed menu from multiple bool members to flag
* shortcut for save
* cleaned up filter dialog handling
This commit is contained in:
Klaus Basan
2015-12-19 03:37:13 +01:00
parent 8aa449bc13
commit 9fcc846b7a
7 changed files with 226 additions and 54 deletions

View File

@@ -41,6 +41,7 @@ namespace BlackGui
this->ui->tvp_StatusMessages->setAutoResizeFrequency(3);
connect(this->ui->tvp_StatusMessages, &CStatusMessageView::objectSelected, this->ui->form_StatusMessage, &CStatusMessageForm::setVariant);
this->ui->tvp_StatusMessages->setCustomMenu(new CLogMenu(this));
this->ui->tvp_StatusMessages->menuAddItems(CStatusMessageView::MenuSave);
}
CLogComponent::~CLogComponent()

View File

@@ -604,6 +604,18 @@ namespace BlackGui
return mimeData;
}
template <typename ObjectType, typename ContainerType, bool UseCompare>
QJsonObject CListModelBase<ObjectType, ContainerType, UseCompare>::toJson() const
{
return container().toJson();
}
template <typename ObjectType, typename ContainerType, bool UseCompare>
QString CListModelBase<ObjectType, ContainerType, UseCompare>::toJsonString(QJsonDocument::JsonFormat format) const
{
return container().toJsonString(format);
}
// see here for the reason of thess forward instantiations
// http://www.parashift.com/c++-faq/separate-template-class-defn-from-decl.html
template class CListModelBase<BlackMisc::Aviation::CLivery, BlackMisc::Aviation::CLiveryList, true>;

View File

@@ -100,6 +100,12 @@ namespace BlackGui
//! \note Meant for scenarios where the container is directly updated and a subsequent signal is required
void sendDataChanged(int startRowIndex, int endRowIndex);
//! Convert to JSON
virtual QJsonObject toJson() const = 0;
//! Convert to JSON string
virtual QString toJsonString(QJsonDocument::JsonFormat format = QJsonDocument::Indented) const = 0;
signals:
//! Asynchronous update finished
void asyncUpdateFinished();
@@ -225,6 +231,12 @@ namespace BlackGui
//! \copydoc QStandardItemModel::mimeData
virtual QMimeData *mimeData(const QModelIndexList &indexes) const override;
//! \copydoc CListModelBaseNonTemplate::toJosn
virtual QJsonObject toJson() const override;
//! \copydoc CListModelBaseNonTemplate::toJsonString
virtual QString toJsonString(QJsonDocument::JsonFormat format = QJsonDocument::Indented) const override;
//! Filter available
bool hasFilter() const;

View File

@@ -37,4 +37,17 @@ namespace BlackGui
return k;
}
const QKeySequence &CShortcut::keySave()
{
static const QKeySequence k(Qt::CTRL + Qt::Key_S);
return k;
}
const QKeySequence &CShortcut::keySaveViews()
{
// remark CTRL+S not working in views
static const QKeySequence k(Qt::SHIFT + Qt::Key_S);
return k;
}
} // ns

View File

@@ -38,6 +38,12 @@ namespace BlackGui
//! Display filter
static const QKeySequence &keyDisplayFilter();
//! Save depending on context
static const QKeySequence &keySave();
//! Save in views
static const QKeySequence &keySaveViews();
};
} // ns
#endif // CSHORTCUT_H
#endif // guard

View File

@@ -8,6 +8,7 @@
*/
#include "viewbase.h"
#include "blackmisc/fileutils.h"
#include "blackgui/models/allmodels.h"
#include "blackgui/stylesheetutility.h"
#include "blackgui/guiutility.h"
@@ -24,6 +25,9 @@
#include <QMovie>
#include <QPainter>
#include <QShortcut>
#include <QFileDialog>
#include <QStandardPaths>
#include <QDir>
using namespace BlackMisc;
using namespace BlackGui;
@@ -49,9 +53,10 @@ namespace BlackGui
QShortcut *filter = new QShortcut(CShortcut::keyDisplayFilter(), this, SLOT(ps_displayFilterDialog()), nullptr, Qt::WidgetShortcut);
filter->setObjectName("Filter shortcut for " + this->objectName());
QShortcut *clearSelection = new QShortcut(CShortcut::keyClearSelection(), this, SLOT(clearSelection()), nullptr, Qt::WidgetShortcut);
clearSelection->setObjectName("Cleat selection shortcut for " + this->objectName());
clearSelection->setObjectName("Clear selection shortcut for " + this->objectName());
QShortcut *saveJson = new QShortcut(CShortcut::keySaveViews(), this, SLOT(ps_saveJsonShortcut()), nullptr, Qt::WidgetShortcut);
saveJson->setObjectName("Save JSON for " + this->objectName());
}
bool CViewBaseNonTemplate::setParentDockWidgetInfoArea(CDockWidgetInfoArea *parentDockableWidget)
@@ -65,35 +70,39 @@ namespace BlackGui
this->performModeBasedResizeToContent();
}
void CViewBaseNonTemplate::setFilterDialog(CFilterDialog *filterDialog)
{
if (filterDialog)
{
this->m_withMenuFilter = true;
this->m_filterWidget = filterDialog;
connect(filterDialog, &CFilterDialog::finished, this, &CViewBaseNonTemplate::ps_filterDialogFinished);
}
else
{
if (this->m_filterWidget) { disconnect(this->m_filterWidget); }
this->m_withMenuFilter = false;
this->m_filterWidget->deleteLater();
this->m_filterWidget = nullptr;
}
}
void CViewBaseNonTemplate::setFilterWidget(CFilterWidget *filterWidget)
void CViewBaseNonTemplate::setFilterWidgetImpl(QWidget *filterWidget)
{
if (this->m_filterWidget)
{
disconnect(this->m_filterWidget);
this->menuRemoveItems(MenuFilter);
if (m_filterWidget->parent() == this) { m_filterWidget->deleteLater(); }
m_filterWidget = nullptr;
}
if (filterWidget)
{
this->m_withMenuFilter = false;
this->menuAddItems(MenuFilter);
this->m_filterWidget = filterWidget;
}
}
void CViewBaseNonTemplate::setFilterDialog(CFilterDialog *filterDialog)
{
this->setFilterWidgetImpl(filterDialog);
if (filterDialog)
{
bool s = connect(filterDialog, &CFilterDialog::finished, this, &CViewBaseNonTemplate::ps_filterDialogFinished);
Q_ASSERT_X(s, Q_FUNC_INFO, "filter dialog connect");
Q_UNUSED(s);
}
}
void CViewBaseNonTemplate::setFilterWidget(CFilterWidget *filterWidget)
{
this->setFilterWidgetImpl(filterWidget);
if (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);
@@ -153,24 +162,25 @@ namespace BlackGui
if (this->m_menu) { this->m_menu->customMenu(menu); }
// standard menus
bool withStandardMenu = this->m_withMenuItemRefresh || this->m_withMenuItemBackend || this->m_withMenuItemClear || this->m_withMenuDisplayAutomatically;
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_withMenuDisplayAutomatically)
int items = menu.actions().size();
if (this->m_menus.testFlag(MenuRefresh)) { menu.addAction(BlackMisc::CIcons::refresh16(), "Update", this, SIGNAL(requestUpdate())); }
if (this->m_menus.testFlag(MenuBackend)) { menu.addAction(BlackMisc::CIcons::refresh16(), "Reload from backend", this, SIGNAL(requestNewBackendData())); }
if (this->m_menus.testFlag(MenuClear)) { menu.addAction(BlackMisc::CIcons::delete16(), "Clear", this, SLOT(ps_clear())); }
if (this->m_menus.testFlag(MenuDisplayAutomatically))
{
QAction *a = menu.addAction(CIcons::appMappings16(), "Automatically display (when loaded)", this, SLOT(ps_toggleAutoDisplay()));
a->setCheckable(true);
a->setChecked(this->displayAutomatically());
}
if (withStandardMenu) { menu.addSeparator(); }
if (menu.actions().size() > items) { menu.addSeparator(); }
if (this->m_withMenuFilter)
items = menu.actions().size();
if (this->m_menus.testFlag(MenuFilter))
{
menu.addAction(BlackMisc::CIcons::tableSheet16(), "Filter", this, SLOT(ps_displayFilterDialog()), CShortcut::keyDisplayFilter());
menu.addAction(BlackMisc::CIcons::tableSheet16(), "Remove Filter", this, SLOT(ps_removeFilter()));
}
if (!menu.isEmpty()) { menu.addSeparator(); }
if (menu.actions().size() > items) { menu.addSeparator(); }
// selection menus
SelectionMode sm = this->selectionMode();
@@ -179,7 +189,13 @@ namespace BlackGui
menu.addAction(BlackMisc::CIcons::empty16(), "Select all", this, SLOT(selectAll()), Qt::CTRL + Qt::Key_A);
}
menu.addAction(BlackMisc::CIcons::empty16(), "Clear selection", this, SLOT(clearSelection()), CShortcut::keyClearSelection());
if (!menu.isEmpty()) { menu.addSeparator(); }
menu.addSeparator();
// load/save
items = menu.actions().size();
if (m_menus.testFlag(MenuLoad)) { menu.addAction(CIcons::disk16(), "Load from file", this, SLOT(ps_loadJson())); }
if (m_menus.testFlag(MenuSave) && !isEmpty()) { menu.addAction(CIcons::disk16(), "Save data in file", this, SLOT(ps_saveJson()), CShortcut::keySaveViews()); }
if (menu.actions().size() > items) { menu.addSeparator(); }
// resizing
menu.addAction(BlackMisc::CIcons::resize16(), "Full resize", this, SLOT(fullResizeToContents()));
@@ -340,11 +356,18 @@ namespace BlackGui
void CViewBaseNonTemplate::ps_displayFilterDialog()
{
if (!this->m_withMenuFilter) { return; }
if (!this->m_menus.testFlag(MenuFilter)) { return; }
if (!this->m_filterWidget) { return; }
this->m_filterWidget->show();
}
void CViewBaseNonTemplate::ps_saveJsonShortcut()
{
if (this->isEmpty()) { return; }
if (!this->m_menus.testFlag(MenuSave)) { return; }
this->ps_saveJson();
}
void CViewBaseNonTemplate::rowsResizeModeToInteractive()
{
const int height = this->verticalHeader()->minimumSectionSize();
@@ -694,6 +717,20 @@ namespace BlackGui
return this->m_model->rowCount() < 1;
}
template <class ModelClass, class ContainerType, class ObjectType>
QJsonObject CViewBase<ModelClass, ContainerType, ObjectType>::toJson() const
{
Q_ASSERT(this->m_model);
return this->m_model->toJson();
}
template <class ModelClass, class ContainerType, class ObjectType>
QString CViewBase<ModelClass, ContainerType, ObjectType>::toJsonString(QJsonDocument::JsonFormat format) const
{
Q_ASSERT(this->m_model);
return this->m_model->toJsonString(format);
}
template <class ModelClass, class ContainerType, class ObjectType>
void CViewBase<ModelClass, ContainerType, ObjectType>::setObjectName(const QString &name)
{
@@ -779,6 +816,45 @@ namespace BlackGui
return this->updateContainer(c, sort, resize);
}
template <class ModelClass, class ContainerType, class ObjectType>
CStatusMessage CViewBase<ModelClass, ContainerType, ObjectType>::ps_loadJson()
{
static const CLogCategoryList cats(CLogCategoryList(this).join({ CLogCategory::validation()}));
QString fileName = QFileDialog::getOpenFileName(nullptr,
tr("Load data file"), getDefaultFilename(),
tr("swift (*.json *.txt)"));
if (fileName.isEmpty()) { return CStatusMessage(cats, CStatusMessage::SeverityDebug, "Load canceled"); }
QString json(CFileUtils::readFileToString(fileName));
if (json.isEmpty())
{
return CStatusMessage(cats, CStatusMessage::SeverityWarning, "Reading " + fileName + " yields no data");
}
ContainerType container;
container.convertFromJson(json);
this->updateContainerMaybeAsync(container);
return CStatusMessage(cats, CStatusMessage::SeverityInfo, "Reading " + fileName + " completed");
}
template <class ModelClass, class ContainerType, class ObjectType>
CStatusMessage CViewBase<ModelClass, ContainerType, ObjectType>::ps_saveJson() const
{
static const CLogCategoryList cats(CLogCategoryList(this).join({ CLogCategory::validation()}));
QString fileName = QFileDialog::getSaveFileName(nullptr,
tr("Save data file"), getDefaultFilename(),
tr("swift (*.json *.txt)"));
if (fileName.isEmpty()) { return CStatusMessage(cats, CStatusMessage::SeverityDebug, "Save canceled"); }
const QString json(this->toJsonString());
bool ok = CFileUtils::writeStringToFileInBackground(json, fileName);
if (ok)
{
return CStatusMessage(cats, CStatusMessage::SeverityInfo, "Writing " + fileName + " in progress");
}
else
{
return CStatusMessage(cats, CStatusMessage::SeverityError, "Writing " + fileName + " failed");
}
}
template <class ModelClass, class ContainerType, class ObjectType>
bool CViewBase<ModelClass, ContainerType, ObjectType>::ps_filterDialogFinished(int status)
{

View File

@@ -65,6 +65,22 @@ namespace BlackGui
Content
};
//! Menu flags
enum MenuFlag
{
MenuNone = 0, ///< no menu
MenuClear = 1 << 0, ///< allow clearing the view via menu
MenuRefresh = 1 << 1, ///< allow refreshing the view via menu
MenuBackend = 1 << 2, ///< allow to request data from backend
MenuDisplayAutomatically = 1 << 3, ///< allow to switch display automatically
MenuFilter = 1 << 4, ///< filter can be opened
MenuSave = 1 << 5, ///< save as JSON
MenuLoad = 1 << 6, ///< load from JSON
MenuLoadAndSave = MenuLoad | MenuSave,
MenuDefault = MenuClear | MenuDisplayAutomatically
};
Q_DECLARE_FLAGS(Menu, MenuFlag)
//! When (row count) to use asynchronous updates
static const int ASyncRowsCountThreshold = 50;
@@ -74,6 +90,9 @@ namespace BlackGui
//! Clear data
virtual void clear() = 0;
//! Empty?
virtual bool isEmpty() const = 0 ;
//! Allow to drag and/or drop value objects
virtual void allowDragDropValueObjects(bool allowDrag, bool allowDrop);
@@ -143,6 +162,12 @@ namespace BlackGui
//! Accept double click selection
void acceptDoubleClickSelection(bool accept) { m_acceptDoubleClickSelection = accept; }
//! Remove given menu items
void menuRemoveItems(Menu menusToRemove);
//! Add given menu items
void menuAddItems(Menu menusToRemove);
//! \copydoc QTableView::setSelectionModel();
virtual void setSelectionModel(QItemSelectionModel *model) override;
@@ -246,26 +271,25 @@ namespace BlackGui
//! Init default values
void init();
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_withMenuDisplayAutomatically = false; //!< allow to switch display automatically
bool m_withMenuFilter = false; //!< filter can be opened
bool m_showingLoadIndicator = false; //!< showing loading indicator
bool m_enabledLoadIndicator = true; //!< loading indicator enabled/disabled
bool m_acceptClickSelection = false; //!< clicked
bool m_acceptRowSelected = false; //!< selection changed
bool m_acceptDoubleClickSelection = false; //!< double clicked
bool m_displayAutomatically = true; //!< display directly when loaded
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
//! Default file for load/save operations
QString getDefaultFilename() const;
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_showingLoadIndicator = false; //!< showing loading indicator
bool m_enabledLoadIndicator = true; //!< loading indicator enabled/disabled
bool m_acceptClickSelection = false; //!< clicked
bool m_acceptRowSelected = false; //!< selection changed
bool m_acceptDoubleClickSelection = false; //!< double clicked
bool m_displayAutomatically = true; //!< display directly when loaded
QWidget *m_filterWidget = nullptr; //!< filter widget if any
Menu m_menus = MenuDefault; //!< Default menu settings
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
@@ -292,6 +316,15 @@ namespace BlackGui
//! Row selected
virtual void ps_rowSelected(const QModelIndex &index) = 0;
//! Load JSON
virtual BlackMisc::CStatusMessage ps_loadJson() = 0;
//! Save JSON
virtual BlackMisc::CStatusMessage ps_saveJson() const = 0;
//! Save JSON called by shortcut
virtual void ps_saveJsonShortcut();
private slots:
//! Custom menu was requested
void ps_customMenuRequested(QPoint pos);
@@ -307,7 +340,12 @@ namespace BlackGui
//! Clear the model
virtual void ps_clear() { this->clear(); }
private:
//! Set the filter widget internally
void setFilterWidgetImpl(QWidget *filterWidget);
};
Q_DECLARE_OPERATORS_FOR_FLAGS(BlackGui::Views::CViewBaseNonTemplate::Menu)
//! Base class for views
template <class ModelClass, class ContainerType, class ObjectType> class CViewBase : public CViewBaseNonTemplate
@@ -361,8 +399,14 @@ namespace BlackGui
//! Column count
int columnCount() const;
//! Any data?
bool isEmpty() const;
//! \copydoc CViewBaseNonTemplate::isEmpty
virtual bool isEmpty() const override;
//! Convert to JSON
QJsonObject toJson() const;
//! Convert to JSON string
QString toJsonString(QJsonDocument::JsonFormat format = QJsonDocument::Indented) const;
//! Set own name and the model's name
virtual void setObjectName(const QString &name);
@@ -420,6 +464,14 @@ namespace BlackGui
//! \copydoc BlackGui::Views::CViewBaseNonTemplate::ps_rowSelected
//! \remarks Actually a slot, but not defined as such as the template does not support Q_OBJECT
virtual void ps_rowSelected(const QModelIndex &index) override;
//! \copydoc BlackGui::Views::CViewBaseNonTemplate::ps_loadJson
//! \remarks Actually a slot, but not defined as such as the template does not support Q_OBJECT
virtual BlackMisc::CStatusMessage ps_loadJson() override;
//! \copydoc BlackGui::Views::CViewBaseNonTemplate::ps_saveJson
//! \remarks Actually a slot, but not defined as such as the template does not support Q_OBJECT
virtual BlackMisc::CStatusMessage ps_saveJson() const override;
};
} // namespace
} // namespace