diff --git a/src/lay/SaltManagerDialog.ui b/src/lay/SaltManagerDialog.ui index 4ad0a109b..e369924c9 100644 --- a/src/lay/SaltManagerDialog.ui +++ b/src/lay/SaltManagerDialog.ui @@ -11,7 +11,7 @@ - Manage Packages + Salt Package Manager @@ -55,7 +55,7 @@ 0 - + Qt::NoFocus @@ -88,7 +88,7 @@ - + Qt::NoFocus @@ -108,7 +108,7 @@ - + Qt::NoFocus @@ -131,7 +131,7 @@ - + 0 @@ -160,6 +160,12 @@ true + + + 64 + 64 + + true @@ -215,42 +221,101 @@ 0 0 - 218 - 138 + 320 + 204 - - + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + :/salt.png + + + + + + + + 0 + 0 + + + + + 9 + 50 + false + + + + <h1>Salt Package Manager</h1> + + + + - - 0 + + 1 0 - 200 + 0 0 - <html><body><center>No packages are installed currently.<br><br>You can use:<br> -</center> + <html><body><h4>No packages are installed currently.</h4><p>Use<br/> <table> - <tr><td width="30"><a href=":import"><img src=":/import.png"></a></td><td>to import a package from an external source</td></tr> + <tr><td width="30"><a href=":import"><img src=":/import.png"></a></td><td>to import a package from an<br/>external repository</td></tr> + <tr></tr> <tr><td><a href=":add"><img src=":/add.png"></a></td><td>to create a new package</td></tr> </table> </body></html> - Qt::AlignHCenter|Qt::AlignTop + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - true + false + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + @@ -264,7 +329,7 @@ - + 1 @@ -299,7 +364,7 @@ - Package Details + Details @@ -321,7 +386,11 @@ - + + + true + + @@ -335,15 +404,6 @@ - - - - :/new_folder.png:/new_folder.png - - - New Folder - - @@ -353,7 +413,7 @@ New - New Package + New package @@ -365,19 +425,7 @@ Delete - Delete Package - - - - - - :/rename.png:/rename.png - - - Rename - - - Rename Package + Delete package @@ -389,16 +437,23 @@ Import - Import Package + Import package + + + lay::SaltGrainDetailsTextWidget + QTextBrowser +
laySaltGrainDetailsTextWidget.h
+
+
salt_view - toolButton_4 - toolButton_2 - toolButton_3 - textBrowser + install_button + create_button + delete_button + details_text diff --git a/src/lay/images/salt.png b/src/lay/images/salt.png new file mode 100644 index 000000000..181409a28 Binary files /dev/null and b/src/lay/images/salt.png differ diff --git a/src/lay/images/salt_icon.png b/src/lay/images/salt_icon.png new file mode 100644 index 000000000..ec11c7ac0 Binary files /dev/null and b/src/lay/images/salt_icon.png differ diff --git a/src/lay/lay.pro b/src/lay/lay.pro index 7efeae835..572c78a0d 100644 --- a/src/lay/lay.pro +++ b/src/lay/lay.pro @@ -49,7 +49,8 @@ HEADERS = \ laySalt.h \ laySaltGrain.h \ laySaltGrains.h \ - laySaltManagerDialog.h + laySaltManagerDialog.h \ + laySaltGrainDetailsTextWidget.h FORMS = \ ClipDialog.ui \ @@ -142,7 +143,8 @@ SOURCES = \ laySalt.cc \ laySaltGrain.cc \ laySaltGrains.cc \ - laySaltManagerDialog.cc + laySaltManagerDialog.cc \ + laySaltGrainDetailsTextWidget.cc RESOURCES = layBuildInMacros.qrc \ layHelpResources.qrc \ diff --git a/src/lay/layResources.qrc b/src/lay/layResources.qrc index 3c2e3bdd4..883bcff82 100644 --- a/src/lay/layResources.qrc +++ b/src/lay/layResources.qrc @@ -113,8 +113,10 @@ images/upup.png images/waived.png images/yellow_flag.png + images/salt.png + images/salt_icon.png - + syntax/ruby.xml syntax/python.xml diff --git a/src/lay/laySalt.h b/src/lay/laySalt.h index 39d5c2f02..19d21145f 100644 --- a/src/lay/laySalt.h +++ b/src/lay/laySalt.h @@ -96,6 +96,14 @@ public: return m_root.end_collections (); } + /** + * @brief Returns a value indicating whether the collection is empty + */ + bool is_empty () const + { + return m_root.is_empty (); + } + /** * @brief A flat iterator of (sorted) grains (begin) */ diff --git a/src/lay/laySaltGrain.cc b/src/lay/laySaltGrain.cc index de0eccf6a..80292e6f1 100644 --- a/src/lay/laySaltGrain.cc +++ b/src/lay/laySaltGrain.cc @@ -41,12 +41,17 @@ bool SaltGrain::operator== (const SaltGrain &other) const { return m_name == other.m_name && - m_version == other.m_version && m_path == other.m_path && + m_version == other.m_version && m_url == other.m_url && m_title == other.m_title && m_doc == other.m_doc && - m_dependencies == other.m_dependencies; + m_dependencies == other.m_dependencies && + m_author == other.m_author && + m_author_contact == other.m_author_contact && + m_license == other.m_license && + m_authored_time == other.m_authored_time && + m_installed_time == other.m_installed_time; } void @@ -85,6 +90,36 @@ SaltGrain::set_doc (const std::string &t) m_doc = t; } +void +SaltGrain::set_author (const std::string &a) +{ + m_author = a; +} + +void +SaltGrain::set_author_contact (const std::string &a) +{ + m_author_contact = a; +} + +void +SaltGrain::set_license (const std::string &l) +{ + m_license = l; +} + +void +SaltGrain::set_authored_time (const QDateTime &t) +{ + m_authored_time = t; +} + +void +SaltGrain::set_installed_time (const QDateTime &t) +{ + m_installed_time = t; +} + int SaltGrain::compare_versions (const std::string &v1, const std::string &v2) { @@ -128,12 +163,38 @@ SaltGrain::compare_versions (const std::string &v1, const std::string &v2) } } +struct TimeConverter +{ + std::string to_string (const QDateTime &time) const + { + if (time.isNull ()) { + return std::string (); + } else { + return tl::to_string (time.toString (Qt::ISODate)); + } + } + + void from_string (const std::string &time, QDateTime &res) const + { + if (time.empty ()) { + res = QDateTime (); + } else { + res = QDateTime::fromString (tl::to_qstring (time), Qt::ISODate); + } + } +}; + static tl::XMLStruct xml_struct ("salt-grain", tl::make_member (&SaltGrain::name, &SaltGrain::set_name, "name") + tl::make_member (&SaltGrain::version, &SaltGrain::set_version, "version") + tl::make_member (&SaltGrain::title, &SaltGrain::set_title, "title") + tl::make_member (&SaltGrain::doc, &SaltGrain::set_doc, "doc") + tl::make_member (&SaltGrain::url, &SaltGrain::set_url, "url") + + tl::make_member (&SaltGrain::license, &SaltGrain::set_license, "license") + + tl::make_member (&SaltGrain::author, &SaltGrain::set_author, "author") + + tl::make_member (&SaltGrain::author_contact, &SaltGrain::set_author_contact, "author-contact") + + tl::make_member (&SaltGrain::authored_time, &SaltGrain::set_authored_time, "authored-time", TimeConverter ()) + + tl::make_member (&SaltGrain::installed_time, &SaltGrain::set_installed_time, "installed-time", TimeConverter ()) + tl::make_element (&SaltGrain::begin_dependencies, &SaltGrain::end_dependencies, &SaltGrain::add_dependency, "depends", tl::make_member (&SaltGrain::Dependency::name, "name") + tl::make_member (&SaltGrain::Dependency::url, "url") + diff --git a/src/lay/laySaltGrain.h b/src/lay/laySaltGrain.h index b86a0e0e7..063bade20 100644 --- a/src/lay/laySaltGrain.h +++ b/src/lay/laySaltGrain.h @@ -26,6 +26,8 @@ #include "layCommon.h" #include "tlObject.h" +#include + namespace lay { @@ -142,6 +144,71 @@ public: */ void set_version (const std::string &v); + /** + * @brief Gets the author of the grain + */ + const std::string &author () const + { + return m_author; + } + + /** + * @brief Sets the author of the grain + */ + void set_author (const std::string &a); + + /** + * @brief Gets the author's contact + */ + const std::string &author_contact () const + { + return m_author_contact; + } + + /** + * @brief Sets the author's contact + */ + void set_author_contact (const std::string &a); + + /** + * @brief Gets the license of the grain + */ + const std::string &license () const + { + return m_license; + } + + /** + * @brief Sets the license of the grain + */ + void set_license (const std::string &l); + + /** + * @brief Gets the release date and/or time of the grain + */ + const QDateTime &authored_time () const + { + return m_authored_time; + } + + /** + * @brief Sets the release date and/or time + */ + void set_authored_time (const QDateTime &t); + + /** + * @brief Gets the installation date and/or time of the grain + */ + const QDateTime &installed_time () const + { + return m_installed_time; + } + + /** + * @brief Sets the installation date and/or time + */ + void set_installed_time (const QDateTime &t); + /** * @brief Gets the absolute file path of the installed grain * This is the file path to the grain folder. @@ -261,6 +328,10 @@ private: std::string m_url; std::string m_title; std::string m_doc; + std::string m_author; + std::string m_author_contact; + std::string m_license; + QDateTime m_authored_time, m_installed_time; std::vector m_dependencies; }; diff --git a/src/lay/laySaltGrainDetailsTextWidget.cc b/src/lay/laySaltGrainDetailsTextWidget.cc new file mode 100644 index 000000000..ab44d6d22 --- /dev/null +++ b/src/lay/laySaltGrainDetailsTextWidget.cc @@ -0,0 +1,148 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "laySaltGrainDetailsTextWidget.h" +#include "laySaltGrain.h" +#include "tlString.h" + +#include +#include +#include + +namespace lay +{ + +SaltGrainDetailsTextWidget::SaltGrainDetailsTextWidget (QWidget *w) + : QTextBrowser (w), mp_grain (0) +{ + // .. nothing yet .. +} + +void SaltGrainDetailsTextWidget::set_grain (SaltGrain *g) +{ + if (mp_grain != g) { + mp_grain = g; + setHtml (details_text ()); + } +} + +QVariant +SaltGrainDetailsTextWidget::loadResource (int type, const QUrl &url) +{ + if (url.path () == QString::fromUtf8 ("/icon")) { + // @@@ + return QImage (":/salt_icon.png"); + // @@@ + } else { + return QTextBrowser::loadResource (type, url); + } +} + +QString +SaltGrainDetailsTextWidget::details_text () +{ + SaltGrain *g = mp_grain; + if (! g) { + return QString (); + } + + QBuffer buffer; + buffer.open (QIODevice::WriteOnly); + QTextStream stream (&buffer); + stream.setCodec ("UTF-8"); + + stream << ""; + + stream << ""; + stream << ""; + stream << "
"; + stream << "

"; + stream << tl::to_qstring (tl::escaped_to_html (g->name ())) << " " << tl::to_qstring (tl::escaped_to_html (g->version ())); + stream << "

"; + if (! g->title ().empty()) { + stream << "

" << tl::to_qstring (tl::escaped_to_html (g->title ())) << "

"; + } + + if (g->version ().empty ()) { + stream << "

"; + stream << QObject::tr ("This package does not have a version. " + "Use the <version> element of the specification file or edit the package properties to provide a version."); + stream << "

"; + } + + if (g->title ().empty ()) { + stream << "

"; + stream << QObject::tr ("This package does not have a title. " + "Use the <title> element of the specification file or edit the package properties to provide a title."); + stream << "

"; + } + + stream << "
"; + + stream << "


"; + if (! g->doc ().empty ()) { + stream << tl::to_qstring (tl::escaped_to_html (g->doc ())); + } else { + stream << ""; + stream << QObject::tr ("This package does not have a description. " + "Use the <doc> element of the specification file or edit the package properties to provide a description."); + stream << ""; + } + stream << "

"; + + stream << "

"; + if (! g->author ().empty ()) { + stream << "" << QObject::tr ("Author") << ": " << tl::to_qstring (tl::escaped_to_html (g->author ())) << " "; + if (! g->author_contact ().empty ()) { + stream << "(" << tl::to_qstring (tl::escaped_to_html (g->author_contact ())) << ")"; + } + if (!g->authored_time ().isNull ()) { + stream << "
"; + stream << "" << QObject::tr ("Released") << ": " << g->authored_time ().date ().toString (Qt::ISODate); + } + } else { + stream << ""; + stream << QObject::tr ("This package does not have a author information. " + "Use the <author>, <authored-time> and <author-contact> elements of the specification file or edit the package properties to provide authoring information."); + stream << ""; + } + stream << "

"; + + stream << "

"; + if (! g->url ().empty ()) { + stream << "" << QObject::tr ("Documentation link") << ": url ()) << "\">" << tl::to_qstring (tl::escaped_to_html (g->url ())) << ""; + } else { + stream << ""; + stream << QObject::tr ("This package does not have a documentation link. " + "Use the <url> element of the specification file or edit the package properties to provide a link."); + stream << ""; + } + stream << "

"; + + stream << ""; + + stream.flush (); + + return QString::fromUtf8 (buffer.buffer()); +} + +} diff --git a/src/lay/laySaltGrainDetailsTextWidget.h b/src/lay/laySaltGrainDetailsTextWidget.h new file mode 100644 index 000000000..bb25a0e48 --- /dev/null +++ b/src/lay/laySaltGrainDetailsTextWidget.h @@ -0,0 +1,61 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef HDR_laySaltGrainDetailsTextWidget +#define HDR_laySaltGrainDetailsTextWidget + +#include + +namespace lay +{ + +class SaltGrain; + +/** + * @brief A specialisation of QTextBrowser that displays the details of the salt grain + */ +class SaltGrainDetailsTextWidget + : public QTextBrowser +{ +public: + /** + * @brief Constructor + */ + SaltGrainDetailsTextWidget (QWidget *w); + + /** + * @brief Sets the grain whose details are to be shown + */ + void set_grain (SaltGrain *g); + +protected: + virtual QVariant loadResource (int type, const QUrl &url); + +private: + lay::SaltGrain *mp_grain; + + QString details_text (); +}; + +} + +#endif diff --git a/src/lay/laySaltGrains.cc b/src/lay/laySaltGrains.cc index a40bcea64..9c17944c6 100644 --- a/src/lay/laySaltGrains.cc +++ b/src/lay/laySaltGrains.cc @@ -112,7 +112,15 @@ SaltGrains::remove_grain (grain_iterator iter, bool with_files) bool SaltGrains::is_empty () const { - return m_collections.empty () && m_grains.empty (); + if (! m_grains.empty ()) { + return false; + } + for (collections_type::const_iterator i = m_collections.begin (); i != m_collections.end (); ++i) { + if (!i->is_empty ()) { + return false; + } + } + return true; } bool diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index 5876a1c7a..9606164f3 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -30,10 +30,17 @@ #include #include #include +#include +#include namespace lay { +// -------------------------------------------------------------------------------------- + +/** + * @brief A model representing the salt grains for a QListView + */ class SaltModel : public QAbstractItemModel { @@ -71,6 +78,12 @@ public: return tl::to_qstring (text); + } else if (role == Qt::DecorationRole) { + + // @@@ + return QIcon (":/salt_icon.png"); + // @@@ + } else { return QVariant (); } @@ -81,7 +94,7 @@ public: if (parent.isValid ()) { return QModelIndex (); } else { - return createIndex (row, column); + return createIndex (row, column, mp_salt->begin_flat () [row]); } } @@ -104,10 +117,29 @@ public: } } + SaltGrain *grain_from_index (const QModelIndex &index) const + { + if (index.isValid ()) { + return static_cast (index.internalPointer ()); + } else { + return 0; + } + } + + void update () + { + // @@@ + } + public: lay::Salt *mp_salt; }; +// -------------------------------------------------------------------------------------- + +/** + * @brief A delegate displaying the summary of a grain + */ class SaltItemDelegate : public QStyledItemDelegate { @@ -152,13 +184,22 @@ public: QStyleOptionViewItemV4 optionV4 = option; initStyleOption (&optionV4, index); + const QListView *view = dynamic_cast (optionV4.widget); + QSize icon_size (0, 0); + if (view) { + icon_size = view->iconSize (); + } + QTextDocument doc; doc.setHtml (optionV4.text); doc.setTextWidth (textWidth); - return QSize (textWidth, doc.size ().height ()); + return QSize (textWidth + icon_size.width () + 6, std::max (icon_size.height () + 12, int (doc.size ().height ()))); } }; +// -------------------------------------------------------------------------------------- +// SaltManager implementation + // @@@ lay::Salt salt; static bool salt_initialized = false; @@ -172,16 +213,76 @@ void make_salt () // @@@ SaltManagerDialog::SaltManagerDialog (QWidget *parent) - : QDialog (parent) + : QDialog (parent), + m_current_changed_enabled (true) { Ui::SaltManagerDialog::setupUi (this); - salt = lay::Salt (); salt_initialized = false; // @@@ - make_salt (); // @@@ - salt_view->setModel (new SaltModel (this, &salt)); +// @@@ + salt = lay::Salt (); salt_initialized = false; + make_salt (); + mp_salt = &salt; +// @@@ + + SaltModel *model = new SaltModel (this, mp_salt); + salt_view->setModel (model); salt_view->setItemDelegate (new SaltItemDelegate (this)); + connect (mp_salt, SIGNAL (collections_changed ()), this, SLOT (salt_changed ())); + + // select the first grain + if (model->rowCount (QModelIndex ()) > 0) { + salt_view->setCurrentIndex (model->index (0, 0, QModelIndex ())); + } + + salt_changed (); + + connect (salt_view->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (current_changed ())); + + // ... } +void +SaltManagerDialog::salt_changed () +{ + SaltModel *model = dynamic_cast (salt_view->model ()); + if (! model) { + return; + } + + m_current_changed_enabled = false; + model->update (); + m_current_changed_enabled = true; + + if (mp_salt->is_empty ()) { + list_stack->setCurrentIndex (1); + details_frame->hide (); + } else { + list_stack->setCurrentIndex (0); + details_frame->show (); + } + + current_changed (); +} + +void +SaltManagerDialog::current_changed () +{ + SaltModel *model = dynamic_cast (salt_view->model ()); + if (! model) { + return; + } + + SaltGrain *g = model->grain_from_index (salt_view->currentIndex ()); + details_text->set_grain (g); + if (!g) { + details_frame->setEnabled (false); + delete_button->setEnabled (false); + } else { + details_frame->setEnabled (true); + delete_button->setEnabled (true); + } +} + } diff --git a/src/lay/laySaltManagerDialog.h b/src/lay/laySaltManagerDialog.h index 42e59a993..24b55a62e 100644 --- a/src/lay/laySaltManagerDialog.h +++ b/src/lay/laySaltManagerDialog.h @@ -30,18 +30,37 @@ namespace lay { +class Salt; +class SaltGrain; + /** * @brief The dialog for managing the Salt ("Packages") */ class SaltManagerDialog : public QDialog, private Ui::SaltManagerDialog { +Q_OBJECT + public: /** * @brief Constructor */ SaltManagerDialog (QWidget *parent); +private slots: + /** + * @brief Called when the list of packages (grains) has changed + */ + void salt_changed (); + + /** + * @brief Called when the currently selected package (grain) has changed + */ + void current_changed (); + +private: + lay::Salt *mp_salt; + bool m_current_changed_enabled; }; } diff --git a/src/unit_tests/laySalt.cc b/src/unit_tests/laySalt.cc index 2d761ed62..75d7986ca 100644 --- a/src/unit_tests/laySalt.cc +++ b/src/unit_tests/laySalt.cc @@ -72,7 +72,20 @@ static std::string salt_to_string (lay::Salt &salt) TEST (1) { + std::string tmp0 = tmp_file ("tmp0"); + lay::SaltGrain g; + g.save (tmp0); + EXPECT_EQ (g.authored_time ().isNull (), true); + EXPECT_EQ (g.installed_time ().isNull (), true); + + lay::SaltGrain g0; + g0.load (tmp0); + EXPECT_EQ (g0.authored_time ().isNull (), true); + EXPECT_EQ (g0.installed_time ().isNull (), true); + EXPECT_EQ (g == g0, true); + + std::string tmp = tmp_file (); g.set_name ("abc"); EXPECT_EQ (g.name (), "abc"); @@ -86,6 +99,20 @@ TEST (1) EXPECT_EQ (g.title (), "title"); g.set_doc ("doc"); EXPECT_EQ (g.doc (), "doc"); + g.set_author ("me"); + EXPECT_EQ (g.author (), "me"); + g.set_author_contact ("ac"); + EXPECT_EQ (g.author_contact (), "ac"); + g.set_license ("free"); + EXPECT_EQ (g.license (), "free"); + g.set_authored_time (QDateTime ()); + EXPECT_EQ (g.authored_time ().isNull (), true); + g.set_authored_time (QDateTime::fromMSecsSinceEpoch (1000000000)); + EXPECT_EQ (QDateTime::fromMSecsSinceEpoch (0).msecsTo (g.authored_time ()), 1000000000); + g.set_installed_time (QDateTime ()); + EXPECT_EQ (g.installed_time ().isNull (), true); + g.set_installed_time (QDateTime::fromMSecsSinceEpoch (2000000000)); + EXPECT_EQ (QDateTime::fromMSecsSinceEpoch (0).msecsTo (g.installed_time ()), 2000000000); g.add_dependency (lay::SaltGrain::Dependency ()); g.dependencies ().back ().name = "depname"; @@ -105,8 +132,6 @@ TEST (1) gg.set_doc ("blabla"); EXPECT_EQ (g == gg, false); - std::string tmp = tmp_file (); - EXPECT_EQ (g == gg, false); g.save (tmp); @@ -269,11 +294,14 @@ TEST (4) // That's the main test part lay::Salt salt; + EXPECT_EQ (salt.is_empty (), true); + QSignalSpy spy (&salt, SIGNAL (collections_changed ())); EXPECT_EQ (salt_to_string (salt), "[]"); spy.clear (); salt.add_location (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (salt.is_empty (), false); EXPECT_EQ (spy.count (), 1); EXPECT_EQ (salt_to_string (salt), "[a,b,c/c/v,c/u]");