From 648d48276603774d618b19e37b0796e8bf8896b9 Mon Sep 17 00:00:00 2001 From: Martchus Date: Mon, 29 Aug 2016 20:21:24 +0200 Subject: [PATCH] Uniform line endings (dos2unix) --- cli/cli.h | 8 +- gui/mainwindow.cpp | 2466 +++++++++++++++---------------- gui/mainwindow.h | 276 ++-- gui/passwordgeneratordialog.cpp | 344 ++--- gui/passwordgeneratordialog.h | 76 +- main.cpp | 210 +-- 6 files changed, 1688 insertions(+), 1692 deletions(-) diff --git a/cli/cli.h b/cli/cli.h index c2c546c..73d8849 100644 --- a/cli/cli.h +++ b/cli/cli.h @@ -3,12 +3,10 @@ #include -#include - #if defined(PLATFORM_UNIX) -#include +# include #elif defined(PLATFORM_WINDOWS) -#include +# include #endif #include @@ -17,9 +15,7 @@ #include namespace ApplicationUtilities { - typedef std::vector StringVector; - } namespace Io { diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index ea16100..8e7964e 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -1,1233 +1,1233 @@ -#include "./mainwindow.h" -#include "./fielddelegate.h" - -#include "../model/fieldmodel.h" -#include "../model/entrymodel.h" -#include "../model/entryfiltermodel.h" - -#include "ui_mainwindow.h" - -#include "resources/config.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace std; -using namespace IoUtilities; -using namespace Io; -using namespace Dialogs; -using namespace MiscUtils; - -namespace QtGui { - -/*! - * \namespace QtGui - * \brief Contains all GUI related classes and helper functions. - */ - -/*! - * \namespace QtGui::Ui - * \brief Contains all classes generated by the Qt User Interface Compiler (uic). - */ - -/*! - * \class MainWindow - * \brief The MainWindow class provides the main window of the widgets-based GUI of the application. - */ - -/*! - * \brief Copies the selected cells to the clipboard. - */ -void MainWindow::copyFields() -{ - copyFieldsForXMilliSeconds(-1); -} - -/*! - * \brief Inserts fields from the clipboard. - */ -void MainWindow::insertFieldsFromClipboard() -{ - insertFields(QApplication::clipboard()->text()); -} - -/*! - * \brief Clears the clipboard. - */ -void MainWindow::clearClipboard() -{ - QApplication::clipboard()->clear(); -} - -/*! - * \brief Flags the current file as being changed since the last save. - */ -void MainWindow::setSomethingChanged() -{ - setSomethingChanged(true); -} - -/*! - * \brief Sets whether the current file has been changed since the last save. - */ -void MainWindow::setSomethingChanged(bool somethingChanged) -{ - if(m_somethingChanged != somethingChanged) { - m_somethingChanged = somethingChanged; - updateWindowTitle(); - } -} - -/*! - * \brief Constructs a new main window. - */ -MainWindow::MainWindow(QSettings &settings, Dialogs::QtSettings *qtSettings, QWidget *parent) : - QMainWindow(parent), - m_ui(new Ui::MainWindow), - m_clearClipboardTimer(0), - m_aboutDlg(nullptr), - m_settings(settings), - m_qtSettings(qtSettings), - m_settingsDlg(nullptr) -{ - // setup ui - m_ui->setupUi(this); -#ifdef Q_OS_WIN32 - setStyleSheet(QStringLiteral("%1 #splitter QWidget { background-color: palette(base); color: palette(text); } #splitter QWidget *, #splitter QWidget * { background-color: none; } #leftWidget { border-right: 1px solid %2; }").arg(dialogStyle(), windowFrameColor().name())); -#endif - // set default values - setSomethingChanged(false); - m_dontUpdateSelection = false; - updateUiStatus(); - // load settings - settings.beginGroup(QStringLiteral("mainwindow")); - // init recent menu manager - m_recentMgr = new RecentMenuManager(m_ui->menuRecent, this); - m_recentMgr->restore(settings.value(QStringLiteral("recententries"), QStringList()).toStringList()); - connect(m_recentMgr, &RecentMenuManager::fileSelected, this, &MainWindow::openFile); - // set position and size - restoreGeometry(settings.value(QStringLiteral("geometry")).toByteArray()); - restoreState(settings.value(QStringLiteral("state")).toByteArray()); - // setup undo stack and related actions - m_undoStack = new QUndoStack(this); - m_undoView = nullptr; - m_ui->actionUndo->setShortcuts(QKeySequence::Undo); - m_ui->actionRedo->setShortcuts(QKeySequence::Redo); - // setup models, tree and table view - m_ui->treeView->setModel(m_entryFilterModel = new EntryFilterModel(this)); - m_ui->tableView->setModel(m_fieldModel = new FieldModel(m_undoStack, this)); - m_ui->tableView->setItemDelegate(new FieldDelegate(this)); - m_entryFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); - m_entryFilterModel->setSourceModel(m_entryModel = new EntryModel(m_undoStack, this)); -#ifdef Q_OS_WIN32 - m_ui->treeView->setFrameShape(QFrame::NoFrame); - m_ui->tableView->setFrameShape(QFrame::NoFrame); -#else - m_ui->treeView->setFrameShape(QFrame::StyledPanel); - m_ui->tableView->setFrameShape(QFrame::StyledPanel); -#endif - m_ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - // splitter sizes - m_ui->splitter->setSizes(QList() << 100 << 800); - // password visibility group - auto *passwordVisibilityGroup = new QActionGroup(this); - passwordVisibilityGroup->addAction(m_ui->actionShowAlways); - passwordVisibilityGroup->addAction(m_ui->actionShowOnlyWhenEditing); - passwordVisibilityGroup->addAction(m_ui->actionHideAlways); - QString pwVisibility(settings.value(QStringLiteral("pwvisibility")).toString()); - QAction *pwVisibilityAction; - if(pwVisibility == QStringLiteral("always")) { - pwVisibilityAction = m_ui->actionShowAlways; - } else if(pwVisibility == QStringLiteral("hidden")) { - pwVisibilityAction = m_ui->actionHideAlways; - } else { - pwVisibilityAction = m_ui->actionShowOnlyWhenEditing; - } - pwVisibilityAction->setChecked(true); - setPasswordVisibility(pwVisibilityAction); - // connect signals and slots - // -> file related actions - connect(m_ui->actionSave, &QAction::triggered, this, &MainWindow::saveFile); - connect(m_ui->actionExport, &QAction::triggered, this, &MainWindow::exportFile); - connect(m_ui->actionShowContainingDirectory, &QAction::triggered, this, &MainWindow::showContainingDirectory); - connect(m_ui->actionClose, &QAction::triggered, this, &MainWindow::closeFile); - connect(m_ui->actionCreate, &QAction::triggered, this, static_cast(&MainWindow::createFile)); - connect(m_ui->actionQuit, &QAction::triggered, this, &MainWindow::close); - connect(m_ui->actionChangepassword, &QAction::triggered, this, &MainWindow::changePassword); - // -> showing dialogs - connect(m_ui->actionPasswordGenerator, &QAction::triggered, this, &MainWindow::showPassowrdGeneratorDialog); - connect(m_ui->actionAbout, &QAction::triggered, this, &MainWindow::showAboutDialog); - connect(m_ui->actionOpen, &QAction::triggered, this, &MainWindow::showOpenFileDialog); - connect(m_ui->actionSaveAs, &QAction::triggered, this, &MainWindow::showSaveFileDialog); - connect(m_ui->actionQtSettings, &QAction::triggered, this, &MainWindow::showSettingsDialog); - // -> add/remove account - connect(m_ui->actionAddAccount, &QAction::triggered, this, &MainWindow::addAccount); - connect(m_ui->actionAddCategory, &QAction::triggered, this, &MainWindow::addCategory); - connect(m_ui->actionRemoveRows, &QAction::triggered, this, &MainWindow::removeEntry); - // -> insert/remove fields - connect(m_ui->actionInsertRow, &QAction::triggered, this, &MainWindow::insertRow); - connect(m_ui->actionRemoveAccount, &QAction::triggered, this, &MainWindow::removeRows); - // -> undo/redo - connect(m_ui->actionUndo, &QAction::triggered, m_undoStack, &QUndoStack::undo); - connect(m_ui->actionRedo, &QAction::triggered, m_undoStack, &QUndoStack::redo); - connect(m_undoStack, &QUndoStack::canUndoChanged, m_ui->actionUndo, &QAction::setEnabled); - connect(m_undoStack, &QUndoStack::canRedoChanged, m_ui->actionRedo, &QAction::setEnabled); - // -> view - connect(passwordVisibilityGroup, &QActionGroup::triggered, this, &MainWindow::setPasswordVisibility); - connect(m_ui->actionShowUndoStack, &QAction::triggered, this, &MainWindow::showUndoView); - // -> models - connect(m_ui->treeView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &MainWindow::accountSelected); - connect(m_entryModel, &QAbstractItemModel::dataChanged, this, static_cast(&MainWindow::setSomethingChanged)); - connect(m_fieldModel, &QAbstractItemModel::dataChanged, this, static_cast(&MainWindow::setSomethingChanged)); - // -> context menus - connect(m_ui->treeView, &QTableView::customContextMenuRequested, this, &MainWindow::showTreeViewContextMenu); - connect(m_ui->tableView, &QTableView::customContextMenuRequested, this, &MainWindow::showTableViewContextMenu); - // -> filter - //connect(m_ui->accountFilterLineEdit, &QLineEdit::textChanged, m_entryFilterModel, &QSortFilterProxyModel::setFilterFixedString); - connect(m_ui->accountFilterLineEdit, &QLineEdit::textChanged, this, &MainWindow::applyFilter); - // setup other controls - m_ui->actionAlwaysCreateBackup->setChecked(settings.value(QStringLiteral("alwayscreatebackup"), false).toBool()); - m_ui->accountFilterLineEdit->setText(settings.value(QStringLiteral("accountfilter"), QString()).toString()); - m_ui->centralWidget->installEventFilter(this); - settings.endGroup(); -} - -/*! - * \brief Destroys the main window. - */ -MainWindow::~MainWindow() -{} - -bool MainWindow::eventFilter(QObject *obj, QEvent *event) -{ - if(obj == m_undoView) { - switch(event->type()) { - case QEvent::Hide: - m_ui->actionShowUndoStack->setChecked(false); - break; - default: - ; - } - } else if(obj == m_ui->centralWidget) { - switch(event->type()) { - case QEvent::DragEnter: - case QEvent::Drop: - if(QDropEvent *dropEvent = static_cast(event)) { - QString data; - const QMimeData *mimeData = dropEvent->mimeData(); - if(mimeData->hasUrls()) { - const QUrl url = mimeData->urls().front(); - if(url.scheme() == QLatin1String("file")) { - data = url.path(); - } - } else if(mimeData->hasText()) { - data = mimeData->text(); - } - if(!data.isEmpty()) { - event->accept(); - if(event->type() == QEvent::Drop) { - openFile(data); - } - } - return true; - } - default: - ; - } - } - return QMainWindow::eventFilter(obj, event); -} - -void MainWindow::closeEvent(QCloseEvent *event) -{ - // ask if file is opened - if(m_file.hasRootEntry()) { - if(!closeFile()) { - event->ignore(); - return; - } - } - // close undow view - if(m_undoView) { - m_undoView->close(); - } - // save settings - m_settings.beginGroup(QStringLiteral("mainwindow")); - m_settings.setValue(QStringLiteral("geometry"), saveGeometry()); - m_settings.setValue(QStringLiteral("state"), saveState()); - m_settings.setValue(QStringLiteral("recententries"), m_recentMgr->save()); - m_settings.setValue(QStringLiteral("accountfilter"), m_ui->accountFilterLineEdit->text()); - m_settings.setValue(QStringLiteral("alwayscreatebackup"), m_ui->actionAlwaysCreateBackup->isChecked()); - QString pwVisibility; - if(m_ui->actionShowAlways->isChecked()) { - pwVisibility = QStringLiteral("always"); - } else if(m_ui->actionHideAlways->isChecked()) { - pwVisibility = QStringLiteral("hidden"); - } else { - pwVisibility = QStringLiteral("editing"); - } - m_settings.setValue(QStringLiteral("pwvisibility"), pwVisibility); - m_settings.endGroup(); -} - -void MainWindow::timerEvent(QTimerEvent *event) -{ - if(event->timerId() == m_clearClipboardTimer) { - clearClipboard(); - m_clearClipboardTimer = 0; - } -} - -/*! - * \brief Shows the settings dialog (which currently only consists of the Qt settings category). - */ -void MainWindow::showSettingsDialog() -{ - if(!m_settingsDlg) { - m_settingsDlg = new SettingsDialog(this); - if(m_qtSettings) { - m_settingsDlg->setWindowTitle(tr("Qt settings")); - m_settingsDlg->setSingleCategory(m_qtSettings->category()); - } - //connect(m_settingsDlg, &SettingsDialog::applied, this, &MainWindow::settingsAccepted); - } - if(m_settingsDlg->isHidden()) { - m_settingsDlg->showNormal(); - } else { - m_settingsDlg->activateWindow(); - } -} - -/*! - * \brief Shows the about dialog. - */ -void MainWindow::showAboutDialog() -{ - if(!m_aboutDlg) { - m_aboutDlg = new AboutDialog(this, tr("A simple password store using AES-256-CBC encryption via OpenSSL."), QImage(":/icons/hicolor/128x128/apps/passwordmanager.png")); - } - m_aboutDlg->show(); -} - -/*! - * \brief Shows the password generator dialog. - */ -void MainWindow::showPassowrdGeneratorDialog() -{ - PasswordGeneratorDialog* pwgDialog = new PasswordGeneratorDialog(this); - pwgDialog->show(); -} - -/*! - * \brief Shows the open file dialog and opens the selected file. - */ -void MainWindow::showOpenFileDialog() -{ - if(m_file.hasRootEntry() && !closeFile()) { - return; - } - QString fileName = QFileDialog::getOpenFileName(this, tr("Select a password list"), QString(), tr("Password Manager files (*.pwmgr);;All files (*)")); - if(!fileName.isEmpty()) { - openFile(fileName); - } -} - -/*! - * \brief Shows the save file dialog and saves the file at the selected location. - */ -void MainWindow::showSaveFileDialog() -{ - if(showNoFileOpened()) { - return; - } - if(askForCreatingFile()) { - saveFile(); - } -} - -/*! - * \brief Shows the undo view. - */ -void MainWindow::showUndoView() -{ - if(m_ui->actionShowUndoStack->isChecked()) { - if(!m_undoView) { - m_undoView = new QUndoView(m_undoStack); - m_undoView->setWindowTitle(tr("Undo stack")); - m_undoView->setWindowFlags(Qt::Tool); - m_undoView->setAttribute(Qt::WA_QuitOnClose); - m_undoView->setWindowIcon(QIcon::fromTheme(QStringLiteral("edit-undo"))); - m_undoView->installEventFilter(this); - } - m_undoView->show(); - } else if(m_undoView) { - m_undoView->hide(); - } -} - -/*! - * \brief Opens a file with the specified \a path and updates all widgets to show its contents. - * \returns Returns true on success; otherwise false - */ -bool MainWindow::openFile(const QString &path) -{ - using namespace Dialogs; - // close previous file - if(m_file.hasRootEntry() && !closeFile()) { - return false; - } - // set path and open file - m_file.setPath(path.toStdString()); - try { - m_file.open(); - } catch (...) { - const QString errmsg = tr("An IO error occured when opening the specified file \"%1\".\n\n(%2)").arg(path, QString::fromLocal8Bit(catchIoFailure())); - m_ui->statusBar->showMessage(errmsg, 5000); - QMessageBox::critical(this, QApplication::applicationName(), errmsg); - return false; - } - // warn before loading a very big file - if(m_file.size() > 10485760) { - if(QMessageBox::warning(this, QApplication::applicationName(), tr("The file you want to load seems to be very big. Do you really want to open it?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { - m_file.clear(); - return false; - } - } - // ask for a password if required - if(m_file.isEncryptionUsed()) { - EnterPasswordDialog pwDlg(this); - pwDlg.setWindowTitle(tr("Opening file") + QStringLiteral(" - " APP_NAME)); - pwDlg.setInstruction(tr("Enter the password to open the file \"%1\"").arg(path)); - pwDlg.setPasswordRequired(true); - switch(pwDlg.exec()) { - case QDialog::Accepted: - if(pwDlg.password().isEmpty()) { - m_ui->statusBar->showMessage(tr("A password is needed to open the file."), 5000); - QMessageBox::warning(this, QApplication::applicationName(), tr("A password is needed to open the file.")); - m_file.clear(); - return false; - } else { - break; - } - case QDialog::Rejected: - m_file.clear(); - return false; - default: - ; - } - m_file.setPassword(pwDlg.password().toStdString()); - } - // load the contents of the file - QString msg; - try { - m_file.load(); - } catch(const CryptoException &e) { - msg = tr("The file couldn't be decrypted.\nOpenSSL error queue: %1").arg(QString::fromLocal8Bit(e.what())); - } catch (...) { - try { - msg = QString::fromLocal8Bit(catchIoFailure()); - } catch(const runtime_error &e) { - msg = tr("Unable to parse the file. %1").arg(QString::fromLocal8Bit(e.what())); - } - } - // show a message in the error case - if(!msg.isEmpty()) { - m_file.clear(); - m_ui->statusBar->showMessage(msg, 5000); - if(QMessageBox::critical(this, QApplication::applicationName(), msg, - QMessageBox::Cancel, QMessageBox::Retry) == QMessageBox::Retry) { - return openFile(path); // retry - } else { - return false; - } - } else { - // show contents - return showFile(); - } -} - -/*! - * \brief Creates a new file. - * \returns Returns true on success; otherwise false - */ -bool MainWindow::createFile() -{ - // close previous file - if(m_file.hasRootEntry() && !closeFile()) { - return false; - } - m_file.generateRootEntry(); - return showFile(); -} - -/*! - * \brief Creates a new file with the specified \a path. - * \returns Returns true on success; otherwise false - */ -void MainWindow::createFile(const QString &path) -{ - createFile(path, QString()); -} - -/*! - * \brief Creates a new file with the specified \a path and \a password. - * \returns Returns true on success; otherwise false - */ -void MainWindow::createFile(const QString &path, const QString &password) -{ - // close previous file - if(m_file.hasRootEntry() && !closeFile()) { - return; - } - // set path and password - m_file.setPath(path.toStdString()); - m_file.setPassword(password.toStdString()); - // create the file and show it - try { - m_file.create(); - } catch (...) { - catchIoFailure(); - QMessageBox::critical(this, QApplication::applicationName(), tr("The file %1 couldn't be created.").arg(path)); - return; - } - m_file.generateRootEntry(); - showFile(); -} - -/*! - * \brief Shows the previously opened file. Called within openFile() and createFile(). - * \returns Returns true on success; otherwise false - */ -bool MainWindow::showFile() -{ - m_fieldModel->reset(); - m_entryModel->setRootEntry(m_file.rootEntry()); - applyDefaultExpanding(QModelIndex()); - if(m_file.path().empty()) { - m_ui->statusBar->showMessage(tr("A new password list has been created."), 5000); - } else { - m_recentMgr->addEntry(QString::fromStdString(m_file.path())); - m_ui->statusBar->showMessage(tr("The password list has been load."), 5000); - } - updateWindowTitle(); - updateUiStatus(); - applyFilter(m_ui->accountFilterLineEdit->text()); - setSomethingChanged(false); - return true; -} - -/*! - * \brief Updates the status of the UI elements. - */ -void MainWindow::updateUiStatus() -{ - bool fileOpened = m_file.hasRootEntry(); - m_ui->actionCreate->setEnabled(true); - m_ui->actionOpen->setEnabled(true); - m_ui->actionSave->setEnabled(fileOpened); - m_ui->actionSaveAs->setEnabled(fileOpened); - m_ui->actionExport->setEnabled(fileOpened); - m_ui->actionShowContainingDirectory->setEnabled(fileOpened); - m_ui->actionClose->setEnabled(fileOpened); - m_ui->actionChangepassword->setEnabled(fileOpened); - m_ui->menuEdit->setEnabled(fileOpened); - m_ui->accountFilterLineEdit->setEnabled(true); -} - -/*! - * \brief Updates the window title. - */ -void MainWindow::updateWindowTitle() -{ - Dialogs::DocumentStatus docStatus; - if(m_file.hasRootEntry()) { - if(m_somethingChanged) { - docStatus = Dialogs::DocumentStatus::Unsaved; - } else { - docStatus = Dialogs::DocumentStatus::Saved; - } - } else { - docStatus = Dialogs::DocumentStatus::NoDocument; - } - setWindowTitle(Dialogs::generateWindowTitle(docStatus, QString::fromStdString(m_file.path()))); -} - -void MainWindow::applyDefaultExpanding(const QModelIndex &parent) -{ - for(int row = 0, rows = m_entryFilterModel->rowCount(parent); row < rows; ++row) { - QModelIndex index = m_entryFilterModel->index(row, 0, parent); - if(!index.isValid()) { - return; - } - applyDefaultExpanding(index); - m_ui->treeView->setExpanded(index, m_entryFilterModel->data(index, DefaultExpandedRole).toBool()); - } -} - -/*! - * \brief Returns a string with the values of all selected fields. - * \remarks Columns are sparated with \t, rows with \n. - */ -QString MainWindow::selectedFieldsString() const -{ - QModelIndexList selectedIndexes = m_ui->tableView->selectionModel()->selectedIndexes(); - QString text; - if(!selectedIndexes.isEmpty()) { - if(selectedIndexes.size() > 1) { - int maxRow = m_fieldModel->rowCount() - 1; - int firstRow = maxRow, lastRow = 0; - int maxCol = m_fieldModel->columnCount() - 1; - int firstCol = maxCol, lastCol = 0; - for(const QModelIndex &index : selectedIndexes) { - if(index.row() < firstRow) { - firstRow = index.row(); - } - if(index.row() > lastRow) { - lastRow = index.row(); - } - if(index.column() < firstCol) { - firstCol = index.column(); - } - if(index.column() > lastCol) { - lastCol = index.column(); - } - } - for(int row = firstRow; row <= lastRow; ++row) { - for(int col = firstCol; col <= lastCol; ++col) { - QModelIndex index = m_fieldModel->index(row, col); - if(selectedIndexes.contains(index)) { - text.append(index.data(Qt::EditRole).toString()); - } - text.append('\t'); - } - text.append('\n'); - } - } else { - text = selectedIndexes.front().data(Qt::EditRole).toString(); - } - } - return text; -} - -/*! - * \brief Inserts fields from the specified \a fieldsString. - */ -void MainWindow::insertFields(const QString &fieldsString) -{ - QModelIndexList selectedIndexes = m_ui->tableView->selectionModel()->selectedIndexes(); - if(selectedIndexes.size() == 1) { - int rows = m_fieldModel->rowCount(), cols = m_fieldModel->columnCount(); - int row = selectedIndexes.front().row(); - int initCol = selectedIndexes.front().column(); - assert(row < rows); - QStringList rowValues = fieldsString.split('\n'); - if(rowValues.back().isEmpty()) { - rowValues.pop_back(); - } - m_fieldModel->insertRows(row, rowValues.size(), QModelIndex()); - for(const QString &rowValue : rowValues) { - int col = initCol; - for(const QString &cellValue : rowValue.split('\t')) { - if(col < cols) { - m_fieldModel->setData(m_fieldModel->index(row, col), cellValue, Qt::EditRole); - ++col; - } else { - break; - } - } - ++row; - } - } else { - QMessageBox::warning(this, QApplication::applicationName(), tr("Exactly one fields needs to be selected (top-left corner for insertion).")); - } -} - -/*! - * \brief Asks the user to create a new file. - */ -bool MainWindow::askForCreatingFile() -{ - if(showNoFileOpened()) { - return false; - } - - QString fileName = - QFileDialog::getSaveFileName( - this, - tr("Select where you want to save the password list"), - QString(), - tr("Password Manager files (*.pwmgr);;All files (*)")); - if(fileName.isEmpty()) { - m_ui->statusBar->showMessage(tr("The file was not be saved."), 7000); - return false; - } else { - m_file.setPath(fileName.toStdString()); - try { - m_file.create(); - updateWindowTitle(); - } catch (...) { - QMessageBox::critical(this, QApplication::applicationName(), QString::fromLocal8Bit(catchIoFailure())); - return false; - } - } - return true; -} - -/*! - * \brief Shows an warning if no file is opened. - * \retruns Returns whether the warning has been shown. - */ -bool MainWindow::showNoFileOpened() -{ - if(!m_file.hasRootEntry()) { - QMessageBox::warning(this, QApplication::applicationName(), tr("There is no password list opened.")); - return true; - } - return false; -} - -/*! - * \brief Shows an warning if no account is selected. - * \retruns Returns whether the warning has been shown. - */ -bool MainWindow::showNoAccount() -{ - if(!m_fieldModel->fields()) { - QMessageBox::warning(this, QApplication::applicationName(), tr("There's no account selected.")); - return true; - } - return false; -} - -/*! - * \brief Closes the currently opened file. Asks the user to save changes if the file has been modified. - * \returns Returns whether the file has been closed. - */ -bool MainWindow::closeFile() -{ - if(showNoFileOpened()) { - return false; - } - if(m_somethingChanged) { - QMessageBox msg(this); - msg.setText(tr("The password file has been modified.")); - msg.setInformativeText(tr("Do you want to save the changes before closing?")); - msg.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); - msg.setDefaultButton(QMessageBox::Save); - msg.setIcon(QMessageBox::Warning); - switch (msg.exec()) { - case QMessageBox::Save: - if(saveFile()) { - break; - } else { - return false; - } - case QMessageBox::Cancel: - return false; - default: - ; - } - } - m_fieldModel->reset(); - m_entryModel->reset(); - m_file.clear(); - m_ui->statusBar->showMessage(tr("The password list has been closed.")); - updateWindowTitle(); - updateUiStatus(); - setSomethingChanged(false); - return true; -} - -/*! - * \brief Saves the currently opened file. - * \returns Returns whether the file could be saved. - */ -bool MainWindow::saveFile() -{ - using namespace Dialogs; - if(showNoFileOpened()) { - return false; - } - // create backup - if(!m_file.path().empty() && QFile::exists(QString::fromStdString(m_file.path()))) { - if(m_ui->actionAlwaysCreateBackup->isChecked()) { - try { - m_file.doBackup(); - } catch(...) { - QString message(tr("The backup file couldn't be created because in IO error occured: %1").arg(QString::fromLocal8Bit(catchIoFailure()))); - QMessageBox::critical(this, QApplication::applicationName(), message); - m_ui->statusBar->showMessage(message, 7000); - return false; - } - } - } else { - if(!askForCreatingFile()) { - return false; - } - } - // ask for a password if none is set - if(m_file.password()[0] == 0) { - EnterPasswordDialog pwDlg(this); - pwDlg.setWindowTitle(tr("Saving file") + QStringLiteral(" - " APP_NAME)); - pwDlg.setInstruction(tr("Enter a password to save the file")); - pwDlg.setVerificationRequired(true); - - switch(pwDlg.exec()) { - case QDialog::Accepted: - m_file.setPassword(pwDlg.password().toStdString()); - break; - default: - m_ui->statusBar->showMessage(tr("The file hasn't been saved."), 7000); - return false; - } - } - // save the file - QString msg; - try { - m_file.save(m_file.password()[0] != 0); - } catch (const CryptoException &ex) { - msg = tr("The password list couldn't be saved due to encryption failure.\nOpenSSL error queue: %1").arg(QString::fromLocal8Bit(ex.what())); - } catch(...) { - msg = QString::fromLocal8Bit(catchIoFailure()); - } - // show status - if(!msg.isEmpty()) { - m_ui->statusBar->showMessage(msg, 5000); - QMessageBox::critical(this, QApplication::applicationName(), msg); - return false; - } else { - setSomethingChanged(false); - m_recentMgr->addEntry(QString::fromStdString(m_file.path())); - m_ui->statusBar->showMessage(tr("The password list has been saved."), 5000); - return true; - } -} - -/*! - * \brief Exports the files contents to a plain text file. - */ -void MainWindow::exportFile() -{ - if(showNoFileOpened()) { - return; - } - QString targetPath = QFileDialog::getSaveFileName(this, QApplication::applicationName(), QString(), tr("Plain text document (*.txt);;All files (*.*)")); - if(!targetPath.isNull()) { - QString errmsg; - try { - m_file.exportToTextfile(targetPath.toStdString()); - } catch (...) { - errmsg = tr("The password list couldn't be exported. %1").arg(QString::fromLocal8Bit(catchIoFailure())); - } - if(errmsg.isEmpty()) { - m_ui->statusBar->showMessage(tr("The password list has been exported."), 5000); - } else { - m_ui->statusBar->showMessage(errmsg, 5000); - QMessageBox::critical(this, QApplication::applicationName(), errmsg); - } - } -} - -/*! - * \brief Shows the containing directory for the currently opened file. - */ -void MainWindow::showContainingDirectory() -{ - if(showNoFileOpened()) { - return; - } else if(m_file.path().empty()) { - QMessageBox::warning(this, QApplication::applicationName(), tr("The currently opened file hasn't been saved yet.")); - } else { - QFileInfo file(QString::fromStdString(m_file.path())); - if(file.dir().exists()) { - DesktopUtils::openLocalFileOrDir(file.dir().absolutePath()); - } - } -} - -/*! - * \brief Adds a new account entry to the selected category. - */ -void MainWindow::addAccount() -{ - addEntry(EntryType::Account); -} - -/*! - * \brief Adds a new category/node entry to the selected category. - */ -void MainWindow::addCategory() -{ - addEntry(EntryType::Node); -} - -/*! - * \brief Adds a new entry to the selected category. - * \param type Specifies the type of the entry to be created. - * \param title Specifies the title of the user prompt which will be shown to ask for the entry label. - */ -void MainWindow::addEntry(EntryType type) -{ - if(showNoFileOpened()) { - return; - } - QModelIndexList selectedIndexes = m_ui->treeView->selectionModel()->selectedRows(0); - if(selectedIndexes.size() == 1) { - QModelIndex selected = m_entryFilterModel->mapToSource(selectedIndexes.at(0)); - if(m_entryModel->isNode(selected)) { - bool result; - const QString text = QInputDialog::getText(this, type == EntryType::Account ? tr("Add account") : tr("Add category"), tr("Enter the entry name"), QLineEdit::Normal, tr("new entry"), &result); - if (result) { - if(!text.isEmpty()) { - int row = m_entryModel->rowCount(selected); - m_entryModel->setInsertType(type); - if(m_entryModel->insertRow(row, selected)) { - m_entryModel->setData(m_entryModel->index(row, 0, selected), text, Qt::DisplayRole); - setSomethingChanged(true); - } else { - QMessageBox::warning(this, QApplication::applicationName(), tr("Unable to create new entry.")); - } - } else { - QMessageBox::warning(this, QApplication::applicationName(), tr("You didn't enter text.")); - } - } - return; - } - } - QMessageBox::warning(this, QApplication::applicationName(), tr("No node element selected.")); -} - -/*! - * \brief Removes the selected entry. - */ -void MainWindow::removeEntry() -{ - if(showNoFileOpened()) { - return; - } - QModelIndexList selectedIndexes = m_ui->treeView->selectionModel()->selectedRows(0); - if(selectedIndexes.size() == 1) { - const QModelIndex selected = m_entryFilterModel->mapToSource(selectedIndexes.at(0)); - if(!m_entryModel->removeRow(selected.row(), selected.parent())) { - QMessageBox::warning(this, QApplication::applicationName(), tr("Unable to remove the entry.")); - } - } else { - QMessageBox::warning(this, QApplication::applicationName(), tr("No entry selected.")); - } -} - -/*! - * \brief Applies the entered filter. - * \remarks Called when the textChanged signal of m_ui->accountFilterLineEdit is emittet. - */ -void MainWindow::applyFilter(const QString &filterText) -{ - m_entryFilterModel->setFilterRegExp(filterText); - if(filterText.isEmpty()) { - applyDefaultExpanding(QModelIndex()); - } else { - m_ui->treeView->expandAll(); - } -} - -/*! - * \brief Called when the user \a selected an entry. - */ -void MainWindow::accountSelected(const QModelIndex &selected, const QModelIndex &) -{ - if(Entry *entry = m_entryModel->entry(m_entryFilterModel->mapToSource(selected))) { - if(entry->type() == EntryType::Account) { - m_fieldModel->setAccountEntry(static_cast(entry)); - return; - } - } - m_fieldModel->setAccountEntry(nullptr); -} - -/*! - * \brief Inserts an empty row before the selected one. - */ -void MainWindow::insertRow() -{ - if(showNoFileOpened() || showNoAccount()) { - return; - } - QModelIndexList selectedIndexes = m_ui->tableView->selectionModel()->selectedIndexes(); - if(selectedIndexes.size()) { - int row = m_fieldModel->rowCount(); - for(const QModelIndex &index : selectedIndexes) { - if(index.row() < row) { - row = index.row(); - } - } - if(row < m_fieldModel->rowCount() - 1) { - m_fieldModel->insertRow(row); - } - } else { - QMessageBox::warning(this, windowTitle(), tr("A field has to be selected since new fields are always inserted before the currently selected field.")); - } -} - -/*! - * \brief Removes the selected rows. - */ -void MainWindow::removeRows() -{ - if(showNoFileOpened() || showNoAccount()) { - return; - } - const QModelIndexList selectedIndexes = m_ui->tableView->selectionModel()->selectedIndexes(); - QList rows; - for(const QModelIndex &index : selectedIndexes) { - rows << index.row(); - } - if(rows.size()) { - for(int i = m_fieldModel->rowCount() - 1; i >= 0; --i) { - if(rows.contains(i)) { - m_fieldModel->removeRow(i); - } - } - } else { - QMessageBox::warning(this, windowTitle(), tr("No fields have been removed since there are currently no fields selected.")); - } -} - -/*! - * \brief Marks the selected field as password field. - */ -void MainWindow::markAsPasswordField() -{ - setFieldType(FieldType::Password); -} - -/*! - * \brief Marks the selected field as normal field. - */ -void MainWindow::markAsNormalField() -{ - setFieldType(FieldType::Normal); -} - -/*! - * \brief Sets the type of the selected field to the specified \a fieldType. - */ -void MainWindow::setFieldType(FieldType fieldType) -{ - if(showNoFileOpened() || showNoAccount()) { - return; - } - QModelIndexList selectedIndexes = m_ui->tableView->selectionModel()->selectedIndexes(); - if(!selectedIndexes.isEmpty()) { - const QVariant typeVariant(static_cast(fieldType)); - for(const QModelIndex &index : selectedIndexes) { - m_fieldModel->setData(index, typeVariant, FieldTypeRole); - } - } else { - QMessageBox::warning(this, windowTitle(), tr("No fields have been changed since there are currently no fields selected.")); - } -} - -/*! - * \brief Sets the password visibility of m_fieldModel depending on which action has been chosen. - * - * This private slot is connected to the triggered signal of the passwordVisibility QActionGroup. - */ -void MainWindow::setPasswordVisibility(QAction *selectedAction) -{ - if(selectedAction == m_ui->actionShowAlways) { - m_fieldModel->setPasswordVisibility(PasswordVisibility::Always); - } else if(selectedAction == m_ui->actionShowOnlyWhenEditing) { - m_fieldModel->setPasswordVisibility(PasswordVisibility::OnlyWhenEditing); - } else if(selectedAction == m_ui->actionHideAlways) { - m_fieldModel->setPasswordVisibility(PasswordVisibility::Never); - } -} - -/*! - * \brief Asks the user to change the password which will be used when calling saveFile() next time. - */ -void MainWindow::changePassword() -{ - using namespace Dialogs; - if(showNoFileOpened()) { - return; - } - EnterPasswordDialog pwDlg(this); - pwDlg.setWindowTitle(tr("Changing password") + QStringLiteral(" - " APP_NAME)); - pwDlg.setVerificationRequired(true); - switch(pwDlg.exec()) { - case QDialog::Accepted: - if(pwDlg.password().isEmpty()) { - m_file.clearPassword(); - setSomethingChanged(true); - QMessageBox::warning(this, QApplication::applicationName(), tr("You didn't enter a password. No encryption will be used when saving the file next time.")); - } else { - m_file.setPassword(pwDlg.password().toStdString()); - setSomethingChanged(true); - QMessageBox::warning(this, QApplication::applicationName(), tr("The new password will be used next time you save the file.")); - } - break; - default: - QMessageBox::warning(this, QApplication::applicationName(), tr("You aborted. The old password will still be used when saving the file next time.")); - } -} - -/*! - * \brief Shows the tree view context menu. - */ -void MainWindow::showTreeViewContextMenu() -{ - if(!m_file.hasRootEntry()) { - return; - } - QModelIndexList selectedIndexes = m_ui->treeView->selectionModel()->selectedRows(0); - if(selectedIndexes.size() == 1) { - QMenu contextMenu(this); - QModelIndex selected = m_entryFilterModel->mapToSource(selectedIndexes.at(0)); - Entry *entry = m_entryModel->entry(selected); - if(entry->type() == EntryType::Node) { - contextMenu.addAction(QIcon::fromTheme(QStringLiteral("list-add")), tr("Add account"), this, SLOT(addAccount())); - contextMenu.addAction(QIcon::fromTheme(QStringLiteral("list-add")), tr("Add category"), this, SLOT(addCategory())); - } - contextMenu.addAction(QIcon::fromTheme(QStringLiteral("list-remove")), tr("Remove entry"), this, SLOT(removeEntry())); - if(entry->type() == EntryType::Node) { - NodeEntry *nodeEntry = static_cast(entry); - contextMenu.addSeparator(); - QAction *action = new QAction(&contextMenu); - action->setCheckable(true); - action->setText(tr("Expanded by default")); - action->setChecked(nodeEntry->isExpandedByDefault()); - connect(action, &QAction::triggered, std::bind(&EntryModel::setData, m_entryModel, std::cref(selected), QVariant(!nodeEntry->isExpandedByDefault()), DefaultExpandedRole)); - contextMenu.addAction(action); - } - contextMenu.exec(QCursor::pos()); - } -} - -/*! - * \brief Shows the table view context menu. - */ -void MainWindow::showTableViewContextMenu() -{ - // check whether there is a selection at all - QModelIndexList selectedIndexes = m_ui->tableView->selectionModel()->selectedIndexes(); - if(!m_file.hasRootEntry() || !m_fieldModel->fields() || selectedIndexes.isEmpty()) { - return; - } - - // check what kind of fields have been selected - auto firstType = FieldType::Normal; - bool allOfSameType = true; - bool hasFirstFieldType = false; - int row = selectedIndexes.front().row(); - int multipleRows = 1; - QUrl url; - static const string protocols[] = { - "http:", "https:", "file:" - }; - for(const QModelIndex &index : selectedIndexes) { - if(const Field *field = m_fieldModel->field(index.row())) { - if(url.isEmpty() && field->type() != FieldType::Password) { - for(const string &protocol : protocols) { - if(ConversionUtilities::startsWith(field->value(), protocol)) { - url = QString::fromUtf8(field->value().data()); - } - } - } - if(hasFirstFieldType) { - if(firstType != field->type()) { - allOfSameType = false; - break; - } - } else { - firstType = field->type(); - hasFirstFieldType = true; - } - } - if(multipleRows == 1 && index.row() != row) { - ++multipleRows; - } - } - - // create context menu - QMenu contextMenu(this); - // -> insertion and removal - contextMenu.addAction(QIcon::fromTheme(QStringLiteral("list-add")), tr("Insert field"), this, SLOT(insertRow())); - contextMenu.addAction(QIcon::fromTheme(QStringLiteral("list-remove")), tr("Remove field(s)", 0, multipleRows), this, SLOT(removeRows())); - // -> show the "Mark as ..." action only when all selected indexes are of the same type - if(hasFirstFieldType && allOfSameType) { - switch(firstType) { - case FieldType::Normal: - contextMenu.addAction(QIcon::fromTheme(QStringLiteral("flag-black")), tr("Mark as password field"), this, SLOT(markAsPasswordField())); - break; - case FieldType::Password: - contextMenu.addAction(QIcon::fromTheme(QStringLiteral("flag-blue")), tr("Mark as normal field"), this, SLOT(markAsNormalField())); - break; - } - } - // -> insert copy & paste - contextMenu.addSeparator(); - contextMenu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), tr("Copy"), this, SLOT(copyFields())); - contextMenu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), tr("Copy for 5 seconds"), this, SLOT(copyFieldsForXMilliSeconds())); - if(QApplication::clipboard()->mimeData()->hasText()) { - contextMenu.addAction(QIcon::fromTheme(QStringLiteral("edit-paste")), tr("Paste"), this, SLOT(insertFieldsFromClipboard())); - } - // -> insert open URL - if(multipleRows == 1 && !url.isEmpty()) { - auto *openUrlAction = new QAction(QIcon::fromTheme(QStringLiteral("applications-internet")), tr("Open URL"), &contextMenu); - connect(openUrlAction, &QAction::triggered, bind(&QDesktopServices::openUrl, url)); - contextMenu.addAction(openUrlAction); - } - - contextMenu.exec(QCursor::pos()); -} - -/*! - * \brief Copies the selected cells to the clipboard and clears the clipboard after \a x milli seconds again. - */ -void MainWindow::copyFieldsForXMilliSeconds(int x) -{ - QString text = selectedFieldsString(); - if(!text.isEmpty()) { - if(m_clearClipboardTimer) { - killTimer(m_clearClipboardTimer); - } - QApplication::clipboard()->setText(text); - if(x > 0) { - m_clearClipboardTimer = startTimer(x, Qt::CoarseTimer); - } - } else { - QMessageBox::warning(this, QApplication::applicationName(), tr("The selection is empty.")); - } -} - -} +#include "./mainwindow.h" +#include "./fielddelegate.h" + +#include "../model/fieldmodel.h" +#include "../model/entrymodel.h" +#include "../model/entryfiltermodel.h" + +#include "ui_mainwindow.h" + +#include "resources/config.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace IoUtilities; +using namespace Io; +using namespace Dialogs; +using namespace MiscUtils; + +namespace QtGui { + +/*! + * \namespace QtGui + * \brief Contains all GUI related classes and helper functions. + */ + +/*! + * \namespace QtGui::Ui + * \brief Contains all classes generated by the Qt User Interface Compiler (uic). + */ + +/*! + * \class MainWindow + * \brief The MainWindow class provides the main window of the widgets-based GUI of the application. + */ + +/*! + * \brief Copies the selected cells to the clipboard. + */ +void MainWindow::copyFields() +{ + copyFieldsForXMilliSeconds(-1); +} + +/*! + * \brief Inserts fields from the clipboard. + */ +void MainWindow::insertFieldsFromClipboard() +{ + insertFields(QApplication::clipboard()->text()); +} + +/*! + * \brief Clears the clipboard. + */ +void MainWindow::clearClipboard() +{ + QApplication::clipboard()->clear(); +} + +/*! + * \brief Flags the current file as being changed since the last save. + */ +void MainWindow::setSomethingChanged() +{ + setSomethingChanged(true); +} + +/*! + * \brief Sets whether the current file has been changed since the last save. + */ +void MainWindow::setSomethingChanged(bool somethingChanged) +{ + if(m_somethingChanged != somethingChanged) { + m_somethingChanged = somethingChanged; + updateWindowTitle(); + } +} + +/*! + * \brief Constructs a new main window. + */ +MainWindow::MainWindow(QSettings &settings, Dialogs::QtSettings *qtSettings, QWidget *parent) : + QMainWindow(parent), + m_ui(new Ui::MainWindow), + m_clearClipboardTimer(0), + m_aboutDlg(nullptr), + m_settings(settings), + m_qtSettings(qtSettings), + m_settingsDlg(nullptr) +{ + // setup ui + m_ui->setupUi(this); +#ifdef Q_OS_WIN32 + setStyleSheet(QStringLiteral("%1 #splitter QWidget { background-color: palette(base); color: palette(text); } #splitter QWidget *, #splitter QWidget * { background-color: none; } #leftWidget { border-right: 1px solid %2; }").arg(dialogStyle(), windowFrameColor().name())); +#endif + // set default values + setSomethingChanged(false); + m_dontUpdateSelection = false; + updateUiStatus(); + // load settings + settings.beginGroup(QStringLiteral("mainwindow")); + // init recent menu manager + m_recentMgr = new RecentMenuManager(m_ui->menuRecent, this); + m_recentMgr->restore(settings.value(QStringLiteral("recententries"), QStringList()).toStringList()); + connect(m_recentMgr, &RecentMenuManager::fileSelected, this, &MainWindow::openFile); + // set position and size + restoreGeometry(settings.value(QStringLiteral("geometry")).toByteArray()); + restoreState(settings.value(QStringLiteral("state")).toByteArray()); + // setup undo stack and related actions + m_undoStack = new QUndoStack(this); + m_undoView = nullptr; + m_ui->actionUndo->setShortcuts(QKeySequence::Undo); + m_ui->actionRedo->setShortcuts(QKeySequence::Redo); + // setup models, tree and table view + m_ui->treeView->setModel(m_entryFilterModel = new EntryFilterModel(this)); + m_ui->tableView->setModel(m_fieldModel = new FieldModel(m_undoStack, this)); + m_ui->tableView->setItemDelegate(new FieldDelegate(this)); + m_entryFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + m_entryFilterModel->setSourceModel(m_entryModel = new EntryModel(m_undoStack, this)); +#ifdef Q_OS_WIN32 + m_ui->treeView->setFrameShape(QFrame::NoFrame); + m_ui->tableView->setFrameShape(QFrame::NoFrame); +#else + m_ui->treeView->setFrameShape(QFrame::StyledPanel); + m_ui->tableView->setFrameShape(QFrame::StyledPanel); +#endif + m_ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + // splitter sizes + m_ui->splitter->setSizes(QList() << 100 << 800); + // password visibility group + auto *passwordVisibilityGroup = new QActionGroup(this); + passwordVisibilityGroup->addAction(m_ui->actionShowAlways); + passwordVisibilityGroup->addAction(m_ui->actionShowOnlyWhenEditing); + passwordVisibilityGroup->addAction(m_ui->actionHideAlways); + QString pwVisibility(settings.value(QStringLiteral("pwvisibility")).toString()); + QAction *pwVisibilityAction; + if(pwVisibility == QStringLiteral("always")) { + pwVisibilityAction = m_ui->actionShowAlways; + } else if(pwVisibility == QStringLiteral("hidden")) { + pwVisibilityAction = m_ui->actionHideAlways; + } else { + pwVisibilityAction = m_ui->actionShowOnlyWhenEditing; + } + pwVisibilityAction->setChecked(true); + setPasswordVisibility(pwVisibilityAction); + // connect signals and slots + // -> file related actions + connect(m_ui->actionSave, &QAction::triggered, this, &MainWindow::saveFile); + connect(m_ui->actionExport, &QAction::triggered, this, &MainWindow::exportFile); + connect(m_ui->actionShowContainingDirectory, &QAction::triggered, this, &MainWindow::showContainingDirectory); + connect(m_ui->actionClose, &QAction::triggered, this, &MainWindow::closeFile); + connect(m_ui->actionCreate, &QAction::triggered, this, static_cast(&MainWindow::createFile)); + connect(m_ui->actionQuit, &QAction::triggered, this, &MainWindow::close); + connect(m_ui->actionChangepassword, &QAction::triggered, this, &MainWindow::changePassword); + // -> showing dialogs + connect(m_ui->actionPasswordGenerator, &QAction::triggered, this, &MainWindow::showPassowrdGeneratorDialog); + connect(m_ui->actionAbout, &QAction::triggered, this, &MainWindow::showAboutDialog); + connect(m_ui->actionOpen, &QAction::triggered, this, &MainWindow::showOpenFileDialog); + connect(m_ui->actionSaveAs, &QAction::triggered, this, &MainWindow::showSaveFileDialog); + connect(m_ui->actionQtSettings, &QAction::triggered, this, &MainWindow::showSettingsDialog); + // -> add/remove account + connect(m_ui->actionAddAccount, &QAction::triggered, this, &MainWindow::addAccount); + connect(m_ui->actionAddCategory, &QAction::triggered, this, &MainWindow::addCategory); + connect(m_ui->actionRemoveRows, &QAction::triggered, this, &MainWindow::removeEntry); + // -> insert/remove fields + connect(m_ui->actionInsertRow, &QAction::triggered, this, &MainWindow::insertRow); + connect(m_ui->actionRemoveAccount, &QAction::triggered, this, &MainWindow::removeRows); + // -> undo/redo + connect(m_ui->actionUndo, &QAction::triggered, m_undoStack, &QUndoStack::undo); + connect(m_ui->actionRedo, &QAction::triggered, m_undoStack, &QUndoStack::redo); + connect(m_undoStack, &QUndoStack::canUndoChanged, m_ui->actionUndo, &QAction::setEnabled); + connect(m_undoStack, &QUndoStack::canRedoChanged, m_ui->actionRedo, &QAction::setEnabled); + // -> view + connect(passwordVisibilityGroup, &QActionGroup::triggered, this, &MainWindow::setPasswordVisibility); + connect(m_ui->actionShowUndoStack, &QAction::triggered, this, &MainWindow::showUndoView); + // -> models + connect(m_ui->treeView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &MainWindow::accountSelected); + connect(m_entryModel, &QAbstractItemModel::dataChanged, this, static_cast(&MainWindow::setSomethingChanged)); + connect(m_fieldModel, &QAbstractItemModel::dataChanged, this, static_cast(&MainWindow::setSomethingChanged)); + // -> context menus + connect(m_ui->treeView, &QTableView::customContextMenuRequested, this, &MainWindow::showTreeViewContextMenu); + connect(m_ui->tableView, &QTableView::customContextMenuRequested, this, &MainWindow::showTableViewContextMenu); + // -> filter + //connect(m_ui->accountFilterLineEdit, &QLineEdit::textChanged, m_entryFilterModel, &QSortFilterProxyModel::setFilterFixedString); + connect(m_ui->accountFilterLineEdit, &QLineEdit::textChanged, this, &MainWindow::applyFilter); + // setup other controls + m_ui->actionAlwaysCreateBackup->setChecked(settings.value(QStringLiteral("alwayscreatebackup"), false).toBool()); + m_ui->accountFilterLineEdit->setText(settings.value(QStringLiteral("accountfilter"), QString()).toString()); + m_ui->centralWidget->installEventFilter(this); + settings.endGroup(); +} + +/*! + * \brief Destroys the main window. + */ +MainWindow::~MainWindow() +{} + +bool MainWindow::eventFilter(QObject *obj, QEvent *event) +{ + if(obj == m_undoView) { + switch(event->type()) { + case QEvent::Hide: + m_ui->actionShowUndoStack->setChecked(false); + break; + default: + ; + } + } else if(obj == m_ui->centralWidget) { + switch(event->type()) { + case QEvent::DragEnter: + case QEvent::Drop: + if(QDropEvent *dropEvent = static_cast(event)) { + QString data; + const QMimeData *mimeData = dropEvent->mimeData(); + if(mimeData->hasUrls()) { + const QUrl url = mimeData->urls().front(); + if(url.scheme() == QLatin1String("file")) { + data = url.path(); + } + } else if(mimeData->hasText()) { + data = mimeData->text(); + } + if(!data.isEmpty()) { + event->accept(); + if(event->type() == QEvent::Drop) { + openFile(data); + } + } + return true; + } + default: + ; + } + } + return QMainWindow::eventFilter(obj, event); +} + +void MainWindow::closeEvent(QCloseEvent *event) +{ + // ask if file is opened + if(m_file.hasRootEntry()) { + if(!closeFile()) { + event->ignore(); + return; + } + } + // close undow view + if(m_undoView) { + m_undoView->close(); + } + // save settings + m_settings.beginGroup(QStringLiteral("mainwindow")); + m_settings.setValue(QStringLiteral("geometry"), saveGeometry()); + m_settings.setValue(QStringLiteral("state"), saveState()); + m_settings.setValue(QStringLiteral("recententries"), m_recentMgr->save()); + m_settings.setValue(QStringLiteral("accountfilter"), m_ui->accountFilterLineEdit->text()); + m_settings.setValue(QStringLiteral("alwayscreatebackup"), m_ui->actionAlwaysCreateBackup->isChecked()); + QString pwVisibility; + if(m_ui->actionShowAlways->isChecked()) { + pwVisibility = QStringLiteral("always"); + } else if(m_ui->actionHideAlways->isChecked()) { + pwVisibility = QStringLiteral("hidden"); + } else { + pwVisibility = QStringLiteral("editing"); + } + m_settings.setValue(QStringLiteral("pwvisibility"), pwVisibility); + m_settings.endGroup(); +} + +void MainWindow::timerEvent(QTimerEvent *event) +{ + if(event->timerId() == m_clearClipboardTimer) { + clearClipboard(); + m_clearClipboardTimer = 0; + } +} + +/*! + * \brief Shows the settings dialog (which currently only consists of the Qt settings category). + */ +void MainWindow::showSettingsDialog() +{ + if(!m_settingsDlg) { + m_settingsDlg = new SettingsDialog(this); + if(m_qtSettings) { + m_settingsDlg->setWindowTitle(tr("Qt settings")); + m_settingsDlg->setSingleCategory(m_qtSettings->category()); + } + //connect(m_settingsDlg, &SettingsDialog::applied, this, &MainWindow::settingsAccepted); + } + if(m_settingsDlg->isHidden()) { + m_settingsDlg->showNormal(); + } else { + m_settingsDlg->activateWindow(); + } +} + +/*! + * \brief Shows the about dialog. + */ +void MainWindow::showAboutDialog() +{ + if(!m_aboutDlg) { + m_aboutDlg = new AboutDialog(this, tr("A simple password store using AES-256-CBC encryption via OpenSSL."), QImage(":/icons/hicolor/128x128/apps/passwordmanager.png")); + } + m_aboutDlg->show(); +} + +/*! + * \brief Shows the password generator dialog. + */ +void MainWindow::showPassowrdGeneratorDialog() +{ + PasswordGeneratorDialog* pwgDialog = new PasswordGeneratorDialog(this); + pwgDialog->show(); +} + +/*! + * \brief Shows the open file dialog and opens the selected file. + */ +void MainWindow::showOpenFileDialog() +{ + if(m_file.hasRootEntry() && !closeFile()) { + return; + } + QString fileName = QFileDialog::getOpenFileName(this, tr("Select a password list"), QString(), tr("Password Manager files (*.pwmgr);;All files (*)")); + if(!fileName.isEmpty()) { + openFile(fileName); + } +} + +/*! + * \brief Shows the save file dialog and saves the file at the selected location. + */ +void MainWindow::showSaveFileDialog() +{ + if(showNoFileOpened()) { + return; + } + if(askForCreatingFile()) { + saveFile(); + } +} + +/*! + * \brief Shows the undo view. + */ +void MainWindow::showUndoView() +{ + if(m_ui->actionShowUndoStack->isChecked()) { + if(!m_undoView) { + m_undoView = new QUndoView(m_undoStack); + m_undoView->setWindowTitle(tr("Undo stack")); + m_undoView->setWindowFlags(Qt::Tool); + m_undoView->setAttribute(Qt::WA_QuitOnClose); + m_undoView->setWindowIcon(QIcon::fromTheme(QStringLiteral("edit-undo"))); + m_undoView->installEventFilter(this); + } + m_undoView->show(); + } else if(m_undoView) { + m_undoView->hide(); + } +} + +/*! + * \brief Opens a file with the specified \a path and updates all widgets to show its contents. + * \returns Returns true on success; otherwise false + */ +bool MainWindow::openFile(const QString &path) +{ + using namespace Dialogs; + // close previous file + if(m_file.hasRootEntry() && !closeFile()) { + return false; + } + // set path and open file + m_file.setPath(path.toStdString()); + try { + m_file.open(); + } catch (...) { + const QString errmsg = tr("An IO error occured when opening the specified file \"%1\".\n\n(%2)").arg(path, QString::fromLocal8Bit(catchIoFailure())); + m_ui->statusBar->showMessage(errmsg, 5000); + QMessageBox::critical(this, QApplication::applicationName(), errmsg); + return false; + } + // warn before loading a very big file + if(m_file.size() > 10485760) { + if(QMessageBox::warning(this, QApplication::applicationName(), tr("The file you want to load seems to be very big. Do you really want to open it?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { + m_file.clear(); + return false; + } + } + // ask for a password if required + if(m_file.isEncryptionUsed()) { + EnterPasswordDialog pwDlg(this); + pwDlg.setWindowTitle(tr("Opening file") + QStringLiteral(" - " APP_NAME)); + pwDlg.setInstruction(tr("Enter the password to open the file \"%1\"").arg(path)); + pwDlg.setPasswordRequired(true); + switch(pwDlg.exec()) { + case QDialog::Accepted: + if(pwDlg.password().isEmpty()) { + m_ui->statusBar->showMessage(tr("A password is needed to open the file."), 5000); + QMessageBox::warning(this, QApplication::applicationName(), tr("A password is needed to open the file.")); + m_file.clear(); + return false; + } else { + break; + } + case QDialog::Rejected: + m_file.clear(); + return false; + default: + ; + } + m_file.setPassword(pwDlg.password().toStdString()); + } + // load the contents of the file + QString msg; + try { + m_file.load(); + } catch(const CryptoException &e) { + msg = tr("The file couldn't be decrypted.\nOpenSSL error queue: %1").arg(QString::fromLocal8Bit(e.what())); + } catch (...) { + try { + msg = QString::fromLocal8Bit(catchIoFailure()); + } catch(const runtime_error &e) { + msg = tr("Unable to parse the file. %1").arg(QString::fromLocal8Bit(e.what())); + } + } + // show a message in the error case + if(!msg.isEmpty()) { + m_file.clear(); + m_ui->statusBar->showMessage(msg, 5000); + if(QMessageBox::critical(this, QApplication::applicationName(), msg, + QMessageBox::Cancel, QMessageBox::Retry) == QMessageBox::Retry) { + return openFile(path); // retry + } else { + return false; + } + } else { + // show contents + return showFile(); + } +} + +/*! + * \brief Creates a new file. + * \returns Returns true on success; otherwise false + */ +bool MainWindow::createFile() +{ + // close previous file + if(m_file.hasRootEntry() && !closeFile()) { + return false; + } + m_file.generateRootEntry(); + return showFile(); +} + +/*! + * \brief Creates a new file with the specified \a path. + * \returns Returns true on success; otherwise false + */ +void MainWindow::createFile(const QString &path) +{ + createFile(path, QString()); +} + +/*! + * \brief Creates a new file with the specified \a path and \a password. + * \returns Returns true on success; otherwise false + */ +void MainWindow::createFile(const QString &path, const QString &password) +{ + // close previous file + if(m_file.hasRootEntry() && !closeFile()) { + return; + } + // set path and password + m_file.setPath(path.toStdString()); + m_file.setPassword(password.toStdString()); + // create the file and show it + try { + m_file.create(); + } catch (...) { + catchIoFailure(); + QMessageBox::critical(this, QApplication::applicationName(), tr("The file %1 couldn't be created.").arg(path)); + return; + } + m_file.generateRootEntry(); + showFile(); +} + +/*! + * \brief Shows the previously opened file. Called within openFile() and createFile(). + * \returns Returns true on success; otherwise false + */ +bool MainWindow::showFile() +{ + m_fieldModel->reset(); + m_entryModel->setRootEntry(m_file.rootEntry()); + applyDefaultExpanding(QModelIndex()); + if(m_file.path().empty()) { + m_ui->statusBar->showMessage(tr("A new password list has been created."), 5000); + } else { + m_recentMgr->addEntry(QString::fromStdString(m_file.path())); + m_ui->statusBar->showMessage(tr("The password list has been load."), 5000); + } + updateWindowTitle(); + updateUiStatus(); + applyFilter(m_ui->accountFilterLineEdit->text()); + setSomethingChanged(false); + return true; +} + +/*! + * \brief Updates the status of the UI elements. + */ +void MainWindow::updateUiStatus() +{ + bool fileOpened = m_file.hasRootEntry(); + m_ui->actionCreate->setEnabled(true); + m_ui->actionOpen->setEnabled(true); + m_ui->actionSave->setEnabled(fileOpened); + m_ui->actionSaveAs->setEnabled(fileOpened); + m_ui->actionExport->setEnabled(fileOpened); + m_ui->actionShowContainingDirectory->setEnabled(fileOpened); + m_ui->actionClose->setEnabled(fileOpened); + m_ui->actionChangepassword->setEnabled(fileOpened); + m_ui->menuEdit->setEnabled(fileOpened); + m_ui->accountFilterLineEdit->setEnabled(true); +} + +/*! + * \brief Updates the window title. + */ +void MainWindow::updateWindowTitle() +{ + Dialogs::DocumentStatus docStatus; + if(m_file.hasRootEntry()) { + if(m_somethingChanged) { + docStatus = Dialogs::DocumentStatus::Unsaved; + } else { + docStatus = Dialogs::DocumentStatus::Saved; + } + } else { + docStatus = Dialogs::DocumentStatus::NoDocument; + } + setWindowTitle(Dialogs::generateWindowTitle(docStatus, QString::fromStdString(m_file.path()))); +} + +void MainWindow::applyDefaultExpanding(const QModelIndex &parent) +{ + for(int row = 0, rows = m_entryFilterModel->rowCount(parent); row < rows; ++row) { + QModelIndex index = m_entryFilterModel->index(row, 0, parent); + if(!index.isValid()) { + return; + } + applyDefaultExpanding(index); + m_ui->treeView->setExpanded(index, m_entryFilterModel->data(index, DefaultExpandedRole).toBool()); + } +} + +/*! + * \brief Returns a string with the values of all selected fields. + * \remarks Columns are sparated with \t, rows with \n. + */ +QString MainWindow::selectedFieldsString() const +{ + QModelIndexList selectedIndexes = m_ui->tableView->selectionModel()->selectedIndexes(); + QString text; + if(!selectedIndexes.isEmpty()) { + if(selectedIndexes.size() > 1) { + int maxRow = m_fieldModel->rowCount() - 1; + int firstRow = maxRow, lastRow = 0; + int maxCol = m_fieldModel->columnCount() - 1; + int firstCol = maxCol, lastCol = 0; + for(const QModelIndex &index : selectedIndexes) { + if(index.row() < firstRow) { + firstRow = index.row(); + } + if(index.row() > lastRow) { + lastRow = index.row(); + } + if(index.column() < firstCol) { + firstCol = index.column(); + } + if(index.column() > lastCol) { + lastCol = index.column(); + } + } + for(int row = firstRow; row <= lastRow; ++row) { + for(int col = firstCol; col <= lastCol; ++col) { + QModelIndex index = m_fieldModel->index(row, col); + if(selectedIndexes.contains(index)) { + text.append(index.data(Qt::EditRole).toString()); + } + text.append('\t'); + } + text.append('\n'); + } + } else { + text = selectedIndexes.front().data(Qt::EditRole).toString(); + } + } + return text; +} + +/*! + * \brief Inserts fields from the specified \a fieldsString. + */ +void MainWindow::insertFields(const QString &fieldsString) +{ + QModelIndexList selectedIndexes = m_ui->tableView->selectionModel()->selectedIndexes(); + if(selectedIndexes.size() == 1) { + int rows = m_fieldModel->rowCount(), cols = m_fieldModel->columnCount(); + int row = selectedIndexes.front().row(); + int initCol = selectedIndexes.front().column(); + assert(row < rows); + QStringList rowValues = fieldsString.split('\n'); + if(rowValues.back().isEmpty()) { + rowValues.pop_back(); + } + m_fieldModel->insertRows(row, rowValues.size(), QModelIndex()); + for(const QString &rowValue : rowValues) { + int col = initCol; + for(const QString &cellValue : rowValue.split('\t')) { + if(col < cols) { + m_fieldModel->setData(m_fieldModel->index(row, col), cellValue, Qt::EditRole); + ++col; + } else { + break; + } + } + ++row; + } + } else { + QMessageBox::warning(this, QApplication::applicationName(), tr("Exactly one fields needs to be selected (top-left corner for insertion).")); + } +} + +/*! + * \brief Asks the user to create a new file. + */ +bool MainWindow::askForCreatingFile() +{ + if(showNoFileOpened()) { + return false; + } + + QString fileName = + QFileDialog::getSaveFileName( + this, + tr("Select where you want to save the password list"), + QString(), + tr("Password Manager files (*.pwmgr);;All files (*)")); + if(fileName.isEmpty()) { + m_ui->statusBar->showMessage(tr("The file was not be saved."), 7000); + return false; + } else { + m_file.setPath(fileName.toStdString()); + try { + m_file.create(); + updateWindowTitle(); + } catch (...) { + QMessageBox::critical(this, QApplication::applicationName(), QString::fromLocal8Bit(catchIoFailure())); + return false; + } + } + return true; +} + +/*! + * \brief Shows an warning if no file is opened. + * \retruns Returns whether the warning has been shown. + */ +bool MainWindow::showNoFileOpened() +{ + if(!m_file.hasRootEntry()) { + QMessageBox::warning(this, QApplication::applicationName(), tr("There is no password list opened.")); + return true; + } + return false; +} + +/*! + * \brief Shows an warning if no account is selected. + * \retruns Returns whether the warning has been shown. + */ +bool MainWindow::showNoAccount() +{ + if(!m_fieldModel->fields()) { + QMessageBox::warning(this, QApplication::applicationName(), tr("There's no account selected.")); + return true; + } + return false; +} + +/*! + * \brief Closes the currently opened file. Asks the user to save changes if the file has been modified. + * \returns Returns whether the file has been closed. + */ +bool MainWindow::closeFile() +{ + if(showNoFileOpened()) { + return false; + } + if(m_somethingChanged) { + QMessageBox msg(this); + msg.setText(tr("The password file has been modified.")); + msg.setInformativeText(tr("Do you want to save the changes before closing?")); + msg.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); + msg.setDefaultButton(QMessageBox::Save); + msg.setIcon(QMessageBox::Warning); + switch (msg.exec()) { + case QMessageBox::Save: + if(saveFile()) { + break; + } else { + return false; + } + case QMessageBox::Cancel: + return false; + default: + ; + } + } + m_fieldModel->reset(); + m_entryModel->reset(); + m_file.clear(); + m_ui->statusBar->showMessage(tr("The password list has been closed.")); + updateWindowTitle(); + updateUiStatus(); + setSomethingChanged(false); + return true; +} + +/*! + * \brief Saves the currently opened file. + * \returns Returns whether the file could be saved. + */ +bool MainWindow::saveFile() +{ + using namespace Dialogs; + if(showNoFileOpened()) { + return false; + } + // create backup + if(!m_file.path().empty() && QFile::exists(QString::fromStdString(m_file.path()))) { + if(m_ui->actionAlwaysCreateBackup->isChecked()) { + try { + m_file.doBackup(); + } catch(...) { + QString message(tr("The backup file couldn't be created because in IO error occured: %1").arg(QString::fromLocal8Bit(catchIoFailure()))); + QMessageBox::critical(this, QApplication::applicationName(), message); + m_ui->statusBar->showMessage(message, 7000); + return false; + } + } + } else { + if(!askForCreatingFile()) { + return false; + } + } + // ask for a password if none is set + if(m_file.password()[0] == 0) { + EnterPasswordDialog pwDlg(this); + pwDlg.setWindowTitle(tr("Saving file") + QStringLiteral(" - " APP_NAME)); + pwDlg.setInstruction(tr("Enter a password to save the file")); + pwDlg.setVerificationRequired(true); + + switch(pwDlg.exec()) { + case QDialog::Accepted: + m_file.setPassword(pwDlg.password().toStdString()); + break; + default: + m_ui->statusBar->showMessage(tr("The file hasn't been saved."), 7000); + return false; + } + } + // save the file + QString msg; + try { + m_file.save(m_file.password()[0] != 0); + } catch (const CryptoException &ex) { + msg = tr("The password list couldn't be saved due to encryption failure.\nOpenSSL error queue: %1").arg(QString::fromLocal8Bit(ex.what())); + } catch(...) { + msg = QString::fromLocal8Bit(catchIoFailure()); + } + // show status + if(!msg.isEmpty()) { + m_ui->statusBar->showMessage(msg, 5000); + QMessageBox::critical(this, QApplication::applicationName(), msg); + return false; + } else { + setSomethingChanged(false); + m_recentMgr->addEntry(QString::fromStdString(m_file.path())); + m_ui->statusBar->showMessage(tr("The password list has been saved."), 5000); + return true; + } +} + +/*! + * \brief Exports the files contents to a plain text file. + */ +void MainWindow::exportFile() +{ + if(showNoFileOpened()) { + return; + } + QString targetPath = QFileDialog::getSaveFileName(this, QApplication::applicationName(), QString(), tr("Plain text document (*.txt);;All files (*.*)")); + if(!targetPath.isNull()) { + QString errmsg; + try { + m_file.exportToTextfile(targetPath.toStdString()); + } catch (...) { + errmsg = tr("The password list couldn't be exported. %1").arg(QString::fromLocal8Bit(catchIoFailure())); + } + if(errmsg.isEmpty()) { + m_ui->statusBar->showMessage(tr("The password list has been exported."), 5000); + } else { + m_ui->statusBar->showMessage(errmsg, 5000); + QMessageBox::critical(this, QApplication::applicationName(), errmsg); + } + } +} + +/*! + * \brief Shows the containing directory for the currently opened file. + */ +void MainWindow::showContainingDirectory() +{ + if(showNoFileOpened()) { + return; + } else if(m_file.path().empty()) { + QMessageBox::warning(this, QApplication::applicationName(), tr("The currently opened file hasn't been saved yet.")); + } else { + QFileInfo file(QString::fromStdString(m_file.path())); + if(file.dir().exists()) { + DesktopUtils::openLocalFileOrDir(file.dir().absolutePath()); + } + } +} + +/*! + * \brief Adds a new account entry to the selected category. + */ +void MainWindow::addAccount() +{ + addEntry(EntryType::Account); +} + +/*! + * \brief Adds a new category/node entry to the selected category. + */ +void MainWindow::addCategory() +{ + addEntry(EntryType::Node); +} + +/*! + * \brief Adds a new entry to the selected category. + * \param type Specifies the type of the entry to be created. + * \param title Specifies the title of the user prompt which will be shown to ask for the entry label. + */ +void MainWindow::addEntry(EntryType type) +{ + if(showNoFileOpened()) { + return; + } + QModelIndexList selectedIndexes = m_ui->treeView->selectionModel()->selectedRows(0); + if(selectedIndexes.size() == 1) { + QModelIndex selected = m_entryFilterModel->mapToSource(selectedIndexes.at(0)); + if(m_entryModel->isNode(selected)) { + bool result; + const QString text = QInputDialog::getText(this, type == EntryType::Account ? tr("Add account") : tr("Add category"), tr("Enter the entry name"), QLineEdit::Normal, tr("new entry"), &result); + if (result) { + if(!text.isEmpty()) { + int row = m_entryModel->rowCount(selected); + m_entryModel->setInsertType(type); + if(m_entryModel->insertRow(row, selected)) { + m_entryModel->setData(m_entryModel->index(row, 0, selected), text, Qt::DisplayRole); + setSomethingChanged(true); + } else { + QMessageBox::warning(this, QApplication::applicationName(), tr("Unable to create new entry.")); + } + } else { + QMessageBox::warning(this, QApplication::applicationName(), tr("You didn't enter text.")); + } + } + return; + } + } + QMessageBox::warning(this, QApplication::applicationName(), tr("No node element selected.")); +} + +/*! + * \brief Removes the selected entry. + */ +void MainWindow::removeEntry() +{ + if(showNoFileOpened()) { + return; + } + QModelIndexList selectedIndexes = m_ui->treeView->selectionModel()->selectedRows(0); + if(selectedIndexes.size() == 1) { + const QModelIndex selected = m_entryFilterModel->mapToSource(selectedIndexes.at(0)); + if(!m_entryModel->removeRow(selected.row(), selected.parent())) { + QMessageBox::warning(this, QApplication::applicationName(), tr("Unable to remove the entry.")); + } + } else { + QMessageBox::warning(this, QApplication::applicationName(), tr("No entry selected.")); + } +} + +/*! + * \brief Applies the entered filter. + * \remarks Called when the textChanged signal of m_ui->accountFilterLineEdit is emittet. + */ +void MainWindow::applyFilter(const QString &filterText) +{ + m_entryFilterModel->setFilterRegExp(filterText); + if(filterText.isEmpty()) { + applyDefaultExpanding(QModelIndex()); + } else { + m_ui->treeView->expandAll(); + } +} + +/*! + * \brief Called when the user \a selected an entry. + */ +void MainWindow::accountSelected(const QModelIndex &selected, const QModelIndex &) +{ + if(Entry *entry = m_entryModel->entry(m_entryFilterModel->mapToSource(selected))) { + if(entry->type() == EntryType::Account) { + m_fieldModel->setAccountEntry(static_cast(entry)); + return; + } + } + m_fieldModel->setAccountEntry(nullptr); +} + +/*! + * \brief Inserts an empty row before the selected one. + */ +void MainWindow::insertRow() +{ + if(showNoFileOpened() || showNoAccount()) { + return; + } + QModelIndexList selectedIndexes = m_ui->tableView->selectionModel()->selectedIndexes(); + if(selectedIndexes.size()) { + int row = m_fieldModel->rowCount(); + for(const QModelIndex &index : selectedIndexes) { + if(index.row() < row) { + row = index.row(); + } + } + if(row < m_fieldModel->rowCount() - 1) { + m_fieldModel->insertRow(row); + } + } else { + QMessageBox::warning(this, windowTitle(), tr("A field has to be selected since new fields are always inserted before the currently selected field.")); + } +} + +/*! + * \brief Removes the selected rows. + */ +void MainWindow::removeRows() +{ + if(showNoFileOpened() || showNoAccount()) { + return; + } + const QModelIndexList selectedIndexes = m_ui->tableView->selectionModel()->selectedIndexes(); + QList rows; + for(const QModelIndex &index : selectedIndexes) { + rows << index.row(); + } + if(rows.size()) { + for(int i = m_fieldModel->rowCount() - 1; i >= 0; --i) { + if(rows.contains(i)) { + m_fieldModel->removeRow(i); + } + } + } else { + QMessageBox::warning(this, windowTitle(), tr("No fields have been removed since there are currently no fields selected.")); + } +} + +/*! + * \brief Marks the selected field as password field. + */ +void MainWindow::markAsPasswordField() +{ + setFieldType(FieldType::Password); +} + +/*! + * \brief Marks the selected field as normal field. + */ +void MainWindow::markAsNormalField() +{ + setFieldType(FieldType::Normal); +} + +/*! + * \brief Sets the type of the selected field to the specified \a fieldType. + */ +void MainWindow::setFieldType(FieldType fieldType) +{ + if(showNoFileOpened() || showNoAccount()) { + return; + } + QModelIndexList selectedIndexes = m_ui->tableView->selectionModel()->selectedIndexes(); + if(!selectedIndexes.isEmpty()) { + const QVariant typeVariant(static_cast(fieldType)); + for(const QModelIndex &index : selectedIndexes) { + m_fieldModel->setData(index, typeVariant, FieldTypeRole); + } + } else { + QMessageBox::warning(this, windowTitle(), tr("No fields have been changed since there are currently no fields selected.")); + } +} + +/*! + * \brief Sets the password visibility of m_fieldModel depending on which action has been chosen. + * + * This private slot is connected to the triggered signal of the passwordVisibility QActionGroup. + */ +void MainWindow::setPasswordVisibility(QAction *selectedAction) +{ + if(selectedAction == m_ui->actionShowAlways) { + m_fieldModel->setPasswordVisibility(PasswordVisibility::Always); + } else if(selectedAction == m_ui->actionShowOnlyWhenEditing) { + m_fieldModel->setPasswordVisibility(PasswordVisibility::OnlyWhenEditing); + } else if(selectedAction == m_ui->actionHideAlways) { + m_fieldModel->setPasswordVisibility(PasswordVisibility::Never); + } +} + +/*! + * \brief Asks the user to change the password which will be used when calling saveFile() next time. + */ +void MainWindow::changePassword() +{ + using namespace Dialogs; + if(showNoFileOpened()) { + return; + } + EnterPasswordDialog pwDlg(this); + pwDlg.setWindowTitle(tr("Changing password") + QStringLiteral(" - " APP_NAME)); + pwDlg.setVerificationRequired(true); + switch(pwDlg.exec()) { + case QDialog::Accepted: + if(pwDlg.password().isEmpty()) { + m_file.clearPassword(); + setSomethingChanged(true); + QMessageBox::warning(this, QApplication::applicationName(), tr("You didn't enter a password. No encryption will be used when saving the file next time.")); + } else { + m_file.setPassword(pwDlg.password().toStdString()); + setSomethingChanged(true); + QMessageBox::warning(this, QApplication::applicationName(), tr("The new password will be used next time you save the file.")); + } + break; + default: + QMessageBox::warning(this, QApplication::applicationName(), tr("You aborted. The old password will still be used when saving the file next time.")); + } +} + +/*! + * \brief Shows the tree view context menu. + */ +void MainWindow::showTreeViewContextMenu() +{ + if(!m_file.hasRootEntry()) { + return; + } + QModelIndexList selectedIndexes = m_ui->treeView->selectionModel()->selectedRows(0); + if(selectedIndexes.size() == 1) { + QMenu contextMenu(this); + QModelIndex selected = m_entryFilterModel->mapToSource(selectedIndexes.at(0)); + Entry *entry = m_entryModel->entry(selected); + if(entry->type() == EntryType::Node) { + contextMenu.addAction(QIcon::fromTheme(QStringLiteral("list-add")), tr("Add account"), this, SLOT(addAccount())); + contextMenu.addAction(QIcon::fromTheme(QStringLiteral("list-add")), tr("Add category"), this, SLOT(addCategory())); + } + contextMenu.addAction(QIcon::fromTheme(QStringLiteral("list-remove")), tr("Remove entry"), this, SLOT(removeEntry())); + if(entry->type() == EntryType::Node) { + NodeEntry *nodeEntry = static_cast(entry); + contextMenu.addSeparator(); + QAction *action = new QAction(&contextMenu); + action->setCheckable(true); + action->setText(tr("Expanded by default")); + action->setChecked(nodeEntry->isExpandedByDefault()); + connect(action, &QAction::triggered, std::bind(&EntryModel::setData, m_entryModel, std::cref(selected), QVariant(!nodeEntry->isExpandedByDefault()), DefaultExpandedRole)); + contextMenu.addAction(action); + } + contextMenu.exec(QCursor::pos()); + } +} + +/*! + * \brief Shows the table view context menu. + */ +void MainWindow::showTableViewContextMenu() +{ + // check whether there is a selection at all + QModelIndexList selectedIndexes = m_ui->tableView->selectionModel()->selectedIndexes(); + if(!m_file.hasRootEntry() || !m_fieldModel->fields() || selectedIndexes.isEmpty()) { + return; + } + + // check what kind of fields have been selected + auto firstType = FieldType::Normal; + bool allOfSameType = true; + bool hasFirstFieldType = false; + int row = selectedIndexes.front().row(); + int multipleRows = 1; + QUrl url; + static const string protocols[] = { + "http:", "https:", "file:" + }; + for(const QModelIndex &index : selectedIndexes) { + if(const Field *field = m_fieldModel->field(index.row())) { + if(url.isEmpty() && field->type() != FieldType::Password) { + for(const string &protocol : protocols) { + if(ConversionUtilities::startsWith(field->value(), protocol)) { + url = QString::fromUtf8(field->value().data()); + } + } + } + if(hasFirstFieldType) { + if(firstType != field->type()) { + allOfSameType = false; + break; + } + } else { + firstType = field->type(); + hasFirstFieldType = true; + } + } + if(multipleRows == 1 && index.row() != row) { + ++multipleRows; + } + } + + // create context menu + QMenu contextMenu(this); + // -> insertion and removal + contextMenu.addAction(QIcon::fromTheme(QStringLiteral("list-add")), tr("Insert field"), this, SLOT(insertRow())); + contextMenu.addAction(QIcon::fromTheme(QStringLiteral("list-remove")), tr("Remove field(s)", 0, multipleRows), this, SLOT(removeRows())); + // -> show the "Mark as ..." action only when all selected indexes are of the same type + if(hasFirstFieldType && allOfSameType) { + switch(firstType) { + case FieldType::Normal: + contextMenu.addAction(QIcon::fromTheme(QStringLiteral("flag-black")), tr("Mark as password field"), this, SLOT(markAsPasswordField())); + break; + case FieldType::Password: + contextMenu.addAction(QIcon::fromTheme(QStringLiteral("flag-blue")), tr("Mark as normal field"), this, SLOT(markAsNormalField())); + break; + } + } + // -> insert copy & paste + contextMenu.addSeparator(); + contextMenu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), tr("Copy"), this, SLOT(copyFields())); + contextMenu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), tr("Copy for 5 seconds"), this, SLOT(copyFieldsForXMilliSeconds())); + if(QApplication::clipboard()->mimeData()->hasText()) { + contextMenu.addAction(QIcon::fromTheme(QStringLiteral("edit-paste")), tr("Paste"), this, SLOT(insertFieldsFromClipboard())); + } + // -> insert open URL + if(multipleRows == 1 && !url.isEmpty()) { + auto *openUrlAction = new QAction(QIcon::fromTheme(QStringLiteral("applications-internet")), tr("Open URL"), &contextMenu); + connect(openUrlAction, &QAction::triggered, bind(&QDesktopServices::openUrl, url)); + contextMenu.addAction(openUrlAction); + } + + contextMenu.exec(QCursor::pos()); +} + +/*! + * \brief Copies the selected cells to the clipboard and clears the clipboard after \a x milli seconds again. + */ +void MainWindow::copyFieldsForXMilliSeconds(int x) +{ + QString text = selectedFieldsString(); + if(!text.isEmpty()) { + if(m_clearClipboardTimer) { + killTimer(m_clearClipboardTimer); + } + QApplication::clipboard()->setText(text); + if(x > 0) { + m_clearClipboardTimer = startTimer(x, Qt::CoarseTimer); + } + } else { + QMessageBox::warning(this, QApplication::applicationName(), tr("The selection is empty.")); + } +} + +} diff --git a/gui/mainwindow.h b/gui/mainwindow.h index cb7f827..ba6e198 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -1,138 +1,138 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include "./passwordgeneratordialog.h" - -#include - -#include -#include - -#include -#include - -#include - -QT_FORWARD_DECLARE_CLASS(QCloseEvent) -QT_FORWARD_DECLARE_CLASS(QTreeWidgetItem) -QT_FORWARD_DECLARE_CLASS(QUndoStack) -QT_FORWARD_DECLARE_CLASS(QUndoView) -QT_FORWARD_DECLARE_CLASS(QSettings) - -namespace Io { -DECLARE_ENUM_CLASS(EntryType, int); -DECLARE_ENUM_CLASS(FieldType, int); -} - -namespace MiscUtils { -class RecentMenuManager; -} - -namespace Dialogs { -class AboutDialog; -class SettingsDialog; -class QtSettings; -} - -namespace QtGui { - -class FieldModel; -class EntryModel; -class EntryFilterModel; - -namespace Ui { -class MainWindow; -} - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QSettings &settings, Dialogs::QtSettings *qtSettings = nullptr, QWidget *parent = nullptr); - ~MainWindow(); - -public slots: - // file management - bool openFile(const QString &path); - void createFile(const QString &path, const QString &password); - void createFile(const QString &path); - bool createFile(); - void changePassword(); - bool saveFile(); - void exportFile(); - bool closeFile(); - // show dialogs - void showOpenFileDialog(); - void showSaveFileDialog(); - void showSettingsDialog(); - void showAboutDialog(); - void showPassowrdGeneratorDialog(); - void showUndoView(); - -protected: - bool eventFilter(QObject *obj, QEvent *event); - void closeEvent(QCloseEvent *event); - void timerEvent(QTimerEvent *event); - -private slots: - // file management - bool showFile(); - // account/categories management - void addAccount(); - void addCategory(); - void addEntry(Io::EntryType type); - void removeEntry(); - void applyFilter(const QString &filterText); - // row management - void accountSelected(const QModelIndex &selected, const QModelIndex &); - void insertRow(); - void removeRows(); - void markAsPasswordField(); - void markAsNormalField(); - void setFieldType(Io::FieldType fieldType); - void setPasswordVisibility(QAction *selectedAction); - QString selectedFieldsString() const; - void insertFields(const QString &fieldsString); - void copyFieldsForXMilliSeconds(int x = 5000); - void copyFields(); - void insertFieldsFromClipboard(); - // showing context menus - void showTreeViewContextMenu(); - void showTableViewContextMenu(); - // other - void showContainingDirectory(); - void clearClipboard(); - void setSomethingChanged(); - void setSomethingChanged(bool somethingChanged); - -private: - // showing conditional messages/prompts - bool askForCreatingFile(); - bool showNoFileOpened(); - bool showNoAccount(); - // other - void updateUiStatus(); - void updateWindowTitle(); - void applyDefaultExpanding(const QModelIndex &parent); - - std::unique_ptr m_ui; - Io::PasswordFile m_file; - FieldModel *m_fieldModel; - EntryModel *m_entryModel; - EntryFilterModel *m_entryFilterModel; - QUndoStack *m_undoStack; - QUndoView *m_undoView; - bool m_somethingChanged; - bool m_dontUpdateSelection; - int m_clearClipboardTimer; - MiscUtils::RecentMenuManager *m_recentMgr; - Dialogs::AboutDialog *m_aboutDlg; - QSettings &m_settings; - Dialogs::QtSettings *m_qtSettings; - Dialogs::SettingsDialog *m_settingsDlg; -}; - -} - -#endif // MAINWINDOW_H +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "./passwordgeneratordialog.h" + +#include + +#include +#include + +#include +#include + +#include + +QT_FORWARD_DECLARE_CLASS(QCloseEvent) +QT_FORWARD_DECLARE_CLASS(QTreeWidgetItem) +QT_FORWARD_DECLARE_CLASS(QUndoStack) +QT_FORWARD_DECLARE_CLASS(QUndoView) +QT_FORWARD_DECLARE_CLASS(QSettings) + +namespace Io { +DECLARE_ENUM_CLASS(EntryType, int); +DECLARE_ENUM_CLASS(FieldType, int); +} + +namespace MiscUtils { +class RecentMenuManager; +} + +namespace Dialogs { +class AboutDialog; +class SettingsDialog; +class QtSettings; +} + +namespace QtGui { + +class FieldModel; +class EntryModel; +class EntryFilterModel; + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QSettings &settings, Dialogs::QtSettings *qtSettings = nullptr, QWidget *parent = nullptr); + ~MainWindow(); + +public slots: + // file management + bool openFile(const QString &path); + void createFile(const QString &path, const QString &password); + void createFile(const QString &path); + bool createFile(); + void changePassword(); + bool saveFile(); + void exportFile(); + bool closeFile(); + // show dialogs + void showOpenFileDialog(); + void showSaveFileDialog(); + void showSettingsDialog(); + void showAboutDialog(); + void showPassowrdGeneratorDialog(); + void showUndoView(); + +protected: + bool eventFilter(QObject *obj, QEvent *event); + void closeEvent(QCloseEvent *event); + void timerEvent(QTimerEvent *event); + +private slots: + // file management + bool showFile(); + // account/categories management + void addAccount(); + void addCategory(); + void addEntry(Io::EntryType type); + void removeEntry(); + void applyFilter(const QString &filterText); + // row management + void accountSelected(const QModelIndex &selected, const QModelIndex &); + void insertRow(); + void removeRows(); + void markAsPasswordField(); + void markAsNormalField(); + void setFieldType(Io::FieldType fieldType); + void setPasswordVisibility(QAction *selectedAction); + QString selectedFieldsString() const; + void insertFields(const QString &fieldsString); + void copyFieldsForXMilliSeconds(int x = 5000); + void copyFields(); + void insertFieldsFromClipboard(); + // showing context menus + void showTreeViewContextMenu(); + void showTableViewContextMenu(); + // other + void showContainingDirectory(); + void clearClipboard(); + void setSomethingChanged(); + void setSomethingChanged(bool somethingChanged); + +private: + // showing conditional messages/prompts + bool askForCreatingFile(); + bool showNoFileOpened(); + bool showNoAccount(); + // other + void updateUiStatus(); + void updateWindowTitle(); + void applyDefaultExpanding(const QModelIndex &parent); + + std::unique_ptr m_ui; + Io::PasswordFile m_file; + FieldModel *m_fieldModel; + EntryModel *m_entryModel; + EntryFilterModel *m_entryFilterModel; + QUndoStack *m_undoStack; + QUndoView *m_undoView; + bool m_somethingChanged; + bool m_dontUpdateSelection; + int m_clearClipboardTimer; + MiscUtils::RecentMenuManager *m_recentMgr; + Dialogs::AboutDialog *m_aboutDlg; + QSettings &m_settings; + Dialogs::QtSettings *m_qtSettings; + Dialogs::SettingsDialog *m_settingsDlg; +}; + +} + +#endif // MAINWINDOW_H diff --git a/gui/passwordgeneratordialog.cpp b/gui/passwordgeneratordialog.cpp index ad20de9..aaddd9b 100644 --- a/gui/passwordgeneratordialog.cpp +++ b/gui/passwordgeneratordialog.cpp @@ -1,172 +1,172 @@ -#include "./passwordgeneratordialog.h" - -#include "ui_passwordgeneratordialog.h" - -#include - -#include - -#include - -#include - -#include -#include - -#include -#include -#include -#include - -using namespace std; -using namespace Io; -using namespace Util; -using namespace Dialogs; - -namespace QtGui { - -const char smallLetters[] = {'a','b','c','d','e','f', - 'g','h','i','j','k', - 'l','m','n','o','p', - 'q','r','s','t','u', - 'v','w','x','y','z'}; - -const char capitalLetters[] = {'A','B','C','D','E','F', - 'G','H','I','J','K', - 'L','M','N','O','P', - 'Q','R','S','T','U', - 'V','W','X','Y','Z'}; - -const char digits[] = {'0','1','2','3','4', - '5','6','7','8','9'}; - -/*! - * \class PasswordGeneratorDialog - * \brief The PasswordGeneratorDialog class provides a password generation dialog. - */ - -/*! - * \brief Constructs a new password generator dialog. - */ -PasswordGeneratorDialog::PasswordGeneratorDialog(QWidget *parent) : - QDialog(parent), - m_ui(new Ui::PasswordGeneratorDialog) -{ - m_ui->setupUi(this); -#ifdef Q_OS_WIN32 - setStyleSheet(QStringLiteral("%1 QCommandLinkButton { font-size: 12pt; color: %2; font-weight: normal; }").arg(dialogStyle(), instructionTextColor().name())); -#endif - setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint); - - connect(m_ui->copyPasswordCommandLinkButton, &QCommandLinkButton::clicked, this, &PasswordGeneratorDialog::copyPassword); - connect(m_ui->generatePassowordCommandLinkButton, &QCommandLinkButton::clicked, this, &PasswordGeneratorDialog::generateNewPassword); - connect(m_ui->useCapitalLettersCheckBox, &QCheckBox::stateChanged, this, &PasswordGeneratorDialog::handleCheckedCategoriesChanged); - connect(m_ui->useCapitalLettersCheckBox, &QCheckBox::stateChanged, this, &PasswordGeneratorDialog::handleCheckedCategoriesChanged); - connect(m_ui->useSmallLettersCheckBox, &QCheckBox::stateChanged, this, &PasswordGeneratorDialog::handleCheckedCategoriesChanged); - connect(m_ui->useDigitsCheckBox, &QCheckBox::stateChanged, this, &PasswordGeneratorDialog::handleCheckedCategoriesChanged); - connect(m_ui->otherCharsLineEdit, &QLineEdit::textChanged, this, &PasswordGeneratorDialog::handleCheckedCategoriesChanged); - connect(m_ui->passwordLineEdit, &QLineEdit::textChanged, this, &PasswordGeneratorDialog::handlePasswordChanged); - connect(m_ui->closePushButton, &QPushButton::clicked, this, &PasswordGeneratorDialog::close); - - handlePasswordChanged(); -} - -/*! - * \brief Destroys the dialog. - */ -PasswordGeneratorDialog::~PasswordGeneratorDialog() -{ - delete m_ui; -} - -/*! - * \brief Generates and shows a new password. - */ -void PasswordGeneratorDialog::generateNewPassword() -{ - int length = m_ui->LengthSpinBox->value(); - if(length > 0) { - if(m_charset.empty()) { - bool useSmallLetters = m_ui->useSmallLettersCheckBox->isChecked(); - bool useCapitalLetters = m_ui->useCapitalLettersCheckBox->isChecked(); - bool useDigits = m_ui->useDigitsCheckBox->isChecked(); - QString otherChars = m_ui->otherCharsLineEdit->text(); - int charsetSize = otherChars.length(); - if(useSmallLetters) { - charsetSize += sizeof(smallLetters); - } - if(useCapitalLetters) { - charsetSize += sizeof(capitalLetters); - } - if(useDigits) { - charsetSize += sizeof(digits); - } - m_charset.reserve(charsetSize); - if(useSmallLetters) { - m_charset.insert(m_charset.end(), std::begin(smallLetters), std::end(smallLetters)); - } - if(useCapitalLetters) { - m_charset.insert(m_charset.end(), std::begin(capitalLetters), std::end(capitalLetters)); - } - if(useDigits) { - m_charset.insert(m_charset.end(), std::begin(digits), std::end(digits)); - } - char charval; - foreach(QChar qchar, otherChars) { - charval = qchar.toLatin1(); - if(charval != '\x00' && charval != ' ' && std::find(m_charset.begin(), m_charset.end(), charval) == m_charset.end()) { - m_charset.push_back(charval); - } - } - } - if(!m_charset.empty()) { - try { - default_random_engine rng(m_random()); - uniform_int_distribution<> dist(0, m_charset.size() - 1); - auto randchar = [this, &dist, &rng]() { - return m_charset[dist(rng)]; - }; - string res(length, 0); - generate_n(res.begin(), length, randchar); - m_ui->passwordLineEdit->setText(QString::fromLatin1(res.c_str())); - } catch(const CryptoException &ex) { - QMessageBox::warning(this, QApplication::applicationName(), tr("Failed to generate password.\nOpenSSL error: %1").arg(QString::fromLocal8Bit(ex.what()))); - } - } else { - QMessageBox::warning(this, QApplication::applicationName(), tr("You have to select at least one checkbox.")); - } - } else { - QMessageBox::warning(this, QApplication::applicationName(), tr("The length has to be at least one.")); - } -} - -/*! - * \brief Handles when the user checked or unchecked a category. - */ -void PasswordGeneratorDialog::handleCheckedCategoriesChanged() -{ - m_ui->generatePassowordCommandLinkButton->setEnabled(m_ui->useCapitalLettersCheckBox->isChecked() - || m_ui->useDigitsCheckBox->isChecked() - || m_ui->useSmallLettersCheckBox->isChecked() - || !m_ui->otherCharsLineEdit->text().isEmpty()); - m_charset.clear(); -} - -/*! - * \brief Handles when the password changed. - */ -void PasswordGeneratorDialog::handlePasswordChanged() -{ - m_ui->copyPasswordCommandLinkButton->setEnabled(m_ui->passwordLineEdit->text().count() > 0); -} - -/*! - * \brief Copies the current password to the clipboard. - */ -void PasswordGeneratorDialog::copyPassword() -{ - QClipboard *cb = QApplication::clipboard(); - cb->setText(m_ui->passwordLineEdit->text()); -} - -} +#include "./passwordgeneratordialog.h" + +#include "ui_passwordgeneratordialog.h" + +#include + +#include + +#include + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace std; +using namespace Io; +using namespace Util; +using namespace Dialogs; + +namespace QtGui { + +const char smallLetters[] = {'a','b','c','d','e','f', + 'g','h','i','j','k', + 'l','m','n','o','p', + 'q','r','s','t','u', + 'v','w','x','y','z'}; + +const char capitalLetters[] = {'A','B','C','D','E','F', + 'G','H','I','J','K', + 'L','M','N','O','P', + 'Q','R','S','T','U', + 'V','W','X','Y','Z'}; + +const char digits[] = {'0','1','2','3','4', + '5','6','7','8','9'}; + +/*! + * \class PasswordGeneratorDialog + * \brief The PasswordGeneratorDialog class provides a password generation dialog. + */ + +/*! + * \brief Constructs a new password generator dialog. + */ +PasswordGeneratorDialog::PasswordGeneratorDialog(QWidget *parent) : + QDialog(parent), + m_ui(new Ui::PasswordGeneratorDialog) +{ + m_ui->setupUi(this); +#ifdef Q_OS_WIN32 + setStyleSheet(QStringLiteral("%1 QCommandLinkButton { font-size: 12pt; color: %2; font-weight: normal; }").arg(dialogStyle(), instructionTextColor().name())); +#endif + setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint); + + connect(m_ui->copyPasswordCommandLinkButton, &QCommandLinkButton::clicked, this, &PasswordGeneratorDialog::copyPassword); + connect(m_ui->generatePassowordCommandLinkButton, &QCommandLinkButton::clicked, this, &PasswordGeneratorDialog::generateNewPassword); + connect(m_ui->useCapitalLettersCheckBox, &QCheckBox::stateChanged, this, &PasswordGeneratorDialog::handleCheckedCategoriesChanged); + connect(m_ui->useCapitalLettersCheckBox, &QCheckBox::stateChanged, this, &PasswordGeneratorDialog::handleCheckedCategoriesChanged); + connect(m_ui->useSmallLettersCheckBox, &QCheckBox::stateChanged, this, &PasswordGeneratorDialog::handleCheckedCategoriesChanged); + connect(m_ui->useDigitsCheckBox, &QCheckBox::stateChanged, this, &PasswordGeneratorDialog::handleCheckedCategoriesChanged); + connect(m_ui->otherCharsLineEdit, &QLineEdit::textChanged, this, &PasswordGeneratorDialog::handleCheckedCategoriesChanged); + connect(m_ui->passwordLineEdit, &QLineEdit::textChanged, this, &PasswordGeneratorDialog::handlePasswordChanged); + connect(m_ui->closePushButton, &QPushButton::clicked, this, &PasswordGeneratorDialog::close); + + handlePasswordChanged(); +} + +/*! + * \brief Destroys the dialog. + */ +PasswordGeneratorDialog::~PasswordGeneratorDialog() +{ + delete m_ui; +} + +/*! + * \brief Generates and shows a new password. + */ +void PasswordGeneratorDialog::generateNewPassword() +{ + int length = m_ui->LengthSpinBox->value(); + if(length > 0) { + if(m_charset.empty()) { + bool useSmallLetters = m_ui->useSmallLettersCheckBox->isChecked(); + bool useCapitalLetters = m_ui->useCapitalLettersCheckBox->isChecked(); + bool useDigits = m_ui->useDigitsCheckBox->isChecked(); + QString otherChars = m_ui->otherCharsLineEdit->text(); + int charsetSize = otherChars.length(); + if(useSmallLetters) { + charsetSize += sizeof(smallLetters); + } + if(useCapitalLetters) { + charsetSize += sizeof(capitalLetters); + } + if(useDigits) { + charsetSize += sizeof(digits); + } + m_charset.reserve(charsetSize); + if(useSmallLetters) { + m_charset.insert(m_charset.end(), std::begin(smallLetters), std::end(smallLetters)); + } + if(useCapitalLetters) { + m_charset.insert(m_charset.end(), std::begin(capitalLetters), std::end(capitalLetters)); + } + if(useDigits) { + m_charset.insert(m_charset.end(), std::begin(digits), std::end(digits)); + } + char charval; + foreach(QChar qchar, otherChars) { + charval = qchar.toLatin1(); + if(charval != '\x00' && charval != ' ' && std::find(m_charset.begin(), m_charset.end(), charval) == m_charset.end()) { + m_charset.push_back(charval); + } + } + } + if(!m_charset.empty()) { + try { + default_random_engine rng(m_random()); + uniform_int_distribution<> dist(0, m_charset.size() - 1); + auto randchar = [this, &dist, &rng]() { + return m_charset[dist(rng)]; + }; + string res(length, 0); + generate_n(res.begin(), length, randchar); + m_ui->passwordLineEdit->setText(QString::fromLatin1(res.c_str())); + } catch(const CryptoException &ex) { + QMessageBox::warning(this, QApplication::applicationName(), tr("Failed to generate password.\nOpenSSL error: %1").arg(QString::fromLocal8Bit(ex.what()))); + } + } else { + QMessageBox::warning(this, QApplication::applicationName(), tr("You have to select at least one checkbox.")); + } + } else { + QMessageBox::warning(this, QApplication::applicationName(), tr("The length has to be at least one.")); + } +} + +/*! + * \brief Handles when the user checked or unchecked a category. + */ +void PasswordGeneratorDialog::handleCheckedCategoriesChanged() +{ + m_ui->generatePassowordCommandLinkButton->setEnabled(m_ui->useCapitalLettersCheckBox->isChecked() + || m_ui->useDigitsCheckBox->isChecked() + || m_ui->useSmallLettersCheckBox->isChecked() + || !m_ui->otherCharsLineEdit->text().isEmpty()); + m_charset.clear(); +} + +/*! + * \brief Handles when the password changed. + */ +void PasswordGeneratorDialog::handlePasswordChanged() +{ + m_ui->copyPasswordCommandLinkButton->setEnabled(m_ui->passwordLineEdit->text().count() > 0); +} + +/*! + * \brief Copies the current password to the clipboard. + */ +void PasswordGeneratorDialog::copyPassword() +{ + QClipboard *cb = QApplication::clipboard(); + cb->setText(m_ui->passwordLineEdit->text()); +} + +} diff --git a/gui/passwordgeneratordialog.h b/gui/passwordgeneratordialog.h index bf2f4a8..e0999bc 100644 --- a/gui/passwordgeneratordialog.h +++ b/gui/passwordgeneratordialog.h @@ -1,38 +1,38 @@ -#ifndef PASSWORDGENERATORDIALOG_H -#define PASSWORDGENERATORDIALOG_H - -#include - -#include - -#include - -namespace QtGui { - -namespace Ui { -class PasswordGeneratorDialog; -} - -class PasswordGeneratorDialog : public QDialog -{ - Q_OBJECT - -public: - explicit PasswordGeneratorDialog(QWidget *parent = 0); - ~PasswordGeneratorDialog(); - -private Q_SLOTS: - void generateNewPassword(); - void handleCheckedCategoriesChanged(); - void handlePasswordChanged(); - void copyPassword(); - -private: - Ui::PasswordGeneratorDialog *m_ui; - std::vector m_charset; - Util::OpenSslRandomDevice m_random; -}; - -} - -#endif // PASSWORDGENERATORDIALOG_H +#ifndef PASSWORDGENERATORDIALOG_H +#define PASSWORDGENERATORDIALOG_H + +#include + +#include + +#include + +namespace QtGui { + +namespace Ui { +class PasswordGeneratorDialog; +} + +class PasswordGeneratorDialog : public QDialog +{ + Q_OBJECT + +public: + explicit PasswordGeneratorDialog(QWidget *parent = 0); + ~PasswordGeneratorDialog(); + +private Q_SLOTS: + void generateNewPassword(); + void handleCheckedCategoriesChanged(); + void handlePasswordChanged(); + void copyPassword(); + +private: + Ui::PasswordGeneratorDialog *m_ui; + std::vector m_charset; + Util::OpenSslRandomDevice m_random; +}; + +} + +#endif // PASSWORDGENERATORDIALOG_H diff --git a/main.cpp b/main.cpp index cca5702..438ffa1 100644 --- a/main.cpp +++ b/main.cpp @@ -1,105 +1,105 @@ -#include "./cli/cli.h" -#ifdef GUI_QTWIDGETS -# include "./gui/initiategui.h" -#endif -#ifdef GUI_QTQUICK -# include "./quickgui/initiatequick.h" -#endif - -#include "resources/config.h" - -#include - -#include -#include -#include - -#if defined(GUI_QTWIDGETS) || defined(GUI_QTQUICK) -# include -# include -#else -# include -#endif - -#include - -using namespace std; -using namespace ApplicationUtilities; -using namespace Util; - -int main(int argc, char *argv[]) -{ - // init open ssl - OpenSsl::init(); - // setup argument parser - SET_APPLICATION_INFO; - ArgumentParser parser; - // file argument - Argument fileArg("file", 'f', "specifies the file to be opened (or created when using --modify)"); - fileArg.setValueNames({"path"}); - fileArg.setRequiredValueCount(1); - fileArg.setCombinable(true); - fileArg.setRequired(false); - fileArg.setImplicit(true); - // Qt configuration arguments - QT_CONFIG_ARGUMENTS qtConfigArgs; - qtConfigArgs.qtWidgetsGuiArg().addSubArgument(&fileArg); - // cli argument - Argument cliArg("interactive-cli", 'i', "starts the interactive command line interface"); - cliArg.setSubArguments({&fileArg}); - // help argument - HelpArgument helpArg(parser); - parser.setMainArguments({&qtConfigArgs.qtWidgetsGuiArg(), &qtConfigArgs.qtQuickGuiArg(), &cliArg, &helpArg}); - // holds the application's return code - int res = 0; - // parse the specified arguments - try { - parser.parseArgs(argc, argv); - if(cliArg.isPresent()) { - Cli::InteractiveCli cli; - if(fileArg.isPresent()) { - cli.run(fileArg.values().front()); - } else { - cli.run(); - } - } else if(qtConfigArgs.areQtGuiArgsPresent()) { - // run Qt gui if no arguments, --qt-gui or --qt-quick-gui specified, a file might be specified -#if defined(GUI_QTWIDGETS) || defined(GUI_QTQUICK) - QString file; - if(fileArg.isPresent()) { - file = QString::fromLocal8Bit(fileArg.values().front()); - } -#endif - if(qtConfigArgs.qtWidgetsGuiArg().isPresent()) { -#ifdef GUI_QTWIDGETS - res = QtGui::runWidgetsGui(argc, argv, qtConfigArgs, file); -#else - CMD_UTILS_START_CONSOLE; - cout << "The application has not been built with Qt widgets support." << endl; -#endif - } else if(qtConfigArgs.qtQuickGuiArg().isPresent()) { -#ifdef GUI_QTQUICK - res = QtGui::runQuickGui(argc, argv, qtConfigArgs); -#else - CMD_UTILS_START_CONSOLE; - cout << "The application has not been built with Qt quick support." << endl; -#endif - } else { -#if defined(GUI_QTQUICK) - res = QtGui::runQuickGui(argc, argv, qtConfigArgs); -#elif defined(GUI_QTWIDGETS) - res = QtGui::runWidgetsGui(argc, argv, qtConfigArgs, file); -#else - CMD_UTILS_START_CONSOLE; - cout << "See --help for usage." << endl; -#endif - } - } - } catch(const Failure &ex) { - CMD_UTILS_START_CONSOLE; - cout << "Unable to parse arguments. " << ex.what() << "\nSee --help for available commands." << endl; - } - // clean open ssl - OpenSsl::clean(); - return res; -} +#include "./cli/cli.h" +#ifdef GUI_QTWIDGETS +# include "./gui/initiategui.h" +#endif +#ifdef GUI_QTQUICK +# include "./quickgui/initiatequick.h" +#endif + +#include "resources/config.h" + +#include + +#include +#include +#include + +#if defined(GUI_QTWIDGETS) || defined(GUI_QTQUICK) +# include +# include +#else +# include +#endif + +#include + +using namespace std; +using namespace ApplicationUtilities; +using namespace Util; + +int main(int argc, char *argv[]) +{ + // init open ssl + OpenSsl::init(); + // setup argument parser + SET_APPLICATION_INFO; + ArgumentParser parser; + // file argument + Argument fileArg("file", 'f', "specifies the file to be opened (or created when using --modify)"); + fileArg.setValueNames({"path"}); + fileArg.setRequiredValueCount(1); + fileArg.setCombinable(true); + fileArg.setRequired(false); + fileArg.setImplicit(true); + // Qt configuration arguments + QT_CONFIG_ARGUMENTS qtConfigArgs; + qtConfigArgs.qtWidgetsGuiArg().addSubArgument(&fileArg); + // cli argument + Argument cliArg("interactive-cli", 'i', "starts the interactive command line interface"); + cliArg.setSubArguments({&fileArg}); + // help argument + HelpArgument helpArg(parser); + parser.setMainArguments({&qtConfigArgs.qtWidgetsGuiArg(), &qtConfigArgs.qtQuickGuiArg(), &cliArg, &helpArg}); + // holds the application's return code + int res = 0; + // parse the specified arguments + try { + parser.parseArgs(argc, argv); + if(cliArg.isPresent()) { + Cli::InteractiveCli cli; + if(fileArg.isPresent()) { + cli.run(fileArg.values().front()); + } else { + cli.run(); + } + } else if(qtConfigArgs.areQtGuiArgsPresent()) { + // run Qt gui if no arguments, --qt-gui or --qt-quick-gui specified, a file might be specified +#if defined(GUI_QTWIDGETS) || defined(GUI_QTQUICK) + QString file; + if(fileArg.isPresent()) { + file = QString::fromLocal8Bit(fileArg.values().front()); + } +#endif + if(qtConfigArgs.qtWidgetsGuiArg().isPresent()) { +#ifdef GUI_QTWIDGETS + res = QtGui::runWidgetsGui(argc, argv, qtConfigArgs, file); +#else + CMD_UTILS_START_CONSOLE; + cout << "The application has not been built with Qt widgets support." << endl; +#endif + } else if(qtConfigArgs.qtQuickGuiArg().isPresent()) { +#ifdef GUI_QTQUICK + res = QtGui::runQuickGui(argc, argv, qtConfigArgs); +#else + CMD_UTILS_START_CONSOLE; + cout << "The application has not been built with Qt quick support." << endl; +#endif + } else { +#if defined(GUI_QTQUICK) + res = QtGui::runQuickGui(argc, argv, qtConfigArgs); +#elif defined(GUI_QTWIDGETS) + res = QtGui::runWidgetsGui(argc, argv, qtConfigArgs, file); +#else + CMD_UTILS_START_CONSOLE; + cout << "See --help for usage." << endl; +#endif + } + } + } catch(const Failure &ex) { + CMD_UTILS_START_CONSOLE; + cout << "Unable to parse arguments. " << ex.what() << "\nSee --help for available commands." << endl; + } + // clean open ssl + OpenSsl::clean(); + return res; +}