Files
GenericQtClientWidgets/mainwindow.cpp

566 lines
22 KiB
C++

#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include <QCloseEvent>
#include <QFileDialog>
#include <QInputDialog>
#include <QMessageBox>
#include <QStandardPaths>
#include <QUndoStack>
#include <QUndoView>
#include "../../ApplicationConfig.h"
#include "dialogs/edititemdialog.h"
#include "dialogs/newitemdialog.h"
#include "dialogs/settingsdialog.h"
#include "genericcore.h"
#include "model/generalsortfiltermodel.h"
#include "model/metadata.h"
#include "model/tablemodel.h"
#include "widgets/controls/comboboxdelegate.h"
#include "widgets/controls/spinboxdelegate.h"
static QStandardPaths::StandardLocation standardLocation = QStandardPaths::HomeLocation;
static QString updateTextClean = "Do you want to update the application now?";
static QString updateTextDirty = "Do you want to save the tasks & update the application now?";
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow) {
ui->setupUi(this);
m_core = std::make_unique<GenericCore>();
setWindowTitle(QCoreApplication::applicationName() + " [*]");
/// application icon
const QString iconString = "://software-application.png";
#ifdef QT_DEBUG
QPixmap pixmap = QPixmap(iconString);
QTransform transform = QTransform();
transform.rotate(180);
QPixmap rotated = pixmap.transformed(transform);
QIcon appIcon = QIcon(rotated);
setWindowIcon(QIcon(rotated));
#else
setWindowIcon(QIcon(iconString));
#endif
const QVariantMap settings = m_core->getSettings("GUI");
restoreGeometry(settings.value("geometry").toByteArray());
restoreState(settings.value("windowState").toByteArray());
setupModelViews();
createActions();
createHelpMenu();
createGuiDialogs();
connect(m_core.get(), &GenericCore::displayStatusMessage, this,
&MainWindow::displayStatusMessage);
connect(this, &MainWindow::displayStatusMessage, this, &MainWindow::showStatusMessage);
connect(this, &MainWindow::checkForUpdates, this,
&MainWindow::on_actionCheck_for_update_triggered, Qt::QueuedConnection);
// connect(ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
// &MainWindow::onSelectionChanged);
connect(ui->tableView->selectionModel(), &QItemSelectionModel::currentChanged, this,
&MainWindow::onCurrentChanged);
onSelectionChanged(QItemSelection(), QItemSelection());
onCurrentChanged(QModelIndex(), QModelIndex());
}
MainWindow::~MainWindow() { delete ui; }
void MainWindow::closeEvent(QCloseEvent* event) {
if (isWindowModified()) {
QMessageBox msgBox;
msgBox.setWindowTitle(windowTitle() + " - Save dialog");
msgBox.setText("The document has been modified.");
msgBox.setInformativeText("Do you want to save your changes?");
msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Save);
int ret = msgBox.exec();
switch (ret) {
case QMessageBox::Save:
emit saveItems();
event->accept();
break;
case QMessageBox::Discard:
event->accept();
break;
case QMessageBox::Cancel:
event->ignore();
break;
default:
/// should never be reached
qCritical() << "unexpected switch case in closeEvent:" << ret;
event->ignore();
break;
}
} else {
event->accept();
}
if (event->isAccepted()) {
qInfo() << "Saving GUI settings...";
m_core->applySettings({{"geometry", saveGeometry()}, {"windowState", saveState()}}, "GUI");
}
}
void MainWindow::showStatusMessage(const QString text) {
qInfo() << text;
ui->statusbar->showMessage(text);
}
void MainWindow::onCurrentChanged(const QModelIndex& current, const QModelIndex& previous) {
// Q_UNUSED(current);
Q_UNUSED(previous);
// QItemSelection localSelection = ui->tableView->selectionModel()->selection();
if (current == QModelIndex()) {
// qDebug() << "Nothing selected. Disabling delete action";
m_deleteItemAct->setEnabled(false);
} else {
// qDebug() << "Something selected. Enabling delete action";
m_deleteItemAct->setEnabled(true);
}
}
void MainWindow::onSelectionChanged(const QItemSelection& selected,
const QItemSelection& deselected) {
Q_UNUSED(selected);
Q_UNUSED(deselected);
QItemSelection localSelection = ui->tableView->selectionModel()->selection();
if (localSelection.empty()) {
// qDebug() << "Nothing selected. Disabling delete action";
m_deleteItemAct->setEnabled(false);
} else {
// qDebug() << "Something selected. Enabling delete action";
m_deleteItemAct->setEnabled(true);
}
}
void MainWindow::onAboutClicked() {
const QString applicationName = APPLICATION_NAME;
const QString titlePrefix = tr("About ");
const QString aboutText =
tr(QString("<b>%1 v%2</b> is a template for Qt applications."
"<br><br><a href=\"https://working-copy.org/\">Working-Copy_Collective website</a>"
"<br><br><a href=\"mailto:support@working-copy.org\">Mail to support</a>"
"<br><br>It uses the <a href=\"https://qt.io\">Qt Framework</a>.")
.arg(applicationName)
.arg(APPLICATION_VERSION)
.toLatin1());
QMessageBox::about(this, titlePrefix + applicationName, aboutText);
}
void MainWindow::on_actionCheck_for_update_triggered() {
showStatusMessage("Checking for update...");
const bool updateAvailable = m_core->isApplicationUpdateAvailable();
if (updateAvailable) {
const QString text = isWindowModified() ? updateTextDirty : updateTextClean;
const QMessageBox::StandardButton clickedButton = QMessageBox::question(
this, tr("Update available."), text, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if (clickedButton == QMessageBox::Yes) {
m_core->triggerApplicationUpdate(true);
close();
}
}
}
void MainWindow::on_pushButton_clicked() {
const QString prefix("Backend provided by: ");
ui->label->setText(prefix + m_core->toString());
}
void MainWindow::openNewItemDialog() {
showStatusMessage(tr("Invoked 'Edit|New Item'"));
m_newItemDialog->show();
}
void MainWindow::openEditItemDialog() {
showStatusMessage(tr("Invoked 'Edit|Edit Item'"));
m_editItemDialog->show();
}
void MainWindow::deleteCurrentItem() {
showStatusMessage(tr("Invoked 'Edit|Delete Item'"));
// QItemSelection localSelection = ui->tableView->selectionModel()->selection();
const QModelIndex currentIndex = ui->tableView->selectionModel()->currentIndex();
if (currentIndex == QModelIndex()) {
qDebug() << "No current item. Nothing to remove.";
} else {
m_proxyModel->removeRows(currentIndex.row(), 1);
}
}
void MainWindow::deleteSelectedtItems() {
showStatusMessage(tr("Invoked 'Edit|Delete Item'"));
QItemSelection localSelection = ui->tableView->selectionModel()->selection();
if (localSelection.empty()) {
qDebug() << "No items selected. Nothing to remove.";
} else {
for (QList<QItemSelectionRange>::reverse_iterator iter = localSelection.rbegin(),
rend = localSelection.rend();
iter != rend; ++iter) {
// qInfo() << "iter:" << *iter;
// const QModelIndex parentIndex = iter->parent();
const int topRow = iter->top();
const int bottomRow = iter->bottom();
const int nRows = bottomRow - topRow + 1;
m_proxyModel->removeRows(topRow, nRows);
}
}
}
void MainWindow::onCleanStateChanged(bool clean) {
qInfo() << "Slot onCleanStateChanged triggered: clean:" << clean;
setWindowModified(!clean);
if (!clean) {
ui->statusbar->clearMessage();
}
}
void MainWindow::onShowUndoViewToggled(bool checked) {
// qInfo() << "Slot onShowUndoViewToggled toggled: checked:" << checked;
// m_undoView->setVisible(checked);
/// workaround until m_showUndoViewAction is checkable
qInfo() << "Slot onShowUndoViewToggled triggered, toggleing undo view...";
Q_UNUSED(checked);
if (m_modelUndoView->isVisible()) {
m_modelUndoView->setVisible(false);
} else {
m_modelUndoView->setVisible(true);
}
}
void MainWindow::saveItems() {
showStatusMessage(tr("Invoked 'File|Save'"));
m_core->saveItems();
}
void MainWindow::importCSV() {
showStatusMessage(tr("Invoked 'File|Import CSV'"));
const QString csvFilePath = QFileDialog::getOpenFileName(
this, tr("Import CSV"), QStandardPaths::standardLocations(standardLocation).first(),
tr("CSV Files (*.csv)"));
if (QFileInfo::exists(csvFilePath)) {
m_core->importCSVFile(csvFilePath);
} else {
qWarning() << "Selected CSV file path doesn't exist. Doing nothing...";
showStatusMessage(tr("Could't find CSV file!"));
}
}
void MainWindow::exportCSV() {
showStatusMessage(tr("Invoked 'File|Export'"));
const QString filter = tr("CSV Files (*.csv)");
const QString location = QStandardPaths::standardLocations(standardLocation).first();
QFileDialog dialog(this, "Export CSV File", location, "Comma-separated file (*.csv)");
dialog.setDefaultSuffix(".csv");
dialog.setAcceptMode(QFileDialog::AcceptSave);
if (dialog.exec()) {
const QString csvFilePath = dialog.selectedFiles().first();
const bool successful = m_core->exportCSVFile(csvFilePath);
if (successful) {
const QString message = QString(tr("CSV exported to: %1")).arg(csvFilePath);
showStatusMessage(message);
}
} else {
qWarning() << "Selected CSV file path doesn't exist. Doing nothing...";
}
}
void MainWindow::findItems() {
showStatusMessage(tr("Invoked 'Edit|Find items'"));
bool ok;
QString text = QInputDialog::getText(this, tr("Find items"), tr("Enter the text to search for:"),
QLineEdit::Normal, "", &ok);
if (ok && !text.isEmpty()) {
const QItemSelection itemsToSelect = m_proxyModel->findItems(text);
if (itemsToSelect.empty()) {
ui->tableView->setCurrentIndex(QModelIndex());
ui->tableView->selectionModel()->select(QModelIndex(), QItemSelectionModel::ClearAndSelect);
} else {
ui->tableView->setCurrentIndex(itemsToSelect.first().topLeft());
ui->tableView->selectionModel()->select(itemsToSelect, QItemSelectionModel::ClearAndSelect);
}
}
}
void MainWindow::fetchItems() {
showStatusMessage(tr("Invoked 'Server|Fetch items'"));
emit m_core->fetchItemsFromServer();
}
void MainWindow::postItems() {
showStatusMessage(tr("Invoked 'Server|Post items'"));
const QModelIndex currentIndex = ui->tableView->currentIndex();
const QByteArray jsonData = m_proxyModel->jsonDataForServer(currentIndex);
emit m_core->postItemToServer(jsonData);
}
void MainWindow::deleteItem() {
showStatusMessage(tr("Invoked 'Server|Delete items'"));
const QModelIndex currentIndex = ui->tableView->currentIndex();
// const QByteArray jsonData = m_proxyModel->jsonDataForServer(currentIndex);
const QString currentId = m_proxyModel->getUuid(currentIndex);
emit m_core->deleteItemFromServer(currentId);
}
void MainWindow::execSettingsDialog() {
showStatusMessage(tr("Invoked 'Tools|Settings'"));
QVariantMap oldSettings = m_core->getSettings("Server");
// SettingsDialog* settingsDialog = new SettingsDialog(settingMap, this);
SettingsDialog* settingsDialog = new SettingsDialog(this);
settingsDialog->createContent();
settingsDialog->fillContent(oldSettings);
int returnCode = settingsDialog->exec();
if (returnCode == QDialog::Accepted) {
qDebug() << "Settings dialog accepted, writing settings...";
const QVariantMap settings = settingsDialog->getSettings();
m_core->applySettings(settings, "Server");
// TODO use signal-slot connection Core::syncServerSetupChanged(bool enabled) ->
// MainWindow::onSyncServerSetupChanged(bool enabled)
// enableDisableServerActions();
} else {
qDebug() << "Settings dialog rejected";
}
delete settingsDialog;
}
void MainWindow::setupModelViews() {
// m_tableModel = m_core->getModel();
// ui->tableView->setModel(m_tableModel.get());
m_proxyModel = m_core->getSortFilterModel();
// TODO iterate over INT_ROLES and DOUBLE_ROLES to set spinbox delegate
/// setting number delegates to combo boxes
SpinboxDelegate* spinboxDelegate = new SpinboxDelegate(this);
const int amountColumn = GET_COLUMN_FOR_ROLE(AmountRole);
ui->tableView->setItemDelegateForColumn(amountColumn, spinboxDelegate);
const int factorColumn = GET_COLUMN_FOR_ROLE(FactorRole);
ui->tableView->setItemDelegateForColumn(factorColumn, spinboxDelegate);
// TODO iterate over TYPE_ROLES to set combobox delegate
/// setting type delegates to combo boxes
const int typeColumn = GET_COLUMN_FOR_ROLE(TypeRole);
ComboboxDelegate* shareTypeDelegate = new ComboboxDelegate(TYPES, this);
ui->tableView->setItemDelegateForColumn(typeColumn, shareTypeDelegate);
ui->tableView->setModel((QAbstractItemModel*)m_proxyModel.get());
ui->tableView->setSortingEnabled(true);
}
void MainWindow::createActions() {
// TODO add generic menu actions (file/new, edit/cut, ...)
createFileActions();
createUndoActions();
createEditActions();
createServerActions();
createToolsActions();
}
void MainWindow::createFileActions() {
m_newFileAct = make_unique<QAction>(tr("&New File"), this);
m_newFileAct->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_N));
m_newFileAct->setStatusTip(tr("Create a new file"));
// connect(m_newAct, &QAction::triggered, this, &MainWindow::newFile);
m_newFileAct->setEnabled(false);
ui->menu_File->addAction(m_newFileAct.get());
m_openAct = make_unique<QAction>(tr("&Open..."), this);
m_openAct->setShortcuts(QKeySequence::Open);
m_openAct->setStatusTip(tr("Open an existing file"));
// connect(m_openAct, &QAction::triggered, this, &MainWindow::open);
m_openAct->setEnabled(false);
ui->menu_File->addAction(m_openAct.get());
m_saveAct = make_unique<QAction>(tr("&Save"), this);
m_saveAct->setShortcuts(QKeySequence::Save);
m_saveAct->setStatusTip(tr("Save the document to disk"));
connect(m_saveAct.get(), &QAction::triggered, this, &MainWindow::saveItems);
ui->menu_File->addAction(m_saveAct.get());
ui->menu_File->addSeparator();
m_importAct = make_unique<QAction>(tr("&Import CSV..."), this);
m_importAct->setStatusTip(tr("Import an existing CSV file"));
connect(m_importAct.get(), &QAction::triggered, this, &MainWindow::importCSV);
ui->menu_File->addAction(m_importAct.get());
m_exportAct = make_unique<QAction>(tr("&Export CSV..."), this);
m_exportAct->setStatusTip(tr("Export content to a CSV document to disk"));
connect(m_exportAct.get(), &QAction::triggered, this, &MainWindow::exportCSV);
ui->menu_File->addAction(m_exportAct.get());
ui->menu_File->addSeparator();
m_printAct = make_unique<QAction>(tr("&Print..."), this);
m_printAct->setShortcuts(QKeySequence::Print);
m_printAct->setStatusTip(tr("Print the document"));
// connect(m_printAct, &QAction::triggered, this, &MainWindow::print);
m_printAct->setEnabled(false);
ui->menu_File->addAction(m_printAct.get());
ui->menu_File->addSeparator();
m_exitAct = make_unique<QAction>(tr("E&xit"), this);
m_exitAct->setShortcuts(QKeySequence::Quit);
m_exitAct->setStatusTip(tr("Exit the application"));
connect(m_exitAct.get(), &QAction::triggered, this, &QWidget::close);
ui->menu_File->addAction(m_exitAct.get());
}
void MainWindow::createUndoActions() {
if (!m_core) {
qCritical() << "No core instance set, aborting...";
return;
}
m_modelUndoStack = m_core->getModelUndoStack();
connect(m_modelUndoStack, &QUndoStack::cleanChanged, this, &MainWindow::onCleanStateChanged);
m_undoAct.reset(m_modelUndoStack->createUndoAction(this, tr("&Undo")));
m_undoAct->setShortcuts(QKeySequence::Undo);
m_undoAct->setStatusTip(tr("Undo the last operation"));
ui->menu_Edit->addAction(m_undoAct.get());
m_redoAct.reset(m_modelUndoStack->createRedoAction(this, tr("&Redo")));
m_redoAct->setShortcuts(QKeySequence::Redo);
m_redoAct->setStatusTip(tr("Redo the last operation"));
ui->menu_Edit->addAction(m_redoAct.get());
m_modelUndoView = make_unique<QUndoView>(m_modelUndoStack);
m_modelUndoView->setWindowTitle(tr("Undo list"));
m_modelUndoView->setAttribute(Qt::WA_QuitOnClose, false);
m_modelUndoView->setMinimumSize(450, 600);
m_showModelUndoViewAct = make_unique<QAction>(tr("Show undo/redo window"), this);
m_showModelUndoViewAct->setStatusTip(tr("Opens a window displaying the items on the undo stack"));
// TODO showUndoViewAction should be checkable
// m_showUndoViewAction->setCheckable(true);
// connect(m_showUndoViewAction, &QAction::toggled, this, &MainWindow::onShowUndoViewToggled);
connect(m_showModelUndoViewAct.get(), &QAction::triggered, this,
&MainWindow::onShowUndoViewToggled);
ui->menu_View->addAction(m_showModelUndoViewAct.get());
}
void MainWindow::createEditActions() {
m_cutAct = make_unique<QAction>(tr("Cu&t"), this);
m_cutAct->setShortcuts(QKeySequence::Cut);
m_cutAct->setStatusTip(
tr("Cut the current selection's contents to the "
"clipboard"));
// connect(m_cutAct, &QAction::triggered, this, &MainWindow::cut);
m_cutAct->setEnabled(false);
ui->menu_Edit->addAction(m_cutAct.get());
m_copyAct = make_unique<QAction>(tr("&Copy"), this);
m_copyAct->setShortcuts(QKeySequence::Copy);
m_copyAct->setStatusTip(
tr("Copy the current selection's contents to the "
"clipboard"));
// connect(m_copyAct, &QAction::triggered, this, &MainWindow::copy);
m_copyAct->setEnabled(false);
ui->menu_Edit->addAction(m_copyAct.get());
m_pasteAct = make_unique<QAction>(tr("&Paste"), this);
m_pasteAct->setShortcuts(QKeySequence::Paste);
m_pasteAct->setStatusTip(
tr("Paste the clipboard's contents into the current "
"selection"));
// connect(m_pasteAct, &QAction::triggered, this, &MainWindow::paste);
m_pasteAct->setEnabled(false);
ui->menu_Edit->addAction(m_pasteAct.get());
ui->menu_Edit->addSeparator();
m_openNewItemDialogAct = make_unique<QAction>(tr("&New item"), this);
m_openNewItemDialogAct->setShortcut(QKeySequence::New);
m_openNewItemDialogAct->setStatusTip(tr("Opens a dialog to add a new item"));
connect(m_openNewItemDialogAct.get(), &QAction::triggered, this, &MainWindow::openNewItemDialog);
ui->menu_Edit->addAction(m_openNewItemDialogAct.get());
m_openEditItemDialogAct = make_unique<QAction>(tr("&Edit item"), this);
m_openEditItemDialogAct->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_E));
m_openEditItemDialogAct->setStatusTip(tr("Opens a edit dialog for the current item"));
connect(m_openEditItemDialogAct.get(), &QAction::triggered, this,
&MainWindow::openEditItemDialog);
ui->menu_Edit->addAction(m_openEditItemDialogAct.get());
m_deleteItemAct = make_unique<QAction>(tr("&Delete item(s)"), this);
m_deleteItemAct->setShortcut(QKeySequence::Delete);
m_deleteItemAct->setStatusTip(tr("Delete currently selected item(s)"));
// connect(m_deleteItemAct.get(), &QAction::triggered, this, &MainWindow::deleteSelectedtItems);
connect(m_deleteItemAct.get(), &QAction::triggered, this, &MainWindow::deleteCurrentItem);
ui->menu_Edit->addAction(m_deleteItemAct.get());
ui->menu_Edit->addSeparator();
m_findItemAct = make_unique<QAction>(tr("&Find item(s)"), this);
m_findItemAct->setShortcuts(QKeySequence::Find);
m_findItemAct->setStatusTip(tr("Opens a dialog to find item(s) by containing text"));
connect(m_findItemAct.get(), &QAction::triggered, this, &MainWindow::findItems);
ui->menu_Edit->addAction(m_findItemAct.get());
}
void MainWindow::createServerActions() {
m_fetchItemsAct = make_unique<QAction>(tr("&Fetch item(s)"), this);
m_fetchItemsAct->setShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Down));
m_fetchItemsAct->setStatusTip(tr("Fetches all item on configured server"));
connect(m_fetchItemsAct.get(), &QAction::triggered, this, &MainWindow::fetchItems);
ui->menu_Server->addAction(m_fetchItemsAct.get());
m_postItemsAct = make_unique<QAction>(tr("&Post item(s)"), this);
m_postItemsAct->setShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Up));
// m_postItemsAct->setStatusTip(tr("Posts the selected items on configured server"));
m_postItemsAct->setStatusTip(tr("Posts the current item on configured server"));
connect(m_postItemsAct.get(), &QAction::triggered, this, &MainWindow::postItems);
ui->menu_Server->addAction(m_postItemsAct.get());
m_deleteItemsAct = make_unique<QAction>(tr("&Delete item"), this);
m_deleteItemsAct->setShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Backspace));
// m_deleteItemsAct->setStatusTip(tr("Deletes the selected items on configured server"));
m_deleteItemsAct->setStatusTip(tr("Deletes the current item on configured server"));
connect(m_deleteItemsAct.get(), &QAction::triggered, this, &MainWindow::deleteItem);
ui->menu_Server->addAction(m_deleteItemsAct.get());
}
void MainWindow::createToolsActions() {
QMenu* menu = ui->menu_Tools;
QAction* settingsAct = menu->addAction(tr("&Settings"), this, &MainWindow::execSettingsDialog);
settingsAct->setStatusTip(tr("Opens a dialog to configure applications settings."));
}
void MainWindow::createHelpMenu() {
QMenu* helpMenu = ui->menu_Help;
helpMenu->addSeparator();
QAction* aboutAct = helpMenu->addAction(tr("&About"), this, &MainWindow::onAboutClicked);
aboutAct->setStatusTip(tr("Show the application's About box"));
QAction* aboutQtAct = helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt);
aboutQtAct->setStatusTip(tr("Show the Qt library's About box"));
}
void MainWindow::createGuiDialogs() {
/// new item dialog
m_newItemDialog = make_unique<NewItemDialog>(this);
m_newItemDialog->createContent();
connect(m_newItemDialog.get(), &NewItemDialog::addItems, m_proxyModel.get(),
&GeneralSortFilterModel::appendItems);
/// edit item dialog
m_editItemDialog = make_unique<EditItemDialog>(ui->tableView, this);
m_editItemDialog->createContent();
}