diff --git a/GUI/Models/JobItem.cpp b/GUI/Models/JobItem.cpp index 524c2d8083a5c430413844a0245a26d8c2726422..59738e8d10a162ae4a05932380747d4ed99555d1 100644 --- a/GUI/Models/JobItem.cpp +++ b/GUI/Models/JobItem.cpp @@ -36,15 +36,11 @@ JobItem::JobItem() : SessionItem(M_TYPE) addProperty(P_INSTRUMENT_NAME, QString())->setEditable(false); addProperty(P_WITH_FITTING, false)->setVisible(false); - addProperty(P_STATUS, "Idle")->setEditable(false); + addProperty(P_STATUS, jobStatusToString(JobStatus::Idle))->setEditable(false); addProperty(P_BEGIN_TIME, QString())->setEditable(false); addProperty(P_END_TIME, QString())->setEditable(false); - auto durationItem = addProperty(P_DURATION, QString()); - durationItem->setEditable(false); - durationItem->setToolTip("Duration of DWBA simulation in sec.msec format"); - addProperty(P_COMMENTS, QString())->setVisible(false); addProperty(P_PROGRESS, 0)->setVisible(false); addProperty(P_PRESENTATION_TYPE, QVariant::Type::Invalid)->setVisible(false); @@ -100,15 +96,15 @@ void JobItem::addDataItem(DataItem* data) insertItem(-1, data, T_OUTPUT); } -QString JobItem::getStatus() const +JobStatus JobItem::getStatus() const { - return getItemValue(P_STATUS).toString(); + return jobStatusFromString(getItemValue(P_STATUS).toString()); } -void JobItem::setStatus(const QString& status) +void JobItem::setStatus(const JobStatus status) { - setItemValue(P_STATUS, status); - if (status == "Failed") { + setItemValue(P_STATUS, jobStatusToString(status)); + if (status == JobStatus::Failed) { if (DataItem* intensityItem = dataItem()) { if (intensityItem->getOutputData()) intensityItem->getOutputData()->setAllTo(0.0); @@ -124,27 +120,32 @@ bool JobItem::isStatusPropertyName(const QString& name) bool JobItem::isIdle() const { - return getStatus() == "Idle"; + return getStatus() == JobStatus::Idle; } bool JobItem::isRunning() const { - return getStatus() == "Running"; + return getStatus() == JobStatus::Running; } bool JobItem::isCompleted() const { - return getStatus() == "Completed"; + return getStatus() == JobStatus::Completed; } bool JobItem::isCanceled() const { - return getStatus() == "Canceled"; + return getStatus() == JobStatus::Canceled; } bool JobItem::isFailed() const { - return getStatus() == "Failed"; + return getStatus() == JobStatus::Failed; +} + +bool JobItem::isFitting() const +{ + return getStatus() == JobStatus::Fitting; } bool JobItem::isValidForFitting() @@ -162,6 +163,11 @@ void JobItem::setBeginTime(const QDateTime& begin_time) setItemValue(P_BEGIN_TIME, begin_time.toString(Qt::ISODateWithMs)); } +bool JobItem::isBeginTimePropertyName(const QString& name) +{ + return name == P_BEGIN_TIME; +} + QDateTime JobItem::endTime() const { return QDateTime::fromString(getItemValue(P_END_TIME).toString(), Qt::ISODateWithMs); @@ -170,16 +176,20 @@ QDateTime JobItem::endTime() const void JobItem::setEndTime(const QDateTime& end_time) { setItemValue(P_END_TIME, end_time.toString(Qt::ISODateWithMs)); - QString duration; - if (end_time.isValid()) { - QDateTime begin_time = beginTime(); - if (begin_time.isValid()) { - qint64 delta = begin_time.msecsTo(end_time); - if (delta >0) - duration = QString::number(delta / 1000., 'f', 3); - } - } - setItemValue(P_DURATION, duration); +} + +bool JobItem::isEndTimePropertyName(const QString& name) +{ + return name == P_END_TIME; +} + +std::optional<size_t> JobItem::duration() const +{ + QDateTime begin_time = beginTime(); + QDateTime end_time = endTime(); + if (begin_time.isValid() && end_time.isValid() && begin_time < end_time) + return begin_time.msecsTo(end_time); + return std::nullopt; } QString JobItem::getComments() const @@ -207,6 +217,11 @@ void JobItem::setProgress(int progress) setItemValue(P_PROGRESS, progress); } +bool JobItem::isProgressPropertyName(const QString& name) +{ + return name == P_PROGRESS; +} + bool JobItem::runImmediately() const { return simulationOptionsItem()->runImmediately(); @@ -333,11 +348,21 @@ Data1DViewItem* JobItem::createDataViewItem() return model()->insertItem<Data1DViewItem>(this, -1, T_DATAVIEW); } +QString JobItem::sampleName() const +{ + return getItemValue(P_SAMPLE_NAME).toString(); +} + void JobItem::setSampleName(const QString& name) { getItem(P_SAMPLE_NAME)->setValue(name); } +QString JobItem::instrumentName() const +{ + return getItemValue(P_INSTRUMENT_NAME).toString(); +} + void JobItem::setInstrumentName(const QString& name) { getItem(P_INSTRUMENT_NAME)->setValue(name); diff --git a/GUI/Models/JobItem.h b/GUI/Models/JobItem.h index 22506f34906e395e6ffb03139d496ee6ece1fd01..ab0e7226399fdfb3db6d5632965c070995dd0f5f 100644 --- a/GUI/Models/JobItem.h +++ b/GUI/Models/JobItem.h @@ -16,6 +16,7 @@ #define BORNAGAIN_GUI_MODELS_JOBITEM_H #include "GUI/Models/SessionItem.h" +#include "GUI/Models/JobStatus.h" #include "GUI/Models/SessionModel.h" class DataItem; @@ -33,6 +34,7 @@ class ISimulation; class SimulationOptionsItem; #include <QDateTime> +#include <optional> class BA_CORE_API_ JobItem : public SessionItem { @@ -44,7 +46,6 @@ private: static constexpr auto P_STATUS{"Status"}; static constexpr auto P_BEGIN_TIME{"Begin time"}; static constexpr auto P_END_TIME{"End time"}; - static constexpr auto P_DURATION{"Duration"}; static constexpr auto P_COMMENTS{"Comments"}; static constexpr auto P_PROGRESS{"Progress"}; static constexpr auto P_PRESENTATION_TYPE{"Presentation type"}; @@ -71,8 +72,8 @@ public: template <typename T> T* setDataType(); void addDataItem(DataItem* data); - QString getStatus() const; - void setStatus(const QString& status); + JobStatus getStatus() const; + void setStatus(const JobStatus status); static bool isStatusPropertyName(const QString& name); bool isIdle() const; @@ -80,13 +81,19 @@ public: bool isCompleted() const; bool isCanceled() const; bool isFailed() const; + bool isFitting() const; bool isValidForFitting(); QDateTime beginTime() const; void setBeginTime(const QDateTime& begin_time); + static bool isBeginTimePropertyName(const QString& name); QDateTime endTime() const; void setEndTime(const QDateTime& end_time); + static bool isEndTimePropertyName(const QString& name); + + /// if begin and end time are both available the duration in ms, otherwise empty + std::optional<size_t> duration() const; QString getComments() const; void setComments(const QString& comments); @@ -94,6 +101,7 @@ public: int getProgress() const; void setProgress(int progress); + static bool isProgressPropertyName(const QString& name); bool runImmediately() const; bool runInBackground() const; @@ -130,8 +138,10 @@ public: void addDataViewItem(Data1DViewItem* data_view); Data1DViewItem* createDataViewItem(); + QString sampleName() const; void setSampleName(const QString& name); + QString instrumentName() const; void setInstrumentName(const QString& name); const QString presentationType() const; diff --git a/GUI/Models/JobModel.cpp b/GUI/Models/JobModel.cpp index a6ebbc8385085f737c23cd1a420e6d17d042a4f7..ef29dbff63054c17ff0103a31a5058b9159138ac 100644 --- a/GUI/Models/JobModel.cpp +++ b/GUI/Models/JobModel.cpp @@ -114,7 +114,7 @@ bool JobModel::hasUnfinishedJobs() { bool result = m_queue_data->hasUnfinishedJobs(); for (auto jobItem : topItems<JobItem>()) { - if (jobItem->getStatus() == "Fitting") + if (jobItem->getStatus() == JobStatus::Fitting) result = true; } diff --git a/GUI/Models/JobQueueData.cpp b/GUI/Models/JobQueueData.cpp index 7d2922faf82c5d6b0ae73f90cd32d7ecf344a4dc..7b77893f1cdad0e21144c2dee218c5b0eb0295ff 100644 --- a/GUI/Models/JobQueueData.cpp +++ b/GUI/Models/JobQueueData.cpp @@ -54,7 +54,7 @@ void JobQueueData::runJob(JobItem* jobItem) message += QString::fromStdString(std::string(ex.what())); jobItem->setComments(message); jobItem->setProgress(100); - jobItem->setStatus("Failed"); + jobItem->setStatus(JobStatus::Failed); emit focusRequest(jobItem); return; } @@ -106,7 +106,7 @@ void JobQueueData::onStartedJob() auto jobItem = m_jobModel->getJobItemForIdentifier(worker->identifier()); jobItem->setProgress(0); - jobItem->setStatus("Running"); + jobItem->setStatus(JobStatus::Running); jobItem->setBeginTime(worker->simulationStart()); jobItem->setEndTime(QDateTime()); } @@ -226,7 +226,7 @@ void JobQueueData::processFinishedJob(JobWorker* worker, JobItem* jobItem) jobItem->setEndTime(worker->simulationEnd()); // propagating status of runner - if (worker->status() == "Failed") { + if (worker->status() == JobStatus::Failed) { jobItem->setComments(worker->failureMessage()); } else { // propagating simulation results diff --git a/GUI/Models/JobStatus.cpp b/GUI/Models/JobStatus.cpp new file mode 100644 index 0000000000000000000000000000000000000000..10a4adf362c3f4e7f64c90cb95df960e0cf5c0fa --- /dev/null +++ b/GUI/Models/JobStatus.cpp @@ -0,0 +1,42 @@ +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file GUI/Models/JobWorker.cpp +//! @brief Implements class JobWorker +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2018 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ + +#include "GUI/Models/JobStatus.h" +#include "Base/Utils/Assert.h" + +#include <boost/bimap.hpp> + +namespace { + +const std::vector<boost::bimap<JobStatus, QString>::value_type> values = { + {JobStatus::Idle, "Idle"}, {JobStatus::Running, "Running"}, + {JobStatus::Fitting, "Fitting"}, {JobStatus::Completed, "Completed"}, + {JobStatus::Canceled, "Canceled"}, {JobStatus::Failed, "Failed"}}; + +const boost::bimap<JobStatus, QString> status2name(values.begin(), values.end()); +} // namespace + +const QString jobStatusToString(JobStatus status) +{ + auto it = status2name.left.find(status); + ASSERT(it != status2name.left.end()); + return it->second; +} + +JobStatus jobStatusFromString(const QString& name) +{ + auto it = status2name.right.find(name); + ASSERT(it != status2name.right.end()); + return it->second; +} diff --git a/GUI/Models/JobStatus.h b/GUI/Models/JobStatus.h new file mode 100644 index 0000000000000000000000000000000000000000..cd6f9f3ead02b90e04c2c850f0b0310285badd3a --- /dev/null +++ b/GUI/Models/JobStatus.h @@ -0,0 +1,37 @@ +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file GUI/Models/JobStatus.h +//! @brief Defines enum JobStatus +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2018 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ + +#ifndef BORNAGAIN_GUI_MODELS_JOBSTATUS_H +#define BORNAGAIN_GUI_MODELS_JOBSTATUS_H + +#include <QString> + +//! The JobStatus enum lists the possible states of a job + +enum class JobStatus { + Idle, //!< the job has not been started yet + Running, //!< the job is busy calculating + Fitting, //!< the job is busy fitting + Completed, //!< the job was successfully completed + Canceled, //!< the job was stopped by the user + Failed //!< the job aborted because it hit an error +}; + +//! get a string representation of the status +const QString jobStatusToString(JobStatus status); + +//! get status value for given string representation +JobStatus jobStatusFromString(const QString& name); + +#endif // BORNAGAIN_GUI_MODELS_JOBSTATUS_H diff --git a/GUI/Models/JobWorker.cpp b/GUI/Models/JobWorker.cpp index 322e87c32ce673ae30aa02425fcd6114a90a635d..a3acaede4bfc90fa8ab86d7c8e0b3e9367cf0b44 100644 --- a/GUI/Models/JobWorker.cpp +++ b/GUI/Models/JobWorker.cpp @@ -21,7 +21,7 @@ JobWorker::JobWorker(const QString& identifier, ISimulation* simulation) : m_identifier(identifier) , m_simulation(simulation) , m_percentage_done(0) - , m_job_status("Idle") + , m_job_status(JobStatus::Idle) , m_terminate_request_flag(false) { } @@ -48,14 +48,14 @@ void JobWorker::start() return updateProgress(static_cast<int>(percentage_done)); }); - m_job_status = "Running"; + m_job_status = JobStatus::Running; try { m_simulation->runSimulation(); - if (m_job_status != "Canceled") - m_job_status = "Completed"; + if (m_job_status != JobStatus::Canceled) + m_job_status = JobStatus::Completed; } catch (const std::exception& ex) { - m_job_status = "Failed"; + m_job_status = JobStatus::Failed; m_percentage_done = 100; m_failure_message = "JobRunner::start() -> ISimulation failed with exception throw:\n\n"; @@ -64,7 +64,7 @@ void JobWorker::start() } } else { - m_job_status = "Failed"; + m_job_status = JobStatus::Failed; m_percentage_done = 100; m_failure_message = "JobRunner::start() -> Error. ISimulation doesn't exist."; } @@ -74,7 +74,7 @@ void JobWorker::start() emit finished(); } -QString JobWorker::status() const +JobStatus JobWorker::status() const { return m_job_status; } @@ -99,7 +99,7 @@ const QDateTime& JobWorker::simulationEnd() const void JobWorker::terminate() { m_terminate_request_flag = true; - m_job_status = "Canceled"; + m_job_status = JobStatus::Canceled; } //! Sets current progress. Returns true if we want to continue the simulation. diff --git a/GUI/Models/JobWorker.h b/GUI/Models/JobWorker.h index 2060d50f3aee6b5843e7112e984c759b21f3d88e..9eb805ef3102b7e4ea1c5d890c3eeb54093a4a96 100644 --- a/GUI/Models/JobWorker.h +++ b/GUI/Models/JobWorker.h @@ -15,6 +15,7 @@ #ifndef BORNAGAIN_GUI_MODELS_JOBWORKER_H #define BORNAGAIN_GUI_MODELS_JOBWORKER_H +#include "GUI/Models/JobStatus.h" #include <QObject> #include <QDateTime> @@ -31,7 +32,7 @@ public: int progress() const; - QString status() const; + JobStatus status() const; QString failureMessage() const; @@ -53,7 +54,7 @@ private: QString m_identifier; ISimulation* m_simulation; int m_percentage_done; - QString m_job_status; + JobStatus m_job_status; bool m_terminate_request_flag; QString m_failure_message; QDateTime m_simulation_start; diff --git a/GUI/Views/FitWidgets/FitParameterWidget.cpp b/GUI/Views/FitWidgets/FitParameterWidget.cpp index 030ab36a7a9ab702b0638bc7bf35419675a8bc18..5bf4bb4d792de6dae83d14deca36c540f3958bf9 100644 --- a/GUI/Views/FitWidgets/FitParameterWidget.cpp +++ b/GUI/Views/FitWidgets/FitParameterWidget.cpp @@ -245,7 +245,7 @@ void FitParameterWidget::init_actions() void FitParameterWidget::initTuningWidgetContextMenu(QMenu& menu) { - if (jobItem()->getStatus() == "Fitting") { + if (jobItem()->getStatus() == JobStatus::Fitting) { setActionsEnabled(false); return; } @@ -279,7 +279,7 @@ void FitParameterWidget::initTuningWidgetContextMenu(QMenu& menu) void FitParameterWidget::initFitParameterTreeContextMenu(QMenu& menu) { - if (jobItem()->getStatus() == "Fitting") { + if (jobItem()->getStatus() == JobStatus::Fitting) { setActionsEnabled(false); return; } diff --git a/GUI/Views/FitWidgets/FitSessionController.cpp b/GUI/Views/FitWidgets/FitSessionController.cpp index 136a04564633f56a7e09d20328a97b18ec15c6ae..d0bbf1b600911422d762693baa7172deab61cf1c 100644 --- a/GUI/Views/FitWidgets/FitSessionController.cpp +++ b/GUI/Views/FitWidgets/FitSessionController.cpp @@ -83,7 +83,7 @@ void FitSessionController::onStartFittingRequest() m_runFitManager->runFitting(m_objectiveBuilder); } catch (std::exception& e) { - m_jobItem->setStatus("Failed"); + m_jobItem->setStatus(JobStatus::Failed); m_fitlog->append(e.what(), FitLogFlags::ERROR); emit fittingError(QString::fromStdString(e.what())); } @@ -121,7 +121,7 @@ void FitSessionController::onFittingStarted() { m_fitlog->clearLog(); - m_jobItem->setStatus("Fitting"); + m_jobItem->setStatus(JobStatus::Fitting); m_jobItem->setProgress(0); m_jobItem->setBeginTime(m_runFitManager->fitStart()); m_jobItem->setEndTime(QDateTime()); @@ -131,8 +131,8 @@ void FitSessionController::onFittingStarted() void FitSessionController::onFittingFinished() { - if (m_jobItem->getStatus() != "Failed") - m_jobItem->setStatus("Completed"); + if (m_jobItem->getStatus() != JobStatus::Failed) + m_jobItem->setStatus(JobStatus::Completed); m_jobItem->setEndTime(m_runFitManager->fitEnd()); m_jobItem->setProgress(100); diff --git a/GUI/Views/FitWidgets/ParameterTuningWidget.cpp b/GUI/Views/FitWidgets/ParameterTuningWidget.cpp index 549ac2a648b488b1b5006c34a86ec82b786b9aed..e3eedde0fe90ecec0fe1b100d323e524506026b6 100644 --- a/GUI/Views/FitWidgets/ParameterTuningWidget.cpp +++ b/GUI/Views/FitWidgets/ParameterTuningWidget.cpp @@ -191,7 +191,7 @@ JobItem* ParameterTuningWidget::jobItem() void ParameterTuningWidget::updateDragAndDropSettings() { ASSERT(jobItem()); - if (jobItem()->getStatus() == "Fitting") { + if (jobItem()->getStatus() == JobStatus::Fitting) { setTuningDelegateEnabled(false); m_treeView->setDragDropMode(QAbstractItemView::NoDragDrop); } else { diff --git a/GUI/Views/FitWidgets/RunFitControlWidget.cpp b/GUI/Views/FitWidgets/RunFitControlWidget.cpp index 2e08a1e21169a54ef853fd5082724fcddfc4ba63..7c2429acbf666839d3a760d2ece34df6ec20550d 100644 --- a/GUI/Views/FitWidgets/RunFitControlWidget.cpp +++ b/GUI/Views/FitWidgets/RunFitControlWidget.cpp @@ -157,7 +157,7 @@ void RunFitControlWidget::updateControlElements() { setEnabled(isValidJobItem()); - if (jobItem()->getStatus() == "Fitting") { + if (jobItem()->getStatus() == JobStatus::Fitting) { m_startButton->setEnabled(false); m_stopButton->setEnabled(true); m_cautionSign->clear(); diff --git a/GUI/Views/InstrumentWidgets/InstrumentEditor.cpp b/GUI/Views/InstrumentWidgets/InstrumentEditor.cpp index ff621958d1112450e9d6f6d67deac74675fa5ed7..4bd008392d96190584488afd1adec34f443fc32b 100644 --- a/GUI/Views/InstrumentWidgets/InstrumentEditor.cpp +++ b/GUI/Views/InstrumentWidgets/InstrumentEditor.cpp @@ -131,8 +131,6 @@ void InstrumentEditor::setItem(InstrumentItem* item) void InstrumentEditor::onInstrumentNameChanged() { - if (m_item) { + if (m_item) m_item->setItemName(m_nameLineEdit->text()); - emit instrumentNameChanged(); - } } diff --git a/GUI/Views/InstrumentWidgets/InstrumentEditor.h b/GUI/Views/InstrumentWidgets/InstrumentEditor.h index 291eb5dd1dd8dda144d5188532a53537d8a6c4cc..c60a0857cbd8019d385bb45b49f1e8e8766ce373 100644 --- a/GUI/Views/InstrumentWidgets/InstrumentEditor.h +++ b/GUI/Views/InstrumentWidgets/InstrumentEditor.h @@ -40,9 +40,6 @@ public: void setItem(InstrumentItem* instrument); -signals: - void instrumentNameChanged(); - private slots: void onInstrumentNameChanged(); diff --git a/GUI/Views/InstrumentWidgets/InstrumentListModel.cpp b/GUI/Views/InstrumentWidgets/InstrumentListModel.cpp index df58d282c718e0ab5bcb62d633618761376b1a3a..c152878cba0cd0bf6d47307f6e2244681744d216 100644 --- a/GUI/Views/InstrumentWidgets/InstrumentListModel.cpp +++ b/GUI/Views/InstrumentWidgets/InstrumentListModel.cpp @@ -66,6 +66,15 @@ InstrumentListModel::InstrumentListModel(InstrumentModel* instruments, QObject* m_specularIcon.addPixmap(QPixmap(":/images/specular_instrument_shaded.svg"), QIcon::Normal); m_depthProbeIcon.addPixmap(QPixmap(":/images/depth_instrument.svg"), QIcon::Selected); m_depthProbeIcon.addPixmap(QPixmap(":/images/depth_instrument_shaded.svg"), QIcon::Normal); + + for (InstrumentItem* instrument : m_instruments->instrumentItems()) + attachToInstrument(instrument); +} + +InstrumentListModel::~InstrumentListModel() +{ + for (InstrumentItem* instrument : m_instruments->instrumentItems()) + detachFromInstrument(instrument); } int InstrumentListModel::rowCount(const QModelIndex&) const @@ -129,7 +138,9 @@ QModelIndex InstrumentListModel::addNewDepthProbeInstrument() void InstrumentListModel::removeInstrument(const QModelIndex& index) { beginRemoveRows(QModelIndex(), index.row(), index.row()); - m_instruments->removeInstrument(instrumentForIndex(index)); + InstrumentItem* instrument = instrumentForIndex(index); + detachFromInstrument(instrument); + m_instruments->removeInstrument(instrument); endRemoveRows(); } @@ -149,6 +160,7 @@ QModelIndex InstrumentListModel::copyInstrument(const InstrumentItem* source) beginInsertRows(QModelIndex(), row, row); InstrumentItem* copy = m_instruments->insertCopy(*source); copy->setItemName(copyName); + attachToInstrument(copy); endInsertRows(); return m_instruments->indexOfItem(copy); @@ -166,7 +178,31 @@ template <class Instrument> QModelIndex InstrumentListModel::addNewInstrument() beginInsertRows(QModelIndex(), row, row); Instrument* instrument = m_instruments->insertItem<Instrument>(); instrument->setItemName(name); + attachToInstrument(instrument); endInsertRows(); return m_instruments->indexOfItem(instrument); } + +void InstrumentListModel::attachToInstrument(InstrumentItem* instrument) +{ + instrument->mapper()->setOnPropertyChange + (std::bind(&InstrumentListModel::notifyInstrumentPropertyChange, this, instrument, + std::placeholders::_1), this); +} + +void InstrumentListModel::detachFromInstrument(InstrumentItem* instrument) +{ + instrument->mapper()->unsubscribe(this); +} + +void InstrumentListModel::notifyInstrumentPropertyChange(InstrumentItem* instrument, + const QString& property) +{ + if (SessionItem::isItemNamePropertyName(property)) { + QVector<InstrumentItem*> instruments = m_instruments->instrumentItems(); + int i = instruments.indexOf(instrument); + if (i != -1) + emit dataChanged(index(i,0),index(i,0)); + } +} diff --git a/GUI/Views/InstrumentWidgets/InstrumentListModel.h b/GUI/Views/InstrumentWidgets/InstrumentListModel.h index de0b10658b80a13e1ce929360fcfff1e8f7c7acc..182f381bdf5c73b4dcb9236bace09faf82f45148 100644 --- a/GUI/Views/InstrumentWidgets/InstrumentListModel.h +++ b/GUI/Views/InstrumentWidgets/InstrumentListModel.h @@ -26,6 +26,7 @@ class InstrumentListModel : public QAbstractListModel { public: InstrumentListModel(InstrumentModel* instruments, QObject* parent = nullptr); + ~InstrumentListModel(); virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; @@ -50,6 +51,10 @@ public: private: template <class Instrument> QModelIndex addNewInstrument(); + void attachToInstrument(InstrumentItem* item); + void detachFromInstrument(InstrumentItem* item); + void notifyInstrumentPropertyChange(InstrumentItem* instrument, const QString& property); + private: InstrumentModel* m_instruments; diff --git a/GUI/Views/InstrumentWidgets/InstrumentListView.cpp b/GUI/Views/InstrumentWidgets/InstrumentListView.cpp index 04545dc1c6c85ceb9ff75cce036bfd83c1e649f5..e9c64295323067c25579e8002b1c99e60d8dc700 100644 --- a/GUI/Views/InstrumentWidgets/InstrumentListView.cpp +++ b/GUI/Views/InstrumentWidgets/InstrumentListView.cpp @@ -146,11 +146,6 @@ QList<QAction*> InstrumentListView::toolbarActions() const m_loadFromLibraryAction}; } -void InstrumentListView::updateSelectedInstrument() -{ - update(); -} - void InstrumentListView::onItemSelectionChanged() { updateActions(); diff --git a/GUI/Views/InstrumentWidgets/InstrumentListView.h b/GUI/Views/InstrumentWidgets/InstrumentListView.h index 3619741f41a84587d986b1fda9bd4f9ef8a3e2b1..917c07163e66c37688ffee426ba59c3ea6915d31 100644 --- a/GUI/Views/InstrumentWidgets/InstrumentListView.h +++ b/GUI/Views/InstrumentWidgets/InstrumentListView.h @@ -41,9 +41,6 @@ public: signals: void instrumentSelected(InstrumentItem* instrument); -public slots: - void updateSelectedInstrument(); - private slots: void onItemSelectionChanged(); void onNewGisas(); diff --git a/GUI/Views/InstrumentWidgets/InstrumentView.cpp b/GUI/Views/InstrumentWidgets/InstrumentView.cpp index f2827c8dbcde053531fec48fd261a26e0e1a3456..ece40599aa1d76f20bd08ae800ec507afba46d43 100644 --- a/GUI/Views/InstrumentWidgets/InstrumentView.cpp +++ b/GUI/Views/InstrumentWidgets/InstrumentView.cpp @@ -44,9 +44,6 @@ InstrumentView::InstrumentView(QWidget* parent, ProjectDocument* document) connect(m_instrumentListView, &InstrumentListView::instrumentSelected, this, &InstrumentView::onInstrumentSelected); - connect(m_instrumentEditor, &InstrumentEditor::instrumentNameChanged, m_instrumentListView, - &InstrumentListView::updateSelectedInstrument); - onInstrumentSelected(nullptr); } diff --git a/GUI/Views/JobView.cpp b/GUI/Views/JobView.cpp index ce987983d953fe2b1f4c0d818575462874f745d2..daefd3f44d651989d4f9d9821ef4413805deeef7 100644 --- a/GUI/Views/JobView.cpp +++ b/GUI/Views/JobView.cpp @@ -61,7 +61,8 @@ void JobView::onFocusRequest(JobItem* jobItem) if (jobItem->runInBackground()) return; - if (jobItem != m_jobSelector->currentJobItem()) { + QVector<JobItem*> jobs = m_jobSelector->selectedJobs(); + if (jobs.size() != 1 || jobItem != jobs.front()) { m_jobSelector->makeJobItemSelected(jobItem); setAppropriateActivityForJob(jobItem); } @@ -87,8 +88,11 @@ void JobView::setActivity(int activity) //! Propagates change in JobItem's selection down into main widgets. -void JobView::onSelectionChanged(JobItem* jobItem) +void JobView::onSelectionChanged(const QVector<JobItem*>& jobs) { + JobItem* jobItem = nullptr; + if (jobs.size() == 1) + jobItem = jobs.front(); m_jobOutputDataWidget->setItem(jobItem); m_jobRealTimeWidget->setItem(jobItem); m_fitActivityPanel->setItem(jobItem); @@ -154,7 +158,7 @@ void JobView::connectJobRelated() connect(m_document->jobModel(), &JobModel::focusRequest, this, &JobView::onFocusRequest); // JobItem selection: JobSelectorWidget -> this - connect(m_jobSelector, &JobSelectorWidget::selectionChanged, this, + connect(m_jobSelector, &JobSelectorWidget::selectedJobsChanged, this, &JobView::onSelectionChanged); } diff --git a/GUI/Views/JobView.h b/GUI/Views/JobView.h index e7b302618dba258d47881f9346efbdb3807baf3a..79264b24229b64af934ceae4178ee5c010d7bab9 100644 --- a/GUI/Views/JobView.h +++ b/GUI/Views/JobView.h @@ -48,7 +48,7 @@ signals: public slots: void onFocusRequest(JobItem* jobItem); void setActivity(int activity); - void onSelectionChanged(JobItem* jobItem); + void onSelectionChanged(const QVector<JobItem*>& jobs); private: void createSubWindows(); diff --git a/GUI/Views/JobWidgets/JobListModel.cpp b/GUI/Views/JobWidgets/JobListModel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3d8640a8c2eaabec7dd61abec90ab07a55b169cb --- /dev/null +++ b/GUI/Views/JobWidgets/JobListModel.cpp @@ -0,0 +1,146 @@ +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file GUI/Views/JobWidgets/JobListModel.cpp +//! @brief Implements class JobListModel +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2018 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ + +#include "GUI/Views/JobWidgets/JobListModel.h" +#include "GUI/Models/JobItem.h" +#include "GUI/Models/JobModel.h" + +//================================================================================================== +// JobListModel +//================================================================================================== + +//-------------------------------------------------------------------------------------------------- +// public member functions +//-------------------------------------------------------------------------------------------------- + +JobListModel::JobListModel(JobModel* jobs, QObject* parent) + : QAbstractListModel(parent), m_jobs(jobs) +{ + for (JobItem* job : m_jobs->jobItems()) + attachToJob(job); + + connect(jobs, &QAbstractItemModel::rowsAboutToBeInserted, this, + &JobListModel::onRowsAboutToBeInserted); + connect(jobs, &QAbstractItemModel::rowsInserted, this, &JobListModel::onRowsInserted); +} + +JobListModel::~JobListModel() +{ + for (JobItem* job : m_jobs->jobItems()) + detachFromJob(job); +} + +int JobListModel::rowCount(const QModelIndex&) const +{ + return m_jobs->jobItems().size(); +} + +QVariant JobListModel::data(const QModelIndex& index, int role) const +{ + QVector<JobItem*> jobs = m_jobs->jobItems(); + if (!index.isValid() || index.row() >= jobs.size() || index.row() < 0) + return QVariant(); + + JobItem* item = jobs[index.row()]; + if (role == Qt::DisplayRole) + return item->itemName(); + + return QVariant(); +} + +JobItem* JobListModel::jobForIndex(const QModelIndex& index) const +{ + QVector<JobItem*> jobs = m_jobs->jobItems(); + if (index.row() >= 0 && index.row() < jobs.size()) + return jobs[index.row()]; + else + return nullptr; +} + +QModelIndex JobListModel::indexForJob(JobItem* job) +{ + QVector<JobItem*> jobs = m_jobs->jobItems(); + int idx = jobs.indexOf(job); + if (idx != -1) + return index(idx, 0); + else + return QModelIndex(); +} + +void JobListModel::runJob(const QModelIndex& index) +{ + m_jobs->runJob(m_jobs->indexOfItem(jobForIndex(index))); +} + +void JobListModel::removeJob(const QModelIndex& index) +{ + beginRemoveRows(QModelIndex(), index.row(), index.row()); + JobItem* job = jobForIndex(index); + detachFromJob(job); + m_jobs->removeJob(m_jobs->indexOfItem(job)); + endRemoveRows(); +} + +void JobListModel::cancelJob(const QModelIndex& index) +{ + m_jobs->cancelJob(m_jobs->indexOfItem(jobForIndex(index))); +} + +//-------------------------------------------------------------------------------------------------- +// private slots +//-------------------------------------------------------------------------------------------------- + +void JobListModel::onRowsAboutToBeInserted(const QModelIndex& parent, int start, int end) +{ + if (!parent.isValid()) + beginInsertRows(QModelIndex(), start, end); +} + +void JobListModel::onRowsInserted(const QModelIndex& parent, int start, int end) +{ + if (!parent.isValid()) { + endInsertRows(); + QVector<JobItem*> jobs = m_jobs->jobItems(); + for (int i = start; i <= end; i++) + attachToJob(jobs.at(i)); + } +} + +//-------------------------------------------------------------------------------------------------- +// private member functions +//-------------------------------------------------------------------------------------------------- + +void JobListModel::attachToJob(JobItem* job) +{ + job->mapper()->setOnPropertyChange( + std::bind(&JobListModel::notifyJobPropertyChange, this, job, std::placeholders::_1), this); +} + +void JobListModel::detachFromJob(JobItem* job) +{ + job->mapper()->unsubscribe(this); +} + +void JobListModel::notifyJobPropertyChange(JobItem* job, const QString& property) +{ + if (SessionItem::isItemNamePropertyName(property) || JobItem::isStatusPropertyName(property) + || JobItem::isProgressPropertyName(property)) { + QVector<JobItem*> jobs = m_jobs->jobItems(); + int i = jobs.indexOf(job); + if (i != -1) { + QModelIndex idx = index(i, 0); + emit dataChanged(idx, idx); + } + } +} diff --git a/GUI/Views/JobWidgets/JobListModel.h b/GUI/Views/JobWidgets/JobListModel.h new file mode 100644 index 0000000000000000000000000000000000000000..6082852a6d80c60baa6e03ac9ad61e718efe42f2 --- /dev/null +++ b/GUI/Views/JobWidgets/JobListModel.h @@ -0,0 +1,54 @@ +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file GUI/Views/JobWidgets/JobListModel.h +//! @brief Defines class JobListModel +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2018 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ + +#ifndef BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBLISTMODEL_H +#define BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBLISTMODEL_H + +#include <QAbstractListModel> + +class JobItem; +class JobModel; + +class JobListModel : public QAbstractListModel { + Q_OBJECT + +public: + JobListModel(JobModel* jobs, QObject* parent = nullptr); + ~JobListModel(); + + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + + JobItem* jobForIndex(const QModelIndex& index) const; + QModelIndex indexForJob(JobItem* job); + + void runJob(const QModelIndex& index); + void removeJob(const QModelIndex& index); + void cancelJob(const QModelIndex& index); + +private slots: + void onRowsAboutToBeInserted(const QModelIndex& parent, int start, int end); + void onRowsInserted(const QModelIndex& parent, int start, int end); + +private: + void attachToJob(JobItem* item); + void detachFromJob(JobItem* item); + void notifyJobPropertyChange(JobItem* job, const QString& property); + +private: + JobModel* m_jobs; +}; + +#endif // BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBLISTMODEL_H diff --git a/GUI/Views/JobWidgets/JobListView.cpp b/GUI/Views/JobWidgets/JobListView.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a1a29c98ef3fd7e9edd561d68bb33570be2595e4 --- /dev/null +++ b/GUI/Views/JobWidgets/JobListView.cpp @@ -0,0 +1,251 @@ +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file GUI/Views/JobWidgets/JobListView.cpp +//! @brief Implements class JobListView +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2018 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ + +#include "GUI/Views/JobWidgets/JobListView.h" +#include "GUI/Models/IntensityDataItem.h" +#include "GUI/Models/JobItem.h" +#include "GUI/Models/JobModel.h" +#include "GUI/Views/CommonWidgets/StyledToolBar.h" +#include "GUI/Views/JobWidgets/JobListModel.h" +#include "GUI/Views/JobWidgets/JobListViewDelegate.h" +#include <QAction> +#include <QListView> +#include <QMenu> +#include <QVBoxLayout> + +namespace { +//! compare function for sorting indexes according to row descending +bool row_descending(const QModelIndex& idx1, const QModelIndex& idx2) +{ + return idx1.row() > idx2.row(); +} + +//! compare function for sorting indexes according to row asscending +bool row_ascending(const QModelIndex& idx1, const QModelIndex& idx2) +{ + return idx1.row() < idx2.row(); +} +} // namespace + +//================================================================================================== +// JobListView +//================================================================================================== + +//-------------------------------------------------------------------------------------------------- +// public member functions +//-------------------------------------------------------------------------------------------------- + +JobListView::JobListView(JobModel* jobs, QWidget* parent, Qt::WindowFlags f) : QWidget(parent, f) +{ + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + + m_runAction = new QAction("Run", this); + m_runAction->setIcon(QIcon(":/images/play.svg")); + m_runAction->setToolTip("Run currently selected jobs"); + connect(m_runAction, &QAction::triggered, this, &JobListView::onRun); + addAction(m_runAction); + + m_cancelAction = new QAction("Stop", this); + m_cancelAction->setIcon(QIcon(":/images/hand-right.svg")); + m_cancelAction->setToolTip("Stop currently selected jobs"); + connect(m_cancelAction, &QAction::triggered, this, &JobListView::onCancel); + addAction(m_cancelAction); + + m_removeAction = new QAction("Remove", this); + m_removeAction->setIcon(QIcon(":/images/delete.svg")); + m_removeAction->setToolTip("Remove currently selected jobs"); + connect(m_removeAction, &QAction::triggered, this, &JobListView::onRemove); + addAction(m_removeAction); + + m_equalizeMenu = new QMenu("Equalize selected plots", this); + + QToolBar* toolBar = new StyledToolBar(this); + toolBar->setMinimumSize(toolBar->minimumHeight(), toolBar->minimumHeight()); + toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + toolBar->addAction(m_runAction); + toolBar->addAction(m_cancelAction); + toolBar->addAction(m_removeAction); + layout->addWidget(toolBar); + + m_listView = new QListView(this); + m_listView->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_listViewDelegate = new JobListViewDelegate(this); + m_listView->setItemDelegate(m_listViewDelegate); + layout->addWidget(m_listView); + + m_model = new JobListModel(jobs, this); + m_listView->setModel(m_model); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, &QWidget::customContextMenuRequested, this, &JobListView::showContextMenu); + + connect(m_listView->selectionModel(), &QItemSelectionModel::selectionChanged, this, + &JobListView::onItemSelectionChanged); + connect(m_model, &QAbstractItemModel::dataChanged, this, &JobListView::onDataChanged); + + updateActions(); + ensureItemSelected(); +} + +QVector<JobItem*> JobListView::selectedJobs() const +{ + QVector<JobItem*> jobs; + for (const QModelIndex& index : m_listView->selectionModel()->selectedIndexes()) + jobs.push_back(m_model->jobForIndex(index)); + return jobs; +} + +void JobListView::selectJob(JobItem* job) +{ + QModelIndex idx = m_model->indexForJob(job); + QModelIndexList selected = m_listView->selectionModel()->selectedIndexes(); + + // Already selected, but we still will emit the signal to notify widgets. + // To handle the case, when the job was selected before it completed (and some stack widgets + // were refusing to show the content for non-complete job). + if (selected.size() == 1 && selected.front() == idx) { + emit selectedJobsChanged({job}); + return; + } + + m_listView->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect); +} + +//-------------------------------------------------------------------------------------------------- +// private slots +//-------------------------------------------------------------------------------------------------- + +void JobListView::onItemSelectionChanged() +{ + updateActions(); + + emit selectedJobsChanged(selectedJobs()); +} + +void JobListView::onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + // currently only single items change, not ranges; thus ranges are not supported + ASSERT(topLeft == bottomRight); + + if (m_listView->selectionModel()->isSelected(topLeft)) + updateActions(); +} + +void JobListView::onRun() +{ + for (const QModelIndex& index : m_listView->selectionModel()->selectedIndexes()) + m_model->runJob(index); +} + +void JobListView::onCancel() +{ + for (const QModelIndex& index : m_listView->selectionModel()->selectedIndexes()) + m_model->cancelJob(index); +} + +void JobListView::onRemove() +{ + QModelIndexList indexes = m_listView->selectionModel()->selectedIndexes(); + std::sort(indexes.begin(), indexes.end(), row_descending); + for (const QModelIndex& index : indexes) + m_model->removeJob(index); + ensureItemSelected(); +} + +void JobListView::equalizeSelectedToJob(JobItem* srcJob) +{ + QModelIndexList indexes = m_listView->selectionModel()->selectedIndexes(); + + IntensityDataItem* srcData = srcJob->intensityDataItem(); + if (!srcData) + return; + + for (const QModelIndex& index : indexes) { + JobItem* job = m_model->jobForIndex(index); + if (job != srcJob) { + IntensityDataItem* data = job->intensityDataItem(); + if (data) { + data->setLowerX(srcData->getLowerX()); + data->setUpperX(srcData->getUpperX()); + data->setLowerY(srcData->getLowerY()); + data->setUpperY(srcData->getUpperY()); + data->setLowerZ(srcData->getLowerZ()); + data->setUpperZ(srcData->getUpperZ()); + } + } + } +} + +void JobListView::showContextMenu(const QPoint&) +{ + QMenu menu(this); + menu.addAction(m_runAction); + menu.addAction(m_cancelAction); + menu.addAction(m_removeAction); + menu.addSeparator(); + + m_equalizeMenu->clear(); + QModelIndexList indexes = m_listView->selectionModel()->selectedIndexes(); + if (indexes.size() > 1) { + std::sort(indexes.begin(), indexes.end(), row_ascending); + for (const QModelIndex& index : indexes) { + JobItem* job = m_model->jobForIndex(index); + QAction* action = m_equalizeMenu->addAction(QString("to ").append(job->itemName())); + connect(action, &QAction::triggered, this, + std::bind(&JobListView::equalizeSelectedToJob, this, job)); + } + m_equalizeMenu->setEnabled(true); + } else + m_equalizeMenu->setEnabled(false); + menu.addMenu(m_equalizeMenu); + menu.exec(QCursor::pos()); +} + +//-------------------------------------------------------------------------------------------------- +// private member functions +//-------------------------------------------------------------------------------------------------- + +void JobListView::updateActions() +{ + QModelIndexList indexes = m_listView->selectionModel()->selectedIndexes(); + + struct IsRunningOrFitting { + JobListModel* m_model; + IsRunningOrFitting(JobListModel* model) : m_model(model) {} + bool operator()(const QModelIndex& i) const + { + JobItem* job = m_model->jobForIndex(i); + ASSERT(job); + return job->isRunning() || job->isFitting(); + } + }; + + bool none_running = std::none_of(indexes.begin(), indexes.end(), IsRunningOrFitting(m_model)); + bool all_running = std::all_of(indexes.begin(), indexes.end(), IsRunningOrFitting(m_model)); + bool nonempty = !indexes.empty(); + m_runAction->setEnabled(nonempty && none_running); + m_cancelAction->setEnabled(nonempty && all_running); + m_removeAction->setEnabled(nonempty && none_running); +} + +void JobListView::ensureItemSelected() +{ + if (!m_listView->selectionModel()->hasSelection() && m_model->rowCount()) { + QModelIndex last = m_model->index(m_model->rowCount() - 1, 0, QModelIndex()); + m_listView->selectionModel()->select(last, QItemSelectionModel::ClearAndSelect); + } +} diff --git a/GUI/Views/JobWidgets/JobListView.h b/GUI/Views/JobWidgets/JobListView.h new file mode 100644 index 0000000000000000000000000000000000000000..063dbfb2472fa4d0bcf169ed4e1442859a32efff --- /dev/null +++ b/GUI/Views/JobWidgets/JobListView.h @@ -0,0 +1,64 @@ +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file GUI/Views/JobWidgets/JobListView.h +//! @brief Defines class JobListView +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2018 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ + +#ifndef BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBLISTVIEW_H +#define BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBLISTVIEW_H + +#include <QWidget> + +class JobModel; +class QAction; +class QListView; +class QMenu; +class JobItem; +class JobListModel; +class JobListViewDelegate; + +//! List of jobs on the top left side of JobView. + +class JobListView : public QWidget { + Q_OBJECT + +public: + JobListView(JobModel* jobs, QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); + QVector<JobItem*> selectedJobs() const; + void selectJob(JobItem* job); + +signals: + void selectedJobsChanged(const QVector<JobItem*>& jobs); + +private slots: + void onItemSelectionChanged(); + void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + void onRun(); + void onCancel(); + void onRemove(); + void equalizeSelectedToJob(JobItem* job); + void showContextMenu(const QPoint& pos); + +private: + void updateActions(); + void ensureItemSelected(); + +private: + QListView* m_listView; + JobListViewDelegate* m_listViewDelegate; + JobListModel* m_model; + QAction* m_runAction; + QAction* m_cancelAction; + QAction* m_removeAction; + QMenu* m_equalizeMenu; +}; + +#endif // BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBLISTVIEW_H diff --git a/GUI/Views/JobWidgets/JobListViewDelegate.cpp b/GUI/Views/JobWidgets/JobListViewDelegate.cpp index 0c306ac4ede4f1c61e465b1258d17a75a2cf8ad7..e159fce4d8c1d0dc58a9eb8f24af825a8e712905 100644 --- a/GUI/Views/JobWidgets/JobListViewDelegate.cpp +++ b/GUI/Views/JobWidgets/JobListViewDelegate.cpp @@ -14,7 +14,7 @@ #include "GUI/Views/JobWidgets/JobListViewDelegate.h" #include "GUI/Models/JobItem.h" -#include "GUI/Models/JobModel.h" +#include "GUI/Views/JobWidgets/JobListModel.h" #include <QApplication> #include <QMouseEvent> #include <QPaintDevice> @@ -25,11 +25,11 @@ JobListViewDelegate::JobListViewDelegate(QWidget* parent) : QItemDelegate(parent) { m_buttonState = QStyle::State_Enabled; - m_status_to_color["Idle"] = QColor(255, 286, 12); - m_status_to_color["Running"] = QColor(5, 150, 230); - m_status_to_color["Completed"] = QColor(5, 150, 230); - m_status_to_color["Canceled"] = QColor(186, 0, 0); - m_status_to_color["Failed"] = QColor(255, 186, 12); + m_status_to_color[JobStatus::Idle] = QColor(255, 286, 12); + m_status_to_color[JobStatus::Running] = QColor(5, 150, 230); + m_status_to_color[JobStatus::Completed] = QColor(5, 150, 230); + m_status_to_color[JobStatus::Canceled] = QColor(186, 0, 0); + m_status_to_color[JobStatus::Failed] = QColor(255, 186, 12); } void JobListViewDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, @@ -38,10 +38,10 @@ void JobListViewDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o if (option.state & QStyle::State_Selected) painter->fillRect(option.rect, option.palette.highlight()); - const JobModel* model = static_cast<const JobModel*>(index.model()); + const JobListModel* model = dynamic_cast<const JobListModel*>(index.model()); ASSERT(model); - const JobItem* item = model->getJobItemForIndex(index); + const JobItem* item = model->jobForIndex(index); ASSERT(item); painter->save(); @@ -76,10 +76,10 @@ bool JobListViewDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, return QItemDelegate::editorEvent(event, model, option, index); } - const JobModel* jqmodel = static_cast<const JobModel*>(index.model()); + const JobListModel* jqmodel = dynamic_cast<const JobListModel*>(index.model()); ASSERT(model); - const JobItem* item = jqmodel->getJobItemForIndex(index); + const JobItem* item = jqmodel->jobForIndex(index); ASSERT(item); if (!item->isRunning()) @@ -97,7 +97,6 @@ bool JobListViewDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, m_buttonState = QStyle::State_Sunken; } else if (event->type() == QEvent::MouseButtonRelease) { m_buttonState = QStyle::State_Raised; - qDebug("JobListViewDelegate::editorEvent() -> cancel clicked"); emit cancelButtonClicked(index); } return true; diff --git a/GUI/Views/JobWidgets/JobListViewDelegate.h b/GUI/Views/JobWidgets/JobListViewDelegate.h index f8edfa8ca5196218cb18fdcedaa4eb6b35e9a9f1..e046ceeac3e1b6c31a78f8bd5e993157d1a438f7 100644 --- a/GUI/Views/JobWidgets/JobListViewDelegate.h +++ b/GUI/Views/JobWidgets/JobListViewDelegate.h @@ -20,6 +20,7 @@ #include <QRect> class JobItem; +enum class JobStatus; //! ViewDelegate to show progress bar JobQueuListView class JobListViewDelegate : public QItemDelegate { @@ -45,7 +46,7 @@ private: QRect getProgressBarRect(QRect optionRect) const; QRect getButtonRect(QRect optionRect) const; - QMap<QString, QColor> m_status_to_color; + QMap<JobStatus, QColor> m_status_to_color; }; #endif // BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBLISTVIEWDELEGATE_H diff --git a/GUI/Views/JobWidgets/JobListWidget.cpp b/GUI/Views/JobWidgets/JobListWidget.cpp deleted file mode 100644 index 2ec724a56e257902c1abcaa0bf55ef5892bc73dd..0000000000000000000000000000000000000000 --- a/GUI/Views/JobWidgets/JobListWidget.cpp +++ /dev/null @@ -1,120 +0,0 @@ -// ************************************************************************************************ -// -// BornAgain: simulate and fit reflection and scattering -// -//! @file GUI/Views/JobWidgets/JobListWidget.cpp -//! @brief Implements class JobListWidget -//! -//! @homepage http://www.bornagainproject.org -//! @license GNU General Public License v3 or higher (see COPYING) -//! @copyright Forschungszentrum Jülich GmbH 2018 -//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) -// -// ************************************************************************************************ - -#include "GUI/Views/JobWidgets/JobListWidget.h" -#include "GUI/Models/JobItem.h" -#include "GUI/Models/JobModel.h" -#include "GUI/Views/CommonWidgets/ItemSelectorWidget.h" -#include "GUI/Views/CommonWidgets/StyleUtils.h" -#include "GUI/Views/JobWidgets/JobListViewDelegate.h" -#include <QItemSelectionModel> -#include <QListView> -#include <QVBoxLayout> - -JobListWidget::JobListWidget(QWidget* parent) - : QWidget(parent) - , m_listViewDelegate(new JobListViewDelegate(this)) - , m_listView(new ItemSelectorWidget(this)) - , m_jobModel(nullptr) -{ - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - - m_listView->listView()->setItemDelegate(m_listViewDelegate); - m_listView->listView()->setSelectionMode(QAbstractItemView::ExtendedSelection); - - auto mainLayout = new QVBoxLayout; - mainLayout->setMargin(0); - mainLayout->setSpacing(0); - - auto vlayout = new QVBoxLayout; - vlayout->setMargin(10); - vlayout->setSpacing(10); - - vlayout->addWidget(m_listView); - mainLayout->addLayout(vlayout); - - setLayout(mainLayout); - - connect(m_listView, &ItemSelectorWidget::contextMenuRequest, this, - &JobListWidget::contextMenuRequest); - - connect(m_listView, &ItemSelectorWidget::selectionChanged, this, - &JobListWidget::onItemSelectionChanged); -} - -void JobListWidget::setModel(JobModel* model) -{ - ASSERT(model); - if (model != m_jobModel) { - m_jobModel = model; - m_listView->setModel(model); - - connect(m_listViewDelegate, &JobListViewDelegate::cancelButtonClicked, m_jobModel, - &JobModel::cancelJob, Qt::UniqueConnection); - } -} - -QItemSelectionModel* JobListWidget::selectionModel() -{ - return m_listView->selectionModel(); -} - -//! Returns currently selected JobItem - -const JobItem* JobListWidget::currentJobItem() const -{ - QModelIndexList selected = m_listView->selectionModel()->selectedIndexes(); - return selected.size() == 1 ? m_jobModel->getJobItemForIndex(selected.at(0)) : nullptr; -} - -QSize JobListWidget::sizeHint() const -{ - return QSize(GUI::Utils::Style::PropertyPanelWidth(), - GUI::Utils::Style::PropertyPanelWidth() * 2); -} - -QSize JobListWidget::minimumSizeHint() const -{ - return QSize(GUI::Utils::Style::PropertyPanelWidth(), GUI::Utils::Style::PropertyPanelWidth()); -} - -void JobListWidget::makeJobItemSelected(JobItem* jobItem) -{ - ASSERT(jobItem); - QModelIndexList selected = m_listView->selectionModel()->selectedIndexes(); - - // Already selected, but we still will emit the signal to notify widgets. - // To handle the case, when the job was selected before it completed (and some stack widgets - // were refusing to show the content for non-complete job). - if (selected.size() == 1 && selected.at(0) == jobItem->index()) { - emit selectionChanged(jobItem); - return; - } - - m_listView->selectionModel()->clearSelection(); - m_listView->selectionModel()->select(jobItem->index(), QItemSelectionModel::Select); -} - -//! Recieves SeesionItem from ItemSelectorWidget and emits it further as JobItem. -//! Null item means the absence of selection. - -void JobListWidget::onItemSelectionChanged(SessionItem* item) -{ - JobItem* jobItem(nullptr); - if (item) { - jobItem = dynamic_cast<JobItem*>(item); - ASSERT(jobItem); - } - emit selectionChanged(jobItem); -} diff --git a/GUI/Views/JobWidgets/JobListWidget.h b/GUI/Views/JobWidgets/JobListWidget.h deleted file mode 100644 index 88f9eff9d8db902f64e7d4d3147388e38986bb89..0000000000000000000000000000000000000000 --- a/GUI/Views/JobWidgets/JobListWidget.h +++ /dev/null @@ -1,61 +0,0 @@ -// ************************************************************************************************ -// -// BornAgain: simulate and fit reflection and scattering -// -//! @file GUI/Views/JobWidgets/JobListWidget.h -//! @brief Defines class JobListWidget -//! -//! @homepage http://www.bornagainproject.org -//! @license GNU General Public License v3 or higher (see COPYING) -//! @copyright Forschungszentrum Jülich GmbH 2018 -//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) -// -// ************************************************************************************************ - -#ifndef BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBLISTWIDGET_H -#define BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBLISTWIDGET_H - -#include <QModelIndexList> -#include <QWidget> - -class JobModel; -class JobListViewDelegate; -class QItemSelectionModel; -class ItemSelectorWidget; -class QModelIndex; -class JobItem; -class SessionItem; - -//! The JobListWidget class contains list view to select job items. - -class JobListWidget : public QWidget { - Q_OBJECT -public: - explicit JobListWidget(QWidget* parent = nullptr); - - void setModel(JobModel* model); - - QItemSelectionModel* selectionModel(); - - const JobItem* currentJobItem() const; - - QSize sizeHint() const; - QSize minimumSizeHint() const; - -signals: - void contextMenuRequest(const QPoint& point, const QModelIndex& index); - void selectionChanged(JobItem*); - -public slots: - void makeJobItemSelected(JobItem* jobItem); - -private slots: - void onItemSelectionChanged(SessionItem* item); - -private: - JobListViewDelegate* m_listViewDelegate; - ItemSelectorWidget* m_listView; - JobModel* m_jobModel; -}; - -#endif // BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBLISTWIDGET_H diff --git a/GUI/Views/JobWidgets/JobPropertiesTableModel.cpp b/GUI/Views/JobWidgets/JobPropertiesTableModel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..04c55fb0bfd934bc1efeb06ba00395793e4897ab --- /dev/null +++ b/GUI/Views/JobWidgets/JobPropertiesTableModel.cpp @@ -0,0 +1,170 @@ +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file GUI/Views/JobWidgets/JobPropertiesTableModel.cpp +//! @brief Implements class JobPropertiesTableModel +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2018 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ + +#include "GUI/Views/JobWidgets/JobPropertiesTableModel.h" +#include "GUI/Models/JobItem.h" + +namespace { +namespace Column { +enum Columns { Name, Value }; +} +const QString ColumnNames[] = {"Name", "Value"}; +const int NumColumns = std::size(ColumnNames); + +namespace Row { +enum Rows { Name, Sample, Instrument, Status, Begin, End, Duration }; +} +const QString RowNames[] = {"Name", "Sample", "Instrument", "Status", "Begin", "End", "Duration"}; +const int NumRows = std::size(RowNames); + +const QString ModelDateShortFormat = "yyyy.MM.dd hh:mm:ss"; +} // namespace + +//================================================================================================== +// JobPropertiesTableModel +//================================================================================================== + +//-------------------------------------------------------------------------------------------------- +// public member functions +//-------------------------------------------------------------------------------------------------- + +JobPropertiesTableModel::JobPropertiesTableModel(QObject* parent) + : QAbstractTableModel(parent), m_item(nullptr) +{ +} + +JobPropertiesTableModel::~JobPropertiesTableModel() +{ + if (m_item) + m_item->mapper()->unsubscribe(this); +} + +int JobPropertiesTableModel::rowCount(const QModelIndex& parent) const +{ + if (!parent.isValid() && m_item) + return NumRows; + return 0; +} + +int JobPropertiesTableModel::columnCount(const QModelIndex& parent) const +{ + if (!parent.isValid() && m_item) + return NumColumns; + return 0; +} + +QVariant JobPropertiesTableModel::data(const QModelIndex& index, int role) const +{ + if ((role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::ToolTipRole) + || index.column() < 0 || index.column() >= NumColumns || index.row() < 0 + || index.row() >= NumRows || !m_item) + return QVariant(); + + switch (index.column()) { + case Column::Name: + return RowNames[index.row()]; + case Column::Value: { + switch (index.row()) { + case Row::Name: + return m_item->itemName(); + case Row::Sample: + return m_item->sampleName(); + case Row::Instrument: + return m_item->instrumentName(); + case Row::Status: + return jobStatusToString(m_item->getStatus()); + case Row::Begin: + if (role == Qt::ToolTipRole) + return m_item->beginTime().toString(Qt::DefaultLocaleLongDate); + return m_item->beginTime().toString(ModelDateShortFormat); + case Row::End: + if (role == Qt::ToolTipRole) + return m_item->endTime().toString(Qt::DefaultLocaleLongDate); + return m_item->endTime().toString(ModelDateShortFormat); + case Row::Duration: { + std::optional<size_t> duration = m_item->duration(); + if (duration) + return QString("%1 s").arg(duration.value() / 1000., 0, 'f', 3); + return QVariant(); + } + default: + return QVariant(); + } + } + default: + return QVariant(); + } +} + +QVariant JobPropertiesTableModel::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (role == Qt::DisplayRole && orientation == Qt::Horizontal && section >= 0 + && section < NumColumns) + return ColumnNames[section]; + return QVariant(); +} + +Qt::ItemFlags JobPropertiesTableModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags f = QAbstractTableModel::flags(index); + if (index.column() == Column::Value && index.row() == Row::Name && m_item) + f.setFlag(Qt::ItemIsEditable); + return f; +} + +bool JobPropertiesTableModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (role != Qt::EditRole || index.column() != Column::Value || index.row() != Row::Name + || !m_item) + return false; + m_item->setItemName(value.toString()); + emit dataChanged(index, index, {role}); + return true; +} + +void JobPropertiesTableModel::setItem(JobItem* item) +{ + beginResetModel(); + if (m_item) + m_item->mapper()->unsubscribe(this); + m_item = item; + if (m_item) + m_item->mapper()->setOnPropertyChange( + std::bind(&JobPropertiesTableModel::notifyJobPropertyChange, this, + std::placeholders::_1), + this); + endResetModel(); +} + +//-------------------------------------------------------------------------------------------------- +// private member functions +//-------------------------------------------------------------------------------------------------- + +void JobPropertiesTableModel::notifyJobPropertyChange(const QString& property) +{ + if (SessionItem::isItemNamePropertyName(property)) + emit dataChanged(index(Row::Name, Column::Value), index(Row::Name, Column::Value), + {Qt::DisplayRole, Qt::EditRole}); + else if (JobItem::isStatusPropertyName(property)) + emit dataChanged(index(Row::Status, Column::Value), index(Row::Status, Column::Value), + {Qt::DisplayRole, Qt::EditRole}); + else if (JobItem::isBeginTimePropertyName(property)) + emit dataChanged(index(Row::Begin, Column::Value), index(Row::Begin, Column::Value), + {Qt::DisplayRole, Qt::EditRole}); + else if (JobItem::isEndTimePropertyName(property)) + // will change duration very probably too + emit dataChanged(index(Row::End, Column::Value), index(Row::Duration, Column::Value), + {Qt::DisplayRole, Qt::EditRole}); +} diff --git a/GUI/Views/JobWidgets/JobPropertiesTableModel.h b/GUI/Views/JobWidgets/JobPropertiesTableModel.h new file mode 100644 index 0000000000000000000000000000000000000000..92481115eec7e68999fdc296f1ad9edf95c94f81 --- /dev/null +++ b/GUI/Views/JobWidgets/JobPropertiesTableModel.h @@ -0,0 +1,48 @@ +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file GUI/Views/JobWidgets/JobPropertiesWidget.h +//! @brief Defines class JobPropertiesWidget +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2018 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ + +#ifndef BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBPROPERTIESTABLEMODEL_H +#define BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBPROPERTIESTABLEMODE_H + +#include <QAbstractTableModel> + +class JobItem; + + +//! The JobPropertiesTableModel is a table model for the properties of a job except for the comment. +//! The name of the job is editable, all other fields are read only. + +class JobPropertiesTableModel : public QAbstractTableModel { + Q_OBJECT +public: + explicit JobPropertiesTableModel(QObject* parent = nullptr); + ~JobPropertiesTableModel(); + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + virtual QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + virtual Qt::ItemFlags flags(const QModelIndex& index) const override; + virtual bool setData(const QModelIndex& index, const QVariant& value, + int role = Qt::EditRole) override; + void setItem(JobItem* item); + +private: + void notifyJobPropertyChange(const QString& property); + +private: + JobItem* m_item; +}; + +#endif // BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBPROPERTIESTABLEMODEL_H diff --git a/GUI/Views/JobWidgets/JobPropertiesWidget.cpp b/GUI/Views/JobWidgets/JobPropertiesWidget.cpp index a8ae841465f42d28e3852c15fa45eb94b081b627..00552914d67e6c5a7391feac3d3d71c9ead4929d 100644 --- a/GUI/Views/JobWidgets/JobPropertiesWidget.cpp +++ b/GUI/Views/JobWidgets/JobPropertiesWidget.cpp @@ -15,97 +15,106 @@ #include "GUI/Views/JobWidgets/JobPropertiesWidget.h" #include "GUI/Models/JobItem.h" #include "GUI/Views/CommonWidgets/StyleUtils.h" -#include "GUI/Views/PropertyEditor/ComponentEditor.h" +#include "GUI/Views/JobWidgets/JobPropertiesTableModel.h" #include "GUI/Views/Tools/mainwindow_constants.h" #include <QTabBar> #include <QTabWidget> #include <QTextEdit> +#include <QTreeView> #include <QVBoxLayout> -JobPropertiesWidget::JobPropertiesWidget(QWidget* parent) - : SessionItemWidget(parent) - , m_tabWidget(new QTabWidget) - , m_componentEditor(new ComponentEditor) - , m_commentsEditor(new QTextEdit) - , m_block_update(false) +namespace { +enum ETabId { JOB_PROPERTIES, JOB_COMMENTS }; +} + +//================================================================================================== +// JobPropertiesWidget +//================================================================================================== + +//-------------------------------------------------------------------------------------------------- +// public member functions +//-------------------------------------------------------------------------------------------------- + +JobPropertiesWidget::JobPropertiesWidget(QWidget* parent, Qt::WindowFlags f) + : QWidget(parent, f), m_item(nullptr) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding); setWindowTitle(GUI::Constants::JobPropertiesWidgetName); - auto mainLayout = new QVBoxLayout; - mainLayout->setMargin(0); - mainLayout->setSpacing(0); - mainLayout->setContentsMargins(0, 0, 0, 0); + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); + m_tabWidget = new QTabWidget(this); m_tabWidget->setTabPosition(QTabWidget::South); - m_tabWidget->insertTab(JOB_PROPERTIES, m_componentEditor, "Job Properties"); - m_tabWidget->insertTab(JOB_COMMENTS, m_commentsEditor, "Details"); + layout->addWidget(m_tabWidget); - mainLayout->addWidget(m_tabWidget); - setLayout(mainLayout); + m_propertiesView = new QTreeView(this); + m_propertiesView->setRootIsDecorated(false); + m_propertiesView->setAlternatingRowColors(true); + m_tabWidget->insertTab(JOB_PROPERTIES, m_propertiesView, "Job Properties"); + m_propertiesModel = new JobPropertiesTableModel(this); + m_propertiesView->setModel(m_propertiesModel); - connect(m_commentsEditor, &QTextEdit::textChanged, this, &JobPropertiesWidget::onTextChanged); + m_commentsEditor = new QTextEdit(this); + m_tabWidget->insertTab(JOB_COMMENTS, m_commentsEditor, "Comments"); + + connect(m_commentsEditor, &QTextEdit::textChanged, this, + &JobPropertiesWidget::onCommentsEdited); } -QSize JobPropertiesWidget::sizeHint() const +JobPropertiesWidget::~JobPropertiesWidget() { - return QSize(GUI::Utils::Style::PropertyPanelWidth(), GUI::Utils::Style::PropertyPanelWidth()); + if (m_item) + m_item->mapper()->unsubscribe(this); } -QSize JobPropertiesWidget::minimumSizeHint() const +QSize JobPropertiesWidget::sizeHint() const { return QSize(GUI::Utils::Style::PropertyPanelWidth(), GUI::Utils::Style::PropertyPanelWidth()); } -void JobPropertiesWidget::subscribeToItem() +QSize JobPropertiesWidget::minimumSizeHint() const { - currentItem()->mapper()->setOnPropertyChange( - [this](const QString& name) { - if (JobItem::isCommentsPropertyName(name)) - updateItem(); - }, - this); - - m_componentEditor->setItem(currentItem()); - - updateItem(); + return QSize(GUI::Utils::Style::PropertyPanelWidth(), GUI::Utils::Style::PropertyPanelWidth()); } -void JobPropertiesWidget::unsubscribeFromItem() +void JobPropertiesWidget::setItem(JobItem* item) { - m_componentEditor->setItem(nullptr); + if (m_item) + m_item->mapper()->unsubscribe(this); + m_item = item; + m_propertiesModel->setItem(m_item); + if (m_item) { + if (m_item->isFailed()) + m_tabWidget->tabBar()->setTabTextColor(JOB_COMMENTS, Qt::red); + else + m_tabWidget->tabBar()->setTabTextColor(JOB_COMMENTS, Qt::black); + m_commentsEditor->setText(m_item->getComments()); + m_item->mapper()->setOnPropertyChange( + std::bind(&JobPropertiesWidget::notifyJobPropertyChange, this, std::placeholders::_1)); + } else + m_commentsEditor->clear(); } -void JobPropertiesWidget::contextMenuEvent(QContextMenuEvent*) -{ - // Reimplemented to suppress menu from main window -} +//-------------------------------------------------------------------------------------------------- +// private member functions +//-------------------------------------------------------------------------------------------------- -void JobPropertiesWidget::onTextChanged() +void JobPropertiesWidget::notifyJobPropertyChange(const QString& property) { - m_block_update = true; - JobItem* item = jobItem(); - if (item) - item->setComments(m_commentsEditor->toPlainText()); - m_block_update = false; + if (JobItem::isCommentsPropertyName(property) && m_item + && m_item->getComments() != m_commentsEditor->toPlainText()) + m_commentsEditor->setPlainText(m_item->getComments()); } -void JobPropertiesWidget::updateItem() -{ - if (m_block_update) - return; - - if (JobItem* item = jobItem()) { - if (item->getStatus() == "Failed") - m_tabWidget->tabBar()->setTabTextColor(JOB_COMMENTS, Qt::red); - else - m_tabWidget->tabBar()->setTabTextColor(JOB_COMMENTS, Qt::black); - - m_commentsEditor->setText(item->getComments()); - } -} +//-------------------------------------------------------------------------------------------------- +// private slots +//-------------------------------------------------------------------------------------------------- -JobItem* JobPropertiesWidget::jobItem() +void JobPropertiesWidget::onCommentsEdited() { - return dynamic_cast<JobItem*>(currentItem()); + if (m_item) + m_item->setComments(m_commentsEditor->toPlainText()); } diff --git a/GUI/Views/JobWidgets/JobPropertiesWidget.h b/GUI/Views/JobWidgets/JobPropertiesWidget.h index 47ddd1a3e1e37bb042b187be8fe2704f7089ee4c..cad504f2edc6e3e65755cb1d06440024d62be227 100644 --- a/GUI/Views/JobWidgets/JobPropertiesWidget.h +++ b/GUI/Views/JobWidgets/JobPropertiesWidget.h @@ -15,41 +15,39 @@ #ifndef BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBPROPERTIESWIDGET_H #define BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBPROPERTIESWIDGET_H -#include "GUI/Views/CommonWidgets/SessionItemWidget.h" +#include <QWidget> class JobItem; -class QTextEdit; +class JobPropertiesTableModel; class QTabWidget; -class ComponentEditor; +class QTextEdit; +class QTreeView; //! The JobPropertiesWidget class holds component editor for JobItem. Part of JobSelectorWidget, //! resides at lower left corner of JobView. -class JobPropertiesWidget : public SessionItemWidget { +class JobPropertiesWidget : public QWidget { Q_OBJECT public: - enum ETabId { JOB_PROPERTIES, JOB_COMMENTS }; - explicit JobPropertiesWidget(QWidget* parent = nullptr); + explicit JobPropertiesWidget(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); + ~JobPropertiesWidget(); + void setItem(JobItem* item); QSize sizeHint() const; QSize minimumSizeHint() const; -protected: - void subscribeToItem(); - void unsubscribeFromItem(); - void contextMenuEvent(QContextMenuEvent*); +private: + void notifyJobPropertyChange(const QString& property); private slots: - void onTextChanged(); + void onCommentsEdited(); private: - void updateItem(); - JobItem* jobItem(); - QTabWidget* m_tabWidget; - ComponentEditor* m_componentEditor; + QTreeView* m_propertiesView; + JobPropertiesTableModel* m_propertiesModel; QTextEdit* m_commentsEditor; - bool m_block_update; + JobItem* m_item; }; #endif // BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBPROPERTIESWIDGET_H diff --git a/GUI/Views/JobWidgets/JobSelectorActions.cpp b/GUI/Views/JobWidgets/JobSelectorActions.cpp deleted file mode 100644 index 0e225dbefc5064fe4970d748ac8a900e039bec5a..0000000000000000000000000000000000000000 --- a/GUI/Views/JobWidgets/JobSelectorActions.cpp +++ /dev/null @@ -1,182 +0,0 @@ -// ************************************************************************************************ -// -// BornAgain: simulate and fit reflection and scattering -// -//! @file GUI/Views/JobWidgets/JobSelectorActions.cpp -//! @brief Implements class JobSelectorActions -//! -//! @homepage http://www.bornagainproject.org -//! @license GNU General Public License v3 or higher (see COPYING) -//! @copyright Forschungszentrum Jülich GmbH 2018 -//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) -// -// ************************************************************************************************ - -#include "GUI/Views/JobWidgets/JobSelectorActions.h" -#include "GUI/Models/IntensityDataItem.h" -#include "GUI/Models/JobItem.h" -#include "GUI/Models/JobModel.h" -#include <QAction> -#include <QItemSelectionModel> -#include <QMenu> -#include <QPersistentModelIndex> -#include <memory> - -JobSelectorActions::JobSelectorActions(JobModel* jobModel, QObject* parent) - : QObject(parent) - , m_runJobAction(nullptr) - , m_removeJobAction(nullptr) - , m_selectionModel(nullptr) - , m_jobModel(jobModel) -{ - m_runJobAction = new QAction("Run", this); - m_runJobAction->setIcon(QIcon(":/images/play.svg")); - m_runJobAction->setToolTip("Run currently selected job"); - connect(m_runJobAction, &QAction::triggered, this, &JobSelectorActions::onRunJob); - - m_removeJobAction = new QAction("Remove", this); - m_removeJobAction->setIcon(QIcon(":/images/delete.svg")); - m_removeJobAction->setToolTip("Remove currently selected job."); - connect(m_removeJobAction, &QAction::triggered, this, &JobSelectorActions::onRemoveJob); -} - -void JobSelectorActions::setSelectionModel(QItemSelectionModel* selectionModel) -{ - m_selectionModel = selectionModel; -} - -void JobSelectorActions::onRunJob() -{ - QModelIndexList indexList = m_selectionModel->selectedIndexes(); - for (auto index : indexList) { - if (canRunJob(index)) - m_jobModel->runJob(index); - } -} - -void JobSelectorActions::onRemoveJob() -{ - QList<QPersistentModelIndex> toRemove; - for (auto index : m_selectionModel->selectedIndexes()) - if (canRemoveJob(index)) - toRemove.append(QPersistentModelIndex(index)); - - for (auto index : toRemove) - m_jobModel->removeJob(index); -} - -//! Generates context menu at given point. If indexAtPoint is provided, the actions will be done -//! for corresponding JobItem - -void JobSelectorActions::onContextMenuRequest(const QPoint& point, const QModelIndex& indexAtPoint) -{ - QMenu menu; - initItemContextMenu(menu, indexAtPoint); - menu.exec(point); - setAllActionsEnabled(true); -} - -//! Puts all IntensityDataItem axes range to the selected job - -void JobSelectorActions::equalizeSelectedToJob(int selected_id) -{ - QModelIndexList selectedList = m_selectionModel->selectedIndexes(); - - if (selected_id >= selectedList.size()) - return; - - JobItem* referenceItem = m_jobModel->getJobItemForIndex(selectedList.at(selected_id)); - ASSERT(referenceItem); - - IntensityDataItem* referenceDataItem = referenceItem->intensityDataItem(); - if (!referenceDataItem) - return; - - for (auto index : selectedList) { - JobItem* jobItem = m_jobModel->getJobItemForIndex(index); - if (jobItem == referenceItem) - continue; - if (IntensityDataItem* dataItem = jobItem->intensityDataItem()) { - dataItem->setLowerX(referenceDataItem->getLowerX()); - dataItem->setUpperX(referenceDataItem->getUpperX()); - dataItem->setLowerY(referenceDataItem->getLowerY()); - dataItem->setUpperY(referenceDataItem->getUpperY()); - dataItem->setLowerZ(referenceDataItem->getLowerZ()); - dataItem->setUpperZ(referenceDataItem->getUpperZ()); - } - } -} - -void JobSelectorActions::initItemContextMenu(QMenu& menu, const QModelIndex& indexAtPoint) -{ - menu.setToolTipsVisible(true); - - menu.addAction(m_runJobAction); - menu.addAction(m_removeJobAction); - - QModelIndex targetIndex = indexAtPoint; - if (!targetIndex.isValid()) { - QModelIndexList indexList = m_selectionModel->selectedIndexes(); - if (!indexList.empty()) - targetIndex = indexList.first(); - } - m_runJobAction->setEnabled(canRunJob(targetIndex)); - m_removeJobAction->setEnabled(canRemoveJob(targetIndex)); - - setupEqualizeMenu(menu); -} - -void JobSelectorActions::setupEqualizeMenu(QMenu& menu) -{ - menu.addSeparator(); - - QMenu* equalize_menu = menu.addMenu("Equalize selected plots"); - equalize_menu->setToolTipsVisible(true); - equalize_menu->setToolTip( - "All plots from the list of selected jobs will be equalized to the one."); - QModelIndexList selected = m_selectionModel->selectedIndexes(); - - if (selected.size() <= 1) { - equalize_menu->setDisabled(true); - return; - } - std::sort(selected.begin(), selected.end(), - [](const QModelIndex& a, const QModelIndex& b) { return a.row() < b.row(); }); - - for (int i = 0; i < selected.count(); ++i) { - JobItem* jobItem = m_jobModel->getJobItemForIndex(selected.at(i)); - QAction* action = equalize_menu->addAction(QString("to ").append(jobItem->itemName())); - connect(action, &QAction::triggered, [=] { equalizeSelectedToJob(i); }); - } -} - -void JobSelectorActions::setAllActionsEnabled(bool value) const -{ - m_runJobAction->setEnabled(value); - m_removeJobAction->setEnabled(value); -} - -bool JobSelectorActions::canRunJob(const QModelIndex& index) const -{ - if (!index.isValid()) - return false; - - const JobItem* jobItem = m_jobModel->getJobItemForIndex(index); - - if (jobItem->isRunning() || jobItem->getStatus() == "Fitting") - return false; - - return true; -} - -bool JobSelectorActions::canRemoveJob(const QModelIndex& index) const -{ - if (!index.isValid()) - return false; - - const JobItem* jobItem = m_jobModel->getJobItemForIndex(index); - if (jobItem->isRunning() || jobItem->getStatus() == "Fitting") - return false; - - return true; -} diff --git a/GUI/Views/JobWidgets/JobSelectorActions.h b/GUI/Views/JobWidgets/JobSelectorActions.h deleted file mode 100644 index 4973d53ab54bfa069806ff4c970742501bc7e357..0000000000000000000000000000000000000000 --- a/GUI/Views/JobWidgets/JobSelectorActions.h +++ /dev/null @@ -1,59 +0,0 @@ -// ************************************************************************************************ -// -// BornAgain: simulate and fit reflection and scattering -// -//! @file GUI/Views/JobWidgets/JobSelectorActions.h -//! @brief Defines class JobSelectorActions -//! -//! @homepage http://www.bornagainproject.org -//! @license GNU General Public License v3 or higher (see COPYING) -//! @copyright Forschungszentrum Jülich GmbH 2018 -//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) -// -// ************************************************************************************************ - -#ifndef BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBSELECTORACTIONS_H -#define BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBSELECTORACTIONS_H - -#include <QModelIndex> -#include <QObject> - -class QAction; -class JobModel; -class QItemSelectionModel; -class QModelIndex; -class QMenu; - -//! The JobSelectorActions class contains actions to run/remove jobs. Actions are used by the -//! toolbar and JobSelectorList's context menu. - -class JobSelectorActions : public QObject { - Q_OBJECT -public: - JobSelectorActions(JobModel* jobModel, QObject* parent = 0); - - void setSelectionModel(QItemSelectionModel* selectionModel); - -public slots: - void onRunJob(); - void onRemoveJob(); - void onContextMenuRequest(const QPoint& point, const QModelIndex& indexAtPoint = {}); - void equalizeSelectedToJob(int selected_id); - -private: - void initItemContextMenu(QMenu& menu, const QModelIndex& indexAtPoint); - void setupEqualizeMenu(QMenu& menu); - void setAllActionsEnabled(bool value) const; - bool canRunJob(const QModelIndex& index) const; - bool canRemoveJob(const QModelIndex& index) const; - -public: - QAction* m_runJobAction; - QAction* m_removeJobAction; - -private: - QItemSelectionModel* m_selectionModel; - JobModel* m_jobModel; -}; - -#endif // BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBSELECTORACTIONS_H diff --git a/GUI/Views/JobWidgets/JobSelectorWidget.cpp b/GUI/Views/JobWidgets/JobSelectorWidget.cpp index 88d49fdd9ac0d8e0e863d25203c8529bd2eeee8f..7cb961159964a7f4bfefcd31bf8f1b4d13e85de0 100644 --- a/GUI/Views/JobWidgets/JobSelectorWidget.cpp +++ b/GUI/Views/JobWidgets/JobSelectorWidget.cpp @@ -13,88 +13,62 @@ // ************************************************************************************************ #include "GUI/Views/JobWidgets/JobSelectorWidget.h" +#include "Base/Utils/Assert.h" #include "GUI/Models/JobItem.h" -#include "GUI/Views/CommonWidgets/StyleUtils.h" -#include "GUI/Views/CommonWidgets/StyledToolBar.h" -#include "GUI/Views/JobWidgets/JobListWidget.h" +#include "GUI/Views/JobWidgets/JobListView.h" #include "GUI/Views/JobWidgets/JobPropertiesWidget.h" -#include "GUI/Views/JobWidgets/JobSelectorActions.h" #include "GUI/Views/Tools/mainwindow_constants.h" -#include <QHBoxLayout> #include <QSplitter> +#include <QVBoxLayout> JobSelectorWidget::JobSelectorWidget(JobModel* jobModel, QWidget* parent) : QWidget(parent) - , m_jobSelectorActions(new JobSelectorActions(jobModel, this)) - , m_jobListWidget(new JobListWidget) - , m_jobProperties(new JobPropertiesWidget) - , m_jobModel(nullptr) + , m_jobModel(jobModel) { setWindowTitle(GUI::Constants::JobSelectorWidgetName); setObjectName("JobSelectorWidget"); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - setModel(jobModel); + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); - auto splitter = new QSplitter; - splitter->setOrientation(Qt::Vertical); - splitter->addWidget(m_jobListWidget); - splitter->addWidget(m_jobProperties); + QSplitter* splitter = new QSplitter(Qt::Vertical, this); splitter->setChildrenCollapsible(true); + layout->addWidget(splitter); - QToolBar* toolBar = new StyledToolBar(this); - toolBar->setMinimumSize(toolBar->minimumHeight(), toolBar->minimumHeight()); - toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - toolBar->addAction(m_jobSelectorActions->m_runJobAction); - toolBar->addAction(m_jobSelectorActions->m_removeJobAction); - - auto mainLayout = new QVBoxLayout; - mainLayout->setMargin(0); - mainLayout->setSpacing(0); - mainLayout->setContentsMargins(0, 0, 0, 0); - mainLayout->addWidget(toolBar); - mainLayout->addWidget(splitter); - setLayout(mainLayout); - - m_jobSelectorActions->setSelectionModel(m_jobListWidget->selectionModel()); + m_jobListView = new JobListView(m_jobModel, splitter); + splitter->addWidget(m_jobListView); - connect(m_jobListWidget, &JobListWidget::contextMenuRequest, m_jobSelectorActions, - &JobSelectorActions::onContextMenuRequest); - connect(m_jobListWidget, &JobListWidget::selectionChanged, this, - &JobSelectorWidget::onSelectionChanged); -} - -void JobSelectorWidget::setModel(JobModel* jobModel) -{ - m_jobModel = jobModel; - m_jobListWidget->setModel(m_jobModel); -} + m_jobProperties = new JobPropertiesWidget(this); + splitter->addWidget(m_jobProperties); -QSize JobSelectorWidget::sizeHint() const -{ - return QSize(GUI::Utils::Style::PropertyPanelWidth(), - GUI::Utils::Style::PropertyPanelWidth() * 2); -} + connect(m_jobListView, &JobListView::selectedJobsChanged, this, + &JobSelectorWidget::onSelectedJobsChanged); + connect(m_jobListView, &JobListView::selectedJobsChanged, this, + &JobSelectorWidget::selectedJobsChanged); -QSize JobSelectorWidget::minimumSizeHint() const -{ - return QSize(GUI::Utils::Style::PropertyPanelWidth(), GUI::Utils::Style::PropertyPanelWidth()); + onSelectedJobsChanged(selectedJobs()); } -const JobItem* JobSelectorWidget::currentJobItem() const +QVector<JobItem*> JobSelectorWidget::selectedJobs() const { - return m_jobListWidget->currentJobItem(); + return m_jobListView->selectedJobs(); } void JobSelectorWidget::makeJobItemSelected(JobItem* item) { ASSERT(item); - m_jobListWidget->makeJobItemSelected(item); + m_jobListView->selectJob(item); } -void JobSelectorWidget::onSelectionChanged(JobItem* jobItem) + +void JobSelectorWidget::onSelectedJobsChanged(const QVector<JobItem*>& jobs) { - m_jobProperties->setItem(jobItem); - emit selectionChanged(jobItem); + if (jobs.size() == 1) + m_jobProperties->setItem(jobs.front()); + else + m_jobProperties->setItem(nullptr); } diff --git a/GUI/Views/JobWidgets/JobSelectorWidget.h b/GUI/Views/JobWidgets/JobSelectorWidget.h index 39795eba3c2d0dc59b5cb449681c3aa560661a54..5d94525d2cb79780e42ac524d1dab6877bc01109 100644 --- a/GUI/Views/JobWidgets/JobSelectorWidget.h +++ b/GUI/Views/JobWidgets/JobSelectorWidget.h @@ -19,8 +19,7 @@ class JobModel; class JobItem; -class JobSelectorActions; -class JobListWidget; +class JobListView; class JobPropertiesWidget; //! The JobSelectorWidget class represents left panel of JobView. Contains a tree to select jobs @@ -31,26 +30,19 @@ class JobSelectorWidget : public QWidget { public: explicit JobSelectorWidget(JobModel* jobModel, QWidget* parent = nullptr); - - void setModel(JobModel* jobModel); - - QSize sizeHint() const; - QSize minimumSizeHint() const; - - const JobItem* currentJobItem() const; + QVector<JobItem*> selectedJobs() const; signals: - void selectionChanged(JobItem*); + void selectedJobsChanged(const QVector<JobItem*>& jobs); public slots: void makeJobItemSelected(JobItem*); private slots: - void onSelectionChanged(JobItem* jobItem); + void onSelectedJobsChanged(const QVector<JobItem*>& jobs); private: - JobSelectorActions* m_jobSelectorActions; - JobListWidget* m_jobListWidget; + JobListView* m_jobListView; JobPropertiesWidget* m_jobProperties; JobModel* m_jobModel; }; diff --git a/GUI/mainwindow/OutputDataIOService.cpp b/GUI/mainwindow/OutputDataIOService.cpp index e5817567301b15ba4e746a29dfa8d4873daeda40..13d21bbe8b0b0b0f34507c0ae45c9097e201e07c 100644 --- a/GUI/mainwindow/OutputDataIOService.cpp +++ b/GUI/mainwindow/OutputDataIOService.cpp @@ -78,7 +78,7 @@ void OutputDataIOService::loadDataFiles(const QString& projectDir, MessageServic if (auto jobItem = parentJobItem(item)) { if (jobItem->isRunning()) { jobItem->setComments("Possible GUI crash while job was running"); - jobItem->setStatus("Failed"); + jobItem->setStatus(JobStatus::Failed); } } @@ -87,7 +87,7 @@ void OutputDataIOService::loadDataFiles(const QString& projectDir, MessageServic // Handling corrupted file on disk jobItem->setComments( QString("Load of the data from disk failed with '%1'").arg(QString(ex.what()))); - jobItem->setStatus("Failed"); + jobItem->setStatus(JobStatus::Failed); } if (!messageService) throw ex;