Mantid
Loading...
Searching...
No Matches
ScriptRepositoryView.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
11#include "MantidKernel/Logger.h"
12#include "MantidQtIcons/Icon.h"
16
17#include <QCoreApplication>
18#include <QDebug>
19#include <QFileDialog>
20#include <QLabel>
21#include <QMessageBox>
22#include <QPainter>
23#include <QPushButton>
24#include <QTime>
25#include <QVBoxLayout>
26
27namespace MantidQt::API {
28namespace {
30Mantid::Kernel::Logger g_log("ScriptRepositoryView");
31} // namespace
32
33const QString install_mantid_label =
34 "<html><head/><body><p>The <span style=\" font-weight:600;\">"
35 "Script Repository</span> allows you to:</p>"
36 "<p> * Get <span style=\" font-weight:600;\">Mantid</span> Scripts from "
37 "the mantid developers and the community;</p>"
38 "<p> * Keep those scripts up to date automatically. </p>"
39 "<p><span style=\" font-style:italic;\">"
40 "N.B. The installation usually requires a couple of minutes, depending on "
41 "your network bandwidth. </span></p>"
42 "<p>More Information available at "
43 "<a href=\"http://docs.mantidproject.org/workbench/scriptrepository\"><span style=\" "
44 "text-decoration: underline; color:#0000ff;\">"
45 "http://docs.mantidproject.org/workbench/scriptrepository</span></a></p></br><p><span "
46 "style=\" font-weight:600;\">"
47 "Would you like to install it now?</span></p></body></html>";
48
49const QString installation_in_progress = "<html><head/><body><p><span style=\" font-weight:600;\">"
50 "Installing Script Repository Installation in background!</span></p>"
51 "<p>You may continue to use mantid.</p>"
52 "<p>The Result Log willl give you information of the installation "
53 "progress.</p>"
54 "<p>When finished, please, reopen the <span style=\" "
55 "font-weight:600;\">Script Repository</span>. </p></body></html>";
56
57const QString installation_failed = "<html><head/><body><p>The installation of Script Repository "
58 "<span style=\" font-weight:600;\">Failed</span>!</p>"
59 "<p>Please, check the Result Log to see why the installation failed. "
60 "</p></body></html>";
61
62const QString dir_not_empty_label = "<html><head/><body><p>The directory/folder that you have selected is not "
63 "empty</p>"
64 "<p>Are you sure that you want to install the script repository here? All "
65 "the files and directories found in "
66 "the selected directory/folder will be listed as part of the "
67 "repository.</p>"
68 "<p>If you are not sure, please choose 'no' and then select an empty (or "
69 "newly created) directory/folder.</p>"
70 "<p>If this is your home directory, desktop or similar you should "
71 "definitely choose 'no'.</p>"
72 "<p>If you are sure of what you are doing, please choose 'yes'. The "
73 "installation may take a couple of minutes.</p>"
74 "</body></html>";
75
76//----------------------------------------------------------------------------------------------
96
97 ui->setupUi(this);
98
99 try {
100 // create and instance of ScriptRepository
101 Mantid::API::ScriptRepository_sptr repo_ptr = ScriptRepositoryFactory::Instance().create("ScriptRepositoryImpl");
102
103 // check if the ScriptRepository was ever installed
104 if (!repo_ptr->isValid()) {
105 bool successful = chooseLocationAndInstall(std::move(repo_ptr));
106 if (!successful) {
107 return;
108 }
109 }
110 // create the model
111 model = new RepoModel(this);
112
114 // means that the installation failed
115 g_log.warning() << "ScriptRepository installation: " << ex.what() << '\n';
116 g_log.information() << "ScriptRepository installation failed with this information: " << ex.systemError() << '\n';
117 QMessageBox::warning(this, "Installation Failed", QString(ex.what()));
118 close();
119 deleteLater();
120 return;
121 } catch (...) {
122 g_log.error() << "Unknown error occurred to install ScriptRepository. It "
123 "will not be shown.\n";
124 close();
125 deleteLater();
126 return;
127 }
128 // from this point, it is assumed that ScriptRepository is installed.
129
130 // configure the Ui
131 connect(ui->reloadPushButton, SIGNAL(clicked()), this, SLOT(updateModel()));
132 connect(ui->pbHelp, SIGNAL(clicked()), this, SLOT(helpClicked()));
133 connect(model, SIGNAL(executingThread(bool)), ui->reloadPushButton, SLOT(setDisabled(bool)));
134
135 // setup the model and delegates
136 ui->repo_treeView->setModel(model);
137 ui->repo_treeView->setItemDelegateForColumn(1, new RepoDelegate(this));
138 ui->repo_treeView->setItemDelegateForColumn(2, new CheckBoxDelegate(this));
139 ui->repo_treeView->setColumnWidth(0, 290);
140
141 // stablish the connections.
142 connect(ui->repo_treeView, SIGNAL(activated(const QModelIndex &)), this, SLOT(cell_activated(const QModelIndex &)));
143 connect(ui->repo_treeView, SIGNAL(currentCell(const QModelIndex &)), this, SLOT(currentChanged(const QModelIndex &)));
144 const ConfigServiceImpl &config = ConfigService::Instance();
145 updateLocationString(config.getString("ScriptLocalRepository"));
146 connect(ui->folderPathLabel, SIGNAL(linkActivated(QString)), this, SLOT(openFolderLink(QString)));
147}
148
158 enum EXC_OPTIONS { NOTWANTED, NODIRECTORY };
159
160 QString dir;
161 try {
162 // Ask the user if he wants to install the ScriptRepository
163 if (QMessageBox::Ok != QMessageBox::question(this, "Install Script Repository?", install_mantid_label,
164 QMessageBox::Ok | QMessageBox::Cancel)) {
165 throw NOTWANTED;
166 }
167 // get the directory to install the script repository
168 const ConfigServiceImpl &config = ConfigService::Instance();
169 QString loc = QString::fromStdString(config.getString("ScriptLocalRepository"));
170
171 bool sureAboutDir = false;
172
173 while (!sureAboutDir) {
174 dir = QFileDialog::getExistingDirectory(this, tr("Where do you want to install Script Repository?"), loc,
175 QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
176
177 // configuring
178 if (dir.isEmpty()) {
179 throw NODIRECTORY;
180 }
181
182 // warn if dir is not empty
183 if (0 == QDir(dir).entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot).count()) {
184 // empty dir, just go ahead
185 sureAboutDir = true;
186 } else {
187 // warn user in case the repo is being installed in its home, etc.
188 // directory
189 QMessageBox::StandardButton sel =
190 QMessageBox::question(this, "Are you sure you want to install the Script Repository here?",
191 dir_not_empty_label, QMessageBox::Yes | QMessageBox::No);
192 if (QMessageBox::Yes == sel)
193 sureAboutDir = true;
194 }
195 }
196 } catch (EXC_OPTIONS &ex) {
197 if (ex == NODIRECTORY) {
198 // probably the user change mind. He does not want to install any more.
199 QMessageBox::warning(this, "Installation Failed", "Invalid Folder to install Script Repository!\n");
200 }
201
202 close();
203 deleteLater();
204 return false;
205 }
206 // attempt to install
207 repo_ptr->install(dir.toStdString());
208 updateLocationString(dir.toStdString());
209 g_log.information() << "ScriptRepository installed at " << dir.toStdString() << '\n';
210 return true;
211}
212
213void ScriptRepositoryView::updateLocationString(const std::string &installDir) {
214 const QString loc = QString::fromStdString(installDir);
215 const QString loc_info = "<html><head/><body><p><a href=\"%1\"><span style=\" "
216 "text-decoration: underline; "
217 "color:#0000ff;\">%2</span></a></p></body></html>";
218 QString path_label;
219 if (loc.size() < 50)
220 path_label = loc;
221 else {
222 path_label = QString("%1...%2").arg(loc.left(20)).arg(loc.right(27));
223 }
224
225 ui->folderPathLabel->setText(loc_info.arg(loc).arg(path_label));
226 ui->folderPathLabel->setToolTip(QString("Click here to open Script Repository Folder: %1.").arg(loc));
227}
228
237 RepoModel *before = model;
238 auto model_repo_ptr = model->getRepoPtr();
239 if (!model_repo_ptr->isValid()) {
240 const bool success = chooseLocationAndInstall(std::move(model_repo_ptr));
241 if (!success) {
242 return;
243 }
244 }
245 model = new RepoModel();
246 connect(model, SIGNAL(executingThread(bool)), ui->reloadPushButton, SLOT(setDisabled(bool)));
247 ui->repo_treeView->setModel(model);
248 delete before;
249}
250
251//----------------------------------------------------------------------------------------------
255
262void ScriptRepositoryView::cell_activated(const QModelIndex &in) {
263
264 RepoModel *_model = qobject_cast<RepoModel *>(ui->repo_treeView->model());
265 if (_model) {
266 QString path = _model->filePath(in);
267 if (path.isEmpty()) {
268 // no real file to be opened.
269 return;
270 }
271 emit loadScript(path);
272 }
273}
274
281void ScriptRepositoryView::currentChanged(const QModelIndex &in) {
282 RepoModel *_model = qobject_cast<RepoModel *>(ui->repo_treeView->model());
283 if (_model) {
284 // try to get the description of the file pointed at the current index.
285 // and update the description text browser.
286 QString description = _model->fileDescription(in);
287 ui->desc_textBrowser->setText(description);
288 QString author_name = _model->author(in);
289 if (author_name.isEmpty())
290 ui->authorNameLabel->setText("");
291 else
292 ui->authorNameLabel->setText(QString("<b>Author:</b> ") + author_name);
293 return;
294 }
295}
296
298void ScriptRepositoryView::helpClicked() { HelpWindow::showPage(QStringLiteral("workbench/scriptrepository.html")); }
299
301// DELEGATE : Allow to display and interact with the View in a nicer way.
302// Improve the User Experience.
304
305ScriptRepositoryView::RepoDelegate::RepoDelegate(QObject *parent) : QStyledItemDelegate(parent) {}
330void ScriptRepositoryView::RepoDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
331 const QModelIndex &index) const {
332
333 if (!index.isValid())
334 return;
335 if (painter->device() == nullptr)
336 return;
337
338 // get the state and chose the best fit icon
339 QString state = index.model()->data(index, Qt::DisplayRole).toString();
340 auto icon = getIcon(state);
341 // entries with no action available (purely local ones) get no button at all
342 if (icon.isNull())
343 return;
344
345 // define the region to draw the icon
346 QRect iconRect(option.rect);
347 int min_val = iconRect.width() < iconRect.height() ? iconRect.width() : iconRect.height();
348 // make it square
349 iconRect.setWidth(min_val);
350 iconRect.setHeight(min_val);
351 iconRect.moveCenter(option.rect.center());
352
353 int icon_size = (int)(min_val * .8);
354
355 // the locally-modified state is informational only, so draw the icon flat
356 // (no push-button chrome) to make clear there is nothing to click
357 if (state == RepoModel::localChangedSt()) {
358 icon.paint(painter, iconRect, Qt::AlignCenter);
359 return;
360 }
361
362 // define the options to draw a push button with the icon displayed
363 QStyleOptionButton button;
364 button.rect = iconRect;
365 button.icon = icon;
366 button.iconSize = QSize(icon_size, icon_size);
367 button.state = QStyle::State_Enabled;
368 // draw a push button
369 QApplication::style()->drawControl(QStyle::CE_PushButton, &button, painter);
370}
371
372QIcon ScriptRepositoryView::RepoDelegate::getIcon(const QString &state) const {
373 QIcon icon;
374 if (state == RepoModel::remoteOnlySt())
375 icon = Icons::getIcon("mdi.download");
376 else if (state == RepoModel::remoteChangedSt() || state == RepoModel::bothChangedSt()) {
377 icon = Icons::getIcon("mdi.transfer-down");
378 } else if (state == RepoModel::updatedSt())
379 icon = Icons::getIcon("mdi.check-bold");
380 else if (state == RepoModel::downloadSt())
381 icon = Icons::getIcon("mdi.progress-download");
382 else if (state == RepoModel::localChangedSt())
383 icon = Icons::getIcon("mdi.pencil");
384 // local-only entries have no remote action, so they deliberately get a null
385 // icon
386 return icon;
387}
388
411bool ScriptRepositoryView::RepoDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
412 const QStyleOptionViewItem & /*option*/,
413 const QModelIndex &index) {
414 // if event is mouse click
415 if (event->type() == QEvent::MouseButtonPress) {
416 QString value = model->data(index, Qt::DisplayRole).toString();
417 // these entries have nothing to fetch from the central repository
419 return false; // ignore
420 return model->setData(index, "Download", Qt::EditRole);
421 } else {
422 return true; // Does not allow others events to be processed (example:
423 // double-click)
424 }
425}
429QSize ScriptRepositoryView::RepoDelegate::sizeHint(const QStyleOptionViewItem & /*option*/,
430 const QModelIndex & /*index*/) const {
431 return QSize(35, 35);
432}
433
435// CheckBoxDelegate
437
438ScriptRepositoryView::CheckBoxDelegate::CheckBoxDelegate(QObject *parent) : QStyledItemDelegate(parent) {}
460void ScriptRepositoryView::CheckBoxDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
461 const QModelIndex &index) const {
462 if (!index.isValid())
463 return;
464 if (painter->device() == nullptr)
465 return;
466
467 QStyleOptionViewItem modifiedOption(option);
468
469 QPoint p = modifiedOption.rect.center();
470 QSize curr = modifiedOption.rect.size();
471 int min_value = (int)((curr.width() < curr.height()) ? curr.width() : curr.height() * .8);
472 // make the checkbox a square in the center of the cell
473 modifiedOption.rect.setSize(QSize(min_value, min_value));
474 modifiedOption.rect.moveCenter(p);
475 // get the current state of this entry
476 QString state = index.model()->data(index, Qt::DisplayRole).toString();
477
478 if (state == "true")
479 modifiedOption.state |= QStyle::State_On;
480 else if (state == "false")
481 modifiedOption.state |= QStyle::State_Off;
482 else
483 return;
484 // draw it
485 QApplication::style()->drawPrimitive(QStyle::PE_IndicatorItemViewItemCheck, &modifiedOption, painter);
486}
487
510bool ScriptRepositoryView::CheckBoxDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
511 const QStyleOptionViewItem & /*option*/,
512 const QModelIndex &index) {
513 if (event->type() == QEvent::MouseButtonPress) {
514 QString value = model->data(index, Qt::DisplayRole).toString();
515 QString action = "setFalse";
516 if (value == "false")
517 action = "setTrue";
518 return model->setData(index, action, Qt::EditRole);
519 } else {
520 // QStyledItemDelegate::editorEvent(event, model, option, index);
521 return true; // Does not allow the event to be catched by another one
522 }
523}
524
530void ScriptRepositoryView::openFolderLink(const QString &link) {
531 const std::string error_msg = "Unable to open \"" + link.toStdString() + "\". Reason: ";
532
533 // QUrl::fromLocalFile seems to be the most robust way of constructing QUrls
534 // on
535 // the local file system for all platforms.
536 const QUrl url = QUrl::fromLocalFile(link);
537 if (!url.isValid()) {
538 g_log.error() << error_msg << "Invalid (malformed) URL.\n";
539 return;
540 }
541
542 const bool openSuccessful = MantidDesktopServices::openUrl(url);
543 if (!openSuccessful)
544 g_log.error() << error_msg << "Could not find directory.\n";
545}
546
547} // namespace MantidQt::API
double value
The value of the point.
Definition FitMW.cpp:51
std::map< DeltaEMode::Type, std::string > index
int count
counter
Definition Matrix.cpp:37
static void showPage(const std::string &url=std::string())
static bool openUrl(const QUrl &url)
Opens a url in the appropriate web browser.
RepoModel : Wrapper for ScriptRepository to fit the Model View Qt Framework.
Definition RepoModel.h:68
static const QString & localChangedSt()
QString filePath(const QModelIndex &index)
Return the operative system file path if it exists.
Mantid::API::ScriptRepository_sptr getRepoPtr()
Definition RepoModel.h:153
QString author(const QModelIndex &index)
static const QString & remoteChangedSt()
static const QString & updatedSt()
static const QString & bothChangedSt()
QVariant data(const QModelIndex &index, int role) const override
access to the ScriptRepository data
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
change data
QString fileDescription(const QModelIndex &index)
Return the description of the file for a defined entry.
static const QString & localOnlySt()
static const QString & downloadSt()
static const QString & remoteOnlySt()
Delegate to show the checkbox for configuring the auto update.
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override
Reacts to the iteraction with the user when he clicks on the buttons displayed at paint.
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
Draws the column 2 (AutoUpdate) of ScriptRepositoryView.
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override
Reacts to the iteraction with the user when he clicks on the buttons displayed at paint.
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
Draws the column 1 (Status) of ScriptRepositoryView.
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
Provides the ideal size for this column.
ScriptRepositoryView : Provide the User Interface to the ScriptRepository.
ScriptRepositoryView(QWidget *parent=nullptr)
Creates the widget for the ScriptRepositoryView.
bool chooseLocationAndInstall(Mantid::API::ScriptRepository_sptr repo_ptr)
Prompt the user with where to install the script repository, and install it.
void openFolderLink(const QString &)
Attempt to open the given folder link using an appropriate application.
void helpClicked()
Open the ScriptRepository Page on Web Browser.
void currentChanged(const QModelIndex &current)
This method will be executed every time the user change the selection.
void updateModel()
This method refreshes the ScriptRepository and allows it to check list the files again.
void cell_activated(const QModelIndex &)
Allows the user to open a file to investigate it.
void updateLocationString(const std::string &installDir)
The ScriptRepository class is intended to be used mainly by users, who will want to share and downloa...
const char * what() const noexcept override
Returns the message string.
const std::string & systemError() const
Returns the error description with technical details on the origin and cause.
The ConfigService class provides a simple facade to access the Configuration functionality of the Man...
The Logger class is in charge of the publishing messages from the framework through various channels.
Definition Logger.h:51
void error(const std::string &msg)
Logs at error level.
Definition Logger.cpp:108
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
void information(const std::string &msg)
Logs at information level.
Definition Logger.cpp:136
const QString installation_in_progress
const QString install_mantid_label
const QString dir_not_empty_label
const QString installation_failed
std::shared_ptr< ScriptRepository > ScriptRepository_sptr
shared pointer to the ScriptRepository base class
Mantid::Kernel::SingletonHolder< ScriptRepositoryFactoryImpl > ScriptRepositoryFactory
Kernel::Logger g_log("DetermineSpinStateOrder")
Mantid::Kernel::SingletonHolder< ConfigServiceImpl > ConfigService