diff --git a/src/lay/LogViewerDialog.ui b/src/lay/LogViewerDialog.ui index eeb86327f..41834aa0c 100644 --- a/src/lay/LogViewerDialog.ui +++ b/src/lay/LogViewerDialog.ui @@ -1,117 +1,201 @@ - + + LogViewerDialog - - + + 0 0 - 578 - 579 + 516 + 287 - + Log Viewer - - + + 9 - + + 9 + + + 9 + + + 9 + + 6 - - - - Clear - - - - - - - Separator - - - - - - - Copy - - - - - - - Qt::Horizontal - - - - 101 - 22 - - - - - - - - Verbosity - - - - - + + - + Silent - + Information - + Details - + Verbose - + Noisy - - - + + + + Separator + + + + + + + Copy + + + + + + + Clear + + + + + + + Verbosity + + + + + + QListView::Adjust - + true - - - + + + Qt::Horizontal - - QDialogButtonBox::Close + + + 101 + 0 + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + :/warn.png + + + + + + + There are errors or warnings + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + - + + verbosity_cbx + clear_pb + separator_pb + copy_pb + log_view + + + + buttonBox @@ -119,11 +203,11 @@ LogViewerDialog accept() - + 248 254 - + 157 274 @@ -135,11 +219,11 @@ LogViewerDialog reject() - + 316 260 - + 286 274 diff --git a/src/lay/SaltGrainPropertiesDialog.ui b/src/lay/SaltGrainPropertiesDialog.ui index 7ef38eda1..1f8cc352f 100644 --- a/src/lay/SaltGrainPropertiesDialog.ui +++ b/src/lay/SaltGrainPropertiesDialog.ui @@ -6,8 +6,8 @@ 0 0 - 754 - 571 + 693 + 538 @@ -41,23 +41,17 @@ 0 - - - - - 75 - true - - - - License - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + 16777215 + 80 + - + Qt::Vertical @@ -73,7 +67,10 @@ - + + + + @@ -89,297 +86,6 @@ - - - - - 1 - 0 - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - 2 - - - 6 - - - - - Qt::Vertical - - - - 20 - 0 - - - - - - - - Select a Screenshot Image - - - ... - - - - :/add.png:/add.png - - - - 64 - 64 - - - - - - - - Reset Icon - - - ... - - - - :/clear_edit.png:/clear_edit.png - - - true - - - - - - - Showcase image -(max 1024x1024) - - - - - - - Select an Icon Image - - - ... - - - - :/salt_icon.png:/salt_icon.png - - - - 64 - 64 - - - - - - - - Reset Screenshot - - - ... - - - - :/clear_edit.png:/clear_edit.png - - - true - - - - - - - Icon -(64x64) - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 20 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - 75 - true - - - - Title - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - 0 - 0 - - - - - - - - - true - - - - (use numeric versions like "1.5" or "2.1.3") - - - - - - - - 16777215 - 80 - - - - - - - - - 75 - true - - - - Documentation - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 75 - true - - - - -Images - - - Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing - - - - - - - - - - - 75 - true - - - - -Description - - - Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing - - - - - - - - 75 - true - - - - Author - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - @@ -396,66 +102,96 @@ Description - - - - - 0 - 0 - + + + + + 75 + true + + + + License + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + + 75 + true + + + + Description + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + Qt::Vertical - 736 + 20 32 - - - - - 0 - 0 - - + + - true + 75 + true - (enter an URL to provide a documentation link) + Title + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - 0 - 0 - - - - - - + + - true + 75 + true - (license information like "GPLv3" or "MIT") + Depends on + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing @@ -475,41 +211,146 @@ Description - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 5 - - - + + - - + + + + + 0 + 0 + + - 75 - true + true - -Depends on - - - Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + (enter an URL to provide a documentation link) - + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + + Qt::NoFocus + + + ... + + + + :/warn.png:/warn.png + + + true + + + + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + + Qt::NoFocus + + + ... + + + + :/warn.png:/warn.png + + + true + + + + + + + + + + 0 + 0 + + QFrame::NoFrame @@ -529,24 +370,20 @@ Depends on 0 - - - - Add new dependency + + + + Qt::Vertical - - ... + + + 20 + 40 + - - - :/add.png:/add.png - - - true - - + - + Delete dependency @@ -563,18 +400,22 @@ Depends on - - - - Qt::Vertical + + + + Add new dependency - - - 20 - 40 - + + ... - + + + :/add.png:/add.png + + + true + + @@ -607,6 +448,317 @@ Depends on + + + + Qt::NoFocus + + + ... + + + + :/warn.png:/warn.png + + + true + + + + + + + + + + + 75 + true + + + + Images + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + + + + + true + + + + (use numeric versions like "1.5" or "2.1.3") + + + + + + + + 75 + true + + + + Author + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 1 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 2 + + + 6 + + + + + Reset Screenshot + + + ... + + + + :/clear_edit.png:/clear_edit.png + + + true + + + + + + + Icon +(64x64) + + + + + + + Select an Icon Image + + + ... + + + + :/salt_icon.png:/salt_icon.png + + + + 64 + 64 + + + + + + + + Select a Screenshot Image + + + ... + + + + :/add.png:/add.png + + + + 64 + 64 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 30 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + Reset Icon + + + ... + + + + :/clear_edit.png:/clear_edit.png + + + true + + + + + + + Showcase image +(max 1024x1024) + + + + + + + + + + + 75 + true + + + + Documentation + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + true + + + + (license information like "GPLv3" or "MIT") + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + + Qt::NoFocus + + + ... + + + + :/warn.png:/warn.png + + + true + + + @@ -622,6 +774,13 @@ Depends on + + + lay::AlertLogButton + QToolButton +
layLogViewerDialog.h
+
+
version title diff --git a/src/lay/images/warn.png b/src/lay/images/warn.png new file mode 100644 index 000000000..adea92d4f Binary files /dev/null and b/src/lay/images/warn.png differ diff --git a/src/lay/layLogViewerDialog.cc b/src/lay/layLogViewerDialog.cc index f6be30d4a..0ccec52ca 100644 --- a/src/lay/layLogViewerDialog.cc +++ b/src/lay/layLogViewerDialog.cc @@ -27,6 +27,7 @@ #include #include #include +#include #include @@ -97,25 +98,30 @@ LogReceiver::begin () // ----------------------------------------------------------------- // LogFile implementation -LogFile::LogFile (size_t max_entries) - : m_error_receiver (this, 0, &LogFile::error), - m_warn_receiver (this, 0, &LogFile::warn), - m_log_receiver (this, 10, &LogFile::info), - m_info_receiver (this, 0, &LogFile::info), +LogFile::LogFile (size_t max_entries, bool register_global) + : m_error_receiver (this, 0, &LogFile::add_error), + m_warn_receiver (this, 0, &LogFile::add_warn), + m_log_receiver (this, 10, &LogFile::add_info), + m_info_receiver (this, 0, &LogFile::add_info), m_max_entries (max_entries), m_generation_id (0), - m_last_generation_id (0) + m_last_generation_id (0), + m_has_errors (false), + m_has_warnings (false), + m_last_attn (false) { connect (&m_timer, SIGNAL (timeout ()), this, SLOT (timeout ())); - m_timer.setSingleShot (true); + m_timer.setSingleShot (false); m_timer.setInterval (100); m_timer.start (); - tl::info.add (&m_info_receiver, false); - tl::log.add (&m_log_receiver, false); - tl::error.add (&m_error_receiver, false); - tl::warn.add (&m_warn_receiver, false); + if (register_global) { + tl::info.add (&m_info_receiver, false); + tl::log.add (&m_log_receiver, false); + tl::error.add (&m_error_receiver, false); + tl::warn.add (&m_warn_receiver, false); + } } void @@ -123,8 +129,23 @@ LogFile::clear () { QMutexLocker locker (&m_lock); - m_messages.clear (); - ++m_generation_id; + if (!m_messages.empty ()) { + m_messages.clear (); + m_has_errors = m_has_warnings = false; + ++m_generation_id; + } +} + +bool +LogFile::has_errors () const +{ + return m_has_errors; +} + +bool +LogFile::has_warnings () const +{ + return m_has_warnings; } void @@ -159,9 +180,13 @@ void LogFile::timeout () { bool changed = false; + bool attn = false, last_attn = false; m_lock.lock (); if (m_generation_id != m_last_generation_id) { + attn = m_has_errors || m_has_warnings; + last_attn = m_last_attn; + m_last_attn = attn; m_last_generation_id = m_generation_id; changed = true; } @@ -169,9 +194,10 @@ LogFile::timeout () if (changed) { emit layoutChanged (); + if (last_attn != attn) { + emit attention_changed (attn); + } } - - m_timer.start (); } void @@ -183,6 +209,12 @@ LogFile::add (LogFileEntry::mode_type mode, const std::string &msg, bool continu m_messages.pop_front (); } + if (mode == LogFileEntry::Warning || mode == LogFileEntry::WarningContinued) { + m_has_warnings = true; + } else if (mode == LogFileEntry::Error || mode == LogFileEntry::ErrorContinued) { + m_has_errors = true; + } + m_messages.push_back (LogFileEntry (mode, msg, continued)); ++m_generation_id; @@ -251,21 +283,36 @@ LogFile::data(const QModelIndex &index, int role) const // ----------------------------------------------------------------- // LogViewerDialog implementation -LogViewerDialog::LogViewerDialog (QWidget *parent) +LogViewerDialog::LogViewerDialog (QWidget *parent, bool register_global, bool interactive) : QDialog (parent), - m_file (50000) // TODO: make this variable .. + m_file (50000, register_global) // TODO: make this variable .. { setupUi (this); + // For non-global log views, the verbosity selector does not make sense + if (!register_global) { + verbosity_cbx->hide (); + verbosity_label->hide (); + } else { + verbosity_cbx->setCurrentIndex (std::min (4, tl::verbosity () / 10)); + connect (verbosity_cbx, SIGNAL (currentIndexChanged (int)), this, SLOT (verbosity_changed (int))); + } + + if (!interactive) { + clear_pb->hide (); + separator_pb->hide (); + copy_pb->hide (); + } else { + connect (clear_pb, SIGNAL (clicked ()), &m_file, SLOT (clear ())); + connect (separator_pb, SIGNAL (clicked ()), &m_file, SLOT (separator ())); + connect (copy_pb, SIGNAL (clicked ()), &m_file, SLOT (copy ())); + } + + attn_frame->hide (); log_view->setModel (&m_file); - verbosity_cbx->setCurrentIndex (std::min (4, tl::verbosity () / 10)); - connect (&m_file, SIGNAL (layoutChanged ()), log_view, SLOT (scrollToBottom ())); - connect (verbosity_cbx, SIGNAL (currentIndexChanged (int)), this, SLOT (verbosity_changed (int))); - connect (clear_pb, SIGNAL (clicked ()), &m_file, SLOT (clear ())); - connect (separator_pb, SIGNAL (clicked ()), &m_file, SLOT (separator ())); - connect (copy_pb, SIGNAL (clicked ()), &m_file, SLOT (copy ())); + connect (&m_file, SIGNAL (attention_changed (bool)), attn_frame, SLOT (setVisible (bool))); } void @@ -274,5 +321,56 @@ LogViewerDialog::verbosity_changed (int index) tl::verbosity (index * 10 + 1); } +// ----------------------------------------------------------------- +// AlertLog implementation + +AlertLogButton::AlertLogButton (QWidget *parent) + : QToolButton (parent) +{ + mp_logger = new LogViewerDialog (this, false, false); + hide (); + connect (&mp_logger->file (), SIGNAL (attention_changed (bool)), this, SLOT (attention_changed (bool))); + connect (this, SIGNAL (clicked ()), mp_logger, SLOT (exec ())); +} + +void +AlertLogButton::attention_changed (bool attn) +{ + setVisible (attn); + + // as a special service, enlarge and color any surrounding frame red - + // this feature allows putting the alert button together with other entry fields into a frame and + // make this frame highlighted on error or warning. + QFrame *frame = dynamic_cast (parent ()); + if (frame) { + + if (frame->layout ()) { + int l = 0, t = 0, r = 0, b = 0; + frame->layout ()->getContentsMargins (&l, &t, &r, &b); + if (attn) { + l += 3; t += 3; r += 2; b += 2; + } else { + l -= 3; t -= 3; r -= 2; b -= 2; + } + frame->layout ()->setContentsMargins (l, t, r, b); + } + + if (attn) { + + frame->setAutoFillBackground (true); + QPalette palette = frame->palette (); + palette.setColor (QPalette::Window, QColor (255, 160, 160)); + frame->setPalette (palette); + + } else { + + frame->setAutoFillBackground (false); + frame->setPalette (QPalette ()); + + } + + } +} + } diff --git a/src/lay/layLogViewerDialog.h b/src/lay/layLogViewerDialog.h index e2d132146..c31eca626 100644 --- a/src/lay/layLogViewerDialog.h +++ b/src/lay/layLogViewerDialog.h @@ -26,11 +26,13 @@ #include "ui_LogViewerDialog.h" #include "tlLog.h" +#include "layCommon.h" #include #include #include #include +#include #include #include @@ -40,6 +42,9 @@ namespace lay class LogFile; +/** + * @brief A helper class describing one log entry + */ class LogFileEntry { public: @@ -70,7 +75,10 @@ private: bool m_continued; }; -class LogReceiver +/** + * @brief The log receiver abstraction that connects a channel with the LogFile object + */ +class LAY_PUBLIC LogReceiver : public tl::Channel { public: @@ -91,40 +99,110 @@ private: QMutex m_lock; }; -class LogFile +/** + * @brief A log collection ("log file") + * + * The log collector collects warnings, errors and info messages + * and presents this collection as a QAbstractListModel view + * viewing inside a QTreeWidget or the LogViewerDialog. + * + * The log collector can either be used standalone or as a + * global receiver that will collect the global log + * messages. + */ +class LAY_PUBLIC LogFile : public QAbstractListModel { Q_OBJECT public: - LogFile (size_t max_entries); - - void error (const std::string &msg, bool continued) - { - add (continued ? LogFileEntry::ErrorContinued : LogFileEntry::Error, msg, continued); - } - - void info (const std::string &msg, bool continued) - { - add (continued ? LogFileEntry::InfoContinued : LogFileEntry::Info, msg, continued); - } - - void warn (const std::string &msg, bool continued) - { - add (continued ? LogFileEntry::WarningContinued : LogFileEntry::Warning, msg, continued); - } + /** + * @brief Constructs a log file receiver + * If "register_global" is true, the receiver will register itself as a global log receiver. + * Otherwise it's a private one that can be used with the "error", "warn" and "info" channels + * provided by the respective methods. + */ + LogFile (size_t max_entries, bool register_global = true); + /** + * @brief Implementation of the QAbstractItemModel interface + */ int rowCount(const QModelIndex &parent) const; + /** + * @brief Implementation of the QAbstractItemModel interface + */ QVariant data(const QModelIndex &index, int role) const; -private slots: - void timeout (); + /** + * @brief Gets a value indicating whether errors are present + */ + bool has_errors () const; + + /** + * @brief Gets a value indicating whether warnings are present + */ + bool has_warnings () const; + +public slots: + /** + * @brief Clears the log + */ void clear (); + + /** + * @brief Adds a separator + */ void separator (); + + /** + * @brief copies the contents to the clipboard + */ void copy (); -public: + /** + * @brief Gets the error channel + */ + tl::Channel &error () + { + return m_error_receiver; + } + + /** + * @brief Gets the warning channel + */ + tl::Channel &warn () + { + return m_warn_receiver; + } + + /** + * @brief Gets the info channel + */ + tl::Channel &info () + { + return m_info_receiver; + } + + /** + * @brief Gets the log channel + */ + tl::Channel &log () + { + return m_log_receiver; + } + +private slots: + void timeout (); + +signals: + /** + * @brief This signal is emitted if the log's attention state has changed + * Attention state is "true" if either errors or warnings are present. + */ + void attention_changed (bool f); + +private: QTimer m_timer; mutable QMutex m_lock; LogReceiver m_error_receiver; @@ -135,18 +213,63 @@ public: size_t m_max_entries; size_t m_generation_id; size_t m_last_generation_id; + bool m_has_errors, m_has_warnings; + bool m_last_attn; + /** + * @brief Adds an error + */ + void add_error (const std::string &msg, bool continued) + { + add (continued ? LogFileEntry::ErrorContinued : LogFileEntry::Error, msg, continued); + } + + /** + * @brief Adds a info message + */ + void add_info (const std::string &msg, bool continued) + { + add (continued ? LogFileEntry::InfoContinued : LogFileEntry::Info, msg, continued); + } + + /** + * @brief Adds a warning + */ + void add_warn (const std::string &msg, bool continued) + { + add (continued ? LogFileEntry::WarningContinued : LogFileEntry::Warning, msg, continued); + } + + /** + * @brief Adds anything + */ void add (LogFileEntry::mode_type mode, const std::string &msg, bool continued); }; -class LogViewerDialog +/** + * @brief A dialog presenting the log file + */ +class LAY_PUBLIC LogViewerDialog : public QDialog, public Ui::LogViewerDialog { Q_OBJECT public: - LogViewerDialog (QWidget *parent); + /** + * @brief The constructor + * If "register_global" is true, the log is registered globally + * and will receiver global log messages. + */ + LogViewerDialog (QWidget *parent, bool register_global = true, bool interactive = true); + + /** + * @brief Gets the log file object + */ + LogFile &file () + { + return m_file; + } public slots: void verbosity_changed (int l); @@ -155,7 +278,93 @@ private: LogFile m_file; }; +/** + * @brief A tool button that collects logs and makes itself visible once attention is required + */ +class LAY_PUBLIC AlertLogButton + : public QToolButton +{ +Q_OBJECT + +public: + /** + * @brief Constructor + */ + AlertLogButton (QWidget *parent); + + /** + * @brief Gets the error channel + */ + tl::Channel &error () const + { + return mp_logger->file ().error (); + } + + /** + * @brief Gets the warn channel + */ + tl::Channel &warn () const + { + return mp_logger->file ().warn (); + } + + /** + * @brief Gets the info channel + */ + tl::Channel &info () const + { + return mp_logger->file ().info (); + } + + /** + * @brief Gets the log channel + */ + tl::Channel &log () const + { + return mp_logger->file ().log (); + } + + /** + * @brief Gets the error status of the log + */ + bool has_errors () const + { + return mp_logger->file ().has_errors (); + } + + /** + * @brief Gets the warning status of the log + */ + bool has_warnings () const + { + return mp_logger->file ().has_warnings (); + } + + /** + * @brief Gets the attention status of the log + * (either warnings or errors are present) + */ + bool needs_attention () const + { + return has_errors () || has_warnings (); + } + +public slots: + /** + * @brief Clears the log (and makes the button invisible) + */ + void clear () + { + mp_logger->file ().clear (); + } + +private slots: + void attention_changed (bool); + +private: + LogViewerDialog *mp_logger; +}; + } #endif - diff --git a/src/lay/layResources.qrc b/src/lay/layResources.qrc index 883bcff82..ef87d4e5f 100644 --- a/src/lay/layResources.qrc +++ b/src/lay/layResources.qrc @@ -115,6 +115,7 @@ images/yellow_flag.png images/salt.png images/salt_icon.png + images/warn.png syntax/ruby.xml diff --git a/src/lay/laySaltGrain.cc b/src/lay/laySaltGrain.cc index 266dbeff7..4aee271f1 100644 --- a/src/lay/laySaltGrain.cc +++ b/src/lay/laySaltGrain.cc @@ -185,6 +185,28 @@ SaltGrain::compare_versions (const std::string &v1, const std::string &v2) } } +bool +SaltGrain::valid_version (const std::string &v) +{ + tl::Extractor ex (v.c_str ()); + + while (! ex.at_end ()) { + int n = 0; + if (! ex.try_read (n)) { + return false; + } + if (! ex.at_end ()) { + if (*ex != '.') { + return false; + } else { + ++ex; + } + } + } + + return true; +} + struct TimeConverter { std::string to_string (const QDateTime &time) const diff --git a/src/lay/laySaltGrain.h b/src/lay/laySaltGrain.h index eda1d63bd..96af788a4 100644 --- a/src/lay/laySaltGrain.h +++ b/src/lay/laySaltGrain.h @@ -352,6 +352,11 @@ public: */ static int compare_versions (const std::string &v1, const std::string &v2); + /** + * @brief Gets a value indicating whether the given version string is a valid version + */ + static bool valid_version (const std::string &v); + /** * @brief Detects a grain from the given directory * This method will return a grain constructed from the given directory. diff --git a/src/lay/laySaltGrainPropertiesDialog.cc b/src/lay/laySaltGrainPropertiesDialog.cc index c0991894d..eef2ddd40 100644 --- a/src/lay/laySaltGrainPropertiesDialog.cc +++ b/src/lay/laySaltGrainPropertiesDialog.cc @@ -31,6 +31,8 @@ #include #include #include +#include + #include namespace lay @@ -149,6 +151,10 @@ void SaltGrainPropertiesDialog::update_controls () { setWindowTitle (m_title + tl::to_qstring (" - " + m_grain.name ())); + license_alert->clear (); + version_alert->clear (); + doc_url_alert->clear (); + dependencies_alert->clear (); version->setText (tl::to_qstring (m_grain.version ())); title->setText (tl::to_qstring (m_grain.title ())); @@ -300,15 +306,27 @@ BEGIN_PROTECTED const int max_dim = 256; - QString fileName = QFileDialog::getOpenFileName (this, tr ("Pick Icon Image File"), m_image_dir, tr ("Images (*.png *.jpg)")); + QString fileName = QFileDialog::getOpenFileName (this, tr ("Pick Icon Image File"), m_image_dir, tr ("Images (*.png *.jpg);;All Files (*)")); if (! fileName.isNull ()) { + + bool ok = true; QImage img = QImage (fileName); if (img.width () > max_dim || img.height () > max_dim) { - throw tl::Exception (tl::to_string (tr ("Icon image too big -\nmust be %1x%2 pixels max, but is %3x%4").arg (max_dim).arg (max_dim).arg (img.width ()).arg (img.height ()))); + if (QMessageBox::warning (this, tr ("Image Too Big"), + tr ("Icon image too big - must be %1x%2 pixels max, but is %3x%4.\n\nScale image?").arg (max_dim).arg (max_dim).arg (img.width ()).arg (img.height ()), + QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { + ok = false; + } else { + img = img.scaled (max_dim, max_dim, Qt::KeepAspectRatio); + } } - m_grain.set_icon (img); - m_image_dir = QFileInfo (fileName).path (); - update_icon (); + + if (ok) { + m_grain.set_icon (img); + m_image_dir = QFileInfo (fileName).path (); + update_icon (); + } + } END_PROTECTED @@ -328,15 +346,27 @@ BEGIN_PROTECTED const int max_dim = 1024; - QString fileName = QFileDialog::getOpenFileName (this, tr ("Pick Showcase Image File"), m_image_dir, tr ("Images (*.png *.jpg)")); + QString fileName = QFileDialog::getOpenFileName (this, tr ("Pick Showcase Image File"), m_image_dir, tr ("Images (*.png *.jpg);;All Files (*)")); if (! fileName.isNull ()) { + + bool ok = true; QImage img = QImage (fileName); if (img.width () > max_dim || img.height () > max_dim) { - throw tl::Exception (tl::to_string (tr ("Showcase image too big -\nmust be %1x%2 pixels max, but is %3x%4").arg (max_dim).arg (max_dim).arg (img.width ()).arg (img.height ()))); + if (QMessageBox::warning (this, tr ("Image Too Big"), + tr ("Showcase image too big - must be %1x%2 pixels max, but is %3x%4.\n\nScale image?").arg (max_dim).arg (max_dim).arg (img.width ()).arg (img.height ()), + QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { + ok = false; + } else { + img = img.scaled (max_dim, max_dim, Qt::KeepAspectRatio); + } } - m_grain.set_screenshot (img); - m_image_dir = QFileInfo (fileName).path (); - update_screenshot (); + + if (ok) { + m_grain.set_screenshot (img); + m_image_dir = QFileInfo (fileName).path (); + update_screenshot (); + } + } END_PROTECTED @@ -367,6 +397,44 @@ SaltGrainPropertiesDialog::remove_dependency_clicked () } } +void +SaltGrainPropertiesDialog::accept () +{ + update_data (); + + // Perform some checks + license_alert->clear (); + if (m_grain.license ().empty ()) { + license_alert->warn () << tr ("License field is empty. Please consider specifying a license model."); + } + + version_alert->clear (); + if (m_grain.version ().empty ()) { + version_alert->warn () << tr ("Version field is empty. Please consider specifying a version number."); + } else if (! SaltGrain::valid_version (m_grain.version ())) { + version_alert->error () << tr ("'%1' is not a valid version string. A version string needs to be numeric (like '1.2.3' or '4.5'').").arg (tl::to_qstring (m_grain.version ())); + } + + doc_url_alert->clear (); + // @@@ TODO + + dependencies_alert->clear (); + // @@@ TODO + + if (!license_alert->needs_attention () && + !doc_url_alert->needs_attention () && + !dependencies_alert->needs_attention () && + !version_alert->needs_attention ()) { + QDialog::accept (); + } else { + if (QMessageBox::warning (this, tr ("Issues Encountered"), + tr ("Some issues have been found when inspecting the package details.\nThe respective fields are marked with warning icons.\n\nIgnore these issues and commit the package details?"), + QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { + QDialog::accept (); + } + } +} + bool SaltGrainPropertiesDialog::exec_dialog (lay::SaltGrain *grain, lay::Salt *salt) { @@ -379,7 +447,6 @@ SaltGrainPropertiesDialog::exec_dialog (lay::SaltGrain *grain, lay::Salt *salt) bool res = exec (); if (res) { - update_data (); *grain = m_grain; } diff --git a/src/lay/laySaltGrainPropertiesDialog.h b/src/lay/laySaltGrainPropertiesDialog.h index 1ea219f98..cb2b62285 100644 --- a/src/lay/laySaltGrainPropertiesDialog.h +++ b/src/lay/laySaltGrainPropertiesDialog.h @@ -74,6 +74,9 @@ private slots: void remove_dependency_clicked (); void dependency_changed (QTreeWidgetItem *item, int column); +protected: + void accept (); + private: lay::SaltGrain m_grain; lay::Salt *mp_salt; diff --git a/src/unit_tests/laySalt.cc b/src/unit_tests/laySalt.cc index ae5581e72..7eb557499 100644 --- a/src/unit_tests/laySalt.cc +++ b/src/unit_tests/laySalt.cc @@ -156,6 +156,12 @@ TEST (1) TEST (2) { + EXPECT_EQ (lay::SaltGrain::valid_version (""), true); + EXPECT_EQ (lay::SaltGrain::valid_version ("1"), true); + EXPECT_EQ (lay::SaltGrain::valid_version ("1.2"), true); + EXPECT_EQ (lay::SaltGrain::valid_version ("\t1 . 2.\n3"), true); + EXPECT_EQ (lay::SaltGrain::valid_version ("x"), false); + EXPECT_EQ (lay::SaltGrain::valid_version ("1.2x"), false); EXPECT_EQ (lay::SaltGrain::compare_versions ("", ""), 0); EXPECT_EQ (lay::SaltGrain::compare_versions ("1", "2"), -1); EXPECT_EQ (lay::SaltGrain::compare_versions ("1", ""), 1);