From 00d158856a8e8d9cd57abbca342ddea0355383d9 Mon Sep 17 00:00:00 2001 From: xavier Date: Fri, 13 Jul 2012 07:21:19 +0000 Subject: [PATCH] Reworked the way project saving is presented to users. git-svn-id: svn+ssh://svn.tuxfamily.org/svnroot/qet/qet/trunk@1907 bfdf4180-ca20-0410-9c96-a3a8aa849046 --- sources/closediagramsdialog.cpp | 313 +++++++++++++++++++++++++++++++ sources/closediagramsdialog.h | 92 ++++++++++ sources/projectview.cpp | 314 +++++++++++++++++--------------- sources/projectview.h | 26 ++- sources/qetdiagrameditor.cpp | 76 +++++--- sources/qetdiagrameditor.h | 15 +- sources/qetproject.cpp | 27 +-- sources/qetproject.h | 3 +- sources/qetresult.cpp | 70 +++++++ sources/qetresult.h | 46 +++++ 10 files changed, 787 insertions(+), 195 deletions(-) create mode 100644 sources/closediagramsdialog.cpp create mode 100644 sources/closediagramsdialog.h create mode 100644 sources/qetresult.cpp create mode 100644 sources/qetresult.h diff --git a/sources/closediagramsdialog.cpp b/sources/closediagramsdialog.cpp new file mode 100644 index 000000000..63a05dba5 --- /dev/null +++ b/sources/closediagramsdialog.cpp @@ -0,0 +1,313 @@ +/* + Copyright 2006-2012 Xavier Guerrin + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#include "closediagramsdialog.h" +#include "diagram.h" +#include "qeticons.h" + +/** + Construct a dialog showing \a diagrams. + @param parent Parent QWidget +*/ +CloseDiagramsDialog::CloseDiagramsDialog(const QList &diagrams, QWidget *parent) : + QDialog(parent), + diagrams_list_(diagrams), + answer_(-1) +{ + initWidgets(); + initLayout(); +} + +/** + Destructor +*/ +CloseDiagramsDialog::~CloseDiagramsDialog() { +} + +/** + @return the user answer once the dialog has been executed. +*/ +int CloseDiagramsDialog::answer() const { + return(answer_); +} + +/** + @return what the user wants to do with \a diagram + @see CloseDiagramsDialog::Actions +*/ +int CloseDiagramsDialog::actionForDiagram(Diagram *diagram) { + if (QCheckBox *checkbox = getCheckBoxForDiagram(diagram)) { + if (!diagram -> undoStack().isClean()) { + return(checkbox -> isChecked() ? Save : DoNotSave); + } else if (!diagram -> wasWritten()) { + return(checkbox -> isChecked() ? Save : Remove); + } + } + return(Unknown); +} + +/** + @return the list of diagrams for which users have chosen the \a action + action. +*/ +QList CloseDiagramsDialog::diagramsByAction(Actions action) { + QList diagrams; + foreach (Diagram *diagram, diagrams_list_) { + if (actionForDiagram(diagram) == action) { + diagrams << diagram; + } + } + return(diagrams); +} + +/** + Initialize widgets. +*/ +void CloseDiagramsDialog::initWidgets() { + setWindowTitle(tr("Fermer un projet", "window title")); + + connect(&show_mapper_, SIGNAL(mapped(int)), this, SLOT(requireShowDiagram(int))); + + // label when diagrams were modified + informative_label1_ = new QLabel( + tr( + "Les sch\351mas ci-dessous contiennent des modifications non " + "enregistr\351es. Faut-il les sauvegarder ?", + "informative label" + ) + ); + informative_label1_ -> setWordWrap(true); + + // label when no diagrams were modified + informative_label2_ = new QLabel(tr("Voulez-vous enregistrer le projet ?", "informative label")); + informative_label2_ -> setWordWrap(true); + + // header labels + QLabel *state_label = new QLabel(tr("\311tat", "column header")); + QLabel *title_label = new QLabel(tr("Sch\351ma", "column header")); + + // header checkbox in order to check/uncheck all diagrams + all_checkbox_ = new QCheckBox(tr("Action", "column header")); + all_checkbox_ -> setChecked(true); + connect(all_checkbox_, SIGNAL(stateChanged(int)), this, SLOT(topCheckBoxChangedState(int))); + + // spacers inserted in the header row + QSpacerItem *spacer1 = new QSpacerItem(10, 10, QSizePolicy::Expanding, QSizePolicy::Minimum); + QSpacerItem *spacer2 = new QSpacerItem(25, 10, QSizePolicy::Preferred, QSizePolicy::Minimum); + + buttons_ = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Discard | QDialogButtonBox::Cancel); + connect(buttons_, SIGNAL(clicked(QAbstractButton *)), this, SLOT(storeAnswer(QAbstractButton *))); + + // widget layout + diagrams_list_layout_ = new QGridLayout(); + diagrams_list_layout_ -> addWidget(state_label, 0, 1, 1, 1, Qt::AlignCenter); + diagrams_list_layout_ -> addItem(spacer1, 0, 2); + diagrams_list_layout_ -> addWidget(title_label, 0, 3, 1, 1, Qt::AlignCenter); + diagrams_list_layout_ -> addItem(spacer2, 0, 4); + diagrams_list_layout_ -> addWidget(all_checkbox_, 0, 5, 1, 1, Qt::AlignCenter); + + // widget + diagrams_list_widget_ = new QWidget(); + diagrams_list_widget_ -> setLayout(diagrams_list_layout_); + diagrams_list_widget_ -> setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + // scroll area + diagrams_list_area_ = new QScrollArea(); + diagrams_list_area_ -> setWidgetResizable(true); + diagrams_list_area_ -> setFrameStyle(QFrame::Plain | QFrame::NoFrame); + + loadDiagramsList(); + diagrams_list_area_ -> setWidget(diagrams_list_widget_); +} + +/** + Initialize layout. +*/ +void CloseDiagramsDialog::initLayout() { + setMinimumSize(650, 340); + + QVBoxLayout *vlayout0 = new QVBoxLayout(); + vlayout0 -> addWidget(informative_label1_); + vlayout0 -> addWidget(informative_label2_); + vlayout0 -> addWidget(diagrams_list_area_); + vlayout0 -> addWidget(buttons_); + setLayout(vlayout0); +} + +/** + Create a visual list of all modified diagrams. +*/ +void CloseDiagramsDialog::loadDiagramsList() { + if (diagrams_list_.count()) { + int row_id = 1; + foreach (Diagram *diagram, diagrams_list_) { + addDiagram(diagram, row_id); + ++ row_id; + } + informative_label2_ -> setVisible(false); + } else { + informative_label1_ -> setVisible(false); + diagrams_list_area_ -> setVisible(false); + } +} + +/** + Add \a diagram to the \a row_id row of the visual list. +*/ +void CloseDiagramsDialog::addDiagram(Diagram *diagram, int row_id) { + QLabel *diagram_title = new QLabel(diagramTitle(diagram)); + QPushButton *diagram_show = new QPushButton(QET::Icons::ZoomOriginal, ""); // tr("Afficher ce sch\351ma", "button label") + show_mapper_.setMapping(diagram_show, row_id - 1); + connect(diagram_show, SIGNAL(released()), &show_mapper_, SLOT(map())); + QLabel *diagram_status = new QLabel(diagramStatus(diagram)); + QCheckBox *diagram_checkbox = new QCheckBox(diagramAction(diagram)); + diagram_checkbox -> setChecked(true); + connect(diagram_checkbox, SIGNAL(stateChanged(int)), this, SLOT(lambdaCheckBoxChangedState(int))); + + diagrams_list_layout_ -> addWidget(diagram_show, row_id, 0, 1, 1, Qt::AlignCenter); + diagrams_list_layout_ -> addWidget(diagram_title, row_id, 1, 1, 1, Qt::AlignCenter); + diagrams_list_layout_ -> addWidget(diagram_status, row_id, 3, 1, 1, Qt::AlignCenter); + diagrams_list_layout_ -> addWidget(diagram_checkbox, row_id, 5, 1, 1, Qt::AlignCenter); +} + +/** + @return the action checkbox for \a diagram, or 0 if no adequate checkbox could be found. +*/ +QCheckBox *CloseDiagramsDialog::getCheckBoxForDiagram(Diagram *diagram) { + int diagram_index = diagrams_list_.indexOf(diagram); + if (diagram_index == -1) return(0); + + // We add 1 because there is one row dedicated to column headers; + // 4 is the column containing checkboxes, see initWidgets(). + QLayoutItem *item = diagrams_list_layout_ -> itemAtPosition(diagram_index + 1, 5); + if (!item) return(0); + + QWidget *widget = item -> widget(); + if (!widget) return(0); + + return(static_cast(widget)); +} + +/** + @return the title for \a diagram +*/ +QString CloseDiagramsDialog::diagramTitle(Diagram *diagram) { + if (!diagram -> title().isEmpty()) { + return(diagram -> title()); + } + return(tr("Sch\351ma sans titre", "fallback diagram title")); +} + +/** + @return a string describing the status of \a diagram +*/ +QString CloseDiagramsDialog::diagramStatus(Diagram *diagram) { + if (!diagram) return(QString()); + if (!diagram -> undoStack().isClean()) { + return(tr("Modifi\351", "diagram status")); + } else if (!diagram -> wasWritten()) { + return(tr("Ajout\351, non modifi\351", "diagram status")); + } else { + return(QString()); + } +} + +/** + @return a string describing the effect of saving \a diagram (e.g. "Save" or + "Keep"). +*/ +QString CloseDiagramsDialog::diagramAction(Diagram *diagram) { + if (!diagram) return(QString()); + if (!diagram -> undoStack().isClean()) { + return(tr("Enregistrer", "diagram action")); + } else if (!diagram -> wasWritten()) { + return(tr("Conserver", "diagram action")); + } else { + return(QString()); + } +} + +/** + Adjust the state of the header checkbox when a diagram checkbox was + switched to \a new_state. +*/ +void CloseDiagramsDialog::lambdaCheckBoxChangedState(int new_state) { + bool state = new_state; + + bool all_same_state = true; + foreach (Diagram *diagram, diagrams_list_) { + if (QCheckBox *checkbox = getCheckBoxForDiagram(diagram)) { + if (checkbox -> isChecked() != state) { + all_same_state = false; + break; + } + } + } + + all_checkbox_ -> blockSignals(true); + if (all_same_state) { + all_checkbox_ -> setTristate(false); + all_checkbox_ -> setChecked(state); + } else { + all_checkbox_ -> setTristate(true); + all_checkbox_ -> setCheckState(Qt::PartiallyChecked); + } + all_checkbox_ -> blockSignals(false); + all_checkbox_ -> update(); +} + +/** + Adjust diagram checkboxes when the header checkbox was switched to \a + new_state. +*/ +void CloseDiagramsDialog::topCheckBoxChangedState(int new_state) { + setCheckedAll((bool)new_state); +} + +/** + Set all diagram checkboxes to the checked (true) or unchecked (false) + state. +*/ +void CloseDiagramsDialog::setCheckedAll(bool checked) { + foreach (Diagram *diagram, diagrams_list_) { + if (QCheckBox *checkbox = getCheckBoxForDiagram(diagram)) { + if (checkbox -> isChecked() != checked) { + checkbox -> blockSignals(true); + checkbox -> setChecked(checked); + checkbox -> blockSignals(false); + } + } + } +} + +/** + Find the diagram at \a diagram_index and emts the showDiagram() signal with + it. +*/ +void CloseDiagramsDialog::requireShowDiagram(int diagram_index) { + Diagram *diagram = diagrams_list_.value(diagram_index); + if (!diagram) return; + emit(showDiagram(diagram)); +} + +/** + Store the user answer when the dialog is validated or rejected. +*/ +void CloseDiagramsDialog::storeAnswer(QAbstractButton *button) { + answer_ = buttons_ -> buttonRole(button); + accept(); +} diff --git a/sources/closediagramsdialog.h b/sources/closediagramsdialog.h new file mode 100644 index 000000000..97d61ad7c --- /dev/null +++ b/sources/closediagramsdialog.h @@ -0,0 +1,92 @@ +/* + Copyright 2006-2012 Xavier Guerrin + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#ifndef CLOSE_DIAGRAMS_DIALOG_H +#define CLOSE_DIAGRAMS_DIALOG_H +#include +#include +class QAbstractButton; +class QDialogButtonBox; +class QCheckBox; +class QLabel; +class QPushButton; +class Diagram; +class QGridLayout; +class QScrollArea; + +/** + This class represents a dialog asking users whether they want to save + their modified project when it is being closed and what they wish to save + in it. +*/ +class CloseDiagramsDialog : public QDialog { + Q_OBJECT + public: + enum Actions { + Unknown, + Save, + DoNotSave, + Remove + }; + + // Constructors, destructor + public: + CloseDiagramsDialog(const QList &diagrams, QWidget *parent = 0); + virtual ~CloseDiagramsDialog(); + private: + CloseDiagramsDialog(const CloseDiagramsDialog &); + + // methods + public: + int answer() const; + int actionForDiagram(Diagram *); + QList diagramsByAction(Actions); + + private: + void initWidgets(); + void initLayout(); + void loadDiagramsList(); + void addDiagram(Diagram *, int); + QCheckBox *getCheckBoxForDiagram(Diagram *); + QString diagramTitle(Diagram *); + QString diagramStatus(Diagram *); + QString diagramAction(Diagram *); + + signals: + void showDiagram(Diagram *); + + private slots: + void lambdaCheckBoxChangedState(int); + void topCheckBoxChangedState(int); + void setCheckedAll(bool); + void requireShowDiagram(int); + void storeAnswer(QAbstractButton *); + + // attributes + private: + QList diagrams_list_; ///< List of (modified or newly added) diagrams displayed by the dialog + QLabel *informative_label1_; ///< Informative label when there are modified diagrams + QLabel *informative_label2_; ///< Informative label when there is no modified diagram + QCheckBox *all_checkbox_; ///< Header checkbox to check/uncheck all other checkboxes + QScrollArea *diagrams_list_area_; ///< Scroll area to make the diagrams visual list fit in the dialog + QWidget *diagrams_list_widget_; ///< Scrolled widget + QGridLayout *diagrams_list_layout_; ///< Layout used to list diagrams + QDialogButtonBox *buttons_; ///< Buttons for users to input their final choice + int answer_; ///< Reflects the user answer once the diagram has been executed + QSignalMapper show_mapper_; ///< Signal mapper for the "show diagram" buttons to work +}; +#endif diff --git a/sources/projectview.cpp b/sources/projectview.cpp index 3d574a2bd..e40192dab 100644 --- a/sources/projectview.cpp +++ b/sources/projectview.cpp @@ -18,6 +18,7 @@ #include "projectview.h" #include "qetproject.h" #include "configdialog.h" +#include "closediagramsdialog.h" #include "projectconfigpages.h" #include "diagramview.h" #include "diagram.h" @@ -93,6 +94,36 @@ QList ProjectView::diagrams() const { return(diagrams_); } +/** + @return A list containing child diagrams matching provided \a options. +*/ +QList ProjectView::getDiagrams(ProjectSaveOptions options) { + QList selection; + if ((options & AllDiagrams) == AllDiagrams) { + selection << project_ -> diagrams(); + } else { + Diagram *current = 0; + if (DiagramView *view = currentDiagram()) { + current = view -> diagram(); + } + if (options & CurrentDiagram) { + if (current) selection << current; + } else if (options & AllDiagramsButCurrent) { + selection = project_ -> diagrams(); + selection.removeOne(current); + } + } + + if (options & ModifiedDiagramsOnly) { + foreach (Diagram *diagram, selection) { + if (!diagram -> undoStack().isClean() || !diagram -> wasWritten()) continue; + selection.removeOne(diagram); + } + } + + return(selection); +} + /** @return le schema actuellement active */ @@ -106,35 +137,7 @@ DiagramView *ProjectView::currentDiagram() const { @param qce Le QCloseEvent decrivant l'evenement */ void ProjectView::closeEvent(QCloseEvent *qce) { - // si la vue n'est pas liee a un projet, on ferme directement - if (!project_) { - qce -> accept(); - emit(projectClosed(this)); - return; - } - - // si le projet est comme neuf et n'est pas enregistre, on ferme directement - if (!project_ -> projectWasModified() && project_ -> filePath().isEmpty()) { - qce -> accept(); - emit(projectClosed(this)); - return; - } - - bool can_close_project = true; - if (!tryClosing()) { - // l'utilisateur a refuse la fermeture du projet - on arrete la - can_close_project = false; - } else { - // a ce stade, l'utilisateur a accepte la fermeture de tout le contenu du projet - if (!project_ -> filePath().isEmpty()) { - // si le projet a un chemin specifie, on l'enregistre et on le ferme - can_close_project = project_ -> write(); - } else { - // l'utilisateur n'enregistre pas son projet - can_close_project = true; - } - } - + bool can_close_project = tryClosing(); if (can_close_project) { qce -> accept(); emit(projectClosed(this)); @@ -152,34 +155,46 @@ void ProjectView::closeEvent(QCloseEvent *qce) { @see tryClosingDiagrams() */ bool ProjectView::tryClosing() { + if (!project_) return(true); + + // First step: require external editors closing -- users may either cancel + // the whole closing process or save (and therefore add) content into this + // project. Of course, they may also discard them. if (!tryClosingElementEditors()) { return(false); } - if (!tryClosingDiagrams()) { - return(false); + // Check how different the current situation is from a brand new, untouched project + if (project_ -> filePath().isEmpty() && !project_ -> projectWasModified()) { + return(true); } - // a ce stade, l'utilisateur a accepte de fermer tous les editeurs - // d'elements et tous les schemas - // on regarde s'il reste du contenu dans le projet - if (project_ -> projectWasModified() && project_ -> filePath().isEmpty()) { - // si oui, on propose a l'utilisateur d'enregistrer le projet - QMessageBox::StandardButton answer = QET::MessageBox::question( - this, - tr("Enregistrer le projet en cours ?", "message box title"), - QString(tr("Voulez-vous enregistrer le projet ?", "message box content")), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, - QMessageBox::Cancel - ); - if (answer == QMessageBox::Cancel) { - return(false); - } else if (answer == QMessageBox::Yes) { - return(save()); - } + // Second step: users are presented with a dialog that enables them to + // choose whether they want to: + // - cancel the closing process, + // - discard all modifications, + // - or specify what is to be saved, i.e. they choose whether they wants to + // save/give up/remove diagrams considered as modified. + int user_input = tryClosingDiagrams(); + if (user_input == QDialogButtonBox::RejectRole) { + return(false); // the closing process was cancelled + } else if (user_input == QDialogButtonBox::DestructiveRole) { + return(true); // all modifications were discarded } - return(true); + // Check how different the current situation is from a brand new, untouched project (yes , again) + if (project_ -> filePath().isEmpty() && !project_ -> projectWasModified()) { + return(true); + } + + if (project_ -> filePath().isEmpty()) { + QString filepath = askUserForFilePath(); + if (filepath.isEmpty()) return(false); // users may cancel the closing + } + QETResult result = project_ -> write(); + updateWindowTitle(); + if (!result.isOk()) emit(errorEncountered(result.errorMessage())); + return(result.isOk()); } /** @@ -215,49 +230,45 @@ bool ProjectView::tryClosingElementEditors() { l'utilisateur s'il souhaite l'enlever. @return true si tous les schemas peuvent etre fermes, false sinon */ -bool ProjectView::tryClosingDiagrams() { - if (!project_) return(true); +int ProjectView::tryClosingDiagrams() { + if (!project_) return(QDialogButtonBox::DestructiveRole); - foreach(DiagramView *diagram_view, diagrams()) { - if (!diagram_view -> diagram() -> undoStack().isClean()) { - // ce schema a ete modifie - on demande a l'utilisateur s'il veut l'enregistrer - showDiagram(diagram_view -> diagram()); - QMessageBox::StandardButton answer = QET::MessageBox::question( - this, - tr("Enregistrer le sch\351ma en cours ?", "message box title"), - QString(tr("Voulez-vous enregistrer le sch\351ma %1 ?", "message box content - %1 is a diagram title")).arg(diagram_view -> windowTitle()), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, - QMessageBox::Cancel - ); - if (answer == QMessageBox::Cancel) { - return(false); - } else if (answer == QMessageBox::Yes) { - if (!save()) { - return(false); - } - } - } else if (!diagram_view -> diagram() -> wasWritten()) { - // ce schema a ete ajoute mais pas modifie - on demande a l'utilisateur s'il veut le conserver - showDiagram(diagram_view -> diagram()); - QMessageBox::StandardButton answer = QET::MessageBox::question( - this, - tr("Enregistrer le nouveau sch\351ma ?", "message box title"), - tr("Ce sch\351ma a \351t\351 ajout\351 mais n'a \351t\351 ni modifi\351 ni enregistr\351. Voulez-vous le conserver ?", "message box content"), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, - QMessageBox::Cancel - ); - if (answer == QMessageBox::Cancel) { - return(false); - } else if (answer == QMessageBox::Yes) { - if (!save()) { - return(false); - } - } else if (answer == QMessageBox::No) { - removeDiagram(diagram_view); - } + QList modified_diagrams = getDiagrams(AllDiagrams | ModifiedDiagramsOnly); + if (!modified_diagrams.count() && !project_ -> filePath().isEmpty()) { + // nothing was modified, and we have a filepath, i.e. everything was already + // saved, i.e we can close the project right now + return(QDialogButtonBox::DestructiveRole); + } + + CloseDiagramsDialog close_dialog(modified_diagrams, this); + if (!project_ -> title().isEmpty()) { + close_dialog.setWindowTitle( + QString( + tr( + "Fermer le projet \"%1\"", + "project closing dialog title -- %1 is a project title" + ) + ).arg(project_ -> title()) + ); + } + connect(&close_dialog, SIGNAL(showDiagram(Diagram*)), this, SLOT(showDiagram(Diagram*))); + if (close_dialog.exec() == QDialog::Rejected) { + return(QDialogButtonBox::RejectRole); + } + + if (close_dialog.answer() == QDialogButtonBox::AcceptRole) { + // save diagrams the user marked as to be saved + QList to_save = close_dialog.diagramsByAction(CloseDiagramsDialog::Save); + saveDiagrams(to_save); + + // remove diagrams the user marked as to be removed + QList to_close = close_dialog.diagramsByAction(CloseDiagramsDialog::Remove); + foreach (Diagram *diagram, to_close) { + removeDiagram(diagram); } } - return(true); + + return(close_dialog.answer()); } /** @@ -290,6 +301,15 @@ QString ProjectView::askUserForFilePath(bool assign) { return(filepath); } +/** + @return the QETResult object to be returned when it appears this project + view is not associated to any project. +*/ +QETResult ProjectView::noProjectResult() const { + QETResult no_project(tr("aucun projet affich\351", "error message"), false); + return(no_project); +} + /** Ajoute un nouveau schema au ProjectView */ @@ -530,58 +550,78 @@ void ProjectView::exportProject() { } /** - Enregistre le projet dans un fichier. + Save project properties along with all modified diagrams. @see filePath() @see setFilePath() - @return true si l'enregistrement a reussi, false sinon + @return a QETResult object reflecting the situation */ -bool ProjectView::save() { - bool result = false; - if (project_) { - if (project_ -> filePath().isEmpty()) { - // le projet n'est pas encore enregistre dans un fichier - // save() equivaut alors a saveAs() - return(saveAs()); - } - // on enregistre le schema en cours - if (DiagramView *current_view = currentDiagram()) { - if (Diagram *diagram = current_view -> diagram()) { - diagram -> write(); - result = true; - } - } else { - // s'il n'y a pas de schema, on appelle directement la methode write() - result = project_ -> write(); - } +QETResult ProjectView::save() { + return(doSave(AllDiagrams | ModifiedDiagramsOnly)); +} + +/** + Ask users for a filepath in order to save the project. + @param options May be used to specify what should be saved; defaults to + all modified diagrams. + @return a QETResult object reflecting the situation; note that a valid + QETResult object is returned if the operation was cancelled. +*/ +QETResult ProjectView::saveAs(ProjectSaveOptions options) { + if (!project_) return(noProjectResult()); + + QString filepath = askUserForFilePath(); + if (filepath.isEmpty()) return(QETResult()); + return(doSave(options)); +} + +/** + Save the current diagram. + @return A QETResult object reflecting the situation. +*/ +QETResult ProjectView::saveCurrentDiagram() { + return(doSave(CurrentDiagram)); +} + +/** + Save project content according to \a options, then write the project file. May + call saveAs if no filepath was provided before. + @param options May be used to specify what should be saved (e.g. modified + diagrams only). + @return a QETResult object reflecting the situation; note that a valid + QETResult object is returned if the operation was cancelled. +*/ +QETResult ProjectView::doSave(ProjectSaveOptions options) { + if (!project_) return(noProjectResult()); + + if (project_ -> filePath().isEmpty()) { + // The project has not been saved to a file yet, + // so save() actually means saveAs(). + return(saveAs(options)); } + + // look for diagrams matching the required save options + saveDiagrams(getDiagrams(options)); + + // write to file + QETResult result = project_ -> write(); updateWindowTitle(); return(result); } /** - Save all diagrams in the project. - @return False if something went wrong (no project, no filepath provided, write - error), true otherwise. + Save \a diagrams without emitting the written() signal and without writing + the project file itself. */ -bool ProjectView::saveAll() { - if (project_) { - if (project_ -> filePath().isEmpty()) { - QString filepath = askUserForFilePath(); - if (filepath.isEmpty()) return(false); - } - foreach (Diagram *diagram, project_ -> diagrams()) { - // Diagram::write() emits the written() signal, which is connected to - // QETProject::write() through QETProject::componentWritten(). We do not want - // to write the project immediately, so we block this signal. - diagram -> blockSignals(true); - diagram -> write(); - diagram -> blockSignals(false); - } - bool writing = project_ -> write(); - updateWindowTitle(); - return(writing); +void ProjectView::saveDiagrams(const QList &diagrams) { + foreach (Diagram *diagram, diagrams) { + // Diagram::write() emits the written() signal, which is connected + // to QETProject::write() through QETProject::componentWritten(). + // We do not want to write the project immediately, so we block + // this signal. + diagram -> blockSignals(true); + diagram -> write(); + diagram -> blockSignals(false); } - return(false); } /** @@ -652,20 +692,6 @@ int ProjectView::cleanProject() { return(clean_count); } -/** - Demande un nom de fichier a l'utilisateur pour enregistrer le projet - Si aucun nom n'est entre, elle renvoie faux. - Si le nom ne se termine pas par l'extension .qet, celle-ci est ajoutee. - Si l'enregistrement reussit, le nom du fichier est conserve et la fonction renvoie true. - Sinon, faux est renvoye. - @return true si l'enregistrement a reussi, false sinon -*/ -bool ProjectView::saveAs() { - QString filepath = askUserForFilePath(); - if (filepath.isEmpty()) return(false); - return(save()); -} - /** Initialize actions for this widget. */ diff --git a/sources/projectview.h b/sources/projectview.h index 74906954a..65d88a8b9 100644 --- a/sources/projectview.h +++ b/sources/projectview.h @@ -19,6 +19,7 @@ #define PROJECT_VIEW_H #include #include "templatelocation.h" +#include "qetresult.h" class QETProject; class DiagramView; class Diagram; @@ -29,6 +30,17 @@ class QETTabWidget; */ class ProjectView : public QWidget { Q_OBJECT + + public: + enum ProjectSaveOption { + ModifiedDiagramsOnly = 1, + CurrentDiagram = 2, + AllDiagramsButCurrent = 4, + AllDiagrams = 6 + }; + Q_DECLARE_FLAGS(ProjectSaveOptions, ProjectSaveOption) + + // constructeurs, destructeur public: ProjectView(QETProject *, QWidget * = 0); @@ -41,6 +53,7 @@ class ProjectView : public QWidget { QETProject *project(); void setProject(QETProject *); QList diagrams() const; + QList getDiagrams(ProjectSaveOptions options); DiagramView *currentDiagram() const; void closeEvent(QCloseEvent *); @@ -61,9 +74,11 @@ class ProjectView : public QWidget { void moveDiagramDown(Diagram *); void printProject(); void exportProject(); - bool save(); - bool saveAs(); - bool saveAll(); + QETResult save(); + QETResult saveAs(ProjectSaveOptions = ProjectSaveOptions(AllDiagrams | ModifiedDiagramsOnly)); + QETResult saveCurrentDiagram(); + QETResult doSave(ProjectSaveOptions); + void saveDiagrams(const QList &); int cleanProject(); void updateWindowTitle(); void updateTabTitle(DiagramView *, const QString &); @@ -77,6 +92,7 @@ class ProjectView : public QWidget { void diagramActivated(DiagramView *); void diagramOrderChanged(ProjectView *, int, int); void projectClosed(ProjectView *); + void errorEncountered(const QString &); // relayed signals void findElementRequired(const ElementsLocation &); void editElementRequired(const ElementsLocation &); @@ -91,8 +107,9 @@ class ProjectView : public QWidget { void rebuildDiagramsMap(); bool tryClosing(); bool tryClosingElementEditors(); - bool tryClosingDiagrams(); + int tryClosingDiagrams(); QString askUserForFilePath(bool = true); + QETResult noProjectResult() const; private slots: void tabChanged(int); @@ -113,4 +130,5 @@ class ProjectView : public QWidget { QMap diagram_ids_; QList diagrams_; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(ProjectView::ProjectSaveOptions) #endif diff --git a/sources/qetdiagrameditor.cpp b/sources/qetdiagrameditor.cpp index df83be1aa..84f7c9771 100644 --- a/sources/qetdiagrameditor.cpp +++ b/sources/qetdiagrameditor.cpp @@ -27,6 +27,7 @@ #include "qeticons.h" #include "qetelementeditor.h" #include "qetmessagebox.h" +#include "qetresult.h" #include "genericpanel.h" /** @@ -179,8 +180,8 @@ void QETDiagramEditor::actions() { open_file = new QAction(QET::Icons::DocumentOpen, tr("&Ouvrir"), this); close_file = new QAction(QET::Icons::DocumentClose, tr("&Fermer"), this); save_file = new QAction(QET::Icons::DocumentSave, tr("&Enregistrer"), this); - save_file_sous = new QAction(QET::Icons::DocumentSaveAs, tr("Enregistrer sous"), this); - save_all = new QAction(QET::Icons::DocumentSaveAll, tr("&Enregistrer tous les sch\351mas"), this); + save_file_as = new QAction(QET::Icons::DocumentSaveAs, tr("Enregistrer sous"), this); + save_cur_diagram = new QAction(QET::Icons::DocumentSaveAll, tr("&Enregistrer tous les sch\351mas"), this); import_diagram = new QAction(QET::Icons::DocumentImport, tr("&Importer"), this); export_diagram = new QAction(QET::Icons::DocumentExport, tr("E&xporter"), this); print = new QAction(QET::Icons::DocumentPrint, tr("Imprimer"), this); @@ -273,9 +274,9 @@ void QETDiagramEditor::actions() { new_file -> setStatusTip(tr("Cr\351e un nouveau sch\351ma", "status bar tip")); open_file -> setStatusTip(tr("Ouvre un sch\351ma existant", "status bar tip")); close_file -> setStatusTip(tr("Ferme le sch\351ma courant", "status bar tip")); - save_file -> setStatusTip(tr("Enregistre le sch\351ma courant", "status bar tip")); - save_file_sous -> setStatusTip(tr("Enregistre le sch\351ma courant avec un autre nom de fichier", "status bar tip")); - save_all -> setStatusTip(tr("Enregistre tous les sch\351mas du projet courant", "status bar tip")); + save_file -> setStatusTip(tr("Enregistre le projet courant et tous ses sch\351mas", "status bar tip")); + save_file_as -> setStatusTip(tr("Enregistre le project courant avec un autre nom de fichier", "status bar tip")); + save_cur_diagram -> setStatusTip(tr("Enregistre tous les sch\351mas du projet courant", "status bar tip")); import_diagram -> setStatusTip(tr("Importe un sch\351ma dans le sch\351ma courant", "status bar tip")); export_diagram -> setStatusTip(tr("Exporte le sch\351ma courant dans un autre format", "status bar tip")); print -> setStatusTip(tr("Imprime le sch\351ma courant", "status bar tip")); @@ -356,9 +357,9 @@ void QETDiagramEditor::actions() { connect(zoom_reset, SIGNAL(triggered()), this, SLOT(slot_zoomReset()) ); connect(print, SIGNAL(triggered()), this, SLOT(printDialog()) ); connect(export_diagram, SIGNAL(triggered()), this, SLOT(exportDialog()) ); - connect(save_file_sous, SIGNAL(triggered()), this, SLOT(saveAsDialog()) ); + connect(save_file_as, SIGNAL(triggered()), this, SLOT(saveAs()) ); connect(save_file, SIGNAL(triggered()), this, SLOT(save()) ); - connect(save_all, SIGNAL(triggered()), this, SLOT(saveAll()) ); + connect(save_cur_diagram, SIGNAL(triggered()), this, SLOT(saveCurrentDiagram()) ); connect(new_file, SIGNAL(triggered()), this, SLOT(newProject()) ); connect(open_file, SIGNAL(triggered()), this, SLOT(openProject()) ); connect(close_file, SIGNAL(triggered()), this, SLOT(closeCurrentProject()) ); @@ -421,8 +422,8 @@ void QETDiagramEditor::menus() { menu_fichier -> addMenu(QETApp::projectsRecentFiles() -> menu()); connect(QETApp::projectsRecentFiles(), SIGNAL(fileOpeningRequested(const QString &)), this, SLOT(openRecentFile(const QString &))); menu_fichier -> addAction(save_file); - menu_fichier -> addAction(save_file_sous); - menu_fichier -> addAction(save_all); + menu_fichier -> addAction(save_file_as); + menu_fichier -> addAction(save_cur_diagram); menu_fichier -> addAction(close_file); menu_fichier -> addSeparator(); //menu_fichier -> addAction(import_diagram); @@ -504,8 +505,8 @@ void QETDiagramEditor::toolbar() { main_bar -> addAction(new_file); main_bar -> addAction(open_file); main_bar -> addAction(save_file); - main_bar -> addAction(save_file_sous); - main_bar -> addAction(save_all); + main_bar -> addAction(save_file_as); + main_bar -> addAction(save_cur_diagram); main_bar -> addAction(close_file); main_bar -> addAction(print); main_bar -> addSeparator(); @@ -561,45 +562,45 @@ void QETDiagramEditor::exportDialog() { Methode enregistrant le schema dans le dernier nom de fichier connu. @return true si l'enregistrement a reussi, false sinon */ -bool QETDiagramEditor::save() { +void QETDiagramEditor::save() { if (ProjectView *project_view = currentProject()) { - bool save_file = project_view -> save(); - if (save_file) { + QETResult save_file = project_view -> save(); + if (save_file.isOk()) { QETApp::projectsRecentFiles() -> fileWasOpened(project_view -> project() -> filePath()); + } else { + showError(save_file); } - return(save_file); } - return(false); } /** Cette methode demande un nom de fichier a l'utilisateur pour enregistrer le schema @return true si l'enregistrement a reussi, false sinon */ -bool QETDiagramEditor::saveAsDialog() { +void QETDiagramEditor::saveAs() { if (ProjectView *project_view = currentProject()) { - bool save_file = project_view -> saveAs(); - if (save_file) { + QETResult save_file = project_view -> saveAs(); + if (save_file.isOk()) { QETApp::projectsRecentFiles() -> fileWasOpened(project_view -> project() -> filePath()); + } else { + showError(save_file); } - return(save_file); } - return(false); } /** Methode enregistrant tous les schemas. @return true si l'enregistrement a reussi, false sinon */ -bool QETDiagramEditor::saveAll() { +void QETDiagramEditor::saveCurrentDiagram() { if (ProjectView *project_view = currentProject()) { - bool save_file = project_view -> saveAll(); - if (save_file) { + QETResult save_file = project_view -> saveCurrentDiagram(); + if (save_file.isOk()) { QETApp::projectsRecentFiles() -> fileWasOpened(project_view -> project() -> filePath()); + } else { + showError(save_file); } - return(save_file); } - return(false); } /** @@ -1044,8 +1045,8 @@ void QETDiagramEditor::slot_updateActions() { // actions ayant juste besoin d'un document ouvert close_file -> setEnabled(opened_project); save_file -> setEnabled(editable_project); - save_file_sous -> setEnabled(opened_project); - save_all -> setEnabled(editable_diagram); + save_file_as -> setEnabled(opened_project); + save_cur_diagram -> setEnabled(editable_diagram); prj_edit_prop -> setEnabled(opened_project); prj_add_diagram -> setEnabled(editable_project); prj_del_diagram -> setEnabled(editable_project); @@ -1197,6 +1198,9 @@ void QETDiagramEditor::addProjectView(ProjectView *project_view) { QETApp::instance(), SLOT(openTitleBlockTemplate(TitleBlockTemplateLocation, bool)) ); + // display error messages sent by the project view + connect(project_view, SIGNAL(errorEncountered(QString)), this, SLOT(showError(const QString &))); + // affiche la fenetre if (maximise) project_view -> showMaximized(); else project_view -> show(); @@ -1686,6 +1690,22 @@ void QETDiagramEditor::editElementInEditor(const ElementsLocation &location) { QETApp::instance() -> openElementLocations(QList() << location); } +/** + Show the error message contained in \a result. +*/ +void QETDiagramEditor::showError(const QETResult &result) { + if (result.isOk()) return; + showError(result.errorMessage()); +} + +/** + Show the \a error message. +*/ +void QETDiagramEditor::showError(const QString &error) { + if (error.isEmpty()) return; + QET::MessageBox::critical(this, tr("Erreur", "message box title"), error); +} + /** @return Les proprietes par defaut pour le cartouche d'un schema */ diff --git a/sources/qetdiagrameditor.h b/sources/qetdiagrameditor.h index 378a41cc0..b686ea5f2 100644 --- a/sources/qetdiagrameditor.h +++ b/sources/qetdiagrameditor.h @@ -24,6 +24,7 @@ #include "titleblockproperties.h" #include "exportproperties.h" class QETProject; +class QETResult; class ProjectView; class Diagram; class DiagramView; @@ -83,9 +84,9 @@ class QETDiagramEditor : public QETMainWindow { public slots: void printDialog(); void exportDialog(); - bool saveAsDialog(); - bool save(); - bool saveAll(); + void save(); + void saveAs(); + void saveCurrentDiagram(); bool newProject(); bool openProject(); bool openRecentFile(const QString &); @@ -147,6 +148,8 @@ class QETDiagramEditor : public QETMainWindow { void diagramWasRemoved(DiagramView *); void findElementInPanel(const ElementsLocation &); void editElementInEditor(const ElementsLocation &); + void showError(const QETResult &); + void showError(const QString &); // attributs public: @@ -160,9 +163,9 @@ class QETDiagramEditor : public QETMainWindow { QAction *new_file; ///< Cree un nouveau schema QAction *open_file; ///< OUvre un fichier QAction *close_file; ///< Ferme le fichier - QAction *save_file; ///< Enregistre le fichier - QAction *save_file_sous; ///< Enregistrer le fichier sous un nom donne - QAction *save_all; ///< Enregistre tous les schemas + QAction *save_file; ///< Save current project + QAction *save_file_as; ///< Save current project as a specific file + QAction *save_cur_diagram; ///< Save current diagram of the current project only QAction *import_diagram; ///< Importe un schema existant (non implemente) QAction *export_diagram; ///< Exporte le schema sous forme d'image QAction *print; ///< Imprime le schema diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index ba475ac7c..3eac94bbd 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -22,6 +22,7 @@ #include "elementscategory.h" #include "qetapp.h" #include "qetdiagrameditor.h" +#include "qetresult.h" #include "integrationmoveelementshandler.h" #include "movetemplateshandler.h" #include "basicmoveelementshandler.h" @@ -468,17 +469,15 @@ bool QETProject::close() { @see setFilePath() @return true si l'enregistrement a reussi, false sinon */ -bool QETProject::write() { - // le chemin du fichier doit etre connu +QETResult QETProject::write() { + // this operation requires a filepath if (file_path_.isEmpty()) { - qDebug() << qPrintable(QString("QETProject::write() : called without a known filepath [%1]").arg(QET::pointerString(this))); - return(false); + return(QString("unable to save project to file: no filepath was specified")); } - // si le projet a ete ouvert en mode lecture seule et que le fichier n'est pas accessible en ecriture, on n'effectue pas l'enregistrement + // if the project was opened read-only and the file is still non-writable, do not save the project if (isReadOnly() && !QFileInfo(file_path_).isWritable()) { - qDebug() << qPrintable(QString("QETProject::write() : the file %1 was opened read-only and thus will not be written. [%2]").arg(file_path_).arg(QET::pointerString(this))); - return(true); + return(QString("the file %1 was opened read-only and thus will not be written").arg(file_path_)); } // realise l'export en XML du projet dans le document XML interne @@ -488,11 +487,11 @@ bool QETProject::write() { QString error_message; bool writing = QET::writeXmlFile(document_root_, file_path_, &error_message); if (!writing) { - qDebug() << qPrintable(QString("QETProject::write() : %1 [%2]").arg(error_message).arg(QET::pointerString(this))); - } else { - setModified(false); + return(error_message); } - return(writing); + + setModified(false); + return(QETResult()); } /** @@ -1180,6 +1179,10 @@ bool QETProject::embeddedCollectionWasModified() { return(true); } + // the integration category must be empty + if (integ_cat -> categories().count()) return(true); + if (integ_cat -> elements().count()) return(true); + return(false); } @@ -1205,7 +1208,7 @@ bool QETProject::diagramsWereModified() { if (diagrams_.count() != 1) return(true); // dont la pile d'annulation est "clean" - return(!(diagrams_[0] -> undoStack().isClean())); + return(!(diagrams_[0] -> undoStack().isClean() && !diagrams_[0] -> wasWritten())); } /** diff --git a/sources/qetproject.h b/sources/qetproject.h index 5c842c3de..ebf15e076 100644 --- a/sources/qetproject.h +++ b/sources/qetproject.h @@ -30,6 +30,7 @@ class ElementsCollection; class ElementsCategory; class ElementDefinition; class ElementsLocation; +class QETResult; class TitleBlockTemplate; class XmlElementsCollection; class MoveElementsHandler; @@ -98,7 +99,7 @@ class QETProject : public QObject { void setDefaultConductorProperties(const ConductorProperties &); QDomDocument toXml(); bool close(); - bool write(); + QETResult write(); bool isReadOnly() const; void setReadOnly(bool); bool isEmpty() const; diff --git a/sources/qetresult.cpp b/sources/qetresult.cpp new file mode 100644 index 000000000..6daa38d9a --- /dev/null +++ b/sources/qetresult.cpp @@ -0,0 +1,70 @@ +/* + Copyright 2006-2012 Xavier Guerrin + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#include "qetresult.h" + +/** + Construct a default "true" QET result without an error message. +*/ +QETResult::QETResult() : + result_(true) +{ +} + +/** + Construct a QET result embedding \a error_message and \a result (defaults + to false). +*/ +QETResult::QETResult(const QString &error_message, bool result) : + result_(result), + error_message_(error_message) +{ +} + +/** + Destructor +*/ +QETResult::~QETResult() { +} + +/** + @return the boolean value embedded within this result. +*/ +bool QETResult::isOk() const { + return(result_); +} + +/** + Embed \a result. +*/ +void QETResult::setResult(bool result) { + result_ = result; +} + +/** + @return the error message embedded within this result. +*/ +QString QETResult::errorMessage() const { + return(error_message_); +} + +/** + Embed \a error_message wihthin this result. +*/ +void QETResult::setErrorMessage(const QString &error_message) { + error_message_ = error_message; +} diff --git a/sources/qetresult.h b/sources/qetresult.h new file mode 100644 index 000000000..eebe6be1e --- /dev/null +++ b/sources/qetresult.h @@ -0,0 +1,46 @@ +/* + Copyright 2006-2012 Xavier Guerrin + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#ifndef QET_RESULT_H +#define QET_RESULT_H +#include + +/** + This class represents the result of a lambda operation. Technically, it is + a mere boolean+error message pair. +*/ +class QETResult { + // Constructor, destructor + public: + QETResult(); + QETResult(const QString &error_message, bool = false); + virtual ~QETResult(); + + // methods + public: + bool isOk() const; + void setResult(bool); + QString errorMessage() const; + void setErrorMessage(const QString &); + + // attributes + private: + bool result_; ///< Embedded boolean value + QString error_message_; ///< Embedded error message, typically used to explain what failed to users +}; + +#endif