From e421026366607c6b6c442196ea7d9ed2dd08bb57 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 12 Mar 2017 23:26:04 +0100 Subject: [PATCH 01/57] Started package manager development ('salt') --- src/lay/SaltManagerDialog.ui | 351 ++++++++++++++++++++++++++++++++ src/lay/gsiDeclLayMainWindow.cc | 4 + src/lay/lay.pro | 13 +- src/lay/layMainWindow.cc | 10 +- src/lay/layMainWindow.h | 1 + src/lay/laySalt.cc | 31 +++ src/lay/laySalt.h | 34 ++++ src/lay/laySaltGrain.cc | 183 +++++++++++++++++ src/lay/laySaltGrain.h | 264 ++++++++++++++++++++++++ src/lay/laySaltGrains.cc | 31 +++ src/lay/laySaltGrains.h | 34 ++++ src/lay/laySaltManagerDialog.cc | 36 ++++ src/lay/laySaltManagerDialog.h | 49 +++++ src/unit_tests/laySaltGrain.cc | 99 +++++++++ src/unit_tests/unit_tests.pro | 3 +- 15 files changed, 1139 insertions(+), 4 deletions(-) create mode 100644 src/lay/SaltManagerDialog.ui create mode 100644 src/lay/laySalt.cc create mode 100644 src/lay/laySalt.h create mode 100644 src/lay/laySaltGrain.cc create mode 100644 src/lay/laySaltGrain.h create mode 100644 src/lay/laySaltGrains.cc create mode 100644 src/lay/laySaltGrains.h create mode 100644 src/lay/laySaltManagerDialog.cc create mode 100644 src/lay/laySaltManagerDialog.h create mode 100644 src/unit_tests/laySaltGrain.cc diff --git a/src/lay/SaltManagerDialog.ui b/src/lay/SaltManagerDialog.ui new file mode 100644 index 000000000..0a97e8cda --- /dev/null +++ b/src/lay/SaltManagerDialog.ui @@ -0,0 +1,351 @@ + + + SaltManagerDialog + + + + 0 + 0 + 692 + 440 + + + + Manage Packages + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 2 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::NoFocus + + + ... + + + + :/import.png:/import.png + + + true + + + + + + + Qt::Horizontal + + + + 50 + 20 + + + + + + + + Qt::NoFocus + + + ... + + + + :/add.png:/add.png + + + true + + + + + + + Qt::NoFocus + + + ... + + + + :/clear.png:/clear.png + + + true + + + + + + + Qt::NoFocus + + + ... + + + + :/new_folder.png:/new_folder.png + + + true + + + + + + + Qt::NoFocus + + + ... + + + + :/rename.png:/rename.png + + + true + + + + + + + + + + + 0 + 0 + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + + 1 + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + <html><body><center>No packages are installed currently.<br><br>You can use:<br> +</center> +<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><a href=":add"><img src=":/add.png"></a></td><td>to create a new package</td></tr> +</table> +</body></html> + + + Qt::AlignHCenter|Qt::AlignTop + + + true + + + + + + + + + + + + + + + + 1 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 75 + true + + + + Package Details + + + + + + + + + + + + + + + :/new_folder.png:/new_folder.png + + + New Folder + + + + + + :/add.png:/add.png + + + New + + + New Package + + + + + + :/clear.png:/clear.png + + + Delete + + + Delete Package + + + + + + :/rename.png:/rename.png + + + Rename + + + Rename Package + + + + + + :/import.png:/import.png + + + Import + + + Import Package + + + + + treeWidget + toolButton_4 + toolButton_2 + toolButton_3 + toolButton + toolButton_5 + textBrowser + + + + + + diff --git a/src/lay/gsiDeclLayMainWindow.cc b/src/lay/gsiDeclLayMainWindow.cc index a64fad18d..77c8eb45e 100644 --- a/src/lay/gsiDeclLayMainWindow.cc +++ b/src/lay/gsiDeclLayMainWindow.cc @@ -539,6 +539,10 @@ Class decl_MainWindow (QT_EXTERNAL_BASE (QMainWindow) "MainWind "@brief 'cm_technologies' action (bound to a menu)" "\nThis method has been added in version 0.22." ) + + gsi::method ("cm_packages", &lay::MainWindow::cm_packages, + "@brief 'cm_packages' action (bound to a menu)" + "\nThis method has been added in version 0.25." + ) + gsi::method ("cm_open_too", &lay::MainWindow::cm_open_too, "@brief 'cm_open_too' action (bound to a menu)" ) + diff --git a/src/lay/lay.pro b/src/lay/lay.pro index c1451e9b8..7efeae835 100644 --- a/src/lay/lay.pro +++ b/src/lay/lay.pro @@ -45,7 +45,11 @@ HEADERS = \ layTextProgress.h \ layVersion.h \ layCommon.h \ - layConfig.h + layConfig.h \ + laySalt.h \ + laySaltGrain.h \ + laySaltGrains.h \ + laySaltManagerDialog.h FORMS = \ ClipDialog.ui \ @@ -90,7 +94,8 @@ FORMS = \ XORToolDialog.ui \ TechLoadOptionsEditorPage.ui \ TechSaveOptionsEditorPage.ui \ - MainConfigPage7.ui + MainConfigPage7.ui \ + SaltManagerDialog.ui SOURCES = \ gsiDeclLayApplication.cc \ @@ -134,6 +139,10 @@ SOURCES = \ layTechSetupDialog.cc \ layTextProgress.cc \ layVersion.cc \ + laySalt.cc \ + laySaltGrain.cc \ + laySaltGrains.cc \ + laySaltManagerDialog.cc RESOURCES = layBuildInMacros.qrc \ layHelpResources.qrc \ diff --git a/src/lay/layMainWindow.cc b/src/lay/layMainWindow.cc index 809e90fd5..70acd1544 100644 --- a/src/lay/layMainWindow.cc +++ b/src/lay/layMainWindow.cc @@ -87,8 +87,8 @@ #include "layLogViewerDialog.h" #include "layLayerToolbox.h" #include "laySettingsForm.h" -#include "laySettingsForm.h" #include "layTechSetupDialog.h" +#include "laySaltManagerDialog.h" #include "layTipDialog.h" #include "laySelectCellViewForm.h" #include "layLayoutPropertiesForm.h" @@ -950,6 +950,7 @@ MainWindow::init_menu () }; MenuLayoutEntry tools_menu [] = { + MenuLayoutEntry ("packages", tl::to_string (QObject::tr ("Manage Packages")), SLOT (cm_packages ())), MenuLayoutEntry ("technologies", tl::to_string (QObject::tr ("Manage Technologies")), SLOT (cm_technologies ())), MenuLayoutEntry::separator ("verification_group"), MenuLayoutEntry ("drc", tl::to_string (QObject::tr ("DRC")), drc_menu), @@ -4699,6 +4700,13 @@ MainWindow::eventFilter (QObject *obj, QEvent *event) } } +void +MainWindow::cm_packages () +{ + lay::SaltManagerDialog dialog (this); + dialog.exec (); +} + void MainWindow::cm_technologies () { diff --git a/src/lay/layMainWindow.h b/src/lay/layMainWindow.h index 96331cc95..ebcd8d4ba 100644 --- a/src/lay/layMainWindow.h +++ b/src/lay/layMainWindow.h @@ -734,6 +734,7 @@ public slots: void cm_macro_editor (); void cm_new_drc_script (); void cm_edit_drc_scripts (); + void cm_packages (); void cm_technologies (); void cm_open_too (); void cm_open_new_view (); diff --git a/src/lay/laySalt.cc b/src/lay/laySalt.cc new file mode 100644 index 000000000..897fc0457 --- /dev/null +++ b/src/lay/laySalt.cc @@ -0,0 +1,31 @@ + +/* + + 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 "laySalt.h" + +namespace lay +{ + + + + +} diff --git a/src/lay/laySalt.h b/src/lay/laySalt.h new file mode 100644 index 000000000..b4bf3aa2a --- /dev/null +++ b/src/lay/laySalt.h @@ -0,0 +1,34 @@ + +/* + + 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_laySalt +#define HDR_laySalt + +namespace lay +{ + + + + +} + +#endif diff --git a/src/lay/laySaltGrain.cc b/src/lay/laySaltGrain.cc new file mode 100644 index 000000000..feac7ab16 --- /dev/null +++ b/src/lay/laySaltGrain.cc @@ -0,0 +1,183 @@ + +/* + + 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 "laySaltGrain.h" +#include "tlString.h" +#include "tlXMLParser.h" + +#include +#include + +namespace lay +{ + +static const std::string grain_filename = "grain.xml"; + +SaltGrain::SaltGrain () +{ + // .. nothing yet .. +} + +bool +SaltGrain::operator== (const SaltGrain &other) const +{ + return m_name == other.m_name && + m_version == other.m_version && + m_path == other.m_path && + m_url == other.m_url && + m_title == other.m_title && + m_doc == other.m_doc && + m_dependencies == other.m_dependencies; +} + +void +SaltGrain::set_name (const std::string &n) +{ + m_name = n; +} + +void +SaltGrain::set_version (const std::string &v) +{ + m_version = v; +} + +void +SaltGrain::set_path (const std::string &p) +{ + m_path = p; +} + +void +SaltGrain::set_url (const std::string &u) +{ + m_url = u; +} + +void +SaltGrain::set_title (const std::string &t) +{ + m_title = t; +} + +void +SaltGrain::set_doc (const std::string &t) +{ + m_doc = t; +} + +int +SaltGrain::compare_versions (const std::string &v1, const std::string &v2) +{ + tl::Extractor ex1 (v1.c_str ()); + tl::Extractor ex2 (v2.c_str ()); + + while (true) { + + if (ex1.at_end () && ex2.at_end ()) { + return 0; + } + + int n1 = 0, n2 = 0; + if (! ex1.at_end ()) { + ex1.try_read (n1); + } + if (! ex2.at_end ()) { + ex2.try_read (n2); + } + + if (n1 != n2) { + return n1 < n2 ? -1 : 1; + } + + while (! ex1.at_end ()) { + char c = *ex1; + ++ex1; + if (c == '.') { + break; + } + } + + while (! ex2.at_end ()) { + char c = *ex2; + ++ex2; + if (c == '.') { + break; + } + } + + } +} + +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_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") + + tl::make_member (&SaltGrain::Dependency::version, "version") + ) +); + +void +SaltGrain::load (const std::string &p) +{ + tl::XMLFileSource source (p); + xml_struct.parse (source, *this); +} + +void +SaltGrain::save () const +{ + save (tl::to_string (QDir (tl::to_qstring (path ())).filePath (tl::to_qstring (grain_filename)))); +} + +void +SaltGrain::save (const std::string &p) const +{ + tl::OutputStream os (p, tl::OutputStream::OM_Plain); + xml_struct.write (os, *this); +} + +SaltGrain +SaltGrain::from_path (const std::string &path) +{ + QDir dir (tl::to_qstring (path)); + + SaltGrain g; + g.load (tl::to_string (dir.filePath (tl::to_qstring (grain_filename)))); + g.set_path (tl::to_string (dir.absolutePath ())); + return g; +} + +bool +SaltGrain::is_grain (const std::string &path) +{ + QDir dir (tl::to_qstring (path)); + QString gf = dir.filePath (tl::to_qstring (grain_filename)); + return QFileInfo (gf).exists (); +} + +} diff --git a/src/lay/laySaltGrain.h b/src/lay/laySaltGrain.h new file mode 100644 index 000000000..644521750 --- /dev/null +++ b/src/lay/laySaltGrain.h @@ -0,0 +1,264 @@ + +/* + + 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_laySaltGrain +#define HDR_laySaltGrain + +#include "layCommon.h" +#include "tlObject.h" + +namespace lay +{ + +/** + * @brief This class represents on grain of salt + * "One grain of salt" is one package. + */ +class LAY_PUBLIC SaltGrain + : public tl::Object +{ +public: + /** + * @brief A descriptor for one dependency + * A dependency can be specified either through a name (see name property) + * or a download URL. If download URL are specified, they have precedence + * over names. + * The version is the minimum required version. If empty, any version is + * allowed to resolve this dependency. + */ + struct Dependency + { + std::string name; + std::string url; + std::string version; + + bool operator== (const Dependency &other) const + { + return name == other.name && url == other.url && version == other.version; + } + }; + + /** + * @brief Constructor + */ + SaltGrain (); + + /** + * @brief Equality + */ + bool operator== (const SaltGrain &other) const; + + /** + * @brief Inequality + */ + bool operator!= (const SaltGrain &other) const + { + return !operator== (other); + } + + /** + * @brief Gets the name of the grain + * + * The name is either a plain name (a word) or a path into a collection. + * Name paths are formed using the "/" separator. "mypackage" is a plain name, + * while "mycollection/mypackage" is a package within a collection. Collections + * can be used to group packages. Names are case sensitive in general, but + * names differing only in case should be avoided. + */ + const std::string &name () const + { + return m_name; + } + + /** + * @brief Sets the name of the grain + */ + void set_name (const std::string &p); + + /** + * @brief Gets the title of the grain + * + * The title is a brief description that is shown in the title of the + * package manager. + */ + const std::string &title () const + { + return m_title; + } + + /** + * @brief Sets the title of the grain + */ + void set_title (const std::string &t); + + /** + * @brief Gets the documentation text of the grain + * + * The documentation text is an XML document using + * KLayout's doc format. + */ + const std::string &doc () const + { + return m_doc; + } + + /** + * @brief Sets the documentation text of the grain + */ + void set_doc (const std::string &t); + + /** + * @brief Gets the version of the grain + * + * A version string is of the form "x.y..." where x, y and other version + * components are integer numbers. + */ + const std::string &version () const + { + return m_version; + } + + /** + * @brief Sets the version of the grain + */ + void set_version (const std::string &v); + + /** + * @brief Gets the absolute file path of the installed grain + * This is the file path to the grain folder. + */ + const std::string &path () const + { + return m_path; + } + + /** + * @brief Sets the absolute file path of the installed grain + */ + void set_path (const std::string &p); + + /** + * @brief Gets the download URL + * The download URL is the place from which the grain was installed originally. + */ + const std::string &url () const + { + return m_url; + } + + /** + * @brief Sets the download URL + */ + void set_url (const std::string &u); + + /** + * @brief Gets the dependencies of the grain + * Grains this grain depends on are installed automatically when the grain + * is installed. + */ + const std::vector &dependencies () const + { + return m_dependencies; + } + + /** + * @brief Gets the dependencies of the grain (non-const) + */ + std::vector &dependencies () + { + return m_dependencies; + } + + /** + * @brief Dependency iterator (begin) + */ + std::vector::const_iterator begin_dependencies () const + { + return m_dependencies.begin (); + } + + /** + * @brief Dependency iterator (end) + */ + std::vector::const_iterator end_dependencies () const + { + return m_dependencies.end (); + } + + /** + * @brief Adds a dependency + */ + void add_dependency (const Dependency &dep) + { + m_dependencies.push_back (dep); + } + + /** + * @brief Loads the data from a given file + * This method will *not* set the path. + */ + void load (const std::string &file_path); + + /** + * @brief Saves the data to the path inside the grain folder given by the "path" property + */ + void save () const; + + /** + * @brief Saves the data to the given file + */ + void save (const std::string &file_path) const; + + /** + * @brief Compares two version strings + * Returns -1 if v1 < v2, 0 if v1 == v2 and 1 if v1 > v2. + * Malformed versions are read gracefully. Letters and non-digits are skipped. + * Missing numbers are read as 0. Hence "1.0 == 1" for example. + */ + static int compare_versions (const std::string &v1, const std::string &v2); + + /** + * @brief Detects a grain from the given directory + * This method will return a grain constructed from the given directory. + * The data is read from "path/grain.xml". This method will throw an + * exception if an error occurs during reading. + */ + static SaltGrain from_path (const std::string &path); + + /** + * @brief Returns a value indicating whether the given path represents is a grain + */ + static bool is_grain (const std::string &path); + +private: + std::string m_name; + std::string m_version; + std::string m_path; + std::string m_url; + std::string m_title; + std::string m_doc; + std::vector m_dependencies; +}; + +} + +#endif diff --git a/src/lay/laySaltGrains.cc b/src/lay/laySaltGrains.cc new file mode 100644 index 000000000..8134e3178 --- /dev/null +++ b/src/lay/laySaltGrains.cc @@ -0,0 +1,31 @@ + +/* + + 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 "laySaltGrains.h" + +namespace lay +{ + + + + +} diff --git a/src/lay/laySaltGrains.h b/src/lay/laySaltGrains.h new file mode 100644 index 000000000..74b60b38b --- /dev/null +++ b/src/lay/laySaltGrains.h @@ -0,0 +1,34 @@ + +/* + + 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_laySaltGrains +#define HDR_laySaltGrains + +namespace lay +{ + + + + +} + +#endif diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc new file mode 100644 index 000000000..f63a236cc --- /dev/null +++ b/src/lay/laySaltManagerDialog.cc @@ -0,0 +1,36 @@ + +/* + + 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 "laySaltManagerDialog.h" + +namespace lay +{ + +SaltManagerDialog::SaltManagerDialog (QWidget *parent) + : QDialog (parent) +{ + Ui::SaltManagerDialog::setupUi (this); + + // ... +} + +} diff --git a/src/lay/laySaltManagerDialog.h b/src/lay/laySaltManagerDialog.h new file mode 100644 index 000000000..42e59a993 --- /dev/null +++ b/src/lay/laySaltManagerDialog.h @@ -0,0 +1,49 @@ + +/* + + 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_laySaltManager +#define HDR_laySaltManager + +#include + +#include "ui_SaltManagerDialog.h" + +namespace lay +{ + +/** + * @brief The dialog for managing the Salt ("Packages") + */ +class SaltManagerDialog + : public QDialog, private Ui::SaltManagerDialog +{ +public: + /** + * @brief Constructor + */ + SaltManagerDialog (QWidget *parent); + +}; + +} + +#endif diff --git a/src/unit_tests/laySaltGrain.cc b/src/unit_tests/laySaltGrain.cc new file mode 100644 index 000000000..516a02698 --- /dev/null +++ b/src/unit_tests/laySaltGrain.cc @@ -0,0 +1,99 @@ + +/* + + 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 "laySaltGrain.h" +#include "utHead.h" + +TEST (1) +{ + lay::SaltGrain g; + + g.set_name ("abc"); + EXPECT_EQ (g.name (), "abc"); + g.set_url ("xyz"); + EXPECT_EQ (g.url (), "xyz"); + g.set_version ("1.0"); + EXPECT_EQ (g.version (), "1.0"); + g.set_path ("a/b"); + EXPECT_EQ (g.path (), "a/b"); + g.set_title ("title"); + EXPECT_EQ (g.title (), "title"); + g.set_doc ("doc"); + EXPECT_EQ (g.doc (), "doc"); + + g.add_dependency (lay::SaltGrain::Dependency ()); + g.dependencies ().back ().name = "depname"; + g.dependencies ().back ().url = "depurl"; + g.dependencies ().back ().version = "0.0"; + EXPECT_EQ (int (g.dependencies ().size ()), 1); + + lay::SaltGrain gg; + EXPECT_EQ (g == gg, false); + EXPECT_EQ (g == g, true); + EXPECT_EQ (g != gg, true); + EXPECT_EQ (g != g, false); + + gg = g; + EXPECT_EQ (g == gg, true); + + gg.set_doc ("blabla"); + EXPECT_EQ (g == gg, false); + + std::string tmp = tmp_file (); + + EXPECT_EQ (g == gg, false); + g.save (tmp); + + EXPECT_EQ (g == gg, false); + + gg = lay::SaltGrain (); + gg.load (tmp); + gg.set_path (g.path ()); // path is not set by load(file) + EXPECT_EQ (int (gg.dependencies ().size ()), 1); + EXPECT_EQ (g == gg, true); +} + +TEST (2) +{ + EXPECT_EQ (lay::SaltGrain::compare_versions ("", ""), 0); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1", "2"), -1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1", ""), 1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1", "1"), 0); + EXPECT_EQ (lay::SaltGrain::compare_versions ("2", "1"), 1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.0", "2.0"), -1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.0", "1.0"), 0); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.1", "1.0"), 1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.0.1", "1.0.0"), 1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.0.1", "1.0"), 1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.0.1", "1"), 1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.0.0", "1"), 0); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1a", "1"), 0); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.a.1", "1.0.1"), 0); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.1a", "1.1"), 0); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.1a", "1.0"), 1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.1a.1", "1.0"), 1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("1.1a.1", "1.1.1"), 0); + EXPECT_EQ (lay::SaltGrain::compare_versions ("990", "991"), -1); + EXPECT_EQ (lay::SaltGrain::compare_versions ("990", "990"), 0); + EXPECT_EQ (lay::SaltGrain::compare_versions ("991", "990"), 1); +} diff --git a/src/unit_tests/unit_tests.pro b/src/unit_tests/unit_tests.pro index c889a89e2..aba6c9daf 100644 --- a/src/unit_tests/unit_tests.pro +++ b/src/unit_tests/unit_tests.pro @@ -95,7 +95,8 @@ SOURCES = \ tlVariant.cc \ tlXMLParser.cc \ gsiTest.cc \ - tlFileSystemWatcher.cc + tlFileSystemWatcher.cc \ + laySaltGrain.cc # main components: SOURCES += \ From b16d8b941961d304e7323b7ba486343716d235d4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 12 Mar 2017 23:36:51 +0100 Subject: [PATCH 02/57] Completed unit tests for laySaltGrain.cc --- src/unit_tests/laySaltGrain.cc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/unit_tests/laySaltGrain.cc b/src/unit_tests/laySaltGrain.cc index 516a02698..3ee047a39 100644 --- a/src/unit_tests/laySaltGrain.cc +++ b/src/unit_tests/laySaltGrain.cc @@ -24,6 +24,8 @@ #include "laySaltGrain.h" #include "utHead.h" +#include + TEST (1) { lay::SaltGrain g; @@ -71,6 +73,14 @@ TEST (1) gg.set_path (g.path ()); // path is not set by load(file) EXPECT_EQ (int (gg.dependencies ().size ()), 1); EXPECT_EQ (g == gg, true); + + gg.add_dependency (lay::SaltGrain::Dependency ()); + EXPECT_EQ (g == gg, false); + gg.set_path (tl::to_string (QFileInfo (tl::to_qstring (tmp)).absolutePath ())); + gg.save (); + + g = lay::SaltGrain::from_path (gg.path ()); + EXPECT_EQ (g == gg, true); } TEST (2) From b98afd5a8d22a2c4292deff4414896c96e31ec01 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 13 Mar 2017 23:55:57 +0100 Subject: [PATCH 03/57] WIP: next steps on package manager. --- src/lay/laySaltGrains.cc | 104 +++++++++++++++++++++++++++ src/lay/laySaltGrains.h | 151 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 255 insertions(+) diff --git a/src/lay/laySaltGrains.cc b/src/lay/laySaltGrains.cc index 8134e3178..6e2f30f48 100644 --- a/src/lay/laySaltGrains.cc +++ b/src/lay/laySaltGrains.cc @@ -21,11 +21,115 @@ */ #include "laySaltGrains.h" +#include "tlString.h" + +#include +#include namespace lay { +SaltGrains::SaltGrains () +{ + // .. nothing yet .. +} +bool +SaltGrains::operator== (const SaltGrains &other) const +{ + return m_name == other.m_name && + m_path == other.m_path && + m_title == other.m_title && + m_collections == other.m_collections && + m_grains == other.m_grains; +} +void +SaltGrains::set_name (const std::string &n) +{ + m_name = n; +} + +void +SaltGrains::set_title (const std::string &t) +{ + m_title = t; +} + +void +SaltGrains::set_path (const std::string &p) +{ + m_path = p; +} + +void +SaltGrains::add_collection (const SaltGrains &collection) +{ + m_collections.push_back (collection); +} + +void +SaltGrains::remove_collection (collection_iterator iter) +{ + // NOTE: this is kind of inefficient, but in order to maintain the const iterator semantics this approach is required + for (collections_type::iterator i = m_collections.begin (); i != m_collections.end (); ++i) { + if (i == iter) { + m_collections.erase (i); + break; + } + } +} + +void +SaltGrains::add_grain (const SaltGrain &grain) +{ + m_grains.push_back (grain); +} + +void +SaltGrains::remove_grain (grain_iterator iter) +{ + // NOTE: this is kind of inefficient, but in order to maintain the const iterator semantics this approach is required + for (grains_type::iterator i = m_grains.begin (); i != m_grains.end (); ++i) { + if (i == iter) { + m_grains.erase (i); + break; + } + } +} + +bool +SaltGrains::is_empty () const +{ + return m_collections.empty () && m_grains.empty (); +} + +SaltGrains +SaltGrains::from_path (const std::string &path) +{ + SaltGrains grains; + + QDir dir (tl::to_qstring (path)); + QStringList entries = dir.entryList (QDir::NoFilter, QDir::Name); + for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) { + + std::string epath = tl::to_string (dir.absoluteFilePath (*e)); + if (SaltGrain::is_grain (epath)) { + try { + grains.add_grain (SaltGrain::from_path (epath)); + } catch (...) { + // ignore errors (TODO: what to do here?) + } + } else if (QFileInfo (tl::to_qstring (epath)).isDir ()) { + SaltGrains c = SaltGrains::from_path (epath); + if (! c.is_empty ()) { + grains.add_collection (c); + } + } + + } + + return grains; +} } diff --git a/src/lay/laySaltGrains.h b/src/lay/laySaltGrains.h index 74b60b38b..93f256c58 100644 --- a/src/lay/laySaltGrains.h +++ b/src/lay/laySaltGrains.h @@ -23,11 +23,162 @@ #ifndef HDR_laySaltGrains #define HDR_laySaltGrains +#include "laySaltGrain.h" + +#include + namespace lay { +/** + * @brief A class representing a collection of grains (packages) + * A collection can have child collections and grains (leafs). + */ +class LAY_PUBLIC SaltGrains +{ +public: + typedef std::list collections_type; + typedef collections_type::const_iterator collection_iterator; + typedef std::list grains_type; + typedef grains_type::const_iterator grain_iterator; + /** + * @brief Constructor: creates an empty collection + */ + SaltGrains (); + /** + * @brief Equality + */ + bool operator== (const SaltGrains &other) const; + + /** + * @brief Inequality + */ + bool operator!= (const SaltGrains &other) const + { + return !operator== (other); + } + + /** + * @brief Gets the name of the grain collection + * + * The name is either a plain name (a word) or a path into a collection. + * Name paths are formed using the "/" separator. "mycollection" is a plain name, + * while "mycollection/subcollection" is a collection within a collection. + */ + const std::string &name () const + { + return m_name; + } + + /** + * @brief Sets the name of the grain collection + */ + void set_name (const std::string &p); + + /** + * @brief Gets the title of the grain collection + * + * The title is a brief description that is shown in the title of the + * package manager. + */ + const std::string &title () const + { + return m_title; + } + + /** + * @brief Sets the title of the grain collection + */ + void set_title (const std::string &t); + + /** + * @brief Gets the absolute file path of the installed grain + * This is the file path to the grain folder. + */ + const std::string &path () const + { + return m_path; + } + + /** + * @brief Sets the absolute file path of the installed grain + */ + void set_path (const std::string &p); + + /** + * @brief Gets the collections which are members of this collection (begin iterator) + */ + collection_iterator begin_collections () const + { + return m_collections.begin (); + } + + /** + * @brief Gets the collections which are members of this collection (end iterator) + */ + collection_iterator end_collections () const + { + return m_collections.begin (); + } + + /** + * @brief Adds a collection to this collection + */ + void add_collection (const SaltGrains &collection); + + /** + * @brief Removes the collection given by the collection iterator + */ + void remove_collection (collection_iterator iter); + + /** + * @brief Gets the grains (leaf nodes) which are members of this collection (begin iterator) + */ + grain_iterator begin_grains () const + { + return m_grains.begin (); + } + + /** + * @brief Gets the grains (leaf nodes) which are members of this collection (end iterator) + */ + grain_iterator end_grains () const + { + return m_grains.begin (); + } + + /** + * @brief Adds a grain to this collection + */ + void add_grain (const SaltGrain &grain); + + /** + * @brief Removes the grain given by the grain iterator + */ + void remove_grain (grain_iterator iter); + + /** + * @brief Gets a value indicating whether the collection is empty + */ + bool is_empty () const; + + /** + * @brief Scan grains from a given path + * This will scan the grains found within this path and return a collection containing + * the grains from this path. + * Sub-collections are created from folders which contain grains or sub-collections. + */ + static SaltGrains from_path (const std::string &path); + +private: + std::string m_name; + std::string m_title; + std::string m_path; + collections_type m_collections; + grains_type m_grains; +}; } From 59dd36f692e20fe17ebf11487747394d9a8d0197 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 14 Mar 2017 22:59:24 +0100 Subject: [PATCH 04/57] WIP: grains collections for salt package manager. --- src/lay/laySaltGrains.cc | 18 +++++-- src/lay/laySaltGrains.h | 6 +-- src/unit_tests/laySaltGrain.cc | 99 ++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 7 deletions(-) diff --git a/src/lay/laySaltGrains.cc b/src/lay/laySaltGrains.cc index 6e2f30f48..b44c7c05d 100644 --- a/src/lay/laySaltGrains.cc +++ b/src/lay/laySaltGrains.cc @@ -105,23 +105,33 @@ SaltGrains::is_empty () const } SaltGrains -SaltGrains::from_path (const std::string &path) +SaltGrains::from_path (const std::string &path, const std::string &prefix) { SaltGrains grains; + grains.set_path (path); QDir dir (tl::to_qstring (path)); - QStringList entries = dir.entryList (QDir::NoFilter, QDir::Name); + QStringList entries = dir.entryList (QDir::NoDotAndDotDot | QDir::Dirs, QDir::Name); for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) { + std::string new_prefix = prefix; + if (! new_prefix.empty ()) { + new_prefix += "/"; + } + new_prefix += tl::to_string (*e); + std::string epath = tl::to_string (dir.absoluteFilePath (*e)); if (SaltGrain::is_grain (epath)) { try { - grains.add_grain (SaltGrain::from_path (epath)); + SaltGrain g (SaltGrain::from_path (epath)); + g.set_name (new_prefix); + grains.add_grain (g); } catch (...) { // ignore errors (TODO: what to do here?) } } else if (QFileInfo (tl::to_qstring (epath)).isDir ()) { - SaltGrains c = SaltGrains::from_path (epath); + SaltGrains c = SaltGrains::from_path (epath, new_prefix); + c.set_name (new_prefix); if (! c.is_empty ()) { grains.add_collection (c); } diff --git a/src/lay/laySaltGrains.h b/src/lay/laySaltGrains.h index 93f256c58..a2ff4ea98 100644 --- a/src/lay/laySaltGrains.h +++ b/src/lay/laySaltGrains.h @@ -120,7 +120,7 @@ public: */ collection_iterator end_collections () const { - return m_collections.begin (); + return m_collections.end (); } /** @@ -146,7 +146,7 @@ public: */ grain_iterator end_grains () const { - return m_grains.begin (); + return m_grains.end (); } /** @@ -170,7 +170,7 @@ public: * the grains from this path. * Sub-collections are created from folders which contain grains or sub-collections. */ - static SaltGrains from_path (const std::string &path); + static SaltGrains from_path (const std::string &path, const std::string &pfx = std::string ()); private: std::string m_name; diff --git a/src/unit_tests/laySaltGrain.cc b/src/unit_tests/laySaltGrain.cc index 3ee047a39..581eda8f5 100644 --- a/src/unit_tests/laySaltGrain.cc +++ b/src/unit_tests/laySaltGrain.cc @@ -22,6 +22,7 @@ #include "laySaltGrain.h" +#include "laySaltGrains.h" #include "utHead.h" #include @@ -107,3 +108,101 @@ TEST (2) EXPECT_EQ (lay::SaltGrain::compare_versions ("990", "990"), 0); EXPECT_EQ (lay::SaltGrain::compare_versions ("991", "990"), 1); } + + +static std::string grains_to_string (const lay::SaltGrains &gg) +{ + std::string res; + res += "["; + bool first = true; + for (lay::SaltGrains::grain_iterator g = gg.begin_grains (); g != gg.end_grains (); ++g) { + if (! first) { + res += ","; + } + first = false; + res += g->name (); + } + for (lay::SaltGrains::collection_iterator gc = gg.begin_collections (); gc != gg.end_collections (); ++gc) { + if (! first) { + res += ","; + } + first = false; + res += gc->name (); + res += grains_to_string (*gc); + } + res += "]"; + return res; +} + +static bool empty_dir (const QString &path) +{ + QDir dir (path); + QStringList entries = dir.entryList (QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) { + QFileInfo fi (dir.absoluteFilePath (*e)); + if (fi.isDir ()) { + if (! empty_dir (fi.filePath ())) { + return false; + } + if (! dir.rmdir (*e)) { + return false; + } + } else if (fi.isFile ()) { + if (! dir.remove (*e)) { + return false; + } + } + } + return true; +} + +TEST (3) +{ + const QString grain_spec_file = QString::fromUtf8 ("grain.xml"); + + lay::SaltGrain g; + g.set_name ("x"); + + QDir tmp_dir (QFileInfo (tl::to_qstring (tmp_file ())).absolutePath ()); + QDir dir_a (tmp_dir.filePath (QString::fromUtf8 ("a"))); + QDir dir_b (tmp_dir.filePath (QString::fromUtf8 ("b"))); + QDir dir_c (tmp_dir.filePath (QString::fromUtf8 ("c"))); + QDir dir_cu (dir_c.filePath (QString::fromUtf8 ("u"))); + QDir dir_cc (dir_c.filePath (QString::fromUtf8 ("c"))); + QDir dir_ccv (dir_cc.filePath (QString::fromUtf8 ("v"))); + + tl_assert (empty_dir (tmp_dir.path ())); + + lay::SaltGrains gg; + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (gg.is_empty (), true); + EXPECT_EQ (grains_to_string (gg), "[]"); + + tmp_dir.mkdir (dir_a.dirName ()); + tmp_dir.mkdir (dir_b.dirName ()); + tmp_dir.mkdir (dir_c.dirName ()); + dir_c.mkdir (dir_cu.dirName ()); + dir_c.mkdir (dir_cc.dirName ()); + dir_cc.mkdir (dir_ccv.dirName ()); + + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (gg.is_empty (), true); + EXPECT_EQ (grains_to_string (gg), "[]"); + EXPECT_EQ (gg.path (), tl::to_string (tmp_dir.path ())); + + g.save (tl::to_string (dir_a.absoluteFilePath (grain_spec_file))); + + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (gg.is_empty (), false); + EXPECT_EQ (grains_to_string (gg), "[a]"); + EXPECT_EQ (gg.begin_grains ()->path (), tl::to_string (dir_a.absolutePath ())); + + g.save (tl::to_string (dir_b.absoluteFilePath (grain_spec_file))); + g.save (tl::to_string (dir_cu.absoluteFilePath (grain_spec_file))); + g.save (tl::to_string (dir_ccv.absoluteFilePath (grain_spec_file))); + + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (gg.is_empty (), false); + EXPECT_EQ (grains_to_string (gg), "[a,b,c[c/u,c/c[c/c/v]]]"); + EXPECT_EQ (gg.begin_collections ()->path (), tl::to_string (dir_c.absolutePath ())); +} From 134534adca9de2fbf5391791c6fa58a02c517ab2 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 14 Mar 2017 23:32:16 +0100 Subject: [PATCH 05/57] WIP: removal of grains/collections (needs unit tests). --- src/lay/laySaltGrain.cc | 6 ++++ src/lay/laySaltGrain.h | 5 ++++ src/lay/laySaltGrains.cc | 29 ++++++++++++++---- src/lay/laySaltGrains.h | 13 ++++++-- src/tl/tl.pro | 6 ++-- src/tl/tlFileUtils.cc | 54 ++++++++++++++++++++++++++++++++++ src/tl/tlFileUtils.h | 40 +++++++++++++++++++++++++ src/unit_tests/laySaltGrain.cc | 25 ++-------------- 8 files changed, 145 insertions(+), 33 deletions(-) create mode 100644 src/tl/tlFileUtils.cc create mode 100644 src/tl/tlFileUtils.h diff --git a/src/lay/laySaltGrain.cc b/src/lay/laySaltGrain.cc index feac7ab16..de0eccf6a 100644 --- a/src/lay/laySaltGrain.cc +++ b/src/lay/laySaltGrain.cc @@ -141,6 +141,12 @@ static tl::XMLStruct xml_struct ("salt-grain", ) ); +bool +SaltGrain::is_readonly () const +{ + return QFileInfo (tl::to_qstring (path ())).isWritable (); +} + void SaltGrain::load (const std::string &p) { diff --git a/src/lay/laySaltGrain.h b/src/lay/laySaltGrain.h index 644521750..b86a0e0e7 100644 --- a/src/lay/laySaltGrain.h +++ b/src/lay/laySaltGrain.h @@ -212,6 +212,11 @@ public: m_dependencies.push_back (dep); } + /** + * @brief Returns true, if the collection is read-only + */ + bool is_readonly () const; + /** * @brief Loads the data from a given file * This method will *not* set the path. diff --git a/src/lay/laySaltGrains.cc b/src/lay/laySaltGrains.cc index b44c7c05d..1d8306176 100644 --- a/src/lay/laySaltGrains.cc +++ b/src/lay/laySaltGrains.cc @@ -22,6 +22,7 @@ #include "laySaltGrains.h" #include "tlString.h" +#include "tlFileUtils.h" #include #include @@ -68,16 +69,21 @@ SaltGrains::add_collection (const SaltGrains &collection) m_collections.push_back (collection); } -void -SaltGrains::remove_collection (collection_iterator iter) +bool +SaltGrains::remove_collection (collection_iterator iter, bool with_files) { // NOTE: this is kind of inefficient, but in order to maintain the const iterator semantics this approach is required for (collections_type::iterator i = m_collections.begin (); i != m_collections.end (); ++i) { if (i == iter) { + if (with_files && !tl::rm_dir_recursive (tl::to_qstring (path ()))) { + return false; + } m_collections.erase (i); - break; + return true; } } + + return false; } void @@ -86,16 +92,21 @@ SaltGrains::add_grain (const SaltGrain &grain) m_grains.push_back (grain); } -void -SaltGrains::remove_grain (grain_iterator iter) +bool +SaltGrains::remove_grain (grain_iterator iter, bool with_files) { // NOTE: this is kind of inefficient, but in order to maintain the const iterator semantics this approach is required for (grains_type::iterator i = m_grains.begin (); i != m_grains.end (); ++i) { if (i == iter) { + if (with_files && !tl::rm_dir_recursive (tl::to_qstring (path ()))) { + return false; + } m_grains.erase (i); - break; + return true; } } + + return false; } bool @@ -104,6 +115,12 @@ SaltGrains::is_empty () const return m_collections.empty () && m_grains.empty (); } +bool +SaltGrains::is_readonly () const +{ + return QFileInfo (tl::to_qstring (path ())).isWritable (); +} + SaltGrains SaltGrains::from_path (const std::string &path, const std::string &prefix) { diff --git a/src/lay/laySaltGrains.h b/src/lay/laySaltGrains.h index a2ff4ea98..f47b897fb 100644 --- a/src/lay/laySaltGrains.h +++ b/src/lay/laySaltGrains.h @@ -130,8 +130,10 @@ public: /** * @brief Removes the collection given by the collection iterator + * If "with_files" is true, also the folder and all sub-folders will be removed + * @return true, if the remove was successful. */ - void remove_collection (collection_iterator iter); + bool remove_collection (collection_iterator iter, bool with_files = false); /** * @brief Gets the grains (leaf nodes) which are members of this collection (begin iterator) @@ -156,14 +158,21 @@ public: /** * @brief Removes the grain given by the grain iterator + * If "with_files" is true, also the files and the folder will be removed. + * @return true, if the remove was successful. */ - void remove_grain (grain_iterator iter); + bool remove_grain (grain_iterator iter, bool with_files = false); /** * @brief Gets a value indicating whether the collection is empty */ bool is_empty () const; + /** + * @brief Returns true, if the collection is read-only + */ + bool is_readonly () const; + /** * @brief Scan grains from a given path * This will scan the grains found within this path and return a collection containing diff --git a/src/tl/tl.pro b/src/tl/tl.pro index 95f8f9870..39a9e5713 100644 --- a/src/tl/tl.pro +++ b/src/tl/tl.pro @@ -39,7 +39,8 @@ SOURCES = \ tlVariant.cc \ tlXMLParser.cc \ tlXMLWriter.cc \ - tlFileSystemWatcher.cc + tlFileSystemWatcher.cc \ + tlFileUtils.cc HEADERS = \ tlAlgorithm.h \ @@ -81,7 +82,8 @@ HEADERS = \ tlXMLParser.h \ tlXMLWriter.h \ tlFileSystemWatcher.h \ - tlCommon.h + tlCommon.h \ + tlFileUtils.h INCLUDEPATH = DEPENDPATH = diff --git a/src/tl/tlFileUtils.cc b/src/tl/tlFileUtils.cc new file mode 100644 index 000000000..518bb7989 --- /dev/null +++ b/src/tl/tlFileUtils.cc @@ -0,0 +1,54 @@ + +/* + + 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 "tlFileUtils.h" + +#include +#include + +namespace tl +{ + +bool +rm_dir_recursive (const QString &path) +{ + QDir dir (path); + QStringList entries = dir.entryList (QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) { + QFileInfo fi (dir.absoluteFilePath (*e)); + if (fi.isDir ()) { + if (! rm_dir_recursive (fi.filePath ())) { + return false; + } + if (! dir.rmdir (*e)) { + return false; + } + } else if (fi.isFile ()) { + if (! dir.remove (*e)) { + return false; + } + } + } + return true; +} + +} diff --git a/src/tl/tlFileUtils.h b/src/tl/tlFileUtils.h new file mode 100644 index 000000000..01c171a99 --- /dev/null +++ b/src/tl/tlFileUtils.h @@ -0,0 +1,40 @@ + +/* + + 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_tlFileUtils +#define HDR_tlFileUtils + +#include "tlCommon.h" +#include + +namespace tl +{ + +/** + * @brief Recursively remove the given directory, the files from that directory and all sub-directories + * @return True, if successful. False otherwise. + */ +bool TL_PUBLIC rm_dir_recursive (const QString &path); + +} + +#endif diff --git a/src/unit_tests/laySaltGrain.cc b/src/unit_tests/laySaltGrain.cc index 581eda8f5..fabfab2fd 100644 --- a/src/unit_tests/laySaltGrain.cc +++ b/src/unit_tests/laySaltGrain.cc @@ -23,6 +23,7 @@ #include "laySaltGrain.h" #include "laySaltGrains.h" +#include "tlFileUtils.h" #include "utHead.h" #include @@ -134,28 +135,6 @@ static std::string grains_to_string (const lay::SaltGrains &gg) return res; } -static bool empty_dir (const QString &path) -{ - QDir dir (path); - QStringList entries = dir.entryList (QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); - for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) { - QFileInfo fi (dir.absoluteFilePath (*e)); - if (fi.isDir ()) { - if (! empty_dir (fi.filePath ())) { - return false; - } - if (! dir.rmdir (*e)) { - return false; - } - } else if (fi.isFile ()) { - if (! dir.remove (*e)) { - return false; - } - } - } - return true; -} - TEST (3) { const QString grain_spec_file = QString::fromUtf8 ("grain.xml"); @@ -171,7 +150,7 @@ TEST (3) QDir dir_cc (dir_c.filePath (QString::fromUtf8 ("c"))); QDir dir_ccv (dir_cc.filePath (QString::fromUtf8 ("v"))); - tl_assert (empty_dir (tmp_dir.path ())); + tl_assert (tl::rm_dir_recursive (tmp_dir.path ())); lay::SaltGrains gg; gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); From 695a5d3169800a9824abe6805cda8720155bde3f Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 15 Mar 2017 22:56:47 +0100 Subject: [PATCH 06/57] WIP: Salt class and unit tests --- src/lay/laySalt.cc | 99 ++++++++++ src/lay/laySalt.h | 99 ++++++++++ src/lay/laySaltGrains.cc | 4 +- src/lay/laySaltGrains.h | 1 + .../{laySaltGrain.cc => laySalt.cc} | 169 +++++++++++++++--- src/unit_tests/unit_tests.pro | 2 +- 6 files changed, 347 insertions(+), 27 deletions(-) rename src/unit_tests/{laySaltGrain.cc => laySalt.cc} (60%) diff --git a/src/lay/laySalt.cc b/src/lay/laySalt.cc index 897fc0457..24559a2d1 100644 --- a/src/lay/laySalt.cc +++ b/src/lay/laySalt.cc @@ -21,11 +21,110 @@ */ #include "laySalt.h" +#include "tlString.h" + +#include namespace lay { +Salt::Salt () +{ + // .. nothing yet .. +} +Salt::Salt (const Salt &other) + : QObject () +{ + operator= (other); +} +Salt &Salt::operator= (const Salt &other) +{ + if (this != &other) { + m_root = other.m_root; + } + return *this; +} + +void +Salt::add_location (const std::string &path) +{ + // do nothing if the collection is already there + QFileInfo fi (tl::to_qstring (path)); + for (lay::SaltGrains::collection_iterator g = m_root.begin_collections (); g != m_root.end_collections (); ++g) { + if (QFileInfo (tl::to_qstring (g->path ())) == fi) { + return; + } + } + + lay::SaltGrains gg = lay::SaltGrains::from_path (path); + m_root.add_collection (gg); + mp_flat_grains.clear (); + emit collections_changed (); +} + +void +Salt::remove_location (const std::string &path) +{ + QFileInfo fi (tl::to_qstring (path)); + for (lay::SaltGrains::collection_iterator g = m_root.begin_collections (); g != m_root.end_collections (); ++g) { + if (QFileInfo (tl::to_qstring (g->path ())) == fi) { + m_root.remove_collection (g, false); + mp_flat_grains.clear (); + emit collections_changed (); + return; + } + } +} + +void +Salt::refresh () +{ + lay::SaltGrains new_root; + for (lay::SaltGrains::collection_iterator g = m_root.begin_collections (); g != m_root.end_collections (); ++g) { + new_root.add_collection (lay::SaltGrains::from_path (g->path ())); + } + if (new_root != m_root) { + m_root = new_root; + mp_flat_grains.clear (); + emit collections_changed (); + } +} + +void +Salt::add_collection_to_flat (SaltGrains &gg) +{ + for (lay::SaltGrains::grain_iterator g = gg.begin_grains (); g != gg.end_grains (); ++g) { + // TODO: get rid of the const cast - would require a non-const grain iterator + mp_flat_grains.push_back (const_cast (g.operator-> ())); + } + for (lay::SaltGrains::collection_iterator g = gg.begin_collections (); g != gg.end_collections (); ++g) { + // TODO: get rid of the const cast - would require a non-const grain collection iterator + add_collection_to_flat (const_cast (*g)); + } +} + +namespace { + +struct NameCompare +{ + bool operator () (lay::SaltGrain *a, lay::SaltGrain *b) const + { + // TODO: UTF-8 support? + return a->name () < b->name (); + } +}; + +} + +void +Salt::ensure_flat_present () +{ + if (mp_flat_grains.empty ()) { + add_collection_to_flat (m_root); + std::sort (mp_flat_grains.begin (), mp_flat_grains.end (), NameCompare ()); + } +} } diff --git a/src/lay/laySalt.h b/src/lay/laySalt.h index b4bf3aa2a..39d5c2f02 100644 --- a/src/lay/laySalt.h +++ b/src/lay/laySalt.h @@ -23,11 +23,110 @@ #ifndef HDR_laySalt #define HDR_laySalt +#include "layCommon.h" +#include "laySaltGrain.h" +#include "laySaltGrains.h" + +#include + namespace lay { +/** + * @brief The global salt (package manager) object + * This object can be configured to represent a couple of locations. + * It will provide a collection of grains for these locations. + */ +class LAY_PUBLIC Salt + : public QObject +{ +Q_OBJECT +public: + typedef SaltGrains::collection_iterator iterator; + typedef std::vector::const_iterator flat_iterator; + /** + * @brief Default constructor + */ + Salt (); + + /** + * @brief Copy constructor + */ + Salt (const Salt &other); + + /** + * @brief assignment + */ + Salt &operator= (const Salt &other); + + /** + * @brief Adds the given location to the ones the package manager uses + * Adding a location will scan the folder and make the contents available + * as a new collection. + */ + void add_location (const std::string &path); + + /** + * @brief Removes a given location + * This will remove the collection from the package locations. + */ + void remove_location (const std::string &path); + + /** + * @brief Refreshes the collections + * This method rescans all registered locations. + */ + void refresh (); + + /** + * @brief Iterates the collections (begin) + */ + iterator begin () const + { + return m_root.begin_collections (); + } + + /** + * @brief Iterates the collections (end) + */ + iterator end () const + { + return m_root.end_collections (); + } + + /** + * @brief A flat iterator of (sorted) grains (begin) + */ + flat_iterator begin_flat () + { + ensure_flat_present (); + return mp_flat_grains.begin (); + } + + /** + * @brief A flat iterator of (sorted) grains (end) + */ + flat_iterator end_flat () + { + ensure_flat_present (); + return mp_flat_grains.end (); + } + +signals: + /** + * @brief A signal triggered when one of the collections changed + */ + void collections_changed (); + +private: + SaltGrains m_root; + std::vector mp_flat_grains; + + void ensure_flat_present (); + void add_collection_to_flat (lay::SaltGrains &gg); +}; } diff --git a/src/lay/laySaltGrains.cc b/src/lay/laySaltGrains.cc index 1d8306176..a40bcea64 100644 --- a/src/lay/laySaltGrains.cc +++ b/src/lay/laySaltGrains.cc @@ -75,7 +75,7 @@ SaltGrains::remove_collection (collection_iterator iter, bool with_files) // NOTE: this is kind of inefficient, but in order to maintain the const iterator semantics this approach is required for (collections_type::iterator i = m_collections.begin (); i != m_collections.end (); ++i) { if (i == iter) { - if (with_files && !tl::rm_dir_recursive (tl::to_qstring (path ()))) { + if (with_files && !tl::rm_dir_recursive (tl::to_qstring (i->path ()))) { return false; } m_collections.erase (i); @@ -98,7 +98,7 @@ SaltGrains::remove_grain (grain_iterator iter, bool with_files) // NOTE: this is kind of inefficient, but in order to maintain the const iterator semantics this approach is required for (grains_type::iterator i = m_grains.begin (); i != m_grains.end (); ++i) { if (i == iter) { - if (with_files && !tl::rm_dir_recursive (tl::to_qstring (path ()))) { + if (with_files && !tl::rm_dir_recursive (tl::to_qstring (i->path ()))) { return false; } m_grains.erase (i); diff --git a/src/lay/laySaltGrains.h b/src/lay/laySaltGrains.h index f47b897fb..0387000a1 100644 --- a/src/lay/laySaltGrains.h +++ b/src/lay/laySaltGrains.h @@ -23,6 +23,7 @@ #ifndef HDR_laySaltGrains #define HDR_laySaltGrains +#include "layCommon.h" #include "laySaltGrain.h" #include diff --git a/src/unit_tests/laySaltGrain.cc b/src/unit_tests/laySalt.cc similarity index 60% rename from src/unit_tests/laySaltGrain.cc rename to src/unit_tests/laySalt.cc index fabfab2fd..2d761ed62 100644 --- a/src/unit_tests/laySaltGrain.cc +++ b/src/unit_tests/laySalt.cc @@ -23,10 +23,52 @@ #include "laySaltGrain.h" #include "laySaltGrains.h" +#include "laySalt.h" #include "tlFileUtils.h" #include "utHead.h" #include +#include + +static std::string grains_to_string (const lay::SaltGrains &gg) +{ + std::string res; + res += "["; + bool first = true; + for (lay::SaltGrains::grain_iterator g = gg.begin_grains (); g != gg.end_grains (); ++g) { + if (! first) { + res += ","; + } + first = false; + res += g->name (); + } + for (lay::SaltGrains::collection_iterator gc = gg.begin_collections (); gc != gg.end_collections (); ++gc) { + if (! first) { + res += ","; + } + first = false; + res += gc->name (); + res += grains_to_string (*gc); + } + res += "]"; + return res; +} + +static std::string salt_to_string (lay::Salt &salt) +{ + std::string res; + res += "["; + bool first = true; + for (lay::Salt::flat_iterator i = salt.begin_flat (); i != salt.end_flat (); ++i) { + if (! first) { + res += ","; + } + first = false; + res += (*i)->name (); + } + res += "]"; + return res; +} TEST (1) { @@ -111,30 +153,6 @@ TEST (2) } -static std::string grains_to_string (const lay::SaltGrains &gg) -{ - std::string res; - res += "["; - bool first = true; - for (lay::SaltGrains::grain_iterator g = gg.begin_grains (); g != gg.end_grains (); ++g) { - if (! first) { - res += ","; - } - first = false; - res += g->name (); - } - for (lay::SaltGrains::collection_iterator gc = gg.begin_collections (); gc != gg.end_collections (); ++gc) { - if (! first) { - res += ","; - } - first = false; - res += gc->name (); - res += grains_to_string (*gc); - } - res += "]"; - return res; -} - TEST (3) { const QString grain_spec_file = QString::fromUtf8 ("grain.xml"); @@ -184,4 +202,107 @@ TEST (3) EXPECT_EQ (gg.is_empty (), false); EXPECT_EQ (grains_to_string (gg), "[a,b,c[c/u,c/c[c/c/v]]]"); EXPECT_EQ (gg.begin_collections ()->path (), tl::to_string (dir_c.absolutePath ())); + + gg.remove_grain (gg.begin_grains (), false); + EXPECT_EQ (grains_to_string (gg), "[b,c[c/u,c/c[c/c/v]]]"); + + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (grains_to_string (gg), "[a,b,c[c/u,c/c[c/c/v]]]"); + gg.remove_grain (gg.begin_grains (), true); + + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (grains_to_string (gg), "[b,c[c/u,c/c[c/c/v]]]"); + + gg.remove_collection (gg.begin_collections (), false); + EXPECT_EQ (grains_to_string (gg), "[b]"); + + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (grains_to_string (gg), "[b,c[c/u,c/c[c/c/v]]]"); + + gg.remove_collection (gg.begin_collections (), true); + EXPECT_EQ (grains_to_string (gg), "[b]"); + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (grains_to_string (gg), "[b]"); +} + +TEST (4) +{ + const QString grain_spec_file = QString::fromUtf8 ("grain.xml"); + + // That's just preparation ... + + lay::SaltGrain g; + g.set_name ("x"); + + QDir tmp_dir (QFileInfo (tl::to_qstring (tmp_file ())).absolutePath ()); + QDir dir_a (tmp_dir.filePath (QString::fromUtf8 ("a"))); + QDir dir_b (tmp_dir.filePath (QString::fromUtf8 ("b"))); + QDir dir_c (tmp_dir.filePath (QString::fromUtf8 ("c"))); + QDir dir_cu (dir_c.filePath (QString::fromUtf8 ("u"))); + QDir dir_cc (dir_c.filePath (QString::fromUtf8 ("c"))); + QDir dir_ccv (dir_cc.filePath (QString::fromUtf8 ("v"))); + + tl_assert (tl::rm_dir_recursive (tmp_dir.path ())); + + lay::SaltGrains gg; + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (gg.is_empty (), true); + EXPECT_EQ (grains_to_string (gg), "[]"); + + tmp_dir.mkdir (dir_a.dirName ()); + tmp_dir.mkdir (dir_b.dirName ()); + tmp_dir.mkdir (dir_c.dirName ()); + dir_c.mkdir (dir_cu.dirName ()); + dir_c.mkdir (dir_cc.dirName ()); + dir_cc.mkdir (dir_ccv.dirName ()); + + gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (gg.is_empty (), true); + EXPECT_EQ (grains_to_string (gg), "[]"); + EXPECT_EQ (gg.path (), tl::to_string (tmp_dir.path ())); + + g.save (tl::to_string (dir_a.absoluteFilePath (grain_spec_file))); + g.save (tl::to_string (dir_b.absoluteFilePath (grain_spec_file))); + g.save (tl::to_string (dir_cu.absoluteFilePath (grain_spec_file))); + g.save (tl::to_string (dir_ccv.absoluteFilePath (grain_spec_file))); + + // That's the main test part + + lay::Salt salt; + 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 (spy.count (), 1); + EXPECT_EQ (salt_to_string (salt), "[a,b,c/c/v,c/u]"); + + spy.clear (); + salt.add_location (tl::to_string (tmp_dir.path ())); + EXPECT_EQ (spy.count (), 0); + EXPECT_EQ (salt_to_string (salt), "[a,b,c/c/v,c/u]"); + + spy.clear (); + salt.add_location (tl::to_string (dir_c.path ())); + EXPECT_EQ (spy.count (), 1); + EXPECT_EQ (salt_to_string (salt), "[a,b,c/c/v,c/u,c/v,u]"); + + lay::Salt salt_copy = salt; + (const_cast (*salt_copy.begin ())).remove_grain (salt_copy.begin ()->begin_grains (), true); + + spy.clear (); + salt.refresh (); + EXPECT_EQ (spy.count (), 1); + EXPECT_EQ (salt_to_string (salt), "[b,c/c/v,c/u,c/v,u]"); + + spy.clear (); + salt.remove_location (tl::to_string (dir_c.path ())); + EXPECT_EQ (spy.count (), 1); + EXPECT_EQ (salt_to_string (salt), "[b,c/c/v,c/u]"); + + spy.clear (); + // location already removed + salt.remove_location (tl::to_string (dir_c.path ())); + EXPECT_EQ (spy.count (), 0); + EXPECT_EQ (salt_to_string (salt), "[b,c/c/v,c/u]"); } diff --git a/src/unit_tests/unit_tests.pro b/src/unit_tests/unit_tests.pro index aba6c9daf..547d7616c 100644 --- a/src/unit_tests/unit_tests.pro +++ b/src/unit_tests/unit_tests.pro @@ -96,7 +96,7 @@ SOURCES = \ tlXMLParser.cc \ gsiTest.cc \ tlFileSystemWatcher.cc \ - laySaltGrain.cc + laySalt.cc # main components: SOURCES += \ From b8238a85f96046924d110b70e2647a38496d4f7e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 18 Mar 2017 00:22:45 +0100 Subject: [PATCH 07/57] WIP: first steps towards package browser --- src/lay/SaltManagerDialog.ui | 199 ++++++++++++++++++--------- src/lay/laySaltManagerDialog.cc | 151 ++++++++++++++++++++ src/laybasic/rdbMarkerBrowserPage.cc | 26 +--- src/tl/tlString.cc | 26 ++++ src/tl/tlString.h | 12 ++ src/unit_tests/tlString.cc | 17 +++ 6 files changed, 346 insertions(+), 85 deletions(-) diff --git a/src/lay/SaltManagerDialog.ui b/src/lay/SaltManagerDialog.ui index 0a97e8cda..4ad0a109b 100644 --- a/src/lay/SaltManagerDialog.ui +++ b/src/lay/SaltManagerDialog.ui @@ -21,6 +21,9 @@ + + 4 + @@ -56,6 +59,9 @@ Qt::NoFocus + + Install Package + ... @@ -76,7 +82,7 @@ 50 - 20 + 0 @@ -86,6 +92,9 @@ Qt::NoFocus + + Create New Package + ... @@ -103,6 +112,9 @@ Qt::NoFocus + + Uninstall Package + ... @@ -115,47 +127,13 @@ - - - - Qt::NoFocus - - - ... - - - - :/new_folder.png:/new_folder.png - - - true - - - - - - - Qt::NoFocus - - - ... - - - - :/rename.png:/rename.png - - - true - - - - + 0 0 @@ -178,15 +156,13 @@ 0 - - - false - - - - 1 - - + + + true + + + true + @@ -214,23 +190,69 @@ QFrame::Raised + + 0 + + + 0 + + + 0 + + + 0 + - - - <html><body><center>No packages are installed currently.<br><br>You can use:<br> + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 218 + 138 + + + + + + + + 0 + 0 + + + + + 200 + 0 + + + + <html><body><center>No packages are installed currently.<br><br>You can use:<br> </center> <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><a href=":add"><img src=":/add.png"></a></td><td>to create a new package</td></tr> </table> </body></html> - - - Qt::AlignHCenter|Qt::AlignTop - - - true - + + + Qt::AlignHCenter|Qt::AlignTop + + + true + + + + + @@ -250,13 +272,25 @@ - QFrame::StyledPanel + QFrame::NoFrame QFrame::Raised - - + + + 4 + + + 0 + + + 0 + + + 0 + + @@ -269,13 +303,37 @@ - + + + + Edit Package Details + + + Edit + + + + :/edit.png:/edit.png + + + true + + + + + + + + QDialogButtonBox::Close + + + @@ -336,16 +394,31 @@ - treeWidget + salt_view toolButton_4 toolButton_2 toolButton_3 - toolButton - toolButton_5 textBrowser - + + + buttonBox + clicked(QAbstractButton*) + SaltManagerDialog + accept() + + + 635 + 421 + + + 653 + 432 + + + + diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index f63a236cc..5876a1c7a 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -21,15 +21,166 @@ */ #include "laySaltManagerDialog.h" +#include "laySalt.h" +#include "tlString.h" + +#include +#include +#include +#include +#include +#include namespace lay { +class SaltModel + : public QAbstractItemModel +{ +public: + SaltModel (QObject *parent, lay::Salt *salt) + : QAbstractItemModel (parent), mp_salt (salt) + { + // .. nothing yet .. + } + + QVariant data (const QModelIndex &index, int role) const + { + if (role == Qt::DisplayRole) { + + const lay::SaltGrain *g = mp_salt->begin_flat ()[index.row ()]; + + std::string text = ""; + text += "

"; + text += tl::escaped_to_html (g->name ()); + if (!g->version ().empty ()) { + text += " "; + text += tl::escaped_to_html (g->version ()); + } + if (!g->title ().empty ()) { + text += " - "; + text += tl::escaped_to_html (g->title ()); + } + text += "

"; + if (!g->doc ().empty ()) { + text += "

"; + text += tl::escaped_to_html (g->doc ()); + text += "

"; + } + text += ""; + + return tl::to_qstring (text); + + } else { + return QVariant (); + } + } + + QModelIndex index (int row, int column, const QModelIndex &parent) const + { + if (parent.isValid ()) { + return QModelIndex (); + } else { + return createIndex (row, column); + } + } + + QModelIndex parent (const QModelIndex & /*index*/) const + { + return QModelIndex (); + } + + int columnCount(const QModelIndex & /*parent*/) const + { + return 1; + } + + int rowCount (const QModelIndex &parent) const + { + if (parent.isValid ()) { + return 0; + } else { + return mp_salt->end_flat () - mp_salt->begin_flat (); + } + } + +public: + lay::Salt *mp_salt; +}; + +class SaltItemDelegate + : public QStyledItemDelegate +{ +public: + SaltItemDelegate (QObject *parent) + : QStyledItemDelegate (parent) + { + // .. nothing yet .. + } + + void paint (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + QStyleOptionViewItemV4 optionV4 = option; + initStyleOption (&optionV4, index); + + QStyle *style = optionV4.widget ? optionV4.widget->style () : QApplication::style (); + + QTextDocument doc; + doc.setHtml (optionV4.text); + + optionV4.text = QString (); + style->drawControl (QStyle::CE_ItemViewItem, &optionV4, painter); + + QAbstractTextDocumentLayout::PaintContext ctx; + + if (optionV4.state & QStyle::State_Selected) { + ctx.palette.setColor (QPalette::Text, optionV4.palette.color (QPalette::Active, QPalette::HighlightedText)); + } + + QRect textRect = style->subElementRect (QStyle::SE_ItemViewItemText, &optionV4); + painter->save (); + painter->translate (textRect.topLeft ()); + painter->setClipRect (textRect.translated (-textRect.topLeft ())); + doc.documentLayout()->draw (painter, ctx); + painter->restore (); + } + + QSize sizeHint (const QStyleOptionViewItem &option, const QModelIndex &index) const + { + const int textWidth = 500; + + QStyleOptionViewItemV4 optionV4 = option; + initStyleOption (&optionV4, index); + + QTextDocument doc; + doc.setHtml (optionV4.text); + doc.setTextWidth (textWidth); + return QSize (textWidth, doc.size ().height ()); + } +}; + +// @@@ +lay::Salt salt; +static bool salt_initialized = false; +void make_salt () +{ + if (!salt_initialized) { + salt_initialized = true; + salt.add_location (tl::to_string (QDir::homePath () + QString::fromUtf8("/.klayout/salt"))); + } +} +// @@@ + SaltManagerDialog::SaltManagerDialog (QWidget *parent) : QDialog (parent) { Ui::SaltManagerDialog::setupUi (this); + salt = lay::Salt (); salt_initialized = false; // @@@ + make_salt (); // @@@ + salt_view->setModel (new SaltModel (this, &salt)); + salt_view->setItemDelegate (new SaltItemDelegate (this)); + // ... } diff --git a/src/laybasic/rdbMarkerBrowserPage.cc b/src/laybasic/rdbMarkerBrowserPage.cc index e31087072..f14e57338 100644 --- a/src/laybasic/rdbMarkerBrowserPage.cc +++ b/src/laybasic/rdbMarkerBrowserPage.cc @@ -1820,24 +1820,6 @@ MarkerBrowserPage::set_max_marker_count (size_t max_marker_count) } } -static void -escape_to_html (std::string &out, const std::string &in) -{ - for (const char *cp = in.c_str (); *cp; ++cp) { - if (*cp == '<') { - out += "<"; - } else if (*cp == '>') { - out += ">"; - } else if (*cp == '&') { - out += "&"; - } else if (*cp == '\n') { - out += "
"; - } else { - out += *cp; - } - } -} - void MarkerBrowserPage::enable_updates (bool f) { @@ -1955,13 +1937,13 @@ MarkerBrowserPage::update_info_text () if (category && n_category == 1 && ! category->description ().empty ()) { info += "

"; - escape_to_html (info, category->description ()); + tl::escape_to_html (info, category->description ()); info += "

"; } if (! m_error_text.empty ()) { info += "

"; - escape_to_html (info, m_error_text); + tl::escape_to_html (info, m_error_text); info += "

"; } @@ -1978,7 +1960,7 @@ MarkerBrowserPage::update_info_text () if (v->tag_id () != 0) { const rdb::Tag &tag = mp_database->tags ().tag (v->tag_id ()); info += ""; - escape_to_html (info, tag.name ()); + tl::escape_to_html (info, tag.name ()); info += ":
"; } @@ -1989,7 +1971,7 @@ MarkerBrowserPage::update_info_text () value_string = std::string (value_string.begin (), value_string.begin () + max_length) + "..."; } - escape_to_html (info, value_string); + tl::escape_to_html (info, value_string); info += "
"; diff --git a/src/tl/tlString.cc b/src/tl/tlString.cc index 15aa9d5ef..22d05402f 100644 --- a/src/tl/tlString.cc +++ b/src/tl/tlString.cc @@ -413,6 +413,32 @@ tl::to_word_or_quoted_string (const std::string &s, const char *non_term) } } +void +tl::escape_to_html (std::string &out, const std::string &in, bool replace_newlines) +{ + for (const char *cp = in.c_str (); *cp; ++cp) { + if (*cp == '<') { + out += "<"; + } else if (*cp == '>') { + out += ">"; + } else if (*cp == '&') { + out += "&"; + } else if (replace_newlines && *cp == '\n') { + out += "
"; + } else { + out += *cp; + } + } +} + +std::string +tl::escaped_to_html (const std::string &in, bool replace_newlines) +{ + std::string s; + escape_to_html (s, in, replace_newlines); + return s; +} + void tl::from_string (const std::string &s, const char * &result) { diff --git a/src/tl/tlString.h b/src/tl/tlString.h index 5c9aaf630..6840a7fba 100644 --- a/src/tl/tlString.h +++ b/src/tl/tlString.h @@ -326,6 +326,18 @@ TL_PUBLIC int edit_distance (const std::string &a, const std::string &b); */ TL_PUBLIC std::string to_word_or_quoted_string (const std::string &s, const char *non_term = "_.$"); +/** + * @brief Escapes HTML (or XML) characters from in and adds the result to out + * If "replace_newlines" is true, "\n" will be replaced by "
". + */ +TL_PUBLIC void escape_to_html (std::string &out, const std::string &in, bool replace_newlines = true); + +/** + * @brief Escapes HTML (or XML) characters from in and returns the resulting string + * If "replace_newlines" is true, "\n" will be replaced by "
". + */ +TL_PUBLIC std::string escaped_to_html (const std::string &in, bool replace_newlines = true); + /** * @brief Set the number of digits resolution for a micron display */ diff --git a/src/unit_tests/tlString.cc b/src/unit_tests/tlString.cc index 72a910cda..c4a111a22 100644 --- a/src/unit_tests/tlString.cc +++ b/src/unit_tests/tlString.cc @@ -423,3 +423,20 @@ TEST(10) EXPECT_EQ (unescape_string (escape_string ("'a\n\003")), "'a\n\003"); } +TEST(11) +{ + std::string s; + tl::escape_to_html (s, "x"); + EXPECT_EQ (s, "x"); + tl::escape_to_html (s, "<&>"); + EXPECT_EQ (s, "x<&>"); + s = std::string (); + tl::escape_to_html (s, "a\nb"); + EXPECT_EQ (s, "a
b"); + s = std::string (); + tl::escape_to_html (s, "a\nb", false); + EXPECT_EQ (s, "a\nb"); + EXPECT_EQ (tl::escaped_to_html ("x<&>"), "x<&>"); + EXPECT_EQ (tl::escaped_to_html ("a\nb"), "a
b"); + EXPECT_EQ (tl::escaped_to_html ("a\nb", false), "a\nb"); +} From 3a6e4982c859d4e7be7016db464b1de2f0e17236 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 18 Mar 2017 22:36:33 +0100 Subject: [PATCH 08/57] WIP: more functionality for Salt Package Manager --- src/lay/SaltManagerDialog.ui | 151 ++++++++++++++++------- src/lay/images/salt.png | Bin 0 -> 16551 bytes src/lay/images/salt_icon.png | Bin 0 -> 3679 bytes src/lay/lay.pro | 6 +- src/lay/layResources.qrc | 4 +- src/lay/laySalt.h | 8 ++ src/lay/laySaltGrain.cc | 65 +++++++++- src/lay/laySaltGrain.h | 71 +++++++++++ src/lay/laySaltGrainDetailsTextWidget.cc | 148 ++++++++++++++++++++++ src/lay/laySaltGrainDetailsTextWidget.h | 61 +++++++++ src/lay/laySaltGrains.cc | 10 +- src/lay/laySaltManagerDialog.cc | 113 ++++++++++++++++- src/lay/laySaltManagerDialog.h | 19 +++ src/unit_tests/laySalt.cc | 32 ++++- 14 files changed, 626 insertions(+), 62 deletions(-) create mode 100644 src/lay/images/salt.png create mode 100644 src/lay/images/salt_icon.png create mode 100644 src/lay/laySaltGrainDetailsTextWidget.cc create mode 100644 src/lay/laySaltGrainDetailsTextWidget.h 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 0000000000000000000000000000000000000000..181409a285a589be0c54877ee2ee28d051f75d4f GIT binary patch literal 16551 zcmY+M2{_d47x%}$FUc}l$C9lnvS%mzmNmvsmPYn{E3zfBX9^*EWC__xwvc2Cl@JmV zLI}ls=J{Xmb-nNNOjDO;e!t&+-{(H(d(QdX-8VGQq@iS`L?93}+FEMH@cRJ%7da`s zS6Ni&!EdCFx|(W;Q~du=JIXTPCluaV7(WE!yafIifk&CxNBAL`zqYgZ?KM#uW+*~tR~S&Cd@236(PeW-4M0ey<6ykEPP zw+V@hUL(~Otmf^le-P$kb|_lavc!FE%Zh&`XJ^nvlJby?`BI@`dcVl683Y{}AI!YU zS9%td-xOqSW**d<;lFoM)c}8-4%nLCJ=q+g`m>lQ^6LM6)5VMiolwdV^$q5;!Et{O-&+aA?Sm!H2!SV~?}Zql~>jgzPn2qeo}y`cdPpnvOtfM5FtssDSmULzZr`f%_Ty zIZ_T2rTU0SyT6Mpx0uuqUkWBnClod(tb};8ld&?g_v<4_Z(C8YXDU0+?8jL3``BY} z$B#W`Fhg}GJw8wR4}N~y{qrV?E!Xa3=h2^TmRDcxM7dD?nkU;_r)TE>ypjG@#P#o) ziP>Z)6)~Y5&bdX1o;-3%h6O31+)mabn#xBJg+d^xd%W)=5a=Nudh+JCcEXwMQ$>po z7~H|hZk&Vs@A7N6e+ZqvJw#l&#Ju$CcvJ4G(ZhR;>MhD-%I%qcF1)55JWXbMy&u+W->^0>H34ee8ubXXK8m_J@d>2%s zyQyf8e0oHEvrBJp9j(`#-icZT-f*oP0q+UfM<#Ha+GsY*<=_b z3Z{@ntE65vVl*eXAAmYoyNYuzHWpGVGJ@Op`@HsHS2K#?(?%dBBxYq|dU@nWq8*UP zxXWE6VcUHy+dD%dJreqrn0nGj*1i-l#Z{!YAD6CKo|%wv$mXYvCG>6i!nN6PFqgR1 zvckEtKjU>E@`5fkYbZBp?(m|S*;ZNC85nQ^k4!Sr$nM$KDnu@)C;$GO9CU68|Cuzm zOQKuwGtA3vYX$3mx%1OD7B@f<*$tVzr^UF>%_$WkcU>&0pEPD zeDiH77#kbwt3$KR|NSogkILq1@F}Ay4z6U~Bl_;2S{62OL4SHYBBLL7(JJnF%_~+S zBBFSWqUOIlLj?BenFVx?Dq3*~?9@H4>(GCHn0Knm=Ejv`$zxRqmVGb9Ti{}sWHMfn z{X_1_$*LBNTCqucT5s54xlrN^{XTcr_4^G(JM#M5BdDSx**u#G3Iz?Gg^g}9Rt>yl zDv0(u3dt^2p(lQUPBbnzmDHD?oL`=A`q4K3Z*%4JWb5?g^pE)r*}%(hr8%BQF&;DQ zxlD!iSDi8i$c)_T947)OdqiLRd?B{Cz^PxTIJW#86>Njv`9$6G_20p~reeOPon1cL zOt4AU|mHV&O3h`{vM7*koNlsGAF+9U|Y$LI4NE% zkWO5-*&8$x&ivKGbwV%F@tYx`kLP-&#of|U{?%5+a{k4R$pDTLV~Re{5=M zn#TOT?6fc8yTi1DGUXMNum_Sfz;p^KgiXyz%AaQF;iToxYctSt$i}!!C8{Sfb91J& zsfds4k`oi!W2VgPy{N@yWa9Psqc=A_f5|Hqpe$&pA0B^Gye^!XI5*=Ao+{3Tu3vnRlG4^dg24%Vkcl_;WhWb|8~yw_ zVShgmrb)HnYt)+3$&%79Z~~~D;lk(7sq9XVrpIPyH}34i`fOQp=}&lXbsRV@zs;P$ zO5Sxq!psb;>?oK_^=+W~w z^FnRlhUF8#_g7XoHu`@35{`Sr%SFs+IPJHTrCiPVfmQ^f^I(?82-gge=>poS6t+poPol17; zp8HJWa1-iPqNKv77Y;of7cX8sZG&sJSkuY{{*3gPky(Cd;=Pn4UD(vbf)EuIjf5D) zK;5H^a|SmAucnP6FFtN+j=GU-OgveFB|u`v?a%>fcc^&{)_JOuQYPVo>WFVmRc0X)PEPLp{8&rm_3DtHl$4a;Y5Mc$&o6?v9S7>DwbSiq zq+mhQwD>#+T@LO#{;z4q9Yr{QdRAg6|%FnqDHv9!NG&E9mO1_xc zOh`1f>Kk|2dXG-^%9*MsJRla#w4BkO=mmor0}IWS3v#r#N8F?3=zewkm>-h`*J*5& zdfC*}wkuFvp(G_0=PygsBl?E$Ze&F5Z7Q)1#;wWi;J>HN7-lg4jg5`wowpZ!-kfu# zXQ8g?Jdthmnol+rj`f$_Xcc=&rOr4tW1!0APj~%1m9bERZq%Kcfw~8Vf-zcrgcw{` z9hz80SriFz1k8|^mshK7;^wVeJp%&+;s{J2-{+v?Asy^DQ`W0RAy!Scykd{N12wk|A%R6+-P(d}+268~mza6Me6iwrbvM`PAI+ch zom_X75e0?=Gzr?Wji$mX(|Z&`7%#g=EXZE7-N3UM$b0gye3y05cCXlna2InB&#vC1 zU08l(_a;vv43;fbkADoTFfWgDI=0>;>?3%dL`jbph(biquU82N`ujJlE6y}t788pK zn!n8g+pemrDqfApQ^(ARF}`p$Dw5sbEJv|jKQ^;cv&0jG&l$?tUkh{*$e~9fLHTxq zjQBly`*`1BBu@dRT#JtZjOwPBSO0j4?(E}uXACtRT~A%55@GZ9IykD=(R7OB5s`1n zwDb?hQtregu}LqQcOID?42g7xKW%Br+WPUMJC24;R8cWGnvBkI<$W%alk?47N7!W< z8S=c9u`gfnOYSZ60A?Egpp5!jeZ*I!)_>MNE`gMSBV`o9J*CE|ZtdMg0*`~_;_4q5 zAgAS&hcVq%Er|4&O>z>Wz~J!91^&Cme$IGiwnbjAYQSr=J}GZ!IvEAMA~pMtB7LR)Y1?O8-W>R)=moS2;Fj1pAVQGle%}5XG4SGRm9%p1V}rAwGQX zM4YTd$Ud?O%&K8jR6dC~{4{bW(14HO@NB;iej7f;vdYTIU4}*4-uCHT@(KUb!}Uf} z>B#6=#gC)I5ru;%Gv&|MWofw-AK>qklERy*Q(!9Gvb&~cbpQF)YYgM40`cp`lDWlc zlKMpbK9>%!@-e7rYLZ2Dp+Fy)o3pgGwc#Jx0!@l@uJxFaVx%O&$A*kS`@J8Nf|)u< zR@NxN1c(X>34MimIwgvs{TxBuK@bHlF{YGvZ z@h<@MA-pOr8z@)u!m4L{cRrY0b8vTeH~y)T3?UZA14{YZZb;CjB6wgSs|g947o)wj z@*^-qFuJ;@sqYwZb}&) z9sL45v|(_ak)+F>K30{W-1Ns3_~WG&MP=q@`9EKy?cChljv~OxTzceZ(f~#RJ73Mp zDrctT_~dx&2G~E?SbU!&s62~0r~A5uD;_+R5=mOpB2yXP_ej@>)xbA*t(E0(w%&eC zc9H-1V5@&~wry;FJ_SZoTYDLzQi<*(c&cWVqp>1Q_zOCmvkhWg^UA>Kj!n?)-UvL( z)9@v>dF5NT*4NUszKQ7T>u0#|zj^Zp6#dwzPxou?>^+Aqhz}Wo8=qr4|1OKjcz;De z*asa8VrKSZG~=3<-2EW;N3`Q>UC+v+{C&ZUA&&z8IW5M1#g`Z!OH(*g_0prOVp(dn z7HYR{T?h>ev!NAPoJ&ehcJ%h9*Gy*55}j8~wihLja}Wj3p~h3Ifm;sh+^h0BL||_2 zM05SMH-|^0?51Dj5Yz!j?CJRieB^dh#BtM2ESBJx3d}#()h&L@Da%;GphlR%zwa z_Ga%Nw%?d-W4WvRlt*SD^5e&kNDWQRpxLmkjxtC>?heA-1?c$s?dDBQU61KmNI-D= zF~ZEjLdhI_@$(r|XK7J6TIjjA{dac+OsPwh8ve5flY~XqDlrEAbhZvtJfIm`-){4& zH$C{cX5az&pfwXEiFDviH#b#XT`KS%pb}v#*i4JECPg`82UBQ_L%@xW4b41Y96$L&Gal*xz$mh(Sos$Lg)1Jtr~~P3NxAG zpY^r=YsTRVRCceYs;ux{+Ia})FjlC3R*OMP<3i4Gt4;+r+X{1F{5KUDEg{FyYK*}_ z1~l8z>Sn;i`t)Qx2{JMef?Mr02n$8E>D(GFN zrw|2cw8hJtMrLnOzN5@Bfi1#J)aT;5&bdChD|u@Q1r|M`Ut;W;YT&Za-`_vW!$U(n z-l^^;J}320jb|Zwg_)AXr{Kq66p0p3TJTIAI{b%8ez&ej$G1`w?0mk%V~Q^E@_0I0 z+QQCGt~&IMt+@`*Aqt8Q_vOx$ck>a(x>ezgrIjPk;w9daCG`0+w10l8Cxboxu`@zx zd`-f)<%yi9m2js0%s#Pw>f@N^JKvt(DAD6z3KSB^(B{h++_*zshgcTRKh!HMD-+o7 z*t$)pbb2^)CZ!+p#tvTtnfOz?K)I*utya_a80H0UWX$30^T?R2+~g}29)(Ni@ABSl zEybRV@;5dcS=k+M62r^w7Zeeq!>x-KDMRTnxHN;Z%bbN**ufLYGn^3PHlRzm;E^KWL+W!#E;?|77}=Yt!^g0BSync|xcv8!^jz=_ z6(Xmuk?6l)$&?1wpaYQC{}6LKvLV7XuqY+Pd_NIl-f+ zD~j$jffpY%30jng+E#6JSiatL_Fy3{C|oWgdpSo=--#Dt*8vW-HYS!Ja2taLIW76G2;aq0c) z>P!9s0r*DzCRp8$%9o8kV~FMcC^c==Ol)5biabSgg=zN>#TI}V3qhMh$mts;#> zMLtzo6|JZ1Q^_Oz>0t#ehKU zni6;mh$o9IoqtGhmuw(F$Ke$fY#y$+%f2%;ZJV-OC@THD+xwsHy;nN@Ta%_|D2nu# zNxlI=#BSku{!|ekSSVxh+2_dZO}{@sU6m3+(6{e@z7@P))%3!3gtMt5W;4Eo$gAJS z`i0(P#3)RiDO(-fu$edz-esay{ zKwtZM1%X`Tq=<^_=ZauYk>4So3t8a^N=X4hh|lX*a)UUp*Vt~9Vl*{0UUp1rb>Rv3;g7$fG?!H>1 zh;2LauJY?NzWWZCK-DkyNYdcE{%V`HUmtIzEJ$}A(ioRNJXS2OxJXW@T0kEf8u}Fb zna_JvZY+IrzDqwc|A^#`%5G+|Dd zH?0Bdzt*lruwJ-c3HkC_;k9qambhh6>K=RoH|VtU&ic~9zN&vcb0x_q_-x9fi&lYQ z@|;18>JC~=xq?hI3=CJ}3*$FtnwC0G);oVe5WE4?z%)OzP8+gBEE4i0C{e0aORH%p z%*sKyPcqYKZM>xUYZR3?1kQyA;_tn?G@_&4hf_o;C#1gr^GBvW>|pa=b?c7`k~No$ z7LoG1@5Hm}M-=6r2~zS7?Zlj>bqUmnjW;cuF+)u(7+>LgCqD8O>UUQQ3}p&Bb&VJ+ zZ97<5?7}aDS%&|Thh_bCefs&`C<__Ow^)yry=l7&Zg4q(1&d3TQ1{Gi6RZy6LM z-Tw7y$#}vBiGi0_&5ao;aZyo(b~>a$+mHY*yuM4ciPyn+<@ac4`0W85X*6VrSK)g& zG$sn9ygdxeqQQ;Hu@)(zkN>&*%$sBmIk%{&ch5}EinX0Uxiv3rZ_>Q;)u_L-3NvNU z*4*v;rP$T==N;<#(z!a040V@XMUiSKddL`KoiUfF-eO#HW9#3`4tZFWEzxu*<_x>c zo(M6Bq*~nV6)Fx&_#LcXWCR6*YuPLXHqXNqw;Ic1RJr{F1G_~ZXg@`H2$^W3=uwfK z?2&Y-u_zG89vvMW_&K|5R`U3|FDsujr**%CKL4X3ZX~tAfJ{hG+xg6Z@zW1;bGDK$ z&(51*xEV&kB%5b=K`e_>FTG)DsB^ImfkAZnutRmQU-d&KNmI`UC>@PtmxfHZDjk0^ z$r$8=gy84z-@oWa^HwzE#U_s~F{B4a zy?dGJ>6k6p#u);x0X-p+B|ajb5wbfhowC2bfAYPmY0j59{S{hVf0_BUp7VUiNd%`b=K1;MsL4@IGeGK}jd_t7=KBewjE%RdxLolQTtE0!}# zlOSQgdJGlDk#ckP{;v1Aa#fDC@e>AeNkZ2o1N$5=(?%{66{|QyI{<{GQy6S5VwFg2 z_Uiij4Jd{@;gAcIf!w!(jPE^J>#@r9`0K()jm#Dm1hKvJwyIY|MBDeyZY|mJkWWag z2Ufuypb%UMcL4vI?5N{hyzf ze(&vFHo04r?OfpXyy_!Vqgc^=SJlrBglKxKJE)cO@dakTO$U)_;rter%t!P#=F@_q z;0I#V7Ey6+$pv=WM5i2=!eK6<<&CEHxQ+X7>IyQtx1HA*!~~QHPm8D;f6htBKbv<-z?v-kCzYw~j30 z@YGrKae$|`NF>gJEtfQZFy znVwyFhWjyo?Oi_Yy;SMO>>~%_vdveK)?fu#NC8(o)*zXn1DsL%nINQ#Xkfo9^MgIf|5B&pM z#`|ht5G}?z>LGnjN5{tj7+gq?D0`16(iny2sw@Ku25KTGd3RAG{<*QtkX3kBc$5Yckv8;0?G*PnQoRVtKII+$IWMetOV z`YZ`O|5TUH6Rj(6YO!_X3=yN~3pi($bUxzfu4+TUmrvcesVGs0;d!^P*fs7L&zGGW z0rhX!^k>#RA3mR=-R(MijzQ~@bc+KN4;94379Z-AU`S{WIX{%jp0}CSEyX6_oG&9N z5q!x@umdW-HT{z3s?=a!IA2*bxFd*a5$8=P{0$|QT?^%gR1y;U35hEhoLop=zNnh% zu;zhSLa+@BtNij5gR$BZ#1rH4UT9W?`TKtm%G9Cm5%r{suc$z0DHkgxQY&DyU0Auu zU1y|vL?66~KoQ`aiHa@OWRmqR-0rw-o4h15H?I*LZBgdy=OW0NBAwZ_N>n@sExYUL zMoud95Uq-9&5s1jBG0k1X9@!`Q!FIS!;R`Kp$5bdc$6GghHJAm$1kdpfTQ0M$X z#IlgOZaSX^J+W;%pRJi$;mfF?y_&vcV>0E(I(=8YDw?dR&Jg@YAb-JvInD%{iz2@b zzLA=&BwG0DtdPBQ{<=52tEzLWu~Et$+7N}TQykymeT^bLjHrY}FWdm~LYd^AsT(vd zgp?y!vR)v9iTsWA$s)aF-Vs_D!enKBcSe>;QY`}y|PI#!CQCiD0Eej+aV>+yvj*TJQz)5o0mPiV4?G^nZ!zgSYe z>^5n+yiPJ~?b0FG7&>))+<}W>q#9~~W=N7Sd;i6X>*@yy&X`hZ(yZzoea-C^R`v#K zi8zNRI<*g^C)p@tQRJ#yQ0$W7LecOa%tt6)AXmKxyN75*r|XIl<%GnBW&P(&>*8|j zDmUg;H40~Kj+(ILfIh7+YLlwrkrL^U=NK40H0b`KLRMymj^7y8((hN7#r<&-F3J0G zLXFnyL&m)F!Y~Kt-K(fjxO(+!J6$SiI`m4p)3jbB6ru|gV@#Za_)Q;|8Lxgyxb`EU z&y8epf`FAHF+?f4dXN4RbkBM&hx!G=fCy3z1&iO-eNp>US%wYrZP{)3 zL!T`3`hv&#!>^@{4ZLiGu{{oH&Yw8(gQD*7FRu`uGEo;Hipdgw|5)FAJ@^fet#_KN z-ubWNST6)LywE+O8{ka!+-E$gNTb;mv{%gxN7#wiIV@yx?jPLSEbD`9g$cXaBGUY> zSI45k%h-dLX{xCB8gfU~-crASqu=3NoR14Ho0-@1xAzC!LkJd0UIf*NGTe_`YPQ8c zhWy9@DTrxalM&ta2kR5V8v4tHlSGwk*ZW-=Dj%^#YtPpkPbwg(RT=x#n`Q&qR$#H6 zKq!9=nd5j^G3lZjP-oeno|H{-)5Y;hatTc=?f81gD`yOpjtUH)X_}eMk!eK_d>_tf zQAUlbTfQxG6BjCz+$cYK;YQLRo^;K?kb2Qn2Rp&>wis(fPb`=)E-oSQ%_>zREnIP= zyyElKgcy?+i{*XKFyRZA%ej6+C;5LYfQk9K69L(Vtl5#lTo%hrqjs$*dtzTUtbP3B z(m@i!yV_>ILrW=K)01SckFwXiV^%<#d7vKL;Vdt`r^W5Y25OV1r`T&!iG?;y_{Q!Wh?+7K>V zJ>`F4d5?72Ou5_idGzRk5JL-z2^B^VI|rV?!coPo#rz@9i@jDYzYVwH%)MuOHW&VJ zy4BIcq#wZ*#{uc&xts$PTpv>=nL0BdR;u>0PO|G8?!RPmM$qOd}kPutS}c%#V= z%s^Qt*{ZnWM+}WyDdl*_L=0TFpEdspD)L2xo8UKGb6Gl{irM6*z32d71S_hfCFIKF zS2ZuzqKEjLd?o_YTv+qe;YbZBofLMLOa&f2E(x*h$MUs zgzpwHtj3d2#Huc;K8ux*R~~rEII=5wG2S({N&ES+yGxP5H)<^fOr?%ZduBqcGftgW zIfpJaiKgNJ<*`oR>rDU*{G0*Y6D zsdbg8S3(~elm2>Tq^hM9DUxgfl}Y#g)W;2nNOIgbV@L;8%VQ-Zl=-w1hUzMVZ=o6< z)`o9yEUAcx@@TPE7(JLG^go}B#yL~;3^H(`c1_)9HjT|P%PwkF{WSaZn4c9UWE{L?YPrZ7BHQ6a30FuoT0Q19%N8%q8CN6WK$R+ckz8)V{evD^nF~YCyX$O}Rlg@&pLAH&5pcQRzBuH2by}5`83mgb5%b=D z(l`Gtb4-q>lv}Grk;z{4u*8sA4V8JpY)qAgwM{wgqD*p|ofhx^{mN3awg>-Q=6I1H z|7^VEquKWU9}FCcd(VhTTRTcEIbs@LviD2=wbr^y7{mF{5!2}TzwPjHLxpz~ri)dG z`8wgfAQg<3A2$sZ-s6)J-sMkT;{WY1d8W8+BF4nIsFW%)qaob@A!fD#mA<#NmOXIu zsXm@)<2hC>j)cWc&#=Pg8Uy0gcJ!{vzv~s*w0SGA|D} z;M7GaT6qZmHg!;_5^(A=Vk{|G!4Dlh6nG(lkQHSz9F>ag*2x+CP{&%&l>aVjvNAM6 z*gLJf+pV*R_5Kq+ZTz%aL_&Dh<)>J0KrLtbj7uyGYKY4|x-?}PK)++nFyX^KJ=%~O zM@+6DD48o9dbsF^9+I6f+CNkiw;bxLa5AfCvHo_LWJ`tBicD1zf}#qmR>vMezfW_h zO>qkt@`dy`w|w<|;o`Qi3rSzcM})|J&{+NVX^s+PT-J4(7`?NcUfE-AUvuE2tjxXT zOwqwM)v=;1881NS2{Hem7 zKNC%!#s4J9{X_U;f^EXGDGk|7W@<+jMs@7RmQ)S>-BvlBG^#61n5&;8qnqfSkKNQL zL+A`6dykbCTXEtT8Vr6gqtV8u2ut&)mRqk-^=I-woV{vl^1kH-50^WQQUFJ98@Ys- z=5?@|rDk=gzzR!cSWd(UM7g}=Or1O%KTTAvGe6Rn`k^9vbS3jA%XmD@I8l6%VL~8D zsc-$;YGha;R>M{I2T6i7ec3wKw=(Olr(pd4t+#E-v+piX z9!M1RKmKC>ypJ?rZ?kiY=|9!EnEnRc_q|cUX=v`wE-9`;q9(Lt{WaHkxibs_N ze=XZMyPKC3WXw{crq_Q=5@rYeZ2Ww; zg;R`|BV)>4Ib;4xO-K}AS&?yF&`4Qo^sxKhW+70Xs#2~~)VNpqGqaSUEOa)!_*_-L z@O7el(-okI0W*LuB_#1sfDiY9bT|s`V<_b{_l|^+AV^c_bJT}&6uY+H6w$L!;DNOb z70o>a=LU6a8up^1{dN7=fonq`%AWYXjfFCjDHlqh0Immu)6;k1CYA9u4q`-5J60n_ zvfgatMDMlP35WZ|MYkiIGX^k&9?>tm?M{OXmyy(WJY^|}fu$Q!H|$yP79Xq_Eq=Gx z%+ly#)e!ah2FdUsUuL9nBZ_HFv!j})-^b952@1WBh9FCW{ytPK-@ku90tLQY{av9G zOL_#f&)?mY`22m?eo}i1fiRnBPf`}Qbd4L@w#?KSBpavB23kS?o)adHPaAUnbr(z{ zchp`Sp_UB$iG@5T3<`QW(DZ$N+%SjjK0Et3mtFQQ$cJqRb5KWh|yGtA}dMvsq;G!qhKCG5W?7Vdw0l|9HzT0a?QT;*nPB9Oqt zsIJ1F0k5poWie`^LO~uzN=dyJb5M&an<#+FNB!G2PH0j*tFUCj5Je_DEb4EwRJ0+f zDU(&q9Vt4{E$dSKQn?d%1$w7i{26B}R*U%^p{=UkmB4g?mB+#h8TqmCw#%(sw+tce zBqWfoXP}JzalR@p=tg%&mW#jC#rp!chfFOQ-%=-t>YrU&TFPjp9r)P^bQv)rbmlby z7<85P{325my%$HIC5rsZ1wDTd;#_}4aq41O#3Q?@eAx7K^Xz2a9%BRC3c+v(~I(Kuo$Q6|h z-2a|Q?q70Emnd>TR(n|sGNul@sIVe$IoDd?mfc#o8my~sl$@&~8CY^xu?U@uc6WGW zPzI!hO;94D`&$@zy(a(PCz5ikZi{oloRiGGG8Rjob;2ljt;O z41dSJ-EG(dmkeEUiVHO4m*}O<=!v1X%);*fPK78Yk{Hm=NM{USo@Kd#pWA7t*a%Tw z_P+{&%9555V0mOM0T6|v;%0Rm=RI5p9r(|GRT zGtzjsF47$ z(B_t1x~3Q;?EUWi@J-3j6HCrnS(ex;K? zH-QNNjMEvjx!*f^{mM`s&?o%G;zLV~?p4_CwMZ8!2zmV2$K6q)d>1zZa!Yp~J0Lwt zPJT~ZV_{Oy3W5QV{%1gDW+DmwOZ3DJ-LJ1z*&9ztIMsimK-HpkzskHXXY$fxk*H^) zdPkytRdOtVqtT-cyM2RzqLzofzo4v)3?QQ2YMMK?M9|ZV0mNo1T@@$&xN?~l4{h#- zG|7CINyd2WJ1BzMA(A+1OMAfB?*kR~-q7q)3IGTqO)0xOQheB@g=o1?hyH8h0g!1? z?=sv@HMJtCtT!QFUtcfYy(@2l3z%X5(ejMC=c%C}DpN;gIsgh3`kCFi)z1Trt-p%V zL)DQ_+mMk2O8046#A7${J!jZvz;FHAEHS>n95ToMI;&!Tfo62A9~##9+VuYlnKWQo ziSsA>O(Zb2DyC)~c*-ujw7^i%9w-AQn@Sy)3}+1ZApHqZDJjytl`FEOSzZ!FFWLJ3 zLut`NhGvr_Pz=-%WQ-~l^z_QCUI%9a1Ti!us%`_Tr;IY3RyA3>TsHtE1a^f`#=FXq zrc+AUgHG3v5LalcubP`rk9o^bCohQt&zP2$Mv>&dcl55cdWO133repSXC|YzmIxlK znf4EQF%T2LbxG6!zN)B5bv{=p2>owqeqatg*R>S$?^Uk@f}K#PY{b}f){;I(d&Nz5 zj~?s;O5thN{XX8Gv1Qs(sGEw)$vp{&4K=qn7EVr1e2f^=2A!a5lF+wU%;Z(^q1XXg ziXu;S&Ki?Q!oLD?Yte}XE<8xfGdD?chbr|NVhB9WlOyAuQ3?78ASj^iYh2C+8o-+S|~fK5!hL@GLg#K1OmsxZpR`T4QF$wEQuy;Ik-laoXgZc>mDRW%Fug zBge&+iA&sBg?uEkM~w`3gBrF36L_EkQ;L01QPGGZfAENQpzU6KP%SE@WC9DMFEFX# z#(^4Izyr%`ej^I^bT75^k_}i7l{~Ud0KD>h5ZAv|O)oDuw*(p*8enPlcLtx|jcz7L zUit+oph^`s8)BNL_t37q7I(Vf`I0BpxraMW-A{H-UELjTo(LJ|pyXR&-2j(DDQ9iI zdjbrw*}t(A=A;aidNz#t%gy~((y*b?;b zXm173Bj{4tS{xOI^=o2ZL$@%YP;fLstvM9CctuuHhn0)M$C7C}{^t3s7b+h;@f2oB zY}P5(Fe2UKLyc0s_OC*A93>XUTVEoZkm%a|8?zY`sv;&Y{{R97bb0{L05IGSn2?lV zX^va*`Yb`4Us?i!gPX1BW0Ha20=UisEhVJ*ltA^Q(SuEXWTtq%Y06F4(l~x=bNN2c zE9u+$jWWHG69KtG2QgQoP?78}+td`x#$ENL6QyQ<$kpQd9Mfp-^++jbY0M4fZ1bjE zNhZ>tco76+!r_rH=s@BdoLHPQSH!n8O>gL693!seL5#ig7ns}dUEsZXR#yd~G!3xU ztW0q>6So>s6zUf-Dznk9$R^iqu|ewhdc4>e0&PVSS8RHt78!rxI-2>`<}I11a*p>mhCDvLScp9=wW7I9xAQIInhP{Y znxIU}twN9IZJfj~<_Xgh`f~K--fCGLG>MbeIJYMala^m^dBkDvd?~PLvCNm8be*nu zz?K}838`!URLKwzUT4pYe5A+k^Xsb$;&q+#(Cc0Kz|Aiq50{z+z885)5-V4Co`l91 z{#gRnKOwPy3H$}XK>4E;HdXwE>#ZsYF1B2Egc(*WFZ}j@*czT+e$XIJ^4hefWcFIR z@Kivq17@nc3^OiDjc+^x%>_+&=wJW~@g4BJ9FG2wU(og(Zl9=%^RDF#`*|7hrwMwe zf9>=q*wgvSdDFD)ot%hsb90#}Mz#I|+0RB!Tx$9!>*b5-ij>hUvucZLiDq+bR>N_L z>O?Uww?K<(;GA=GWK1&wv4QOa84pb1nb+*jn8ufP{vAcXBDvDVL*88zaXd3G3z4Ck ziJP~1^1A7SL9NK--QRJf%Rn>j4!yKUe2W|_xZFs`x z(xZCdsm>Gq84RB+k0TghGPi~d@lLDjNS?ujL<;CskTV4Z1=%V>h>T)vN?%|0eXFaU zSe?Rh##f(DU6$HLUn&)os)=Du&I>|=C>l%|AR1`WfldchWks;xM?bq%;V02I;-QIa zN=bm~2SqRwb05Mv8QIy{S=g9lwCvvXmu-cDwm)Znd6s1nlO^a;qHyL`$B4D1c-_s^ z*eHKym=hf2gvVOY^7}fF#``sd6Nqf^$+43wn{~U$+f)TAQ9*R{FJDqrGzD8C7&kj3*BStteri0M!gc zG;o3r2b;5+SCSKn{(QQ9FiQq1>%RMJ>)uQh%A)gRuO=7IScZgjlWSc7>Z6fQ`>*Mx zLF-nF$!v~mF^TH^sNP{7KdEBHMVcRU>@q-S(wLd~ycH^=uT#9$pUgoRxp=g<9uWqZ zB0j78b_;qtjFd9|tE0t@4~)x;&2_dpudyWRD2WSv?&E2a2uK%9aPo}XcKHmnIc!iM zxu7*nYG!7HX2z~>FCSKG(NLrUovVl@@BSqcaj5J1sm>V@3Ct`6S3N|$1Cma=Q_kw3$z$i44k;RCF!8kmeLQXy?$lV9<-Dx5P{WpohS3*kxycL*Ge7{ zzFq+ozcX;HRS}t_0r!Nh)Z%(j z0KRybR^SZ&%bd$Hmo{bI&z}Id0X^RgL*O$o_$loqRtern)8zDt_mT&t0PGSo1;OKn zef9hyv$kw!*)a^-i$9V0hkxs|)h@X5n2T>h0e4YHq`;Yis;zkd{XtV-Z~0O29P!l; zy!?+T2@g@`M3o5%b{TI3fW4%_iJpA&X?ITj(XU~RGG+4yaSVL3Z2(X-eXj+2T$K8% zRSL5T{i~NDyWitRi{eNIfJ&98wnx^y7+!C%L}2?i&t?nH^OwtvQCUP^S+F`mxo6#=>)% zKrKen{m_;R+GGdCXVrP<-@l~dtJb_kPQdPbZ1F3*gI68kYOTVCC8uMcL<~XzrJc^4 zq!Cg#Ca|zxRr1oW;Nfo^wR5AUhS{X{aQxn@19yxZB4afTwhA1MqQ9>O)?oAXX10q0 zV1(#?AL%CmBZS$7u9d`%adT2B!x6O|C6TtDbzV$~eO=H7uLf|qcmn&Xt!<`FL_`04 zMdRD45f-Az-XD|CIVL2|rEktQ+?p2c_qo~xhczaW zO6AbZ@$rShyKtJ|#vLHmAtx!t?yUK@3d^|6^i{Y_Rs@ZJRu30<|oy&hWw~jy801#t>sW6EOJy54M3}iObc&?CZS!Q!>Z+)7} zO|1hX%KYu^g&JV-rRCvxfv=aB2%Phv_+$;ZPl4g%QOC>XcW!rVU7kQs=|u&1XQvN3 zdH?z4fIqTO^Xf?0He{LU_rXEiabb|Zf2x5EZb>G`0eK5n%aM2ZMffjVm+jSkzW<}AyCPw|)C zXzb5SJp`l<*jvDXfeSY-|K{udUo8sHu>neCa!r8=R6DT1a8M+nPzDa9AYC#0VbWi` zqi3Im*4rZp5&3_=2>>&G+5j-$?flcfD_MXO-OT>Jqjx?-8-I}JZ9zqa?h!O?o(jI@ z)9ASoQ0sQLn3;b4{1pFR+tzAcK*&=Xc4qp>A$^hO;;-b4;LGpXRi3WfNuF*9Oj80e;T}WN&qTP z9dyDU+vws~H{uthFAylf8kD`I)EA&FH}%LDeuFZ(`#+wD%tt_*jl$s)cjr9|y*etE zkV_OFFgSf|HV(K~kHhdgH-m#^e#*RGIbF6pA;F`4JwK?UN%->*-?_q>rbz%VHhzJv z-l#(Z5UQ0f@p*jQ_(A`Yjtu*IB5#5WgEE^^m7zLoUm5Lm>*WNEB68AbrYvTxVzsu4 zJ;sOtAEuHWka|{DRQD;19VODO;@QOszIL@WorKhX=_l;Sve) zeXjTb_~fNe>;$4CyU_T9ln$^gc#nyAEWv3!&+ClTP@Q<$=vXUbDxCS5riYwCnf|j$ z`ls;#I632ywj{%Aclg#ET6YzAANLvBzqqllPw7PL7q^28T3SAl=mF12kTLAyfQ-wb z;GH=OAW*O4W$8Zxkp;5?H6&OsuYBb~02g95sI-)!7-gLF$oW$PZJJhZm4C(oD&{lJ zd2hXy6YeL%oEYbTOeCi-m3^x=b=vIE6W7KZmHdn993CNBH!1(Xtewt?!PzLA66ngO zX+8a!-at<^@#&M0h}|oBI(rHNYRsnqn|_eF0GL>_-21uD_`k!U$naW1%cg{%^LF|aV zDNz9}55P@2;Pe&L&Y;Eu+tYJ)Azk+p{yfnB*8EnfnYM{N^y$4QBA6RkwBGtWiNR)j zlwxVm#f??3hrYlqP9JeGJ?o?aPEjONr(|Ul4*L=!+Tk3MMMvmf)HoFvY7g_c9y0s5 zmBS0_Mt@UT(6-*Db+YMvNpBo$Rrwf<&TD5Q9z*eyS2G!vKf;ToF6`)-9ga7ujbD;6 z=yb+#&ol-s5n)V45ETj|jT=DiM0m5Wgv2@6p0sryw>7OknD0D2?i5+QwMFDa>I=M= z9VFjK1BLo5q3|0x4|xd*;vUh_sVP!~P`?iY&bi@Jqk43iqs*Ox!R_I&*&>9&V~rata8T-fWrRAkb}Fm`Ce1#!23$aVg)?%reU7!y z)r{KJ=wku^$q#9@7vkcj?s-vb@gU8)#U80~t%JayhAK^3-!I1*GqTotat{t-?b__H zdA4mx$^Ra`deZlsK*Y+07vIaE3tOL^h5q;abH2ay%w`)_YL zzeMn@BzwP&koNe!o9j>ny43QGp$r5fQ)Tb_y0+iOwHAMSE;fA;Tg=yo#=8i3%cYXL z%J#V^^89Zyp4-5>!3oZ%Ii-hD9y2pKCF6~FuC^v)<+MlXFVSY}`lZv}!#Z!mJ3MgG zS^L|dKxfBrCj3(Y?D&5dAm4p&B;o%*pFh<7@RZJnXDgr)@Kg94LR;NHtyi+-) Cm@23M literal 0 HcmV?d00001 diff --git a/src/lay/images/salt_icon.png b/src/lay/images/salt_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ec11c7ac02d5280a8cd406b672817751117889bc GIT binary patch literal 3679 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 F?tdV&+v5NL literal 0 HcmV?d00001 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]"); From 3f9f06a9b9ebc0ac920c2d338dc2f2a4b8ad8565 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 19 Mar 2017 00:42:09 +0100 Subject: [PATCH 09/57] WIP: more information on packages --- src/lay/SaltManagerDialog.ui | 2 +- src/lay/laySaltGrain.cc | 50 +++++++++++ src/lay/laySaltGrain.h | 51 ++++++++++- src/lay/laySaltGrainDetailsTextWidget.cc | 103 ++++++++++++++++++++--- src/lay/laySaltManagerDialog.cc | 14 ++- src/unit_tests/laySalt.cc | 2 + 6 files changed, 204 insertions(+), 18 deletions(-) diff --git a/src/lay/SaltManagerDialog.ui b/src/lay/SaltManagerDialog.ui index e369924c9..d16b5af31 100644 --- a/src/lay/SaltManagerDialog.ui +++ b/src/lay/SaltManagerDialog.ui @@ -167,7 +167,7 @@ - true + false diff --git a/src/lay/laySaltGrain.cc b/src/lay/laySaltGrain.cc index 80292e6f1..266dbeff7 100644 --- a/src/lay/laySaltGrain.cc +++ b/src/lay/laySaltGrain.cc @@ -26,6 +26,7 @@ #include #include +#include namespace lay { @@ -46,6 +47,9 @@ SaltGrain::operator== (const SaltGrain &other) const m_url == other.m_url && m_title == other.m_title && m_doc == other.m_doc && + m_doc_url == other.m_doc_url && + m_icon == other.m_icon && + m_screenshot == other.m_screenshot && m_dependencies == other.m_dependencies && m_author == other.m_author && m_author_contact == other.m_author_contact && @@ -90,6 +94,12 @@ SaltGrain::set_doc (const std::string &t) m_doc = t; } +void +SaltGrain::set_doc_url (const std::string &u) +{ + m_doc_url = u; +} + void SaltGrain::set_author (const std::string &a) { @@ -120,6 +130,18 @@ SaltGrain::set_installed_time (const QDateTime &t) m_installed_time = t; } +void +SaltGrain::set_screenshot (const QImage &i) +{ + m_screenshot = i; +} + +void +SaltGrain::set_icon (const QImage &i) +{ + m_icon = i; +} + int SaltGrain::compare_versions (const std::string &v1, const std::string &v2) { @@ -184,17 +206,45 @@ struct TimeConverter } }; +struct ImageConverter +{ + std::string to_string (const QImage &image) const + { + if (image.isNull ()) { + return std::string (); + } else { + QBuffer buffer; + buffer.open (QIODevice::WriteOnly); + image.save (&buffer, "PNG"); + buffer.close (); + return buffer.buffer ().toBase64 ().constData (); + } + } + + void from_string (const std::string &image, QImage &res) const + { + if (image.empty ()) { + res = QImage (); + } else { + res = QImage::fromData (QByteArray::fromBase64 (QByteArray (image.c_str (), image.size ()))); + } + } +}; + 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::doc_url, &SaltGrain::set_doc_url, "doc-url") + 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_member (&SaltGrain::icon, &SaltGrain::set_icon, "icon", ImageConverter ()) + + tl::make_member (&SaltGrain::screenshot, &SaltGrain::set_screenshot, "screenshot", ImageConverter ()) + 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 063bade20..eda1d63bd 100644 --- a/src/lay/laySaltGrain.h +++ b/src/lay/laySaltGrain.h @@ -27,6 +27,7 @@ #include "tlObject.h" #include +#include namespace lay { @@ -115,8 +116,7 @@ public: /** * @brief Gets the documentation text of the grain * - * The documentation text is an XML document using - * KLayout's doc format. + * The documentation text is a brief description. */ const std::string &doc () const { @@ -128,6 +128,21 @@ public: */ void set_doc (const std::string &t); + /** + * @brief Gets the documentation URL of the grain + * + * The documentation URL provides a detailed documentation. + */ + const std::string &doc_url () const + { + return m_doc_url; + } + + /** + * @brief Sets the documentation URL of the grain + */ + void set_doc_url (const std::string &u); + /** * @brief Gets the version of the grain * @@ -209,6 +224,35 @@ public: */ void set_installed_time (const QDateTime &t); + /** + * @brief Gets the icon image for the grain. + * The preferred image size is 64x64 pixels. + * The image may be null image. In this case, a default image is used. + */ + const QImage &icon () const + { + return m_icon; + } + + /** + * @brief Sets icon image + */ + void set_icon (const QImage &i); + + /** + * @brief Gets a screenshot image for documentation. + * The image may be null image. In this case, no screenshot is shown. + */ + const QImage &screenshot () const + { + return m_screenshot; + } + + /** + * @brief Sets screenshot image + */ + void set_screenshot (const QImage &i); + /** * @brief Gets the absolute file path of the installed grain * This is the file path to the grain folder. @@ -327,11 +371,12 @@ private: std::string m_path; std::string m_url; std::string m_title; - std::string m_doc; + std::string m_doc, m_doc_url; std::string m_author; std::string m_author_contact; std::string m_license; QDateTime m_authored_time, m_installed_time; + QImage m_icon, m_screenshot; std::vector m_dependencies; }; diff --git a/src/lay/laySaltGrainDetailsTextWidget.cc b/src/lay/laySaltGrainDetailsTextWidget.cc index ab44d6d22..730a6daeb 100644 --- a/src/lay/laySaltGrainDetailsTextWidget.cc +++ b/src/lay/laySaltGrainDetailsTextWidget.cc @@ -27,6 +27,7 @@ #include #include #include +#include namespace lay { @@ -49,9 +50,54 @@ QVariant SaltGrainDetailsTextWidget::loadResource (int type, const QUrl &url) { if (url.path () == QString::fromUtf8 ("/icon")) { - // @@@ - return QImage (":/salt_icon.png"); - // @@@ + + if (!mp_grain || mp_grain->icon ().isNull ()) { + return QImage (":/salt_icon.png"); + } else { + QImage img = mp_grain->icon (); + if (img.width () != 64) { + return img.scaled (QSize (64, 64), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + } else { + return img; + } + } + + } else if (url.path () == QString::fromUtf8 ("/screenshot")) { + + QImage s = mp_grain->screenshot ().convertToFormat (QImage::Format_ARGB32_Premultiplied); + + QImage smask (s.size (), QImage::Format_ARGB32_Premultiplied); + smask.fill (QColor (0, 0, 0, 0)); + { + int border = 4; + int radius = 6; + + QPainter painter (&smask); + + painter.setRenderHint (QPainter::Antialiasing); + + painter.setCompositionMode (QPainter::CompositionMode_Source); + for (int b = border; b > 0; --b) { + QPen pen (QColor (255, 255, 255, ((border - b + 1) * 255) / border)); + pen.setWidth (b * 2 + 1); + painter.setBrush (Qt::NoBrush); + painter.setPen (pen); + painter.drawRoundedRect (QRectF (border, border, s.width () - 2 * border, s.height () - 2 * border), radius, radius, Qt::AbsoluteSize); + } + + painter.setPen (Qt::white); + painter.setBrush (Qt::white); + painter.drawRoundedRect (QRectF (border, border, s.width () - 2 * border, s.height () - 2 * border), radius, radius, Qt::AbsoluteSize); + } + + { + QPainter painter (&s); + painter.setCompositionMode (QPainter::CompositionMode_DestinationIn); + painter.drawImage (0, 0, smask); + } + + return s; + } else { return QTextBrowser::loadResource (type, url); } @@ -72,8 +118,9 @@ SaltGrainDetailsTextWidget::details_text () stream << ""; - stream << ""; - stream << ""; + stream << "
"; + stream << ""; + stream << "
"; stream << "

"; stream << tl::to_qstring (tl::escaped_to_html (g->name ())) << " " << tl::to_qstring (tl::escaped_to_html (g->version ())); @@ -96,8 +143,6 @@ SaltGrainDetailsTextWidget::details_text () stream << "

"; } - stream << "

"; - stream << "


"; if (! g->doc ().empty ()) { stream << tl::to_qstring (tl::escaped_to_html (g->doc ())); @@ -128,16 +173,52 @@ SaltGrainDetailsTextWidget::details_text () stream << "

"; stream << "

"; - if (! g->url ().empty ()) { - stream << "" << QObject::tr ("Documentation link") << ": url ()) << "\">" << tl::to_qstring (tl::escaped_to_html (g->url ())) << ""; + if (! g->license ().empty ()) { + stream << "" << QObject::tr ("License") << ": " << tl::to_qstring (tl::escaped_to_html (g->license ())) << " "; } 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 << QObject::tr ("This package does not have license information. " + "Use the <license> elements of the specification file or edit the package properties to provide license information."); stream << ""; } stream << "

"; + stream << "

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

"; + + if (! g->screenshot ().isNull ()) { + stream << "
"; + stream << "

" << QObject::tr ("Screenshot") << "

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

" << QObject::tr ("Installation") << "

"; + + stream << "

" << QObject::tr ("Installation path: ") << "" << tl::to_qstring (tl::escaped_to_html (g->path ())) << "

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

" << QObject::tr ("Download URL: ") << "" << tl::to_qstring (tl::escaped_to_html (g->url ())) << "

"; + } + if (! g->installed_time ().isNull ()) { + stream << "

" << QObject::tr ("Installed: ") << "" << g->installed_time ().toString () << "

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

" << QObject::tr ("Depends on: ") << "

"; + for (std::vector::const_iterator d = g->dependencies ().begin (); d != g->dependencies ().end (); ++d) { + stream << "    " << tl::to_qstring (tl::escaped_to_html (d->name)) << " " << tl::to_qstring (tl::escaped_to_html (d->url)) << "
"; + } + stream << "

"; + } + + stream << ""; + stream << ""; stream.flush (); diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index 9606164f3..8da3b9934 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -80,9 +80,17 @@ public: } else if (role == Qt::DecorationRole) { - // @@@ - return QIcon (":/salt_icon.png"); - // @@@ + const lay::SaltGrain *g = mp_salt->begin_flat ()[index.row ()]; + if (g->icon ().isNull ()) { + return QIcon (":/salt_icon.png"); + } else { + QPixmap px = QPixmap::fromImage (g->icon ()); + if (px.width () == 64) { + return px; + } else { + return px.scaled (QSize (64, 64), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + } + } } else { return QVariant (); diff --git a/src/unit_tests/laySalt.cc b/src/unit_tests/laySalt.cc index 75d7986ca..ae5581e72 100644 --- a/src/unit_tests/laySalt.cc +++ b/src/unit_tests/laySalt.cc @@ -99,6 +99,8 @@ TEST (1) EXPECT_EQ (g.title (), "title"); g.set_doc ("doc"); EXPECT_EQ (g.doc (), "doc"); + g.set_doc_url ("doc-url"); + EXPECT_EQ (g.doc_url (), "doc-url"); g.set_author ("me"); EXPECT_EQ (g.author (), "me"); g.set_author_contact ("ac"); From b34750f8cdac155998b9a8bc7f8369ee3092b8c6 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 19 Mar 2017 22:44:46 +0100 Subject: [PATCH 10/57] WIP: salt package manager New features like properties editor, proper icon sizing, some styling etc. --- src/lay/SaltGrainPropertiesDialog.ui | 675 +++++++++++++++++++++++ src/lay/lay.pro | 9 +- src/lay/laySaltGrainDetailsTextWidget.cc | 33 +- src/lay/laySaltGrainPropertiesDialog.cc | 268 +++++++++ src/lay/laySaltGrainPropertiesDialog.h | 84 +++ src/lay/laySaltManagerDialog.cc | 52 +- src/lay/laySaltManagerDialog.h | 13 +- 7 files changed, 1109 insertions(+), 25 deletions(-) create mode 100644 src/lay/SaltGrainPropertiesDialog.ui create mode 100644 src/lay/laySaltGrainPropertiesDialog.cc create mode 100644 src/lay/laySaltGrainPropertiesDialog.h diff --git a/src/lay/SaltGrainPropertiesDialog.ui b/src/lay/SaltGrainPropertiesDialog.ui new file mode 100644 index 000000000..f9edcf633 --- /dev/null +++ b/src/lay/SaltGrainPropertiesDialog.ui @@ -0,0 +1,675 @@ + + + SaltGrainPropertiesDialog + + + + 0 + 0 + 754 + 571 + + + + Package Properties + + + + + + + 0 + 1 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 75 + true + + + + License + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + + 0 + 0 + + + + <a href="%1">Open link</a> + + + true + + + + + + + + 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 + + + + + + + + 75 + true + + + + Author contact + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + + Qt::Vertical + + + + 736 + 32 + + + + + + + + + 0 + 0 + + + + + true + + + + (enter an URL to provide a documentation link) + + + + + + + + 0 + 0 + + + + + + + + + true + + + + (license information like "GPLv3" or "MIT") + + + + + + + + 75 + true + + + + Version + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + + 75 + true + + + + +Depends on + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Add new dependency + + + ... + + + + :/add.png:/add.png + + + true + + + + + + + Delete dependency + + + ... + + + + :/clear.png:/clear.png + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + true + + + false + + + true + + + + Name + + + + + Version + + + + + URL + + + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + version + title + author + author_contact + license + doc + doc_url + icon_config_button + icon_delete_button + screenshot_config_button + screenshot_delete_button + dependencies + add_dependency + remove_dependency + + + + + + + buttonBox + accepted() + SaltGrainPropertiesDialog + accept() + + + 546 + 425 + + + 561 + 435 + + + + + buttonBox + rejected() + SaltGrainPropertiesDialog + reject() + + + 638 + 418 + + + 649 + 433 + + + + + diff --git a/src/lay/lay.pro b/src/lay/lay.pro index 572c78a0d..da3d53cb0 100644 --- a/src/lay/lay.pro +++ b/src/lay/lay.pro @@ -50,7 +50,8 @@ HEADERS = \ laySaltGrain.h \ laySaltGrains.h \ laySaltManagerDialog.h \ - laySaltGrainDetailsTextWidget.h + laySaltGrainDetailsTextWidget.h \ + laySaltGrainPropertiesDialog.h FORMS = \ ClipDialog.ui \ @@ -96,7 +97,8 @@ FORMS = \ TechLoadOptionsEditorPage.ui \ TechSaveOptionsEditorPage.ui \ MainConfigPage7.ui \ - SaltManagerDialog.ui + SaltManagerDialog.ui \ + SaltGrainPropertiesDialog.ui SOURCES = \ gsiDeclLayApplication.cc \ @@ -144,7 +146,8 @@ SOURCES = \ laySaltGrain.cc \ laySaltGrains.cc \ laySaltManagerDialog.cc \ - laySaltGrainDetailsTextWidget.cc + laySaltGrainDetailsTextWidget.cc \ + laySaltGrainPropertiesDialog.cc RESOURCES = layBuildInMacros.qrc \ layHelpResources.qrc \ diff --git a/src/lay/laySaltGrainDetailsTextWidget.cc b/src/lay/laySaltGrainDetailsTextWidget.cc index 730a6daeb..f71bdda14 100644 --- a/src/lay/laySaltGrainDetailsTextWidget.cc +++ b/src/lay/laySaltGrainDetailsTextWidget.cc @@ -40,10 +40,8 @@ SaltGrainDetailsTextWidget::SaltGrainDetailsTextWidget (QWidget *w) void SaltGrainDetailsTextWidget::set_grain (SaltGrain *g) { - if (mp_grain != g) { - mp_grain = g; - setHtml (details_text ()); - } + mp_grain = g; + setHtml (details_text ()); } QVariant @@ -51,15 +49,30 @@ SaltGrainDetailsTextWidget::loadResource (int type, const QUrl &url) { if (url.path () == QString::fromUtf8 ("/icon")) { + int icon_dim = 64; + if (!mp_grain || mp_grain->icon ().isNull ()) { + return QImage (":/salt_icon.png"); + } else { + QImage img = mp_grain->icon (); - if (img.width () != 64) { - return img.scaled (QSize (64, 64), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + if (img.width () != icon_dim || img.height () != icon_dim) { + + img = img.scaled (QSize (icon_dim, icon_dim), Qt::KeepAspectRatio, Qt::SmoothTransformation); + + QImage final_img (icon_dim, icon_dim, QImage::Format_ARGB32); + final_img.fill (QColor (0, 0, 0, 0)); + QPainter painter (&final_img); + painter.drawImage ((icon_dim - img.width ()) / 2, (icon_dim - img.height ()) / 2, img); + + return final_img; + } else { return img; } + } } else if (url.path () == QString::fromUtf8 ("/screenshot")) { @@ -69,7 +82,7 @@ SaltGrainDetailsTextWidget::loadResource (int type, const QUrl &url) QImage smask (s.size (), QImage::Format_ARGB32_Premultiplied); smask.fill (QColor (0, 0, 0, 0)); { - int border = 4; + int border = 0; int radius = 6; QPainter painter (&smask); @@ -210,9 +223,11 @@ SaltGrainDetailsTextWidget::details_text () stream << "

" << QObject::tr ("Installed: ") << "" << g->installed_time ().toString () << "

"; } if (! g->dependencies ().empty ()) { - stream << "

" << QObject::tr ("Depends on: ") << "

"; + stream << "

" << QObject::tr ("Depends on: ") << "
"; for (std::vector::const_iterator d = g->dependencies ().begin (); d != g->dependencies ().end (); ++d) { - stream << "    " << tl::to_qstring (tl::escaped_to_html (d->name)) << " " << tl::to_qstring (tl::escaped_to_html (d->url)) << "
"; + stream << "    " << tl::to_qstring (tl::escaped_to_html (d->name)) << " "; + stream << tl::to_qstring (tl::escaped_to_html (d->version)) << " - "; + stream << "[" << tl::to_qstring (tl::escaped_to_html (d->url)) << "]
"; } stream << "

"; } diff --git a/src/lay/laySaltGrainPropertiesDialog.cc b/src/lay/laySaltGrainPropertiesDialog.cc new file mode 100644 index 000000000..1c60abd4d --- /dev/null +++ b/src/lay/laySaltGrainPropertiesDialog.cc @@ -0,0 +1,268 @@ + +/* + + 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 "laySaltGrainPropertiesDialog.h" +#include "laySalt.h" +#include "tlString.h" +#include "tlExceptions.h" + +#include +#include +#include + +namespace lay +{ + +SaltGrainPropertiesDialog::SaltGrainPropertiesDialog (QWidget *parent) + : QDialog (parent), mp_salt (0) +{ + Ui::SaltGrainPropertiesDialog::setupUi (this); + + m_title = windowTitle (); + m_open_label = open_label->text (); + + connect (icon_delete_button, SIGNAL (clicked ()), this, SLOT (reset_icon ())); + connect (icon_config_button, SIGNAL (clicked ()), this, SLOT (set_icon ())); + connect (screenshot_delete_button, SIGNAL (clicked ()), this, SLOT (reset_screenshot ())); + connect (screenshot_config_button, SIGNAL (clicked ()), this, SLOT (set_screenshot ())); + connect (doc_url, SIGNAL (textChanged (const QString &)), this, SLOT (url_changed (const QString &))); + connect (add_dependency, SIGNAL (clicked ()), this, SLOT (add_dependency_clicked ())); + connect (remove_dependency, SIGNAL (clicked ()), this, SLOT (remove_dependency_clicked ())); + connect (dependencies, SIGNAL (itemChanged (QTreeWidgetItem *, int)), this, SLOT (dependency_changed (QTreeWidgetItem *, int))); +} + +void +SaltGrainPropertiesDialog::update_controls () +{ + setWindowTitle (m_title + tl::to_qstring (" - " + m_grain.name ())); + + version->setText (tl::to_qstring (m_grain.version ())); + title->setText (tl::to_qstring (m_grain.title ())); + author->setText (tl::to_qstring (m_grain.author ())); + author_contact->setText (tl::to_qstring (m_grain.author_contact ())); + doc->setText (tl::to_qstring (m_grain.doc ())); + doc_url->setText (tl::to_qstring (m_grain.doc_url ())); + license->setText (tl::to_qstring (m_grain.license ())); + + dependencies->clear (); + for (std::vector::const_iterator d = m_grain.dependencies ().begin (); d != m_grain.dependencies ().end (); ++d) { + QTreeWidgetItem *item = new QTreeWidgetItem (dependencies); + item->setFlags (item->flags () | Qt::ItemIsEditable); + item->setText (0, tl::to_qstring (d->name)); + item->setText (1, tl::to_qstring (d->version)); + item->setText (2, tl::to_qstring (d->url)); + dependencies->addTopLevelItem (item); + } + + update_icon (); + update_screenshot (); +} + +void +SaltGrainPropertiesDialog::update_icon () +{ + if (m_grain.icon ().isNull ()) { + icon_config_button->setIcon (QIcon (":/salt_icon.png")); + } else { + QImage img = m_grain.icon (); + if (img.width () == icon_config_button->iconSize ().width ()) { + icon_config_button->setIcon (QIcon (QPixmap::fromImage (img))); + } else { + icon_config_button->setIcon (QIcon (QPixmap::fromImage (img.scaled (icon_config_button->iconSize (), Qt::KeepAspectRatio, Qt::SmoothTransformation)))); + } + } +} + +void +SaltGrainPropertiesDialog::update_screenshot () +{ + if (m_grain.screenshot ().isNull ()) { + screenshot_config_button->setIcon (QIcon (":/add.png")); + } else { + QImage img = m_grain.screenshot (); + if (img.width () == screenshot_config_button->iconSize ().width ()) { + screenshot_config_button->setIcon (QIcon (QPixmap::fromImage (img))); + } else { + screenshot_config_button->setIcon (QIcon (QPixmap::fromImage (img.scaled (screenshot_config_button->iconSize (), Qt::KeepAspectRatio, Qt::SmoothTransformation)))); + } + } +} + +void +SaltGrainPropertiesDialog::update_data () +{ + m_grain.set_version (tl::to_string (version->text ())); + m_grain.set_title (tl::to_string (title->text ())); + m_grain.set_author (tl::to_string (author->text ())); + m_grain.set_author_contact (tl::to_string (author_contact->text ())); + m_grain.set_doc (tl::to_string (doc->toPlainText ())); + m_grain.set_doc_url (tl::to_string (doc_url->text ())); + m_grain.set_license (tl::to_string (license->text ())); + + m_grain.dependencies ().clear (); + for (int i = 0; i < dependencies->topLevelItemCount (); ++i) { + QTreeWidgetItem *item = dependencies->topLevelItem (i); + QString name = item->text (0).simplified (); + QString version = item->text (1).simplified (); + QString url = item->text (2).simplified (); + if (! name.isEmpty ()) { + lay::SaltGrain::Dependency dep = lay::SaltGrain::Dependency (); + dep.name = tl::to_string (name); + dep.version = tl::to_string (version); + dep.url = tl::to_string (url); + m_grain.dependencies ().push_back (dep); + } + } +} + +void +SaltGrainPropertiesDialog::dependency_changed (QTreeWidgetItem *item, int column) +{ + if (column == 0 && mp_salt) { + + // set URL and version for known grains + std::string name = tl::to_string (item->text (0).simplified ()); + if (name == m_grain.name ()) { + + item->setText (1, QString ()); + item->setText (2, tr ("(must not depend on itself)")); + + } else { + + SaltGrain *g = 0; + for (lay::Salt::flat_iterator i = mp_salt->begin_flat (); i != mp_salt->end_flat (); ++i) { + if ((*i)->name () == name) { + g = *i; + } + } + if (g) { + item->setText (1, tl::to_qstring (g->version ())); + item->setText (2, tl::to_qstring (g->url ())); + } else { + item->setText (1, QString ()); + item->setText (2, tr ("(unknown packet)")); + } + + } + + } +} + +void +SaltGrainPropertiesDialog::url_changed (const QString &url) +{ + // inserts the URL into the label + open_label->setText (m_open_label.arg (url)); +} + +void +SaltGrainPropertiesDialog::set_icon () +{ +BEGIN_PROTECTED + + const int max_dim = 256; + + QString fileName = QFileDialog::getOpenFileName (this, tr ("Pick Icon Image File"), m_image_dir, tr ("Images (*.png *.jpg)")); + if (! fileName.isNull ()) { + 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 ()))); + } + m_grain.set_icon (img); + m_image_dir = QFileInfo (fileName).path (); + update_icon (); + } + +END_PROTECTED +} + +void +SaltGrainPropertiesDialog::reset_icon () +{ + m_grain.set_icon (QImage ()); + update_icon (); +} + +void +SaltGrainPropertiesDialog::set_screenshot () +{ +BEGIN_PROTECTED + + const int max_dim = 1024; + + QString fileName = QFileDialog::getOpenFileName (this, tr ("Pick Showcase Image File"), m_image_dir, tr ("Images (*.png *.jpg)")); + if (! fileName.isNull ()) { + 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 ()))); + } + m_grain.set_screenshot (img); + m_image_dir = QFileInfo (fileName).path (); + update_screenshot (); + } + +END_PROTECTED +} + +void +SaltGrainPropertiesDialog::reset_screenshot () +{ + m_grain.set_screenshot (QImage ()); + update_screenshot (); +} + +void +SaltGrainPropertiesDialog::add_dependency_clicked () +{ + QTreeWidgetItem *item = new QTreeWidgetItem (dependencies); + item->setFlags (item->flags () | Qt::ItemIsEditable); + dependencies->addTopLevelItem (item); + dependencies->setCurrentItem (dependencies->topLevelItem (dependencies->topLevelItemCount () - 1)); +} + +void +SaltGrainPropertiesDialog::remove_dependency_clicked () +{ + int index = dependencies->indexOfTopLevelItem (dependencies->currentItem ()); + if (index >= 0 && index < dependencies->topLevelItemCount ()) { + delete dependencies->topLevelItem (index); + } +} + +bool +SaltGrainPropertiesDialog::exec_dialog (lay::SaltGrain *grain, lay::Salt *salt) +{ + m_grain = *grain; + mp_salt = salt; + update_controls (); + + bool res = exec (); + if (res) { + update_data (); + *grain = m_grain; + } + + mp_salt = 0; + return res; +} + +} diff --git a/src/lay/laySaltGrainPropertiesDialog.h b/src/lay/laySaltGrainPropertiesDialog.h new file mode 100644 index 000000000..e9eeae2d7 --- /dev/null +++ b/src/lay/laySaltGrainPropertiesDialog.h @@ -0,0 +1,84 @@ + +/* + + 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_laySaltGrainPropertiesDialog +#define HDR_laySaltGrainPropertiesDialog + +#include "laySaltGrain.h" + +#include + +#include "ui_SaltGrainPropertiesDialog.h" + +namespace lay +{ + +class Salt; + +/** + * @brief The dialog for managing the Salt ("Packages") + */ +class SaltGrainPropertiesDialog + : public QDialog, private Ui::SaltGrainPropertiesDialog +{ +Q_OBJECT + +public: + /** + * @brief Constructor + */ + SaltGrainPropertiesDialog (QWidget *parent); + + /** + * @brief Executes the dialog for the given grain + * If the dialog is committed with "Ok", the new data is written into + * the grain provided and "true" is returned. Otherwise, "false" is + * returned and the object remains unchanged. + */ + bool exec_dialog (lay::SaltGrain *grain, lay::Salt *salt); + +private slots: + void reset_icon (); + void set_icon (); + void reset_screenshot (); + void set_screenshot (); + void url_changed (const QString &url); + void add_dependency_clicked (); + void remove_dependency_clicked (); + void dependency_changed (QTreeWidgetItem *item, int column); + +private: + lay::SaltGrain m_grain; + lay::Salt *mp_salt; + QString m_title; + QString m_open_label; + QString m_image_dir; + + void update_controls (); + void update_data (); + void update_icon (); + void update_screenshot (); +}; + +} + +#endif diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index 8da3b9934..8c83aa9f8 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -21,6 +21,7 @@ */ #include "laySaltManagerDialog.h" +#include "laySaltGrainPropertiesDialog.h" #include "laySalt.h" #include "tlString.h" @@ -80,16 +81,29 @@ public: } else if (role == Qt::DecorationRole) { + int icon_dim = 64; + const lay::SaltGrain *g = mp_salt->begin_flat ()[index.row ()]; if (g->icon ().isNull ()) { return QIcon (":/salt_icon.png"); } else { - QPixmap px = QPixmap::fromImage (g->icon ()); - if (px.width () == 64) { - return px; + + QImage img = g->icon (); + if (img.width () == icon_dim && img.height () == icon_dim) { + return QPixmap::fromImage (img); } else { - return px.scaled (QSize (64, 64), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + + img = img.scaled (QSize (icon_dim, icon_dim), Qt::KeepAspectRatio, Qt::SmoothTransformation); + + QImage final_img (icon_dim, icon_dim, QImage::Format_ARGB32); + final_img.fill (QColor (0, 0, 0, 0)); + QPainter painter (&final_img); + painter.drawImage ((icon_dim - img.width ()) / 2, (icon_dim - img.height ()) / 2, img); + + return QPixmap::fromImage (final_img); + } + } } else { @@ -225,6 +239,9 @@ SaltManagerDialog::SaltManagerDialog (QWidget *parent) m_current_changed_enabled (true) { Ui::SaltManagerDialog::setupUi (this); + mp_properties_dialog = new lay::SaltGrainPropertiesDialog (this); + + connect (edit_button, SIGNAL (clicked ()), this, SLOT (edit_properties ())); // @@@ salt = lay::Salt (); salt_initialized = false; @@ -247,8 +264,19 @@ SaltManagerDialog::SaltManagerDialog (QWidget *parent) connect (salt_view->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (current_changed ())); + // @@@ +} - // ... +void +SaltManagerDialog::edit_properties () +{ + SaltGrain *g = current_grain (); + if (g) { + if (mp_properties_dialog->exec_dialog (g, mp_salt)) { + current_changed (); + // @@@ + } + } } void @@ -277,12 +305,7 @@ SaltManagerDialog::salt_changed () void SaltManagerDialog::current_changed () { - SaltModel *model = dynamic_cast (salt_view->model ()); - if (! model) { - return; - } - - SaltGrain *g = model->grain_from_index (salt_view->currentIndex ()); + SaltGrain *g = current_grain (); details_text->set_grain (g); if (!g) { details_frame->setEnabled (false); @@ -293,4 +316,11 @@ SaltManagerDialog::current_changed () } } +lay::SaltGrain * +SaltManagerDialog::current_grain () +{ + SaltModel *model = dynamic_cast (salt_view->model ()); + return model ? model->grain_from_index (salt_view->currentIndex ()) : 0; +} + } diff --git a/src/lay/laySaltManagerDialog.h b/src/lay/laySaltManagerDialog.h index 24b55a62e..170e02b7d 100644 --- a/src/lay/laySaltManagerDialog.h +++ b/src/lay/laySaltManagerDialog.h @@ -20,8 +20,8 @@ */ -#ifndef HDR_laySaltManager -#define HDR_laySaltManager +#ifndef HDR_laySaltManagerDialog +#define HDR_laySaltManagerDialog #include @@ -32,6 +32,7 @@ namespace lay class Salt; class SaltGrain; +class SaltGrainPropertiesDialog; /** * @brief The dialog for managing the Salt ("Packages") @@ -58,9 +59,17 @@ private slots: */ void current_changed (); + /** + * @brief Called when the "edit" button is pressed + */ + void edit_properties (); + private: lay::Salt *mp_salt; bool m_current_changed_enabled; + lay::SaltGrainPropertiesDialog *mp_properties_dialog; + + lay::SaltGrain *current_grain (); }; } From 5dab4b19ecdb3e6804bb7555644ddc3f3cb9d2be Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 20 Mar 2017 10:07:39 +0100 Subject: [PATCH 11/57] WIP: some styling topics. --- src/lay/SaltGrainPropertiesDialog.ui | 3 + src/lay/laySaltGrainPropertiesDialog.cc | 124 +++++++++++++++++++++--- src/lay/laySaltGrainPropertiesDialog.h | 9 ++ 3 files changed, 122 insertions(+), 14 deletions(-) diff --git a/src/lay/SaltGrainPropertiesDialog.ui b/src/lay/SaltGrainPropertiesDialog.ui index f9edcf633..7ef38eda1 100644 --- a/src/lay/SaltGrainPropertiesDialog.ui +++ b/src/lay/SaltGrainPropertiesDialog.ui @@ -578,6 +578,9 @@ Depends on + + QAbstractItemView::AnyKeyPressed|QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + true diff --git a/src/lay/laySaltGrainPropertiesDialog.cc b/src/lay/laySaltGrainPropertiesDialog.cc index 1c60abd4d..c05dbc63d 100644 --- a/src/lay/laySaltGrainPropertiesDialog.cc +++ b/src/lay/laySaltGrainPropertiesDialog.cc @@ -28,12 +28,77 @@ #include #include #include +#include +#include namespace lay { +// ---------------------------------------------------------------------------------------------------- + +/** + * @brief A delegate for editing a field of the dependency list + */ +class SaltGrainEditDelegate + : public QItemDelegate +{ +public: + SaltGrainEditDelegate (QWidget *parent, SaltGrainPropertiesDialog *dialog, int column) + : QItemDelegate (parent), mp_dialog (dialog), m_column (column) + { + // .. nothing yet .. + } + + QWidget *createEditor (QWidget *parent, const QStyleOptionViewItem & /*option*/, const QModelIndex & /*index*/) const + { + QLineEdit *editor = new QLineEdit (parent); + editor->setFrame (false); + editor->setTextMargins (2, 0, 2, 0); + return editor; + } + + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex & /*index*/) const + { + editor->setGeometry(option.rect); + } + + void setEditorData (QWidget *widget, const QModelIndex &index) const + { + QLineEdit *editor = dynamic_cast (widget); + if (editor) { + editor->setText (index.model ()->data (index, Qt::UserRole).toString ()); + if (m_column > 0) { + editor->setPlaceholderText (index.model ()->data (index, Qt::EditRole).toString ()); + } + } + } + + void setModelData (QWidget *widget, QAbstractItemModel *model, const QModelIndex &index) const + { + QLineEdit *editor = dynamic_cast (widget); + if (editor) { + model->setData (index, QVariant (editor->text ()), Qt::UserRole); + } + } + + QSize sizeHint (const QStyleOptionViewItem &option, const QModelIndex &index) const + { + QWidget *editor = createEditor (0, option, index); + QSize size = editor->sizeHint (); + delete editor; + return size; + } + +public: + SaltGrainPropertiesDialog *mp_dialog; + int m_column; +}; + +// ---------------------------------------------------------------------------------------------------- +// SaltGrainPropertiesDialog implementation + SaltGrainPropertiesDialog::SaltGrainPropertiesDialog (QWidget *parent) - : QDialog (parent), mp_salt (0) + : QDialog (parent), mp_salt (0), m_update_enabled (true) { Ui::SaltGrainPropertiesDialog::setupUi (this); @@ -48,6 +113,12 @@ SaltGrainPropertiesDialog::SaltGrainPropertiesDialog (QWidget *parent) connect (add_dependency, SIGNAL (clicked ()), this, SLOT (add_dependency_clicked ())); connect (remove_dependency, SIGNAL (clicked ()), this, SLOT (remove_dependency_clicked ())); connect (dependencies, SIGNAL (itemChanged (QTreeWidgetItem *, int)), this, SLOT (dependency_changed (QTreeWidgetItem *, int))); + + dependencies->setItemDelegateForColumn (0, new SaltGrainEditDelegate (dependencies, this, 0)); + dependencies->setItemDelegateForColumn (1, new SaltGrainEditDelegate (dependencies, this, 1)); + dependencies->setItemDelegateForColumn (2, new SaltGrainEditDelegate (dependencies, this, 2)); + + url_changed (QString ()); } void @@ -67,9 +138,9 @@ SaltGrainPropertiesDialog::update_controls () for (std::vector::const_iterator d = m_grain.dependencies ().begin (); d != m_grain.dependencies ().end (); ++d) { QTreeWidgetItem *item = new QTreeWidgetItem (dependencies); item->setFlags (item->flags () | Qt::ItemIsEditable); - item->setText (0, tl::to_qstring (d->name)); - item->setText (1, tl::to_qstring (d->version)); - item->setText (2, tl::to_qstring (d->url)); + item->setData (0, Qt::UserRole, tl::to_qstring (d->name)); + item->setData (1, Qt::UserRole, tl::to_qstring (d->version)); + item->setData (2, Qt::UserRole, tl::to_qstring (d->url)); dependencies->addTopLevelItem (item); } @@ -120,10 +191,12 @@ SaltGrainPropertiesDialog::update_data () m_grain.dependencies ().clear (); for (int i = 0; i < dependencies->topLevelItemCount (); ++i) { + QTreeWidgetItem *item = dependencies->topLevelItem (i); - QString name = item->text (0).simplified (); - QString version = item->text (1).simplified (); - QString url = item->text (2).simplified (); + QString name = item->data (0, Qt::UserRole).toString ().simplified (); + QString version = item->data (1, Qt::UserRole).toString ().simplified (); + QString url = item->data (2, Qt::UserRole).toString ().simplified (); + if (! name.isEmpty ()) { lay::SaltGrain::Dependency dep = lay::SaltGrain::Dependency (); dep.name = tl::to_string (name); @@ -131,20 +204,31 @@ SaltGrainPropertiesDialog::update_data () dep.url = tl::to_string (url); m_grain.dependencies ().push_back (dep); } + } } void SaltGrainPropertiesDialog::dependency_changed (QTreeWidgetItem *item, int column) { + if (! m_update_enabled) { + return; + } + m_update_enabled = false; + if (column == 0 && mp_salt) { + std::string name = tl::to_string (item->data (0, Qt::UserRole).toString ().simplified ()); + item->setData (0, Qt::EditRole, tl::to_qstring (name)); + // set URL and version for known grains - std::string name = tl::to_string (item->text (0).simplified ()); if (name == m_grain.name ()) { - item->setText (1, QString ()); - item->setText (2, tr ("(must not depend on itself)")); + item->setData (1, Qt::UserRole, QString ()); + item->setData (2, Qt::UserRole, QString ()); + // placeholder texts: + item->setData (1, Qt::EditRole, QString ()); + item->setData (2, Qt::EditRole, tr ("(must not depend on itself)")); } else { @@ -155,16 +239,26 @@ SaltGrainPropertiesDialog::dependency_changed (QTreeWidgetItem *item, int column } } if (g) { - item->setText (1, tl::to_qstring (g->version ())); - item->setText (2, tl::to_qstring (g->url ())); + item->setData (1, Qt::UserRole, tl::to_qstring (g->version ())); + item->setData (2, Qt::UserRole, tl::to_qstring (g->url ())); + // placeholder texts: + item->setData (1, Qt::EditRole, tl::to_qstring (g->version ())); + item->setData (2, Qt::EditRole, tl::to_qstring (g->url ())); } else { - item->setText (1, QString ()); - item->setText (2, tr ("(unknown packet)")); + item->setData (1, Qt::UserRole, QString ()); + item->setData (2, Qt::UserRole, QString ()); + // placeholder texts: + item->setData (1, Qt::EditRole, QString ()); + item->setData (2, Qt::EditRole, tr ("(unknown packet)")); } } + } else if (column > 0) { + item->setData (column, Qt::EditRole, item->data (column, Qt::UserRole).toString ()); } + + m_update_enabled = true; } void @@ -172,6 +266,7 @@ SaltGrainPropertiesDialog::url_changed (const QString &url) { // inserts the URL into the label open_label->setText (m_open_label.arg (url)); + open_label->setEnabled (! url.isEmpty ()); } void @@ -253,6 +348,7 @@ SaltGrainPropertiesDialog::exec_dialog (lay::SaltGrain *grain, lay::Salt *salt) { m_grain = *grain; mp_salt = salt; + update_controls (); bool res = exec (); diff --git a/src/lay/laySaltGrainPropertiesDialog.h b/src/lay/laySaltGrainPropertiesDialog.h index e9eeae2d7..1ea219f98 100644 --- a/src/lay/laySaltGrainPropertiesDialog.h +++ b/src/lay/laySaltGrainPropertiesDialog.h @@ -56,6 +56,14 @@ public: */ bool exec_dialog (lay::SaltGrain *grain, lay::Salt *salt); + /** + * @brief Gets the current package index + */ + lay::Salt *salt () + { + return mp_salt; + } + private slots: void reset_icon (); void set_icon (); @@ -72,6 +80,7 @@ private: QString m_title; QString m_open_label; QString m_image_dir; + bool m_update_enabled; void update_controls (); void update_data (); From faf8acb3d1f2fc8a13c3cd35946c0a5d966fa5e7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 20 Mar 2017 17:53:47 +0100 Subject: [PATCH 12/57] WIP: completer for name of dependency package --- src/lay/laySaltGrainPropertiesDialog.cc | 59 ++++++++++++++++++------- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/src/lay/laySaltGrainPropertiesDialog.cc b/src/lay/laySaltGrainPropertiesDialog.cc index c05dbc63d..c0991894d 100644 --- a/src/lay/laySaltGrainPropertiesDialog.cc +++ b/src/lay/laySaltGrainPropertiesDialog.cc @@ -30,6 +30,8 @@ #include #include #include +#include +#include namespace lay { @@ -43,8 +45,8 @@ class SaltGrainEditDelegate : public QItemDelegate { public: - SaltGrainEditDelegate (QWidget *parent, SaltGrainPropertiesDialog *dialog, int column) - : QItemDelegate (parent), mp_dialog (dialog), m_column (column) + SaltGrainEditDelegate (QWidget *parent) + : QItemDelegate (parent) { // .. nothing yet .. } @@ -67,9 +69,6 @@ public: QLineEdit *editor = dynamic_cast (widget); if (editor) { editor->setText (index.model ()->data (index, Qt::UserRole).toString ()); - if (m_column > 0) { - editor->setPlaceholderText (index.model ()->data (index, Qt::EditRole).toString ()); - } } } @@ -81,17 +80,43 @@ public: } } - QSize sizeHint (const QStyleOptionViewItem &option, const QModelIndex &index) const + QSize sizeHint (const QStyleOptionViewItem &option, const QModelIndex & /*index*/) const { - QWidget *editor = createEditor (0, option, index); - QSize size = editor->sizeHint (); - delete editor; - return size; + QSize sz = option.fontMetrics.size (Qt::TextSingleLine, QString::fromUtf8 ("M")); + sz += QSize (0, 8); + return sz; + } +}; + +/** + * @brief A delegate for editing a field of the dependency list + */ +class SaltGrainNameEditDelegate + : public SaltGrainEditDelegate +{ +public: + SaltGrainNameEditDelegate (QWidget *parent, Salt *salt) + : SaltGrainEditDelegate (parent), mp_completer (0) + { + QStringList names; + for (lay::Salt::flat_iterator i = salt->begin_flat (); i != salt->end_flat (); ++i) { + names << tl::to_qstring ((*i)->name ()); + } + mp_completer = new QCompleter (names, this); + } + + QWidget *createEditor (QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + QWidget *editor = SaltGrainEditDelegate::createEditor (parent, option, index); + QLineEdit *line_edit = dynamic_cast (editor); + if (line_edit) { + line_edit->setCompleter (mp_completer); + } + return editor; } public: - SaltGrainPropertiesDialog *mp_dialog; - int m_column; + QCompleter *mp_completer; }; // ---------------------------------------------------------------------------------------------------- @@ -114,9 +139,8 @@ SaltGrainPropertiesDialog::SaltGrainPropertiesDialog (QWidget *parent) connect (remove_dependency, SIGNAL (clicked ()), this, SLOT (remove_dependency_clicked ())); connect (dependencies, SIGNAL (itemChanged (QTreeWidgetItem *, int)), this, SLOT (dependency_changed (QTreeWidgetItem *, int))); - dependencies->setItemDelegateForColumn (0, new SaltGrainEditDelegate (dependencies, this, 0)); - dependencies->setItemDelegateForColumn (1, new SaltGrainEditDelegate (dependencies, this, 1)); - dependencies->setItemDelegateForColumn (2, new SaltGrainEditDelegate (dependencies, this, 2)); + dependencies->setItemDelegateForColumn (1, new SaltGrainEditDelegate (dependencies)); + dependencies->setItemDelegateForColumn (2, new SaltGrainEditDelegate (dependencies)); url_changed (QString ()); } @@ -349,6 +373,8 @@ SaltGrainPropertiesDialog::exec_dialog (lay::SaltGrain *grain, lay::Salt *salt) m_grain = *grain; mp_salt = salt; + dependencies->setItemDelegateForColumn (0, new SaltGrainNameEditDelegate (dependencies, mp_salt)); + update_controls (); bool res = exec (); @@ -357,6 +383,9 @@ SaltGrainPropertiesDialog::exec_dialog (lay::SaltGrain *grain, lay::Salt *salt) *grain = m_grain; } + delete dependencies->itemDelegateForColumn (0); + dependencies->setItemDelegateForColumn (0, 0); + mp_salt = 0; return res; } From ec415d9251c747db5be47f850c8f7f276be680fb Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 20 Mar 2017 22:29:22 +0100 Subject: [PATCH 13/57] WIP: functionality of package properties editor Side effect: the log dialog now has an icon indicating whether there are errors or warnings. A new utility widget has been introduced to attach log messages (warnings/errors) to dialogs. This widget is a QToolButton that is invisible initially. It provides warning and errors channels and can be fed messages. If errors or warnings are fed, the tool button becomes visible. If (directly) embedded inside a QFrame, the frame's background will turn red to indicate the region of interest. The button can be pushed to read the log. The button is called lay::AlertLogButton and is found in layLogViewerDialog.h. TODO: move to layBasic. --- src/lay/LogViewerDialog.ui | 226 ++++-- src/lay/SaltGrainPropertiesDialog.ui | 941 ++++++++++++++---------- src/lay/images/warn.png | Bin 0 -> 637 bytes src/lay/layLogViewerDialog.cc | 144 +++- src/lay/layLogViewerDialog.h | 257 ++++++- src/lay/layResources.qrc | 1 + src/lay/laySaltGrain.cc | 22 + src/lay/laySaltGrain.h | 5 + src/lay/laySaltGrainPropertiesDialog.cc | 89 ++- src/lay/laySaltGrainPropertiesDialog.h | 3 + src/unit_tests/laySalt.cc | 6 + 11 files changed, 1174 insertions(+), 520 deletions(-) create mode 100644 src/lay/images/warn.png 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 0000000000000000000000000000000000000000..adea92d4f312bbcf55ab14d05e69b9a71042c2ad GIT binary patch literal 637 zcmV-@0)qXCP)FaKj!`_Q(N@y`IA9@_HdAGO&b7I-`l~UxITBnDDwI~ z=FcNMd)z1~C06>~k7$#&KfKt({K;d^C-~i)Vb+=o0Nl=D(b=#qacgnjQmIh|NCG4w zoepAt&JJlUuKh1PsZIKke;a4cc?^I7Fqii8MmEW&DartbdlI-d-MxCr3B+RZPkKX= z5Z@cZ<&}6K99DOFr61ZI7UkNO&z$p^Hxn&_+8A?vg15iVayYC?01k8!47hav@x9Zg z3c1aT954aXV4>Wv9A5;6SF^i!#kQ0Z6oJ#&7Bc{9OW7;O>M4>Q+KGS|;>?dKI9 z8gh#OW*P+2vB-b=Xj#+FI=~;B1Y8*d*AD{F%~HxiX6=max|R=sgp^oJr&M(O$P!Sz z-NKiI$>|)9O=a5*0FAQ?lkmK#C#75vj;7TInP~^rmjGwm41^FMl3fNwHZp4$Y}Yk! z2C6mevW+i5*#{K08N7Zq4j|fP02oLnVyIMVaTta?uPrJxKCIs|3{|wO(8XRi(YJmB X_oS&DXk~uz00000NkvXXu0mjfb)gLI literal 0 HcmV?d00001 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); From 334fca3f7619074c9654b44a1980febbca10cc88 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 21 Mar 2017 11:24:45 +0100 Subject: [PATCH 14/57] WIP: checking of grain properties. --- src/lay/laySaltGrain.cc | 42 +++++++ src/lay/laySaltGrain.h | 10 ++ src/lay/laySaltGrainPropertiesDialog.cc | 155 +++++++++++++++++++++++- 3 files changed, 203 insertions(+), 4 deletions(-) diff --git a/src/lay/laySaltGrain.cc b/src/lay/laySaltGrain.cc index 4aee271f1..5d8227592 100644 --- a/src/lay/laySaltGrain.cc +++ b/src/lay/laySaltGrain.cc @@ -185,6 +185,48 @@ SaltGrain::compare_versions (const std::string &v1, const std::string &v2) } } +std::string +SaltGrain::spec_url (const std::string &url) +{ + std::string res = url; + if (! res.empty()) { + if (res [res.size () - 1] != '/') { + res += "/"; + } + res += grain_filename; + } + return res; +} + +bool +SaltGrain::valid_name (const std::string &n) +{ + std::string res; + + tl::Extractor ex (n); + + std::string s; + if (! ex.try_read_word (s, "_")) { + return false; + } + res += s; + + while (! ex.at_end ()) { + if (! ex.test ("/")) { + return false; + } + if (! ex.try_read_word (s, "_")) { + return false; + } + res += "/"; + res += s; + } + + // this captures the cases where the extractor skips blanks + // TODO: the extractor should have a "non-blank-skipping" mode + return s == n; +} + bool SaltGrain::valid_version (const std::string &v) { diff --git a/src/lay/laySaltGrain.h b/src/lay/laySaltGrain.h index 96af788a4..b237243ef 100644 --- a/src/lay/laySaltGrain.h +++ b/src/lay/laySaltGrain.h @@ -357,6 +357,11 @@ public: */ static bool valid_version (const std::string &v); + /** + * @brief Checks whether the given string is a valid name + */ + static bool valid_name (const std::string &n); + /** * @brief Detects a grain from the given directory * This method will return a grain constructed from the given directory. @@ -365,6 +370,11 @@ public: */ static SaltGrain from_path (const std::string &path); + /** + * @brief Forms the spec file download URL from a given download URL + */ + static std::string spec_url (const std::string &url); + /** * @brief Returns a value indicating whether the given path represents is a grain */ diff --git a/src/lay/laySaltGrainPropertiesDialog.cc b/src/lay/laySaltGrainPropertiesDialog.cc index eef2ddd40..a3042783e 100644 --- a/src/lay/laySaltGrainPropertiesDialog.cc +++ b/src/lay/laySaltGrainPropertiesDialog.cc @@ -24,6 +24,7 @@ #include "laySalt.h" #include "tlString.h" #include "tlExceptions.h" +#include "tlHttpStream.h" #include #include @@ -34,6 +35,8 @@ #include #include +#include +#include namespace lay { @@ -397,29 +400,173 @@ SaltGrainPropertiesDialog::remove_dependency_clicked () } } +namespace +{ + +class DependencyGraph +{ +public: + DependencyGraph (Salt *salt) + { + for (lay::Salt::flat_iterator i = salt->begin_flat (); i != salt->end_flat (); ++i) { + m_name_to_grain.insert (std::make_pair ((*i)->name (), *i)); + } + } + + bool is_valid_name (const std::string &name) const + { + return m_name_to_grain.find (name) != m_name_to_grain.end (); + } + + const lay::SaltGrain *grain_for_name (const std::string &name) const + { + std::map ::const_iterator n = m_name_to_grain.find (name); + if (n != m_name_to_grain.end ()) { + return n->second; + } else { + return 0; + } + } + + void check_circular (const lay::SaltGrain *current, const lay::SaltGrain *new_dep) + { + std::vector path; + path.push_back (current); + check_circular_follow (new_dep, path); + } + +private: + std::map m_name_to_grain; + + void check_circular_follow (const lay::SaltGrain *current, std::vector &path) + { + if (! current) { + return; + } + + path.push_back (current); + + for (std::vector ::const_iterator p = path.begin (); p != path.end () - 1; ++p) { + if (*p == current) { + circular_reference_error (path); + } + } + + for (std::vector::const_iterator d = current->dependencies ().begin (); d != current->dependencies ().end (); ++d) { + check_circular_follow (grain_for_name (d->name), path); + } + + path.pop_back (); + } + + void circular_reference_error (std::vector &path) + { + std::string msg = tl::to_string (QObject::tr ("The following path forms a circular dependency: ")); + for (std::vector ::const_iterator p = path.begin (); p != path.end (); ++p) { + if (p != path.begin ()) { + msg += "->"; + } + msg += (*p)->name (); + } + throw tl::Exception (msg); + } +}; + +} + void SaltGrainPropertiesDialog::accept () { update_data (); // Perform some checks + + // license license_alert->clear (); if (m_grain.license ().empty ()) { - license_alert->warn () << tr ("License field is empty. Please consider specifying a license model."); + license_alert->warn () << tr ("License field is empty. Please consider specifying a license model.") << tl::endl + << tr ("A license model tells users whether and how to use the source code of the package."); } + // version version_alert->clear (); if (m_grain.version ().empty ()) { - version_alert->warn () << tr ("Version field is empty. Please consider specifying a version number."); + version_alert->warn () << tr ("Version field is empty. Please consider specifying a version number.") << tl::endl + << tr ("Versions help the system to apply upgrades if required."); } 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 doc_url_alert->clear (); - // @@@ TODO + if (! m_grain.doc_url ().empty ()) { + tl::InputHttpStream stream (m_grain.doc_url ()); + try { + char b; + stream.read (&b, 1); + } catch (tl::Exception &ex) { + doc_url_alert->error () << tr ("Attempt to read documentation URL failed. Error details follow.") << tl::endl + << tr ("URL: ") << m_grain.doc_url () << tl::endl + << tr ("Message: ") << ex.msg (); + } + } + // dependencies dependencies_alert->clear (); - // @@@ TODO + DependencyGraph dep (mp_salt); + std::set dep_seen; + for (std::vector::const_iterator d = m_grain.dependencies ().begin (); d != m_grain.dependencies ().end (); ++d) { + + if (! SaltGrain::valid_name (d->name)) { + dependencies_alert->error () << tr ("'%1' is not a valid package name").arg (tl::to_qstring (d->name)) << tl::endl + << tr ("Valid package names are words (letters, digits, underscores)") << tl::endl + << tr ("Package groups can be specified in the form 'group/package'"); + continue; + } + + if (dep_seen.find (d->name) != dep_seen.end ()) { + dependencies_alert->error () << tr ("Duplicate dependency '%1'").arg (tl::to_qstring (d->name)) << tl::endl + << tr ("A package cannot be dependent on the same package twice. Remove on entry."); + continue; + } + dep_seen.insert (d->name); + + if (! dep.is_valid_name (d->name)) { + dependencies_alert->warn () << tr ("'%1' is not a name of a package loaded already").arg (tl::to_qstring (d->name)) << tl::endl + << tr ("You need to specify the details (version, URL) manually"); + } else { + try { + dep.check_circular (dep.grain_for_name (m_grain.name ()), dep.grain_for_name (d->name)); + } catch (tl::Exception &ex) { + dependencies_alert->error () << ex.msg () << tl::endl + << tr ("Circular dependency means a package is eventually depending on itself."); + } + } + + if (d->version.empty ()) { + dependencies_alert->warn () << tr ("No version specified for dependency '%1'").arg (tl::to_qstring (d->name)) << tl::endl + << tr ("Versions help checking dependencies.") << tl::endl + << tr ("If the dependency package has a version itself, the version is automatically set to it's current version"); + } + + if (d->url.empty ()) { + dependencies_alert->warn () << tr ("No download URL specified for dependency '%1'").arg (tl::to_qstring (d->name)) << tl::endl + << tr ("A download URL should be specified to ensure the package dependencies can be resolved.") << tl::endl + << tr ("If the dependency package was downloaded itself, the URL is automatically set to the download source"); + } else { + std::string spec_url = SaltGrain::spec_url (d->url); + tl::InputHttpStream stream (spec_url); + try { + char b; + stream.read (&b, 1); + } catch (tl::Exception &ex) { + dependencies_alert->error () << tr ("Attempt to read download URL failed. Error details follow.") << tl::endl + << tr ("URL: ") << spec_url << tl::endl + << tr ("Message: ") << ex.msg (); + } + } + + } if (!license_alert->needs_attention () && !doc_url_alert->needs_attention () && From 10345eea7371e99182bdfb77959e585b9b29c264 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 21 Mar 2017 12:18:09 +0100 Subject: [PATCH 15/57] Restyling of log viewer Now the entries have icons telling the type of message. This is more convenient when multi-line messages are encountered. --- src/lay/LogViewerDialog.ui | 14 +++++++++++++- src/lay/images/empty_16.png | Bin 0 -> 149 bytes src/lay/images/error_16.png | Bin 0 -> 674 bytes src/lay/images/info_16.png | Bin 0 -> 499 bytes src/lay/images/warn_16.png | Bin 0 -> 637 bytes src/lay/layLogViewerDialog.cc | 17 ++++++++++++----- src/lay/layResources.qrc | 4 ++++ src/lay/laySaltGrainPropertiesDialog.cc | 12 ++++++------ 8 files changed, 35 insertions(+), 12 deletions(-) create mode 100644 src/lay/images/empty_16.png create mode 100644 src/lay/images/error_16.png create mode 100644 src/lay/images/info_16.png create mode 100644 src/lay/images/warn_16.png diff --git a/src/lay/LogViewerDialog.ui b/src/lay/LogViewerDialog.ui index 41834aa0c..889342ade 100644 --- a/src/lay/LogViewerDialog.ui +++ b/src/lay/LogViewerDialog.ui @@ -88,12 +88,24 @@ + + + 16 + 16 + + + + false + QListView::Adjust true + + false + @@ -157,7 +169,7 @@ - :/warn.png + :/warn_16.png diff --git a/src/lay/images/empty_16.png b/src/lay/images/empty_16.png new file mode 100644 index 0000000000000000000000000000000000000000..10b1f1be69386ddc01a2e7bfea83c208f1210e5a GIT binary patch literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf4nJ zFy8=S#yt0AQ=p(^iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$Qb0vQo-U3d l7QM*{60D08M0%JwfQAb&FxLB4^Z{iVJYD@<);T3K0RT{2BXa-% literal 0 HcmV?d00001 diff --git a/src/lay/images/error_16.png b/src/lay/images/error_16.png new file mode 100644 index 0000000000000000000000000000000000000000..c9dad1310711826bf2c1dd147eca79606aefe4b2 GIT binary patch literal 674 zcmV;T0$u%yP)8*v!MpYNrYN-kv4G=nk8kTiF_LMgU`U>xe; zY)*tWPJ!g6fD%Z0|3NuR_Yk=bDiV||f`eO4ai~8iNw4QxV~cYjriI?U9io9q6Djmv z-r@Z|&-=U|zQF&kVzDSElgW=_Jl+umfdoMyNfbMZq8wxc#djGdhUySsqf4R}0&Ab?%>FEcaFi%f>4(`i}Pb;|_+ zfk2>|n3@W`tk)U#_kp7$Fqf-q!1I9H4aV~T&*MR-!+wOr_;!B&#ImgS8~~W6`7AXt z!RYW1xV#)rx!p!8k-%5ed=3Ej0O0$@#fa2u_1?HnPX4N;l@$QMW1o*To6Qjb;JPFj z4PSf92ms?a>Lmz{kN>iKzWYzFq1uy^J!pLpJstpn*=kWR82o8jRyV;TSw7xuHi6O6 zyVrZY*r?SY%krn*3DIaYyIHBgQdI|x4Hh$ox+pqZl?r09ShjDbs_JSa5}`)9Z2z8} zC3||B?76wVkMwza+YW_7q^jzhp66|oYPFhwwYZ3sBtdq$7P*#nx;Lx8`3}4AB3RNj1pk!dH?_b07*qo IM6N<$f&?lsN&o-= literal 0 HcmV?d00001 diff --git a/src/lay/images/info_16.png b/src/lay/images/info_16.png new file mode 100644 index 0000000000000000000000000000000000000000..d1fa366446e37390cac4e7ef610c24115660d29d GIT binary patch literal 499 zcmVMWLn|PCy!h8=)p#fXW*e#HJ`D5dYoe&Hv5+W`KW6t$XPA`}?M8+5ljh zW>_wl#~T59z2GF9-MP)>c56`-VLsPjW?`l$W^Ox<^P29bbm9B{Wj=rCJC0qOPN#_D zuk@gE0NpSQTPqWEI{s;~c+?XS8jmLs(I}OQ0D#fx9n1t~1`)?Zs%}}<8vqyyN$Kn1 za7aom8kv{ErDZCmZU9^)2|Uls5g8xMq=+~HEGE*gn!!w3L=G&=iUmMFrqF7G;th$H|aJpiD$@as5^2MM6rY)+KI^A>=PZQGv-005Li8$i5) zd8V~)C%*JcxBJp!raNXf*IZ$ylyOxkSPws*rDHc5XZL`v1MslC6Aj=|MCxhtp9EFaKj!`_Q(N@y`IA9@_HdAGO&b7I-`l~UxITBnDDwI~ z=FcNMd)z1~C06>~k7$#&KfKt({K;d^C-~i)Vb+=o0Nl=D(b=#qacgnjQmIh|NCG4w zoepAt&JJlUuKh1PsZIKke;a4cc?^I7Fqii8MmEW&DartbdlI-d-MxCr3B+RZPkKX= z5Z@cZ<&}6K99DOFr61ZI7UkNO&z$p^Hxn&_+8A?vg15iVayYC?01k8!47hav@x9Zg z3c1aT954aXV4>Wv9A5;6SF^i!#kQ0Z6oJ#&7Bc{9OW7;O>M4>Q+KGS|;>?dKI9 z8gh#OW*P+2vB-b=Xj#+FI=~;B1Y8*d*AD{F%~HxiX6=max|R=sgp^oJr&M(O$P!Sz z-NKiI$>|)9O=a5*0FAQ?lkmK#C#75vj;7TInP~^rmjGwm41^FMl3fNwHZp4$Y}Yk! z2C6mevW+i5*#{K08N7Zq4j|fP02oLnVyIMVaTta?uPrJxKCIs|3{|wO(8XRi(YJmB X_oS&DXk~uz00000NkvXXu0mjfA= 0) { LogFileEntry::mode_type mode = m_messages [index.row ()].mode (); - std::string message = m_messages [index.row ()].text (); if (mode == LogFileEntry::Error) { - return QVariant (tl::to_qstring (tl::to_string (QObject::tr ("ERROR: ")) + message)); + return QIcon (QString::fromUtf8 (":/error_16.png")); } else if (mode == LogFileEntry::Warning) { - return QVariant (tl::to_qstring (tl::to_string (QObject::tr ("Warning: ")) + message)); + return QIcon (QString::fromUtf8 (":/warn_16.png")); + } else if (mode == LogFileEntry::Info) { + return QIcon (QString::fromUtf8 (":/info_16.png")); } else { - return QVariant (tl::to_qstring (message)); + return QIcon (QString::fromUtf8 (":/empty_16.png")); } + } + + } else if (role == Qt::DisplayRole) { + + if (index.row () < int (m_messages.size ()) && index.row () >= 0) { + return QVariant (tl::to_qstring (m_messages [index.row ()].text ())); } } else if (role == Qt::FontRole) { diff --git a/src/lay/layResources.qrc b/src/lay/layResources.qrc index ef87d4e5f..3ffd9e81e 100644 --- a/src/lay/layResources.qrc +++ b/src/lay/layResources.qrc @@ -116,6 +116,10 @@ images/salt.png images/salt_icon.png images/warn.png + images/warn_16.png + images/empty_16.png + images/error_16.png + images/info_16.png syntax/ruby.xml diff --git a/src/lay/laySaltGrainPropertiesDialog.cc b/src/lay/laySaltGrainPropertiesDialog.cc index a3042783e..88fd3d652 100644 --- a/src/lay/laySaltGrainPropertiesDialog.cc +++ b/src/lay/laySaltGrainPropertiesDialog.cc @@ -519,8 +519,8 @@ SaltGrainPropertiesDialog::accept () if (! SaltGrain::valid_name (d->name)) { dependencies_alert->error () << tr ("'%1' is not a valid package name").arg (tl::to_qstring (d->name)) << tl::endl - << tr ("Valid package names are words (letters, digits, underscores)") << tl::endl - << tr ("Package groups can be specified in the form 'group/package'"); + << tr ("Valid package names are words (letters, digits, underscores).") << tl::endl + << tr ("Package groups can be specified in the form 'group/package'."); continue; } @@ -539,20 +539,20 @@ SaltGrainPropertiesDialog::accept () dep.check_circular (dep.grain_for_name (m_grain.name ()), dep.grain_for_name (d->name)); } catch (tl::Exception &ex) { dependencies_alert->error () << ex.msg () << tl::endl - << tr ("Circular dependency means a package is eventually depending on itself."); + << tr ("Circular dependency means, a package is eventually depending on itself."); } } if (d->version.empty ()) { dependencies_alert->warn () << tr ("No version specified for dependency '%1'").arg (tl::to_qstring (d->name)) << tl::endl - << tr ("Versions help checking dependencies.") << tl::endl - << tr ("If the dependency package has a version itself, the version is automatically set to it's current version"); + << tr ("Please consider giving a version here. Versions help deciding whether a package needs to be updated.") << tl::endl + << tr ("If the dependency package has a version itself, the version is automatically set to it's current version."); } if (d->url.empty ()) { dependencies_alert->warn () << tr ("No download URL specified for dependency '%1'").arg (tl::to_qstring (d->name)) << tl::endl << tr ("A download URL should be specified to ensure the package dependencies can be resolved.") << tl::endl - << tr ("If the dependency package was downloaded itself, the URL is automatically set to the download source"); + << tr ("If the dependency package was downloaded itself, the URL is automatically set to the download source."); } else { std::string spec_url = SaltGrain::spec_url (d->url); tl::InputHttpStream stream (spec_url); From 820c2916234cb148a690a616d3b203013d1a9c38 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 21 Mar 2017 22:20:24 +0100 Subject: [PATCH 16/57] WIP: salt package manager: features for installer First steps towards installer support. Specifically: - basic installation methods, basic framework - file utilities for directory copy Side effect: temp directories of unit tests are now cleared prior to test run. --- src/lay/lay.pro | 6 +- src/lay/laySalt.cc | 180 +++++++++++++++++++++++- src/lay/laySalt.h | 56 ++++++-- src/lay/laySaltDownloadManager.cc | 40 ++++++ src/lay/laySaltDownloadManager.h | 60 ++++++++ src/lay/laySaltGrainPropertiesDialog.cc | 4 +- src/lay/laySaltManagerDialog.cc | 1 + src/tl/tlFileUtils.cc | 92 +++++++++++- src/tl/tlFileUtils.h | 42 +++++- src/unit_tests/laySalt.cc | 8 +- src/unit_tests/tlFileUtils.cc | 155 ++++++++++++++++++++ src/unit_tests/unit_tests.pro | 3 +- src/ut/utMain.cc | 7 +- 13 files changed, 623 insertions(+), 31 deletions(-) create mode 100644 src/lay/laySaltDownloadManager.cc create mode 100644 src/lay/laySaltDownloadManager.h create mode 100644 src/unit_tests/tlFileUtils.cc diff --git a/src/lay/lay.pro b/src/lay/lay.pro index da3d53cb0..4c4c804f7 100644 --- a/src/lay/lay.pro +++ b/src/lay/lay.pro @@ -51,7 +51,8 @@ HEADERS = \ laySaltGrains.h \ laySaltManagerDialog.h \ laySaltGrainDetailsTextWidget.h \ - laySaltGrainPropertiesDialog.h + laySaltGrainPropertiesDialog.h \ + laySaltDownloadManager.h FORMS = \ ClipDialog.ui \ @@ -147,7 +148,8 @@ SOURCES = \ laySaltGrains.cc \ laySaltManagerDialog.cc \ laySaltGrainDetailsTextWidget.cc \ - laySaltGrainPropertiesDialog.cc + laySaltGrainPropertiesDialog.cc \ + laySaltDownloadManager.cc RESOURCES = layBuildInMacros.qrc \ layHelpResources.qrc \ diff --git a/src/lay/laySalt.cc b/src/lay/laySalt.cc index 24559a2d1..156cf0ec3 100644 --- a/src/lay/laySalt.cc +++ b/src/lay/laySalt.cc @@ -21,9 +21,14 @@ */ #include "laySalt.h" +#include "laySaltDownloadManager.h" #include "tlString.h" +#include "tlFileUtils.h" +#include "tlLog.h" +#include "tlInternational.h" #include +#include namespace lay { @@ -47,6 +52,32 @@ Salt &Salt::operator= (const Salt &other) return *this; } +Salt::flat_iterator +Salt::begin_flat () +{ + validate (); + return mp_flat_grains.begin (); +} + +Salt::flat_iterator +Salt::end_flat () +{ + validate (); + return mp_flat_grains.end (); +} + +SaltGrain * +Salt::grain_by_name (const std::string &name) +{ + validate (); + std::map::const_iterator g = m_grains_by_name.find (name); + if (g != m_grains_by_name.end ()) { + return g->second; + } else { + return 0; + } +} + void Salt::add_location (const std::string &path) { @@ -60,8 +91,7 @@ Salt::add_location (const std::string &path) lay::SaltGrains gg = lay::SaltGrains::from_path (path); m_root.add_collection (gg); - mp_flat_grains.clear (); - emit collections_changed (); + invalidate (); } void @@ -71,8 +101,7 @@ Salt::remove_location (const std::string &path) for (lay::SaltGrains::collection_iterator g = m_root.begin_collections (); g != m_root.end_collections (); ++g) { if (QFileInfo (tl::to_qstring (g->path ())) == fi) { m_root.remove_collection (g, false); - mp_flat_grains.clear (); - emit collections_changed (); + invalidate (); return; } } @@ -87,8 +116,7 @@ Salt::refresh () } if (new_root != m_root) { m_root = new_root; - mp_flat_grains.clear (); - emit collections_changed (); + invalidate (); } } @@ -119,12 +147,150 @@ struct NameCompare } void -Salt::ensure_flat_present () +Salt::validate () { if (mp_flat_grains.empty ()) { + add_collection_to_flat (m_root); + + m_grains_by_name.clear (); + for (std::vector::const_iterator i = mp_flat_grains.begin (); i != mp_flat_grains.end (); ++i) { + m_grains_by_name.insert (std::make_pair ((*i)->name (), *i)); + } + + // NOTE: we intentionally sort after the name list has been built - this way + // the first entry will win in the name to grain map. std::sort (mp_flat_grains.begin (), mp_flat_grains.end (), NameCompare ()); + } } +void +Salt::invalidate () +{ + mp_flat_grains.clear (); + emit collections_changed (); +} + + +static +bool remove_from_collection (SaltGrains &collection, const std::string &name) +{ + bool res = false; + + for (SaltGrains::grain_iterator g = collection.begin_grains (); g != collection.end_grains (); ++g) { + if (g->name () == name) { + SaltGrains::grain_iterator gnext = g; + ++gnext; + collection.remove_grain (g, true); + res = true; + } + } + + for (SaltGrains::collection_iterator gg = collection.begin_collections (); gg != collection.end_collections (); ++gg) { + // TODO: remove this const_cast + if (remove_from_collection (const_cast (*gg), name)) { + res = true; + } + } + + return res; +} + +bool +Salt::remove_grain (const SaltGrain &grain) +{ + tl::info << QObject::tr ("Removing package '%1' ..").arg (tl::to_qstring (grain.name ())); + if (remove_from_collection (m_root, grain.name ())) { + tl::info << QObject::tr ("Package '%1' removed.").arg (tl::to_qstring (grain.name ())); + invalidate (); + return true; + } else { + tl::warn << QObject::tr ("Failed to remove package '%1'.").arg (tl::to_qstring (grain.name ())); + return false; + } +} + +bool +Salt::create_grain (const SaltGrain &templ, SaltGrain &target, SaltDownloadManager &download_manager) +{ + tl_assert (!m_root.is_empty ()); + + const SaltGrains *coll = m_root.begin_collections ().operator-> (); + + std::string path = target.path (); + if (! path.empty ()) { + coll = 0; + for (SaltGrains::collection_iterator gg = m_root.begin_collections (); gg != m_root.end_collections (); ++gg) { + if (tl::is_parent_path (tl::to_qstring (gg->path ()), tl::to_qstring (path))) { + coll = gg.operator-> (); + break; + } + } + tl_assert (coll != 0); + } + + tl::info << QObject::tr ("Installing package '%1' ..").arg (tl::to_qstring (target.name ())); + + QDir target_dir (tl::to_qstring (coll->path ())); + + try { + + // change down to the desired target location and create the directory structure while doing so + std::vector name_parts = tl::split (target.name (), "/"); + for (std::vector::const_iterator n = name_parts.begin (); n != name_parts.end (); ++n) { + QDir subdir (target_dir.filePath (tl::to_qstring (*n))); + if (! subdir.exists ()) { + if (! target_dir.mkdir (tl::to_qstring (*n))) { + throw tl::Exception (tl::to_string (tr ("Unable to create target directory '%1' for installing package").arg (subdir.path ()))); + } + if (! target_dir.cd (tl::to_qstring (*n))) { + throw tl::Exception (tl::to_string (tr ("Unable to change to target directory '%1' for installing package").arg (subdir.path ()))); + } + } + } + + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + return false; + } + + bool res = true; + + target = templ; + target.set_path (tl::to_string (target_dir.absolutePath ())); + + if (! templ.path ().empty ()) { + + // if the template represents an actual folder, use the files from there + tl::info << QObject::tr ("Copying package from '%1' to '%2' ..").arg (tl::to_qstring (templ.path ())).arg (tl::to_qstring (target.path ())); + res = tl::cp_dir_recursive (templ.path (), target.path ()); + + } else if (! templ.url ().empty ()) { + + // otherwise download from the URL + tl::info << QObject::tr ("Downloading package from '%1' to '%2' ..").arg (tl::to_qstring (templ.url ())).arg (tl::to_qstring (target.path ())); + res = download_manager.download (templ.url (), target.path ()); + + } + + if (res) { + + tl::info << QObject::tr ("Package '%1' installed").arg (tl::to_qstring (target.name ())); + + target.set_installed_time (QDateTime::currentDateTime ()); + target.save (); + + } else { + + tl::warn << QObject::tr ("Failed to install package '%1' - removing files ..").arg (tl::to_qstring (target.name ())); + if (! tl::rm_dir_recursive (target.path ())) { + tl::warn << QObject::tr ("Failed to remove files").arg (tl::to_qstring (target.name ())); + } + + } + + return res; +} + } diff --git a/src/lay/laySalt.h b/src/lay/laySalt.h index 19d21145f..4ade8acf9 100644 --- a/src/lay/laySalt.h +++ b/src/lay/laySalt.h @@ -29,9 +29,13 @@ #include +#include + namespace lay { +class SaltDownloadManager; + /** * @brief The global salt (package manager) object * This object can be configured to represent a couple of locations. @@ -107,20 +111,48 @@ public: /** * @brief A flat iterator of (sorted) grains (begin) */ - flat_iterator begin_flat () - { - ensure_flat_present (); - return mp_flat_grains.begin (); - } + flat_iterator begin_flat (); /** * @brief A flat iterator of (sorted) grains (end) */ - flat_iterator end_flat () - { - ensure_flat_present (); - return mp_flat_grains.end (); - } + flat_iterator end_flat (); + + /** + * @brief Gets the grain with the given name + */ + SaltGrain *grain_by_name (const std::string &name); + + /** + * @brief Removes a grain from the salt + * + * This operation will remove the grain with the given name from the salt and delete all files and directories related to it. + * If multiple grains with the same name exist, they will all be removed. + * + * Returns true, if the package could be removed successfully. + */ + bool remove_grain (const SaltGrain &grain); + + /** + * @brief Creates a new grain from a template + * + * This method will create a folder for a grain with the given path and download or copy + * all files related to this grain. It will copy the download URL from the template into the + * new grain, so updates will come from the original location. + * + * The target's name must be set. If a specific target location is desired, the target's + * path must be set too. + * + * This method refuses to overwrite existing grains, so an update needs to be performed by first + * deleting the grain and then re-installing it. + * + * The target grain will be updated with the installation information. If the target grain + * contains an installation path prior to the installation, this path will be used for the + * installation of the grain files. + * + * Returns true, if the package could be created successfully. + */ + bool create_grain (const SaltGrain &templ, SaltGrain &target, SaltDownloadManager &download_manager); signals: /** @@ -131,8 +163,10 @@ signals: private: SaltGrains m_root; std::vector mp_flat_grains; + std::map m_grains_by_name; - void ensure_flat_present (); + void validate (); + void invalidate (); void add_collection_to_flat (lay::SaltGrains &gg); }; diff --git a/src/lay/laySaltDownloadManager.cc b/src/lay/laySaltDownloadManager.cc new file mode 100644 index 000000000..f6970b851 --- /dev/null +++ b/src/lay/laySaltDownloadManager.cc @@ -0,0 +1,40 @@ + +/* + + 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 "laySaltDownloadManager.h" + +namespace lay +{ + +SaltDownloadManager::SaltDownloadManager () +{ + // .. nothing yet .. +} + +bool +SaltDownloadManager::download (const std::string &url, const std::string &target_dir) +{ + // @@@ + return false; +} + +} diff --git a/src/lay/laySaltDownloadManager.h b/src/lay/laySaltDownloadManager.h new file mode 100644 index 000000000..141df238a --- /dev/null +++ b/src/lay/laySaltDownloadManager.h @@ -0,0 +1,60 @@ + +/* + + 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_laySaltDownloadManager +#define HDR_laySaltDownloadManager + +#include "layCommon.h" + +#include +#include + +namespace lay +{ + +/** + * @brief The download manager + * This class is responsible for handling the downloads for + * grains. + */ +class LAY_PUBLIC SaltDownloadManager + : public QObject +{ +Q_OBJECT + +public: + /** + * @brief Default constructor + */ + SaltDownloadManager (); + + /** + * @brief Downloads the files from the given URL to the given target location + * The target directory needs to exist. + * Returns true, if the download was successful, false otherwise. + */ + bool download (const std::string &url, const std::string &target_dir); +}; + +} + +#endif diff --git a/src/lay/laySaltGrainPropertiesDialog.cc b/src/lay/laySaltGrainPropertiesDialog.cc index 88fd3d652..ab4e08d43 100644 --- a/src/lay/laySaltGrainPropertiesDialog.cc +++ b/src/lay/laySaltGrainPropertiesDialog.cc @@ -593,8 +593,10 @@ SaltGrainPropertiesDialog::exec_dialog (lay::SaltGrain *grain, lay::Salt *salt) update_controls (); bool res = exec (); - if (res) { + if (res && *grain != m_grain) { *grain = m_grain; + // save modified grain + grain->save (); } delete dependencies->itemDelegateForColumn (0); diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index 8c83aa9f8..7730310bc 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -313,6 +313,7 @@ SaltManagerDialog::current_changed () } else { details_frame->setEnabled (true); delete_button->setEnabled (true); + edit_button->setEnabled (! g->is_readonly ()); } } diff --git a/src/tl/tlFileUtils.cc b/src/tl/tlFileUtils.cc index 518bb7989..9ae545118 100644 --- a/src/tl/tlFileUtils.cc +++ b/src/tl/tlFileUtils.cc @@ -21,6 +21,8 @@ */ #include "tlFileUtils.h" +#include "tlLog.h" +#include "tlInternational.h" #include #include @@ -28,10 +30,27 @@ namespace tl { +bool +is_parent_path (const QString &parent, const QString &path) +{ + QFileInfo parent_info (parent); + QFileInfo path_info (path); + + while (parent_info != path_info) { + path_info = path_info.path (); + if (path_info.isRoot ()) { + return false; + } + } + + return true; +} + bool rm_dir_recursive (const QString &path) { QDir dir (path); + QStringList entries = dir.entryList (QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) { QFileInfo fi (dir.absoluteFilePath (*e)); @@ -39,15 +58,82 @@ rm_dir_recursive (const QString &path) if (! rm_dir_recursive (fi.filePath ())) { return false; } - if (! dir.rmdir (*e)) { - return false; - } } else if (fi.isFile ()) { if (! dir.remove (*e)) { + tl::error << QObject::tr ("Unable to remove file: %1").arg (dir.absoluteFilePath (*e)); return false; } } } + + QString name = dir.dirName (); + if (dir.cdUp ()) { + if (! dir.rmdir (name)) { + tl::error << QObject::tr ("Unable to remove directory: %1").arg (dir.absoluteFilePath (name)); + return false; + } + } + + return true; +} + +bool +cp_dir_recursive (const QString &source, const QString &target) +{ + QDir dir (source); + QDir dir_target (target); + + QStringList entries = dir.entryList (QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) { + + QFileInfo fi (dir.absoluteFilePath (*e)); + QFileInfo fi_target (dir_target.absoluteFilePath (*e)); + + if (fi.isDir ()) { + + // Copy subdirectory + if (! fi_target.exists ()) { + if (! dir_target.mkdir (*e)) { + tl::error << QObject::tr ("Unable to create target directory: %1").arg (dir_target.absoluteFilePath (*e)); + return false; + } + } else if (! fi_target.isDir ()) { + tl::error << QObject::tr ("Unable to create target directory (is a file already): %1").arg (dir_target.absoluteFilePath (*e)); + return false; + } + if (! cp_dir_recursive (fi.filePath (), fi_target.filePath ())) { + return false; + } + + // TODO: leave symlinks symlinks? How to copy symlinks with Qt? + } else if (fi.isFile ()) { + + QFile file (fi.filePath ()); + QFile file_target (fi_target.filePath ()); + + if (! file.open (QIODevice::ReadOnly)) { + tl::error << QObject::tr ("Unable to open source file for reading: %1").arg (fi.filePath ()); + return false; + } + if (! file_target.open (QIODevice::WriteOnly)) { + tl::error << QObject::tr ("Unable to open target file for writing: %1").arg (fi_target.filePath ()); + return false; + } + + size_t chunk_size = 64 * 1024; + + while (! file.atEnd ()) { + QByteArray data = file.read (chunk_size); + file_target.write (data); + } + + file.close (); + file_target.close (); + + } + + } + return true; } diff --git a/src/tl/tlFileUtils.h b/src/tl/tlFileUtils.h index 01c171a99..be25bd843 100644 --- a/src/tl/tlFileUtils.h +++ b/src/tl/tlFileUtils.h @@ -24,17 +24,57 @@ #define HDR_tlFileUtils #include "tlCommon.h" +#include "tlString.h" #include namespace tl { +/** + * @brief Returns a value indicating whether the parent path is a parent directory of the path + */ +bool TL_PUBLIC is_parent_path (const QString &parent, const QString &path); + +/** + * @brief Returns a value indicating whether the parent path is a parent directory of the path (version with std::string) + */ +inline bool TL_PUBLIC is_parent_path (const std::string &parent, const std::string &path) +{ + return is_parent_path (tl::to_qstring (parent), tl::to_qstring (path)); +} + /** * @brief Recursively remove the given directory, the files from that directory and all sub-directories - * @return True, if successful. False otherwise. + * @return True, if successful. false otherwise. */ bool TL_PUBLIC rm_dir_recursive (const QString &path); +/** + * @brief Recursively remove the given directory, the files from that directory and all sub-directories (version with std::string) + * @return True, if successful. false otherwise. + */ +inline bool TL_PUBLIC rm_dir_recursive (const std::string &path) +{ + return rm_dir_recursive (tl::to_qstring (path)); +} + +/** + * @brief Recursively copies a given directory to a target directory + * Both target and source directories need to exist. New directories are created in the target + * directory if required. + * @return True, if successful. false otherwise. + */ +bool TL_PUBLIC cp_dir_recursive (const QString &source, const QString &target); + +/** + * @brief Recursively remove the given directory, the files from that directory and all sub-directories (version with std::string) + * @return True, if successful. false otherwise. + */ +inline bool TL_PUBLIC cp_dir_recursive (const std::string &source, const std::string &target) +{ + return cp_dir_recursive (tl::to_qstring (source), tl::to_qstring (target)); +} + } #endif diff --git a/src/unit_tests/laySalt.cc b/src/unit_tests/laySalt.cc index 7eb557499..62a62826a 100644 --- a/src/unit_tests/laySalt.cc +++ b/src/unit_tests/laySalt.cc @@ -201,8 +201,6 @@ TEST (3) QDir dir_cc (dir_c.filePath (QString::fromUtf8 ("c"))); QDir dir_ccv (dir_cc.filePath (QString::fromUtf8 ("v"))); - tl_assert (tl::rm_dir_recursive (tmp_dir.path ())); - lay::SaltGrains gg; gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); EXPECT_EQ (gg.is_empty (), true); @@ -275,8 +273,6 @@ TEST (4) QDir dir_cc (dir_c.filePath (QString::fromUtf8 ("c"))); QDir dir_ccv (dir_cc.filePath (QString::fromUtf8 ("v"))); - tl_assert (tl::rm_dir_recursive (tmp_dir.path ())); - lay::SaltGrains gg; gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); EXPECT_EQ (gg.is_empty (), true); @@ -341,4 +337,8 @@ TEST (4) salt.remove_location (tl::to_string (dir_c.path ())); EXPECT_EQ (spy.count (), 0); EXPECT_EQ (salt_to_string (salt), "[b,c/c/v,c/u]"); + + EXPECT_EQ (salt.grain_by_name ("x"), 0); + EXPECT_EQ (salt.grain_by_name ("b")->name (), "b"); + EXPECT_EQ (salt.grain_by_name ("c/c/v")->name (), "c/c/v"); } diff --git a/src/unit_tests/tlFileUtils.cc b/src/unit_tests/tlFileUtils.cc new file mode 100644 index 000000000..7eb3032a8 --- /dev/null +++ b/src/unit_tests/tlFileUtils.cc @@ -0,0 +1,155 @@ + +/* + + 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 "tlFileUtils.h" +#include "utHead.h" + +#include +#include +#include + +TEST (1) +{ + EXPECT_EQ (tl::is_parent_path (std::string ("/home"), "/home/matthias"), true); + EXPECT_EQ (tl::is_parent_path (std::string ("/home"), "/home"), true); + EXPECT_EQ (tl::is_parent_path (std::string (""), ""), true); + EXPECT_EQ (tl::is_parent_path (std::string ("/opt/klayout"), "/home/matthias"), false); + EXPECT_EQ (tl::is_parent_path (std::string ("/home/klayout"), "/home/matthias"), false); +} + +TEST (2) +{ + QDir tmp_dir = QFileInfo (tl::to_qstring (tmp_file ())).absoluteDir (); + tmp_dir.mkdir (QString::fromUtf8 ("a")); + + QDir adir = tmp_dir; + adir.cd (QString::fromUtf8 ("a")); + + EXPECT_EQ (adir.exists (), true); + EXPECT_EQ (tl::rm_dir_recursive (adir.absolutePath ()), true); + EXPECT_EQ (adir.exists (), false); + + tmp_dir.mkdir (QString::fromUtf8 ("a")); + EXPECT_EQ (adir.exists (), true); + + EXPECT_EQ (tl::rm_dir_recursive (tl::to_string (adir.absolutePath ())), true); + EXPECT_EQ (adir.exists (), false); + + tmp_dir.mkdir (QString::fromUtf8 ("a")); + EXPECT_EQ (adir.exists (), true); + + adir.mkdir (QString::fromUtf8 ("b1")); + QDir b1dir = adir; + b1dir.cd (QString::fromUtf8 ("b1")); + + adir.mkdir (QString::fromUtf8 ("b2")); + QDir b2dir = adir; + b2dir.cd (QString::fromUtf8 ("b2")); + + { + QFile file (b2dir.absoluteFilePath (QString::fromUtf8 ("x"))); + file.open (QIODevice::WriteOnly); + file.write ("hello, world!\n"); + file.close (); + } + + { + QFile file (b2dir.absoluteFilePath (QString::fromUtf8 ("y"))); + file.open (QIODevice::WriteOnly); + file.write ("hello, world!\n"); + file.close (); + } + + EXPECT_EQ (adir.exists (), true); + EXPECT_EQ (tl::rm_dir_recursive (adir.absolutePath ()), true); + EXPECT_EQ (adir.exists (), false); + EXPECT_EQ (b1dir.exists (), false); + EXPECT_EQ (b2dir.exists (), false); + EXPECT_EQ (QFileInfo (b2dir.absoluteFilePath (QString::fromUtf8 ("x"))).exists (), false); +} + +TEST (3) +{ + QDir tmp_dir = QFileInfo (tl::to_qstring (tmp_file ())).absoluteDir (); + + tl::rm_dir_recursive (tmp_dir.filePath (QString::fromUtf8 ("a"))); + tmp_dir.mkdir (QString::fromUtf8 ("a")); + + QDir adir = tmp_dir; + adir.cd (QString::fromUtf8 ("a")); + + adir.mkdir (QString::fromUtf8 ("b1")); + QDir b1dir = adir; + b1dir.cd (QString::fromUtf8 ("b1")); + + adir.mkdir (QString::fromUtf8 ("b2")); + QDir b2dir = adir; + b2dir.cd (QString::fromUtf8 ("b2")); + + { + QFile file (b2dir.absoluteFilePath (QString::fromUtf8 ("x"))); + file.open (QIODevice::WriteOnly); + file.write ("hello, world!\n"); + file.close (); + } + + { + QFile file (b2dir.absoluteFilePath (QString::fromUtf8 ("y"))); + file.open (QIODevice::WriteOnly); + file.write ("hello, world II!\n"); + file.close (); + } + + tl::rm_dir_recursive (tmp_dir.filePath (QString::fromUtf8 ("acopy"))); + tmp_dir.mkdir (QString::fromUtf8 ("acopy")); + + tl::cp_dir_recursive (tmp_dir.absoluteFilePath (QString::fromUtf8 ("a")), tmp_dir.absoluteFilePath (QString::fromUtf8 ("acopy"))); + + QDir acopydir = tmp_dir; + EXPECT_EQ (acopydir.cd (QString::fromUtf8 ("acopy")), true); + EXPECT_EQ (acopydir.exists (), true); + + QDir b1copydir = acopydir; + EXPECT_EQ (b1copydir.cd (QString::fromUtf8 ("b1")), true); + EXPECT_EQ (b1copydir.exists (), true); + + QDir b2copydir = acopydir; + EXPECT_EQ (b2copydir.cd (QString::fromUtf8 ("b2")), true); + EXPECT_EQ (b2copydir.exists (), true); + + { + QFile file (b2copydir.absoluteFilePath (QString::fromUtf8 ("x"))); + EXPECT_EQ (file.exists (), true); + file.open (QIODevice::ReadOnly); + EXPECT_EQ (file.readAll ().constData (), "hello, world!\n"); + file.close (); + } + + { + QFile file (b2copydir.absoluteFilePath (QString::fromUtf8 ("y"))); + EXPECT_EQ (file.exists (), true); + file.open (QIODevice::ReadOnly); + EXPECT_EQ (file.readAll ().constData (), "hello, world II!\n"); + file.close (); + } +} diff --git a/src/unit_tests/unit_tests.pro b/src/unit_tests/unit_tests.pro index 547d7616c..31dd201a4 100644 --- a/src/unit_tests/unit_tests.pro +++ b/src/unit_tests/unit_tests.pro @@ -96,7 +96,8 @@ SOURCES = \ tlXMLParser.cc \ gsiTest.cc \ tlFileSystemWatcher.cc \ - laySalt.cc + laySalt.cc \ + tlFileUtils.cc # main components: SOURCES += \ diff --git a/src/ut/utMain.cc b/src/ut/utMain.cc index 8a563e177..359e2bad5 100644 --- a/src/ut/utMain.cc +++ b/src/ut/utMain.cc @@ -26,6 +26,7 @@ #include "pya.h" #include "tlStaticObjects.h" #include "tlTimer.h" +#include "tlFileUtils.h" #include "layApplication.h" #include "gsiExpression.h" #include "gsiExternalMain.h" @@ -518,8 +519,12 @@ bool TestBase::do_test (const std::string & /*mode*/) { // Ensures the test temp directory is present QDir dir (testtmp ()); + QDir tmpdir (dir.absoluteFilePath (tl::to_qstring (m_testdir))); + if (tmpdir.exists () && ! tl::rm_dir_recursive (tmpdir.absolutePath ())) { + throw tl::Exception ("Unable to clean temporary dir: " + tl::to_string (tmpdir.absolutePath ())); + } if (! dir.mkpath (tl::to_qstring (m_testdir))) { - throw tl::Exception ("Unable to create path for temporary files: " + tl::to_string (dir.filePath (tl::to_qstring (m_test)))); + throw tl::Exception ("Unable to create path for temporary files: " + tl::to_string (tmpdir.absolutePath ())); } dir.cd (tl::to_qstring (m_testdir)); From 1353c9dfb0403061373001a891408232400f7c88 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 21 Mar 2017 22:53:04 +0100 Subject: [PATCH 17/57] WIP: download capabilities for salt grains. --- src/lay/laySaltGrain.cc | 22 +++++++++++++++++++++- src/lay/laySaltGrain.h | 14 ++++++++++++++ src/lay/laySaltGrainPropertiesDialog.cc | 15 +++++++++------ 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/lay/laySaltGrain.cc b/src/lay/laySaltGrain.cc index 5d8227592..49152e4ae 100644 --- a/src/lay/laySaltGrain.cc +++ b/src/lay/laySaltGrain.cc @@ -23,6 +23,7 @@ #include "laySaltGrain.h" #include "tlString.h" #include "tlXMLParser.h" +#include "tlHttpStream.h" #include #include @@ -319,7 +320,7 @@ static tl::XMLStruct xml_struct ("salt-grain", bool SaltGrain::is_readonly () const { - return QFileInfo (tl::to_qstring (path ())).isWritable (); + return !QFileInfo (tl::to_qstring (path ())).isWritable (); } void @@ -329,6 +330,13 @@ SaltGrain::load (const std::string &p) xml_struct.parse (source, *this); } +void +SaltGrain::load (tl::InputStream &p) +{ + tl::XMLStreamSource source (p); + xml_struct.parse (source, *this); +} + void SaltGrain::save () const { @@ -353,6 +361,18 @@ SaltGrain::from_path (const std::string &path) return g; } +SaltGrain +SaltGrain::from_url (const std::string &url) +{ + tl::InputHttpStream http (SaltGrain::spec_url (url)); + tl::InputStream stream (http); + + SaltGrain g; + g.load (stream); + g.set_url (url); + return g; +} + bool SaltGrain::is_grain (const std::string &path) { diff --git a/src/lay/laySaltGrain.h b/src/lay/laySaltGrain.h index b237243ef..ce21ae0f1 100644 --- a/src/lay/laySaltGrain.h +++ b/src/lay/laySaltGrain.h @@ -25,6 +25,7 @@ #include "layCommon.h" #include "tlObject.h" +#include "tlStream.h" #include #include @@ -334,6 +335,11 @@ public: */ void load (const std::string &file_path); + /** + * @brief Loads the data from a given stream + */ + void load (tl::InputStream &stream); + /** * @brief Saves the data to the path inside the grain folder given by the "path" property */ @@ -370,6 +376,14 @@ public: */ static SaltGrain from_path (const std::string &path); + /** + * @brief Loads the grain from the given URL + * This method will return a grain constructed from the downloaded data. + * The data is read from "URL/grain.xml". This method will throw an + * exception if an error occurs during reading. + */ + static SaltGrain from_url (const std::string &url); + /** * @brief Forms the spec file download URL from a given download URL */ diff --git a/src/lay/laySaltGrainPropertiesDialog.cc b/src/lay/laySaltGrainPropertiesDialog.cc index ab4e08d43..3059cb653 100644 --- a/src/lay/laySaltGrainPropertiesDialog.cc +++ b/src/lay/laySaltGrainPropertiesDialog.cc @@ -554,14 +554,17 @@ SaltGrainPropertiesDialog::accept () << tr ("A download URL should be specified to ensure the package dependencies can be resolved.") << tl::endl << tr ("If the dependency package was downloaded itself, the URL is automatically set to the download source."); } else { - std::string spec_url = SaltGrain::spec_url (d->url); - tl::InputHttpStream stream (spec_url); + SaltGrain gdep; try { - char b; - stream.read (&b, 1); + gdep = SaltGrain::from_url (d->url); + if (gdep.name () != d->name) { + dependencies_alert->error () << tr ("Package name obtained from download URL is not the expected name.") << tl::endl + << tr ("Downloaded name: ") << gdep.name () << tl::endl + << tr ("Expected name: ") << d->name; + } } catch (tl::Exception &ex) { - dependencies_alert->error () << tr ("Attempt to read download URL failed. Error details follow.") << tl::endl - << tr ("URL: ") << spec_url << tl::endl + dependencies_alert->error () << tr ("Attempt to test-download package from URL failed. Error details follow.") << tl::endl + << tr ("URL: ") << d->url << tl::endl << tr ("Message: ") << ex.msg (); } } From 4a904791870e11d69d32089131900395d283746f Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 23 Mar 2017 22:19:13 +0100 Subject: [PATCH 18/57] WIP: package template selection dialog Plus some initial package templates (without files). --- src/lay/SaltGrainTemplateSelectionDialog.ui | 237 ++++++++++++++++++++ src/lay/lay.pro | 4 +- src/lay/laySalt.cc | 14 +- src/lay/laySaltGrain.cc | 37 ++- src/lay/laySaltGrains.cc | 108 +++++++-- src/lay/laySaltManagerDialog.cc | 107 +++++++++ src/lay/laySaltManagerDialog.h | 15 ++ src/lay/laySaltTemplates.qrc | 47 ++++ src/lay/salt_templates/font/grain.xml | 16 ++ src/lay/salt_templates/lib/grain.xml | 16 ++ src/lay/salt_templates/macro/grain.xml | 16 ++ src/lay/salt_templates/pcell_lib/grain.xml | 16 ++ src/lay/salt_templates/pymacro/grain.xml | 16 ++ src/lay/salt_templates/tech/grain.xml | 16 ++ 14 files changed, 633 insertions(+), 32 deletions(-) create mode 100644 src/lay/SaltGrainTemplateSelectionDialog.ui create mode 100644 src/lay/laySaltTemplates.qrc create mode 100644 src/lay/salt_templates/font/grain.xml create mode 100644 src/lay/salt_templates/lib/grain.xml create mode 100644 src/lay/salt_templates/macro/grain.xml create mode 100644 src/lay/salt_templates/pcell_lib/grain.xml create mode 100644 src/lay/salt_templates/pymacro/grain.xml create mode 100644 src/lay/salt_templates/tech/grain.xml diff --git a/src/lay/SaltGrainTemplateSelectionDialog.ui b/src/lay/SaltGrainTemplateSelectionDialog.ui new file mode 100644 index 000000000..65406eb88 --- /dev/null +++ b/src/lay/SaltGrainTemplateSelectionDialog.ui @@ -0,0 +1,237 @@ + + + SaltGrainTemplateSelectionDialog + + + + 0 + 0 + 401 + 499 + + + + Create Package + + + + + + + 75 + true + + + + Template + + + + + + + true + + + + 64 + 64 + + + + false + + + + + + + + true + + + + (Pick a template with which to initialize your new package) + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + 75 + true + + + + Package Name + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::NoFocus + + + ... + + + + :/warn.png:/warn.png + + + true + + + + + + + + + + + 50 + true + false + + + + (Choose a simple name composed of letters, digits and underscores. Use the notation "group/package" to create a package inside a group) + + + true + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + :/add.png:/add.png + + + New + + + New package + + + + + + :/clear.png:/clear.png + + + Delete + + + Delete package + + + + + + :/import.png:/import.png + + + Import + + + Import package + + + + + + lay::AlertLogButton + QToolButton +
layLogViewerDialog.h
+
+
+ + + + + + buttonBox + accepted() + SaltGrainTemplateSelectionDialog + accept() + + + 273 + 431 + + + 276 + 448 + + + + + buttonBox + rejected() + SaltGrainTemplateSelectionDialog + reject() + + + 351 + 426 + + + 363 + 445 + + + + +
diff --git a/src/lay/lay.pro b/src/lay/lay.pro index 4c4c804f7..7b684827e 100644 --- a/src/lay/lay.pro +++ b/src/lay/lay.pro @@ -99,7 +99,8 @@ FORMS = \ TechSaveOptionsEditorPage.ui \ MainConfigPage7.ui \ SaltManagerDialog.ui \ - SaltGrainPropertiesDialog.ui + SaltGrainPropertiesDialog.ui \ + SaltGrainTemplateSelectionDialog.ui SOURCES = \ gsiDeclLayApplication.cc \ @@ -156,6 +157,7 @@ RESOURCES = layBuildInMacros.qrc \ layLayoutStatistics.qrc \ layMacroTemplates.qrc \ layResources.qrc \ + laySaltTemplates.qrc INCLUDEPATH += ../tl ../gsi ../db ../rdb ../laybasic ../ant ../img ../edt DEPENDPATH += ../tl ../gsi ../db ../rdb ../laybasic ../ant ../img ../edt diff --git a/src/lay/laySalt.cc b/src/lay/laySalt.cc index 156cf0ec3..6e3ba10cb 100644 --- a/src/lay/laySalt.cc +++ b/src/lay/laySalt.cc @@ -81,11 +81,15 @@ Salt::grain_by_name (const std::string &name) void Salt::add_location (const std::string &path) { - // do nothing if the collection is already there - QFileInfo fi (tl::to_qstring (path)); - for (lay::SaltGrains::collection_iterator g = m_root.begin_collections (); g != m_root.end_collections (); ++g) { - if (QFileInfo (tl::to_qstring (g->path ())) == fi) { - return; + tl_assert (! path.empty ()); + + if (path[0] != ':') { + // do nothing if the collection is already there + QFileInfo fi (tl::to_qstring (path)); + for (lay::SaltGrains::collection_iterator g = m_root.begin_collections (); g != m_root.end_collections (); ++g) { + if (QFileInfo (tl::to_qstring (g->path ())) == fi) { + return; + } } } diff --git a/src/lay/laySaltGrain.cc b/src/lay/laySaltGrain.cc index 49152e4ae..af3f39130 100644 --- a/src/lay/laySaltGrain.cc +++ b/src/lay/laySaltGrain.cc @@ -28,6 +28,7 @@ #include #include #include +#include namespace lay { @@ -326,8 +327,28 @@ SaltGrain::is_readonly () const void SaltGrain::load (const std::string &p) { - tl::XMLFileSource source (p); - xml_struct.parse (source, *this); + tl_assert (!p.empty ()); + + if (p[0] != ':') { + + tl::XMLFileSource source (p); + xml_struct.parse (source, *this); + + } else { + + QResource res (tl::to_qstring (p)); + QByteArray data; + if (res.isCompressed ()) { + data = qUncompress ((const unsigned char *)res.data (), (int)res.size ()); + } else { + data = QByteArray ((const char *)res.data (), (int)res.size ()); + } + + std::string str_data (data.constData (), data.size ()); + tl::XMLStringSource source (str_data); + xml_struct.parse (source, *this); + + } } void @@ -376,9 +397,15 @@ SaltGrain::from_url (const std::string &url) bool SaltGrain::is_grain (const std::string &path) { - QDir dir (tl::to_qstring (path)); - QString gf = dir.filePath (tl::to_qstring (grain_filename)); - return QFileInfo (gf).exists (); + tl_assert (! path.empty ()); + + if (path[0] != ':') { + QDir dir (tl::to_qstring (path)); + QString gf = dir.filePath (tl::to_qstring (grain_filename)); + return QFileInfo (gf).exists (); + } else { + return QResource (tl::to_qstring (path + "/" + grain_filename)).isValid (); + } } } diff --git a/src/lay/laySaltGrains.cc b/src/lay/laySaltGrains.cc index 9c17944c6..5c80cc884 100644 --- a/src/lay/laySaltGrains.cc +++ b/src/lay/laySaltGrains.cc @@ -26,6 +26,7 @@ #include #include +#include namespace lay { @@ -129,37 +130,102 @@ SaltGrains::is_readonly () const return QFileInfo (tl::to_qstring (path ())).isWritable (); } +namespace +{ + +/** + * @brief A helper class required because directory traversal is not supported by QResource directly + */ +class OpenResource + : public QResource +{ +public: + using QResource::isDir; + using QResource::isFile; + using QResource::children; + + OpenResource (const QString &path) + : QResource (path) + { + // .. nothing yet .. + } +}; + +} + SaltGrains SaltGrains::from_path (const std::string &path, const std::string &prefix) { + tl_assert (! path.empty ()); + SaltGrains grains; grains.set_path (path); - QDir dir (tl::to_qstring (path)); - QStringList entries = dir.entryList (QDir::NoDotAndDotDot | QDir::Dirs, QDir::Name); - for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) { + if (path[0] != ':') { + + QDir dir (tl::to_qstring (path)); + QStringList entries = dir.entryList (QDir::NoDotAndDotDot | QDir::Dirs, QDir::Name); + for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) { + + std::string new_prefix = prefix; + if (! new_prefix.empty ()) { + new_prefix += "/"; + } + new_prefix += tl::to_string (*e); + + std::string epath = tl::to_string (dir.absoluteFilePath (*e)); + if (SaltGrain::is_grain (epath)) { + try { + SaltGrain g (SaltGrain::from_path (epath)); + g.set_name (new_prefix); + grains.add_grain (g); + } catch (...) { + // ignore errors (TODO: what to do here?) + } + } else if (QFileInfo (tl::to_qstring (epath)).isDir ()) { + SaltGrains c = SaltGrains::from_path (epath, new_prefix); + c.set_name (new_prefix); + if (! c.is_empty ()) { + grains.add_collection (c); + } + } - std::string new_prefix = prefix; - if (! new_prefix.empty ()) { - new_prefix += "/"; } - new_prefix += tl::to_string (*e); - std::string epath = tl::to_string (dir.absoluteFilePath (*e)); - if (SaltGrain::is_grain (epath)) { - try { - SaltGrain g (SaltGrain::from_path (epath)); - g.set_name (new_prefix); - grains.add_grain (g); - } catch (...) { - // ignore errors (TODO: what to do here?) - } - } else if (QFileInfo (tl::to_qstring (epath)).isDir ()) { - SaltGrains c = SaltGrains::from_path (epath, new_prefix); - c.set_name (new_prefix); - if (! c.is_empty ()) { - grains.add_collection (c); + } else { + + OpenResource resource (tl::to_qstring (path)); + if (resource.isDir ()) { + + QStringList templ_dir = resource.children (); + for (QStringList::const_iterator t = templ_dir.begin (); t != templ_dir.end (); ++t) { + + std::string new_prefix = prefix; + if (! new_prefix.empty ()) { + new_prefix += "/"; + } + new_prefix += tl::to_string (*t); + + std::string epath = path + "/" + tl::to_string (*t); + + if (SaltGrain::is_grain (epath)) { + try { + SaltGrain g (SaltGrain::from_path (epath)); + g.set_name (new_prefix); + grains.add_grain (g); + } catch (...) { + // ignore errors (TODO: what to do here?) + } + } else if (OpenResource (tl::to_qstring (epath)).isDir ()) { + SaltGrains c = SaltGrains::from_path (epath, new_prefix); + c.set_name (new_prefix); + if (! c.is_empty ()) { + grains.add_collection (c); + } + } + } + } } diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index 7730310bc..71a34dba3 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -23,6 +23,7 @@ #include "laySaltManagerDialog.h" #include "laySaltGrainPropertiesDialog.h" #include "laySalt.h" +#include "ui_SaltGrainTemplateSelectionDialog.h" #include "tlString.h" #include @@ -33,6 +34,7 @@ #include #include #include +#include namespace lay { @@ -219,6 +221,56 @@ public: } }; +// -------------------------------------------------------------------------------------- + +/** + * @brief A tiny dialog to select a template and a name for the grain + */ +class SaltGrainTemplateSelectionDialog + : public QDialog, private Ui::SaltGrainTemplateSelectionDialog +{ +public: + SaltGrainTemplateSelectionDialog (QWidget *parent) + : QDialog (parent) + { + Ui::SaltGrainTemplateSelectionDialog::setupUi (this); + + m_salt_templates.add_location (":/salt_templates"); + salt_view->setModel (new SaltModel (this, &m_salt_templates)); + salt_view->setItemDelegate (new SaltItemDelegate (this)); + salt_view->setCurrentIndex (salt_view->model ()->index (0, 0, QModelIndex ())); + } + + lay::SaltGrain templ () const + { + SaltModel *model = dynamic_cast (salt_view->model ()); + tl_assert (model != 0); + + SaltGrain *g = model->grain_from_index (salt_view->currentIndex ()); + tl_assert (g != 0); + + g->set_name (tl::to_string (name_edit->text ().simplified ())); + + return *g; + } + + void accept () + { + name_alert->clear (); + std::string name = tl::to_string (name_edit->text ().simplified ()); + if (name.empty ()) { + name_alert->error () << tr ("Name must not be empty"); + } else if (! SaltGrain::valid_name (name)) { + name_alert->error () << tr ("Name is not valid (must be composed of letters, digits or underscores.\nGroups and names need to be separated with slashes."); + } else { + QDialog::accept (); + } + } + +private: + lay::Salt m_salt_templates; +}; + // -------------------------------------------------------------------------------------- // SaltManager implementation @@ -242,6 +294,9 @@ SaltManagerDialog::SaltManagerDialog (QWidget *parent) mp_properties_dialog = new lay::SaltGrainPropertiesDialog (this); connect (edit_button, SIGNAL (clicked ()), this, SLOT (edit_properties ())); + connect (create_button, SIGNAL (clicked ()), this, SLOT (create_grain ())); + connect (delete_button, SIGNAL (clicked ()), this, SLOT (delete_grain ())); + connect (install_button, SIGNAL (clicked ()), this, SLOT (install_grain ())); // @@@ salt = lay::Salt (); salt_initialized = false; @@ -279,6 +334,58 @@ SaltManagerDialog::edit_properties () } } +// @@@ +namespace +{ + +/** + * @brief A helper class required because directory traversal is not supported by QResource directly + */ +class OpenResource + : public QResource +{ +public: + using QResource::isDir; + using QResource::isFile; + using QResource::children; + + OpenResource (const QString &path) + : QResource (path) + { + // .. nothing yet .. + } +}; + +} +// @@@ + +void +SaltManagerDialog::create_grain () +{ + SaltGrainTemplateSelectionDialog temp_dialog (this); + if (temp_dialog.exec ()) { + + // @@@ + + } +} + +void +SaltManagerDialog::delete_grain () +{ + + // @@@ + +} + +void +SaltManagerDialog::install_grain () +{ + + // @@@ + +} + void SaltManagerDialog::salt_changed () { diff --git a/src/lay/laySaltManagerDialog.h b/src/lay/laySaltManagerDialog.h index 170e02b7d..232d40403 100644 --- a/src/lay/laySaltManagerDialog.h +++ b/src/lay/laySaltManagerDialog.h @@ -64,6 +64,21 @@ private slots: */ void edit_properties (); + /** + * @brief Called when the "edit" button is pressed + */ + void create_grain (); + + /** + * @brief Called when the "delete" button is pressed + */ + void delete_grain (); + + /** + * @brief Called when the "install" button is pressed + */ + void install_grain (); + private: lay::Salt *mp_salt; bool m_current_changed_enabled; diff --git a/src/lay/laySaltTemplates.qrc b/src/lay/laySaltTemplates.qrc new file mode 100644 index 000000000..e4155fe58 --- /dev/null +++ b/src/lay/laySaltTemplates.qrc @@ -0,0 +1,47 @@ + + + + + + + salt_templates/font/grain.xml + + + + + + + salt_templates/lib/grain.xml + + + + + + + salt_templates/pcell_lib/grain.xml + + + + + + + salt_templates/macro/grain.xml + + + + + + + salt_templates/pymacro/grain.xml + + + + + + + salt_templates/tech/grain.xml + + + + + diff --git a/src/lay/salt_templates/font/grain.xml b/src/lay/salt_templates/font/grain.xml new file mode 100644 index 000000000..593a48147 --- /dev/null +++ b/src/lay/salt_templates/font/grain.xml @@ -0,0 +1,16 @@ + + + font + 0.0 + Font package + This template provides a font for the Basic.TEXT PCell + + + GPLv3 + + + + + + + diff --git a/src/lay/salt_templates/lib/grain.xml b/src/lay/salt_templates/lib/grain.xml new file mode 100644 index 000000000..a071c2676 --- /dev/null +++ b/src/lay/salt_templates/lib/grain.xml @@ -0,0 +1,16 @@ + + + lib + 0.0 + Static Library + This template provides a static library + + + GPLv3 + + + + + + + diff --git a/src/lay/salt_templates/macro/grain.xml b/src/lay/salt_templates/macro/grain.xml new file mode 100644 index 000000000..ae9c5ffe2 --- /dev/null +++ b/src/lay/salt_templates/macro/grain.xml @@ -0,0 +1,16 @@ + + + macro + 0.0 + Ruby Macro + This template provides a Ruby macro + + + GPLv3 + + + + + + + diff --git a/src/lay/salt_templates/pcell_lib/grain.xml b/src/lay/salt_templates/pcell_lib/grain.xml new file mode 100644 index 000000000..20a656273 --- /dev/null +++ b/src/lay/salt_templates/pcell_lib/grain.xml @@ -0,0 +1,16 @@ + + + macro + 0.0 + PCell library + This template provides a PCell library implemented in Ruby + + + GPLv3 + + + + + + + diff --git a/src/lay/salt_templates/pymacro/grain.xml b/src/lay/salt_templates/pymacro/grain.xml new file mode 100644 index 000000000..2b281fcc9 --- /dev/null +++ b/src/lay/salt_templates/pymacro/grain.xml @@ -0,0 +1,16 @@ + + + macro + 0.0 + Python Macro + This template provides a Python macro + + + GPLv3 + + + + + + + diff --git a/src/lay/salt_templates/tech/grain.xml b/src/lay/salt_templates/tech/grain.xml new file mode 100644 index 000000000..2def15e72 --- /dev/null +++ b/src/lay/salt_templates/tech/grain.xml @@ -0,0 +1,16 @@ + + + macro + 0.0 + Technology + This template provides a technology + + + GPLv3 + + + + + + + From 10b09c35756928dded2284984ff156c0f557b27d Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 23 Mar 2017 23:46:25 +0100 Subject: [PATCH 19/57] WIP: create packages from templates Now, packages are actually created. --- src/lay/laySalt.cc | 124 +++++++++++++++++++++++++++++--- src/lay/laySalt.h | 2 +- src/lay/laySaltGrain.cc | 2 +- src/lay/laySaltManagerDialog.cc | 94 ++++++++++++++---------- src/unit_tests/laySalt.cc | 8 ++- 5 files changed, 182 insertions(+), 48 deletions(-) diff --git a/src/lay/laySalt.cc b/src/lay/laySalt.cc index 6e3ba10cb..b71cbc107 100644 --- a/src/lay/laySalt.cc +++ b/src/lay/laySalt.cc @@ -29,6 +29,7 @@ #include #include +#include namespace lay { @@ -215,8 +216,91 @@ Salt::remove_grain (const SaltGrain &grain) } } +namespace +{ + +/** + * @brief A helper class required because directory traversal is not supported by QResource directly + * This class supports resource file trees and extraction of a tree from the latter + */ +class ResourceDir + : public QResource +{ +public: + using QResource::isFile; + + /** + * @brief Constructor + * Creates a resource representing a resource tree. + */ + ResourceDir (const QString &path) + : QResource (path) + { + // .. nothing yet .. + } + + /** + * @brief Writes the resource tree to the target directory + * Returns false on error - see log in this case. + */ + bool copy_to (const QDir &target) + { + if (isDir ()) { + + QStringList templ_dir = children (); + for (QStringList::const_iterator t = templ_dir.begin (); t != templ_dir.end (); ++t) { + + ResourceDir child_res (fileName () + QString::fromUtf8 ("/") + *t); + if (child_res.isFile ()) { + + QFile file (target.absoluteFilePath (*t)); + if (! file.open (QIODevice::WriteOnly)) { + tl::error << QObject::tr ("Unable to open target file for writing: %1").arg (target.absoluteFilePath (*t)); + return false; + } + + QByteArray data; + if (child_res.isCompressed ()) { + data = qUncompress ((const unsigned char *)child_res.data (), (int)child_res.size ()); + } else { + data = QByteArray ((const char *)child_res.data (), (int)child_res.size ()); + } + + file.write (data); + + file.close (); + + } else { + + QFileInfo child_dir (target.absoluteFilePath (*t)); + if (! child_dir.exists ()) { + if (! target.mkdir (*t)) { + tl::error << QObject::tr ("Unable to create target directory: %1").arg (child_dir.path ()); + return false; + } + } else if (! child_dir.isDir ()) { + tl::error << QObject::tr ("Unable to create target directory (is a file already): %1").arg (child_dir.path ()); + return false; + } + + if (! child_res.copy_to (QDir (target.absoluteFilePath (*t)))) { + return false; + } + + } + + } + + } + + return true; + } +}; + +} + bool -Salt::create_grain (const SaltGrain &templ, SaltGrain &target, SaltDownloadManager &download_manager) +Salt::create_grain (const SaltGrain &templ, SaltGrain &target, SaltDownloadManager *download_manager) { tl_assert (!m_root.is_empty ()); @@ -243,15 +327,23 @@ Salt::create_grain (const SaltGrain &templ, SaltGrain &target, SaltDownloadManag // change down to the desired target location and create the directory structure while doing so std::vector name_parts = tl::split (target.name (), "/"); for (std::vector::const_iterator n = name_parts.begin (); n != name_parts.end (); ++n) { - QDir subdir (target_dir.filePath (tl::to_qstring (*n))); - if (! subdir.exists ()) { + + QFileInfo subdir (target_dir.filePath (tl::to_qstring (*n))); + if (subdir.exists () && ! subdir.isDir ()) { + throw tl::Exception (tl::to_string (tr ("Unable to create target directory '%1' for installing package - is already a file").arg (subdir.path ()))); + } else if (! subdir.exists ()) { if (! target_dir.mkdir (tl::to_qstring (*n))) { throw tl::Exception (tl::to_string (tr ("Unable to create target directory '%1' for installing package").arg (subdir.path ()))); } if (! target_dir.cd (tl::to_qstring (*n))) { throw tl::Exception (tl::to_string (tr ("Unable to change to target directory '%1' for installing package").arg (subdir.path ()))); } + } else { + if (! target_dir.cd (tl::to_qstring (*n))) { + throw tl::Exception (tl::to_string (tr ("Unable to change to target directory '%1' for installing package").arg (subdir.path ()))); + } } + } } catch (tl::Exception &ex) { @@ -261,30 +353,46 @@ Salt::create_grain (const SaltGrain &templ, SaltGrain &target, SaltDownloadManag bool res = true; + std::string target_name = target.name (); target = templ; target.set_path (tl::to_string (target_dir.absolutePath ())); + target.set_name (target_name); if (! templ.path ().empty ()) { - // if the template represents an actual folder, use the files from there - tl::info << QObject::tr ("Copying package from '%1' to '%2' ..").arg (tl::to_qstring (templ.path ())).arg (tl::to_qstring (target.path ())); - res = tl::cp_dir_recursive (templ.path (), target.path ()); + if (templ.path ()[0] != ':') { + + // if the template represents an actual folder, use the files from there + tl::info << QObject::tr ("Copying package from '%1' to '%2' ..").arg (tl::to_qstring (templ.path ())).arg (tl::to_qstring (target.path ())); + res = tl::cp_dir_recursive (templ.path (), target.path ()); + + } else { + + // if the template represents a resource path, use the files from there + tl::info << QObject::tr ("Installing package from resource '%1' to '%2' ..").arg (tl::to_qstring (templ.path ())).arg (tl::to_qstring (target.path ())); + res = ResourceDir (tl::to_qstring (templ.path ())).copy_to (QDir (tl::to_qstring (target.path ()))); + + } } else if (! templ.url ().empty ()) { + tl_assert (download_manager != 0); + // otherwise download from the URL tl::info << QObject::tr ("Downloading package from '%1' to '%2' ..").arg (tl::to_qstring (templ.url ())).arg (tl::to_qstring (target.path ())); - res = download_manager.download (templ.url (), target.path ()); + res = download_manager->download (templ.url (), target.path ()); } if (res) { tl::info << QObject::tr ("Package '%1' installed").arg (tl::to_qstring (target.name ())); - target.set_installed_time (QDateTime::currentDateTime ()); target.save (); + // NOTE: this is a bit brute force .. we could as well try to insert the new grain into the existing structure + refresh (); + } else { tl::warn << QObject::tr ("Failed to install package '%1' - removing files ..").arg (tl::to_qstring (target.name ())); diff --git a/src/lay/laySalt.h b/src/lay/laySalt.h index 4ade8acf9..ad578c737 100644 --- a/src/lay/laySalt.h +++ b/src/lay/laySalt.h @@ -152,7 +152,7 @@ public: * * Returns true, if the package could be created successfully. */ - bool create_grain (const SaltGrain &templ, SaltGrain &target, SaltDownloadManager &download_manager); + bool create_grain (const SaltGrain &templ, SaltGrain &target, SaltDownloadManager *download_manager = 0); signals: /** diff --git a/src/lay/laySaltGrain.cc b/src/lay/laySaltGrain.cc index af3f39130..246d44278 100644 --- a/src/lay/laySaltGrain.cc +++ b/src/lay/laySaltGrain.cc @@ -226,7 +226,7 @@ SaltGrain::valid_name (const std::string &n) // this captures the cases where the extractor skips blanks // TODO: the extractor should have a "non-blank-skipping" mode - return s == n; + return res == n; } bool diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index 71a34dba3..568040404 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -25,6 +25,7 @@ #include "laySalt.h" #include "ui_SaltGrainTemplateSelectionDialog.h" #include "tlString.h" +#include "tlExceptions.h" #include #include @@ -152,7 +153,7 @@ public: void update () { - // @@@ + reset (); } public: @@ -230,8 +231,8 @@ class SaltGrainTemplateSelectionDialog : public QDialog, private Ui::SaltGrainTemplateSelectionDialog { public: - SaltGrainTemplateSelectionDialog (QWidget *parent) - : QDialog (parent) + SaltGrainTemplateSelectionDialog (QWidget *parent, lay::Salt *salt) + : QDialog (parent), mp_salt (salt) { Ui::SaltGrainTemplateSelectionDialog::setupUi (this); @@ -249,11 +250,14 @@ public: SaltGrain *g = model->grain_from_index (salt_view->currentIndex ()); tl_assert (g != 0); - g->set_name (tl::to_string (name_edit->text ().simplified ())); - return *g; } + std::string name () const + { + return tl::to_string (name_edit->text ()); + } + void accept () { name_alert->clear (); @@ -263,12 +267,23 @@ public: } else if (! SaltGrain::valid_name (name)) { name_alert->error () << tr ("Name is not valid (must be composed of letters, digits or underscores.\nGroups and names need to be separated with slashes."); } else { + + // check, if this name does not exist yet + for (Salt::flat_iterator g = mp_salt->begin_flat (); g != mp_salt->end_flat (); ++g) { + if ((*g)->name () == name) { + name_alert->error () << tr ("A package with this name already exists"); + return; + } + } + QDialog::accept (); + } } private: lay::Salt m_salt_templates; + lay::Salt *mp_salt; }; // -------------------------------------------------------------------------------------- @@ -310,11 +325,6 @@ SaltManagerDialog::SaltManagerDialog (QWidget *parent) 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 ())); @@ -334,40 +344,41 @@ SaltManagerDialog::edit_properties () } } -// @@@ -namespace -{ - -/** - * @brief A helper class required because directory traversal is not supported by QResource directly - */ -class OpenResource - : public QResource -{ -public: - using QResource::isDir; - using QResource::isFile; - using QResource::children; - - OpenResource (const QString &path) - : QResource (path) - { - // .. nothing yet .. - } -}; - -} -// @@@ - void SaltManagerDialog::create_grain () { - SaltGrainTemplateSelectionDialog temp_dialog (this); +BEGIN_PROTECTED + + SaltGrainTemplateSelectionDialog temp_dialog (this, mp_salt); if (temp_dialog.exec ()) { - // @@@ + SaltGrain target; + target.set_name (temp_dialog.name ()); + + if (mp_salt->create_grain (temp_dialog.templ (), target)) { + + // select the new one + SaltModel *model = dynamic_cast (salt_view->model ()); + if (model) { + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + QModelIndex index = model->index (i, 0, QModelIndex ()); + SaltGrain *g = model->grain_from_index (index); + if (g && g->name () == target.name ()) { + salt_view->setCurrentIndex (index); + break; + } + } + + } + + } else { + throw tl::Exception (tl::to_string (tr ("Initialization of new package failed - see log window (File/Log Viewer) for details"))); + } } + +END_PROTECTED } void @@ -399,11 +410,20 @@ SaltManagerDialog::salt_changed () 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 (); + + // select the first grain + if (model->rowCount (QModelIndex ()) > 0) { + salt_view->setCurrentIndex (model->index (0, 0, QModelIndex ())); + } + } current_changed (); diff --git a/src/unit_tests/laySalt.cc b/src/unit_tests/laySalt.cc index 62a62826a..9d3ef0a36 100644 --- a/src/unit_tests/laySalt.cc +++ b/src/unit_tests/laySalt.cc @@ -162,6 +162,13 @@ TEST (2) 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::valid_name (""), false); + EXPECT_EQ (lay::SaltGrain::valid_name ("x"), true); + EXPECT_EQ (lay::SaltGrain::valid_name ("x1"), true); + EXPECT_EQ (lay::SaltGrain::valid_name ("x1 "), false); + EXPECT_EQ (lay::SaltGrain::valid_name ("x$1"), false); + EXPECT_EQ (lay::SaltGrain::valid_name ("x/y"), true); + EXPECT_EQ (lay::SaltGrain::valid_name ("x_y"), true); EXPECT_EQ (lay::SaltGrain::compare_versions ("", ""), 0); EXPECT_EQ (lay::SaltGrain::compare_versions ("1", "2"), -1); EXPECT_EQ (lay::SaltGrain::compare_versions ("1", ""), 1); @@ -185,7 +192,6 @@ TEST (2) EXPECT_EQ (lay::SaltGrain::compare_versions ("991", "990"), 1); } - TEST (3) { const QString grain_spec_file = QString::fromUtf8 ("grain.xml"); From d160a5c27c626c8e0609b22a490f9decad996198 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 25 Mar 2017 00:08:17 +0100 Subject: [PATCH 20/57] WIP: delete function, installation dialog (first steps) --- src/lay/SaltGrainInstallationDialog.ui | 313 ++++++++++++++++++++++ src/lay/lay.pro | 11 +- src/lay/laySalt.cc | 7 + src/lay/laySalt.h | 25 ++ src/lay/laySaltGrain.cc | 21 +- src/lay/laySaltGrain.h | 6 + src/lay/laySaltGrainInstallationDialog.cc | 101 +++++++ src/lay/laySaltGrainInstallationDialog.h | 76 ++++++ src/lay/laySaltGrains.cc | 28 ++ src/lay/laySaltGrains.h | 15 ++ src/lay/laySaltManagerDialog.cc | 209 ++------------- src/lay/laySaltModel.cc | 207 ++++++++++++++ src/lay/laySaltModel.h | 110 ++++++++ src/unit_tests/laySalt.cc | 9 + 14 files changed, 941 insertions(+), 197 deletions(-) create mode 100644 src/lay/SaltGrainInstallationDialog.ui create mode 100644 src/lay/laySaltGrainInstallationDialog.cc create mode 100644 src/lay/laySaltGrainInstallationDialog.h create mode 100644 src/lay/laySaltModel.cc create mode 100644 src/lay/laySaltModel.h diff --git a/src/lay/SaltGrainInstallationDialog.ui b/src/lay/SaltGrainInstallationDialog.ui new file mode 100644 index 000000000..ab54731cb --- /dev/null +++ b/src/lay/SaltGrainInstallationDialog.ui @@ -0,0 +1,313 @@ + + + SaltGrainInstallationDialog + + + + 0 + 0 + 692 + 440 + + + + Salt Mine Repository Browser + + + + + + Qt::Horizontal + + + + + 4 + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 2 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 75 + true + + + + Repository + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Install + + + + + + + Qt::Horizontal + + + + 126 + 20 + + + + + + + + + 0 + 0 + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + 64 + 64 + + + + false + + + + + + + + + + + + 1 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 4 + + + 0 + + + 0 + + + 0 + + + + + + 75 + true + + + + Details + + + + + + + true + + + + + + + + + + + QDialogButtonBox::Apply|QDialogButtonBox::Close + + + + + + + + :/add.png:/add.png + + + New + + + New package + + + + + + :/clear.png:/clear.png + + + Delete + + + Delete package + + + + + + :/import.png:/import.png + + + Import + + + Import package + + + + + + lay::DecoratedLineEdit + QLineEdit +
layWidgets.h
+
+ + lay::SaltGrainDetailsTextWidget + QTextBrowser +
laySaltGrainDetailsTextWidget.h
+
+
+ + details_text + + + + + + + button_box + rejected() + SaltGrainInstallationDialog + reject() + + + 559 + 425 + + + 576 + 437 + + + + +
diff --git a/src/lay/lay.pro b/src/lay/lay.pro index 7b684827e..0d1251609 100644 --- a/src/lay/lay.pro +++ b/src/lay/lay.pro @@ -52,7 +52,9 @@ HEADERS = \ laySaltManagerDialog.h \ laySaltGrainDetailsTextWidget.h \ laySaltGrainPropertiesDialog.h \ - laySaltDownloadManager.h + laySaltDownloadManager.h \ + laySaltModel.h \ + laySaltGrainInstallationDialog.h FORMS = \ ClipDialog.ui \ @@ -100,7 +102,8 @@ FORMS = \ MainConfigPage7.ui \ SaltManagerDialog.ui \ SaltGrainPropertiesDialog.ui \ - SaltGrainTemplateSelectionDialog.ui + SaltGrainTemplateSelectionDialog.ui \ + SaltGrainInstallationDialog.ui SOURCES = \ gsiDeclLayApplication.cc \ @@ -150,7 +153,9 @@ SOURCES = \ laySaltManagerDialog.cc \ laySaltGrainDetailsTextWidget.cc \ laySaltGrainPropertiesDialog.cc \ - laySaltDownloadManager.cc + laySaltDownloadManager.cc \ + laySaltModel.cc \ + laySaltGrainInstallationDialog.cc RESOURCES = layBuildInMacros.qrc \ layHelpResources.qrc \ diff --git a/src/lay/laySalt.cc b/src/lay/laySalt.cc index b71cbc107..c16bc8e56 100644 --- a/src/lay/laySalt.cc +++ b/src/lay/laySalt.cc @@ -189,6 +189,7 @@ bool remove_from_collection (SaltGrains &collection, const std::string &name) ++gnext; collection.remove_grain (g, true); res = true; + g = gnext; } } @@ -207,9 +208,15 @@ Salt::remove_grain (const SaltGrain &grain) { tl::info << QObject::tr ("Removing package '%1' ..").arg (tl::to_qstring (grain.name ())); if (remove_from_collection (m_root, grain.name ())) { + tl::info << QObject::tr ("Package '%1' removed.").arg (tl::to_qstring (grain.name ())); + + // NOTE: this is a bit brute force .. we could as well try to insert the new grain into the existing structure + refresh (); + invalidate (); return true; + } else { tl::warn << QObject::tr ("Failed to remove package '%1'.").arg (tl::to_qstring (grain.name ())); return false; diff --git a/src/lay/laySalt.h b/src/lay/laySalt.h index ad578c737..ff16c0280 100644 --- a/src/lay/laySalt.h +++ b/src/lay/laySalt.h @@ -123,6 +123,31 @@ public: */ SaltGrain *grain_by_name (const std::string &name); + /** + * @brief Loads the salt from a "salt mine" file + */ + void load (const std::string &p) + { + m_root.load (p); + } + + /** + * @brief Loads the salt from a "salt mine" stream + */ + void load (tl::InputStream &s) + { + m_root.load (s); + } + + /** + * @brief Saves the salt to a "salt mine" file + * This feature is provided for debugging purposes mainly. + */ + void save (const std::string &p) + { + m_root.save (p); + } + /** * @brief Removes a grain from the salt * diff --git a/src/lay/laySaltGrain.cc b/src/lay/laySaltGrain.cc index 246d44278..61ceb8fef 100644 --- a/src/lay/laySaltGrain.cc +++ b/src/lay/laySaltGrain.cc @@ -297,7 +297,7 @@ struct ImageConverter } }; -static tl::XMLStruct xml_struct ("salt-grain", +static tl::XMLElementList s_xml_elements = 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") + @@ -315,8 +315,15 @@ static tl::XMLStruct xml_struct ("salt-grain", tl::make_member (&SaltGrain::Dependency::name, "name") + tl::make_member (&SaltGrain::Dependency::url, "url") + tl::make_member (&SaltGrain::Dependency::version, "version") - ) -); + ); + +static tl::XMLStruct s_xml_struct ("salt-grain", s_xml_elements); + +tl::XMLElementList & +SaltGrain::xml_struct () +{ + return s_xml_elements; +} bool SaltGrain::is_readonly () const @@ -332,7 +339,7 @@ SaltGrain::load (const std::string &p) if (p[0] != ':') { tl::XMLFileSource source (p); - xml_struct.parse (source, *this); + s_xml_struct.parse (source, *this); } else { @@ -346,7 +353,7 @@ SaltGrain::load (const std::string &p) std::string str_data (data.constData (), data.size ()); tl::XMLStringSource source (str_data); - xml_struct.parse (source, *this); + s_xml_struct.parse (source, *this); } } @@ -355,7 +362,7 @@ void SaltGrain::load (tl::InputStream &p) { tl::XMLStreamSource source (p); - xml_struct.parse (source, *this); + s_xml_struct.parse (source, *this); } void @@ -368,7 +375,7 @@ void SaltGrain::save (const std::string &p) const { tl::OutputStream os (p, tl::OutputStream::OM_Plain); - xml_struct.write (os, *this); + s_xml_struct.write (os, *this); } SaltGrain diff --git a/src/lay/laySaltGrain.h b/src/lay/laySaltGrain.h index ce21ae0f1..df30e7f49 100644 --- a/src/lay/laySaltGrain.h +++ b/src/lay/laySaltGrain.h @@ -26,6 +26,7 @@ #include "layCommon.h" #include "tlObject.h" #include "tlStream.h" +#include "tlXMLParser.h" #include #include @@ -350,6 +351,11 @@ public: */ void save (const std::string &file_path) const; + /** + * @brief Gets the XML structure representing a grain + */ + static tl::XMLElementList &xml_struct (); + /** * @brief Compares two version strings * Returns -1 if v1 < v2, 0 if v1 == v2 and 1 if v1 > v2. diff --git a/src/lay/laySaltGrainInstallationDialog.cc b/src/lay/laySaltGrainInstallationDialog.cc new file mode 100644 index 000000000..f12d7f576 --- /dev/null +++ b/src/lay/laySaltGrainInstallationDialog.cc @@ -0,0 +1,101 @@ + +/* + + 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 "laySaltGrainInstallationDialog.h" +#include "laySaltModel.h" +#include "laySaltGrainPropertiesDialog.h" +#include "laySalt.h" +#include "ui_SaltGrainTemplateSelectionDialog.h" +#include "tlString.h" +#include "tlExceptions.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace lay +{ + +// -------------------------------------------------------------------------------------- +// SaltGrainInstallation implementation + +SaltGrainInstallationDialog::SaltGrainInstallationDialog (QWidget *parent, lay::Salt *salt) + : QDialog (parent), mp_salt (salt) +{ + Ui::SaltGrainInstallationDialog::setupUi (this); + + // TODO: cache package list + // @@@ + m_salt_mine.load ("/home/matthias/salt.mine"); + // @@@ + + SaltModel *model = new SaltModel (this, &m_salt_mine); + salt_view->setModel (model); + salt_view->setItemDelegate (new SaltItemDelegate (this)); + salt_view->setCurrentIndex (model->index (0, 0, QModelIndex ())); + + connect (salt_view->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (current_changed ())); + connect (mark_button, SIGNAL (clicked ()), this, SLOT (mark ())); + connect (button_box->button (QDialogButtonBox::Apply), SIGNAL (clicked ()), this, SLOT (apply ())); + + current_changed (); +} + +void +SaltGrainInstallationDialog::current_changed () +{ + SaltGrain *g = current_grain (); + details_text->set_grain (g); + details_frame->setEnabled (g != 0); +} + +lay::SaltGrain * +SaltGrainInstallationDialog::current_grain () +{ + SaltModel *model = dynamic_cast (salt_view->model ()); + return model ? model->grain_from_index (salt_view->currentIndex ()) : 0; +} + +void +SaltGrainInstallationDialog::apply () +{ + + // @@@ + +} + +void +SaltGrainInstallationDialog::mark () +{ + + // @@@ + +} + +} diff --git a/src/lay/laySaltGrainInstallationDialog.h b/src/lay/laySaltGrainInstallationDialog.h new file mode 100644 index 000000000..9c7986fb2 --- /dev/null +++ b/src/lay/laySaltGrainInstallationDialog.h @@ -0,0 +1,76 @@ + +/* + + 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_laySaltGrainInstallationDialog +#define HDR_laySaltGrainInstallationDialog + +#include "laySalt.h" + +#include + +#include "ui_SaltGrainInstallationDialog.h" + +namespace lay +{ + +class Salt; + +/** + * @brief The dialog for managing the Salt ("Packages") + */ +class SaltGrainInstallationDialog + : public QDialog, private Ui::SaltGrainInstallationDialog +{ +Q_OBJECT + +public: + /** + * @brief Constructor + */ + SaltGrainInstallationDialog (QWidget *parent, lay::Salt *salt); + +private slots: + /** + * @brief Called when the currently selected package (grain) has changed + */ + void current_changed (); + + /** + * @brief Called when the Apply button is clicked + */ + void apply (); + + /** + * @brief Called when the Mark button is pressed + */ + void mark (); + +private: + lay::Salt *mp_salt; + lay::Salt m_salt_mine; + + lay::SaltGrain *current_grain (); +}; + +} + +#endif diff --git a/src/lay/laySaltGrains.cc b/src/lay/laySaltGrains.cc index 5c80cc884..221861340 100644 --- a/src/lay/laySaltGrains.cc +++ b/src/lay/laySaltGrains.cc @@ -233,4 +233,32 @@ SaltGrains::from_path (const std::string &path, const std::string &prefix) return grains; } +static tl::XMLElementList s_group_struct = + tl::make_member (&SaltGrains::name, &SaltGrains::set_name, "name") + + tl::make_element (&SaltGrains::begin_collections, &SaltGrains::end_collections, &SaltGrains::add_collection, "group", &s_group_struct) + + tl::make_element (&SaltGrains::begin_grains, &SaltGrains::end_grains, &SaltGrains::add_grain, "salt-grain", SaltGrain::xml_struct ()); + +static tl::XMLStruct s_xml_struct ("salt-mine", s_group_struct); + +void +SaltGrains::load (const std::string &p) +{ + tl::XMLFileSource source (p); + s_xml_struct.parse (source, *this); +} + +void +SaltGrains::load (tl::InputStream &p) +{ + tl::XMLStreamSource source (p); + s_xml_struct.parse (source, *this); +} + +void +SaltGrains::save (const std::string &p) const +{ + tl::OutputStream os (p, tl::OutputStream::OM_Plain); + s_xml_struct.write (os, *this); +} + } diff --git a/src/lay/laySaltGrains.h b/src/lay/laySaltGrains.h index 0387000a1..2dc524a5a 100644 --- a/src/lay/laySaltGrains.h +++ b/src/lay/laySaltGrains.h @@ -174,6 +174,21 @@ public: */ bool is_readonly () const; + /** + * @brief Loads the grain collection from the given path + */ + void load (const std::string &p); + + /** + * @brief Loads the grain collection from the given input stream + */ + void load (tl::InputStream &p); + + /** + * @brief Saves the grain collection to the given file + */ + void save (const std::string &p) const; + /** * @brief Scan grains from a given path * This will scan the grains found within this path and return a collection containing diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index 568040404..c0d0934be 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -21,209 +21,29 @@ */ #include "laySaltManagerDialog.h" +#include "laySaltModel.h" #include "laySaltGrainPropertiesDialog.h" +#include "laySaltGrainInstallationDialog.h" #include "laySalt.h" #include "ui_SaltGrainTemplateSelectionDialog.h" #include "tlString.h" #include "tlExceptions.h" -#include -#include -#include #include #include #include #include #include #include +#include +#include +#include namespace lay { // -------------------------------------------------------------------------------------- -/** - * @brief A model representing the salt grains for a QListView - */ -class SaltModel - : public QAbstractItemModel -{ -public: - SaltModel (QObject *parent, lay::Salt *salt) - : QAbstractItemModel (parent), mp_salt (salt) - { - // .. nothing yet .. - } - - QVariant data (const QModelIndex &index, int role) const - { - if (role == Qt::DisplayRole) { - - const lay::SaltGrain *g = mp_salt->begin_flat ()[index.row ()]; - - std::string text = ""; - text += "

"; - text += tl::escaped_to_html (g->name ()); - if (!g->version ().empty ()) { - text += " "; - text += tl::escaped_to_html (g->version ()); - } - if (!g->title ().empty ()) { - text += " - "; - text += tl::escaped_to_html (g->title ()); - } - text += "

"; - if (!g->doc ().empty ()) { - text += "

"; - text += tl::escaped_to_html (g->doc ()); - text += "

"; - } - text += ""; - - return tl::to_qstring (text); - - } else if (role == Qt::DecorationRole) { - - int icon_dim = 64; - - const lay::SaltGrain *g = mp_salt->begin_flat ()[index.row ()]; - if (g->icon ().isNull ()) { - return QIcon (":/salt_icon.png"); - } else { - - QImage img = g->icon (); - if (img.width () == icon_dim && img.height () == icon_dim) { - return QPixmap::fromImage (img); - } else { - - img = img.scaled (QSize (icon_dim, icon_dim), Qt::KeepAspectRatio, Qt::SmoothTransformation); - - QImage final_img (icon_dim, icon_dim, QImage::Format_ARGB32); - final_img.fill (QColor (0, 0, 0, 0)); - QPainter painter (&final_img); - painter.drawImage ((icon_dim - img.width ()) / 2, (icon_dim - img.height ()) / 2, img); - - return QPixmap::fromImage (final_img); - - } - - } - - } else { - return QVariant (); - } - } - - QModelIndex index (int row, int column, const QModelIndex &parent) const - { - if (parent.isValid ()) { - return QModelIndex (); - } else { - return createIndex (row, column, mp_salt->begin_flat () [row]); - } - } - - QModelIndex parent (const QModelIndex & /*index*/) const - { - return QModelIndex (); - } - - int columnCount(const QModelIndex & /*parent*/) const - { - return 1; - } - - int rowCount (const QModelIndex &parent) const - { - if (parent.isValid ()) { - return 0; - } else { - return mp_salt->end_flat () - mp_salt->begin_flat (); - } - } - - SaltGrain *grain_from_index (const QModelIndex &index) const - { - if (index.isValid ()) { - return static_cast (index.internalPointer ()); - } else { - return 0; - } - } - - void update () - { - reset (); - } - -public: - lay::Salt *mp_salt; -}; - -// -------------------------------------------------------------------------------------- - -/** - * @brief A delegate displaying the summary of a grain - */ -class SaltItemDelegate - : public QStyledItemDelegate -{ -public: - SaltItemDelegate (QObject *parent) - : QStyledItemDelegate (parent) - { - // .. nothing yet .. - } - - void paint (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const - { - QStyleOptionViewItemV4 optionV4 = option; - initStyleOption (&optionV4, index); - - QStyle *style = optionV4.widget ? optionV4.widget->style () : QApplication::style (); - - QTextDocument doc; - doc.setHtml (optionV4.text); - - optionV4.text = QString (); - style->drawControl (QStyle::CE_ItemViewItem, &optionV4, painter); - - QAbstractTextDocumentLayout::PaintContext ctx; - - if (optionV4.state & QStyle::State_Selected) { - ctx.palette.setColor (QPalette::Text, optionV4.palette.color (QPalette::Active, QPalette::HighlightedText)); - } - - QRect textRect = style->subElementRect (QStyle::SE_ItemViewItemText, &optionV4); - painter->save (); - painter->translate (textRect.topLeft ()); - painter->setClipRect (textRect.translated (-textRect.topLeft ())); - doc.documentLayout()->draw (painter, ctx); - painter->restore (); - } - - QSize sizeHint (const QStyleOptionViewItem &option, const QModelIndex &index) const - { - const int textWidth = 500; - - 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 + icon_size.width () + 6, std::max (icon_size.height () + 12, int (doc.size ().height ()))); - } -}; - -// -------------------------------------------------------------------------------------- - /** * @brief A tiny dialog to select a template and a name for the grain */ @@ -384,17 +204,30 @@ END_PROTECTED void SaltManagerDialog::delete_grain () { +BEGIN_PROTECTED - // @@@ + SaltGrain *g = current_grain (); + if (! g) { + throw tl::Exception (tl::to_string (tr ("No package selected to delete"))); + } + if (QMessageBox::question (this, tr ("Delete Package"), tr ("Are you sure to delete package '%1'?").arg (tl::to_qstring (g->name ())), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { + mp_salt->remove_grain (*g); + } + +END_PROTECTED } void SaltManagerDialog::install_grain () { +BEGIN_PROTECTED - // @@@ + // @@@ TODO: cache this somewhere - don't recreate this dialog always + SaltGrainInstallationDialog inst_dialog (this, mp_salt); + inst_dialog.exec (); +END_PROTECTED } void @@ -405,6 +238,8 @@ SaltManagerDialog::salt_changed () return; } + // NOTE: the disabling of the event handler prevents us from + // letting the model connect to the salt's signal directly. m_current_changed_enabled = false; model->update (); m_current_changed_enabled = true; diff --git a/src/lay/laySaltModel.cc b/src/lay/laySaltModel.cc new file mode 100644 index 000000000..ded796c86 --- /dev/null +++ b/src/lay/laySaltModel.cc @@ -0,0 +1,207 @@ + +/* + + 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 "laySaltModel.h" +#include "laySalt.h" + +#include +#include +#include +#include +#include + +namespace lay +{ + +// -------------------------------------------------------------------------------------- + +SaltItemDelegate::SaltItemDelegate (QObject *parent) + : QStyledItemDelegate (parent) +{ + // .. nothing yet .. +} + +void +SaltItemDelegate::paint (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItemV4 optionV4 = option; + initStyleOption (&optionV4, index); + + QStyle *style = optionV4.widget ? optionV4.widget->style () : QApplication::style (); + + QTextDocument doc; + doc.setHtml (optionV4.text); + + optionV4.text = QString (); + style->drawControl (QStyle::CE_ItemViewItem, &optionV4, painter); + + QAbstractTextDocumentLayout::PaintContext ctx; + + if (optionV4.state & QStyle::State_Selected) { + ctx.palette.setColor (QPalette::Text, optionV4.palette.color (QPalette::Active, QPalette::HighlightedText)); + } + + QRect textRect = style->subElementRect (QStyle::SE_ItemViewItemText, &optionV4); + painter->save (); + painter->translate (textRect.topLeft ()); + painter->setClipRect (textRect.translated (-textRect.topLeft ())); + doc.documentLayout()->draw (painter, ctx); + painter->restore (); +} + +QSize +SaltItemDelegate::sizeHint (const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + const int textWidth = 500; + + 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 + icon_size.width () + 6, std::max (icon_size.height () + 12, int (doc.size ().height ()))); +} + +// -------------------------------------------------------------------------------------- + +SaltModel::SaltModel (QObject *parent, lay::Salt *salt) + : QAbstractItemModel (parent), mp_salt (salt) +{ + // .. nothing yet .. +} + +QVariant +SaltModel::data (const QModelIndex &index, int role) const +{ + if (role == Qt::DisplayRole) { + + const lay::SaltGrain *g = mp_salt->begin_flat ()[index.row ()]; + + std::string text = ""; + text += "

"; + text += tl::escaped_to_html (g->name ()); + if (!g->version ().empty ()) { + text += " "; + text += tl::escaped_to_html (g->version ()); + } + if (!g->title ().empty ()) { + text += " - "; + text += tl::escaped_to_html (g->title ()); + } + text += "

"; + if (!g->doc ().empty ()) { + text += "

"; + text += tl::escaped_to_html (g->doc ()); + text += "

"; + } + text += ""; + + return tl::to_qstring (text); + + } else if (role == Qt::DecorationRole) { + + int icon_dim = 64; + + const lay::SaltGrain *g = mp_salt->begin_flat ()[index.row ()]; + if (g->icon ().isNull ()) { + return QIcon (":/salt_icon.png"); + } else { + + QImage img = g->icon (); + if (img.width () == icon_dim && img.height () == icon_dim) { + return QPixmap::fromImage (img); + } else { + + img = img.scaled (QSize (icon_dim, icon_dim), Qt::KeepAspectRatio, Qt::SmoothTransformation); + + QImage final_img (icon_dim, icon_dim, QImage::Format_ARGB32); + final_img.fill (QColor (0, 0, 0, 0)); + QPainter painter (&final_img); + painter.drawImage ((icon_dim - img.width ()) / 2, (icon_dim - img.height ()) / 2, img); + + return QPixmap::fromImage (final_img); + + } + + } + + } else { + return QVariant (); + } +} + +QModelIndex +SaltModel::index (int row, int column, const QModelIndex &parent) const +{ + if (parent.isValid ()) { + return QModelIndex (); + } else { + return createIndex (row, column, mp_salt->begin_flat () [row]); + } +} + +QModelIndex +SaltModel::parent (const QModelIndex & /*index*/) const +{ + return QModelIndex (); +} + +int +SaltModel::columnCount(const QModelIndex & /*parent*/) const +{ + return 1; +} + +int +SaltModel::rowCount (const QModelIndex &parent) const +{ + if (parent.isValid ()) { + return 0; + } else { + return mp_salt->end_flat () - mp_salt->begin_flat (); + } +} + +SaltGrain * +SaltModel::grain_from_index (const QModelIndex &index) const +{ + if (index.isValid ()) { + return static_cast (index.internalPointer ()); + } else { + return 0; + } +} + +void +SaltModel::update () +{ + reset (); +} + +} diff --git a/src/lay/laySaltModel.h b/src/lay/laySaltModel.h new file mode 100644 index 000000000..1ffa5f86e --- /dev/null +++ b/src/lay/laySaltModel.h @@ -0,0 +1,110 @@ + +/* + + 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_laySaltModel +#define HDR_laySaltModel + +#include "layCommon.h" + +#include +#include +#include +#include + +namespace lay +{ + +class Salt; +class SaltGrain; + +/** + * @brief A model representing the salt grains for a QListView + */ +class SaltModel + : public QAbstractItemModel +{ +Q_OBJECT + +public: + /** + * @brief Constructor + */ + SaltModel (QObject *parent, lay::Salt *salt); + + /** + * @brief Implementation of the QAbstractItemModel interface + */ + QVariant data (const QModelIndex &index, int role) const; + + /** + * @brief Implementation of the QAbstractItemModel interface + */ + QModelIndex index (int row, int column, const QModelIndex &parent) const; + + /** + * @brief Implementation of the QAbstractItemModel interface + */ + QModelIndex parent (const QModelIndex & /*index*/) const; + + /** + * @brief Implementation of the QAbstractItemModel interface + */ + int columnCount(const QModelIndex & /*parent*/) const; + + /** + * @brief Implementation of the QAbstractItemModel interface + */ + int rowCount (const QModelIndex &parent) const; + + /** + * @brief Gets the grain pointer from a model index + */ + SaltGrain *grain_from_index (const QModelIndex &index) const; + + /** + * @brief Updates the model + * Needs to be called when the salt has changed. + */ + void update (); + +public: + lay::Salt *mp_salt; +}; + +// -------------------------------------------------------------------------------------- + +/** + * @brief A delegate displaying the summary of a grain + */ +class SaltItemDelegate + : public QStyledItemDelegate +{ +public: + SaltItemDelegate (QObject *parent); + + void paint (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + QSize sizeHint (const QStyleOptionViewItem &option, const QModelIndex &index) const; +}; + +} + +#endif diff --git a/src/unit_tests/laySalt.cc b/src/unit_tests/laySalt.cc index 9d3ef0a36..8edff9a14 100644 --- a/src/unit_tests/laySalt.cc +++ b/src/unit_tests/laySalt.cc @@ -240,6 +240,15 @@ TEST (3) EXPECT_EQ (grains_to_string (gg), "[a,b,c[c/u,c/c[c/c/v]]]"); EXPECT_EQ (gg.begin_collections ()->path (), tl::to_string (dir_c.absolutePath ())); + std::string gg_path = tmp_file ("gg.tmp"); + gg.save (gg_path); + + lay::SaltGrains ggg; + ggg.load (gg_path); + EXPECT_EQ (grains_to_string (ggg), "[a,b,c[c/u,c/c[c/c/v]]]"); + // NOTE: The path is not set, so this will fail: + // EXPECT_EQ (gg == ggg, true); + gg.remove_grain (gg.begin_grains (), false); EXPECT_EQ (grains_to_string (gg), "[b,c[c/u,c/c[c/c/v]]]"); From fee806704d74e882b10e79cab4ef75ba2aecf4f4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 25 Mar 2017 10:15:24 +0100 Subject: [PATCH 21/57] WIP: combined installation and browser dialog --- src/lay/SaltGrainInstallationDialog.ui | 313 ------- src/lay/SaltManagerDialog.ui | 968 ++++++++++++++-------- src/lay/lay.pro | 9 +- src/lay/laySaltGrainInstallationDialog.cc | 101 --- src/lay/laySaltGrainInstallationDialog.h | 76 -- src/lay/laySaltManagerDialog.cc | 103 ++- src/lay/laySaltManagerDialog.h | 18 +- 7 files changed, 737 insertions(+), 851 deletions(-) delete mode 100644 src/lay/SaltGrainInstallationDialog.ui delete mode 100644 src/lay/laySaltGrainInstallationDialog.cc delete mode 100644 src/lay/laySaltGrainInstallationDialog.h diff --git a/src/lay/SaltGrainInstallationDialog.ui b/src/lay/SaltGrainInstallationDialog.ui deleted file mode 100644 index ab54731cb..000000000 --- a/src/lay/SaltGrainInstallationDialog.ui +++ /dev/null @@ -1,313 +0,0 @@ - - - SaltGrainInstallationDialog - - - - 0 - 0 - 692 - 440 - - - - Salt Mine Repository Browser - - - - - - Qt::Horizontal - - - - - 4 - - - - - - 0 - 0 - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 2 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 75 - true - - - - Repository - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Install - - - - - - - Qt::Horizontal - - - - 126 - 20 - - - - - - - - - 0 - 0 - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - true - - - - 64 - 64 - - - - false - - - - - - - - - - - - 1 - 0 - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 4 - - - 0 - - - 0 - - - 0 - - - - - - 75 - true - - - - Details - - - - - - - true - - - - - - - - - - - QDialogButtonBox::Apply|QDialogButtonBox::Close - - - - - - - - :/add.png:/add.png - - - New - - - New package - - - - - - :/clear.png:/clear.png - - - Delete - - - Delete package - - - - - - :/import.png:/import.png - - - Import - - - Import package - - - - - - lay::DecoratedLineEdit - QLineEdit -
layWidgets.h
-
- - lay::SaltGrainDetailsTextWidget - QTextBrowser -
laySaltGrainDetailsTextWidget.h
-
-
- - details_text - - - - - - - button_box - rejected() - SaltGrainInstallationDialog - reject() - - - 559 - 425 - - - 576 - 437 - - - - -
diff --git a/src/lay/SaltManagerDialog.ui b/src/lay/SaltManagerDialog.ui index d16b5af31..0a4bcf96c 100644 --- a/src/lay/SaltManagerDialog.ui +++ b/src/lay/SaltManagerDialog.ui @@ -15,187 +15,43 @@ - - - Qt::Horizontal + + + 0 - - - - 4 - + + + Installed Packages + + - - - - 0 - 0 - + + + Qt::Horizontal - - QFrame::NoFrame - - - QFrame::Raised - - - - 2 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::NoFocus - - - Install Package - - - ... - - - - :/import.png:/import.png - - - true - - - - - - - Qt::Horizontal - - - - 50 - 0 - - - - - - - - Qt::NoFocus - - - Create New Package - - - ... - - - - :/add.png:/add.png - - - true - - - - - - - Qt::NoFocus - - - Uninstall Package - - - ... - - - - :/clear.png:/clear.png - - - true - - - - - - - - - - - 0 - 0 - - - - 0 - - - - - 0 - - - 0 - + + - 0 - - - 0 + 4 - - - true + + + + 0 + 0 + - - - 64 - 64 - - - - false - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - QFrame::StyledPanel + QFrame::NoFrame QFrame::Raised - + + + 2 + 0 @@ -209,187 +65,621 @@ 0 - - - QFrame::NoFrame + + + Qt::NoFocus - + + Create New Package + + + ... + + + + :/add.png:/add.png + + true - - - - 0 - 0 - 320 - 204 - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - :/salt.png - - - - - - - - 0 - 0 - - - - - 9 - 50 - false - - - - <h1>Salt Package Manager</h1> - - - - - - - - 1 - 0 - - - - - 0 - 0 - - - - <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<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::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - false - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 20 - - - - - - + + + + + + Qt::NoFocus + + + Uninstall Package + + + ... + + + + :/clear.png:/clear.png + + + true + + + + + + + Qt::Horizontal + + + + 50 + 0 + + + + + + + + + + + :/find.png + + + + + + + + 0 + 0 + + + + + + + 0 + 0 + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + 64 + 64 + + + + false + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 343 + 207 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + :/salt.png + + + + + + + + 0 + 0 + + + + + 9 + 50 + false + + + + <h1>Salt Package Manager</h1> + + + + + + + + 1 + 0 + + + + + 0 + 0 + + + + <html><body><h4>No packages are installed currently.</h4><p>Use<br/> +<table> + <tr><td>The "Install New Packages" tab to install a package<br/>from an external repository</td></tr> + <tr></tr> + <tr><td>The <a href=":add"><img src=":/add.png"></a> button to create a new package</td></tr> +</table> +</body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + false + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + + + + + + + + + + + + + 1 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 4 + + + 0 + + + 0 + + + 0 + + + + + + 75 + true + + + + Details + + + + + + + Edit Package Details + + + Edit + + + + :/edit.png:/edit.png + + + true + + + + + + + true + + + - - - - 1 - 0 - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 4 - - - 0 - - - 0 - - - 0 - - - - - - 75 - true - + + + Install New Packages + + + + + + + 0 + 1 + - - Details + + Qt::Horizontal + + + + 4 + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 2 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Mark for installation + + + Mark + + + + + + + Qt::Horizontal + + + + 126 + 20 + + + + + + + + + + + :/find.png + + + + + + + + 0 + 0 + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + 64 + 64 + + + + false + + + + + + + + + + + + 1 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 4 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 75 + true + + + + Details + + + + + + + false + + + ... + + + + :/empty_16.png:/empty_16.png + + + true + + + + + + + + + + true + + + + + - - - - Edit Package Details + + + + QFrame::NoFrame - - Edit - - - - :/edit.png:/edit.png - - - true - - - - - - - true + + QFrame::Raised + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Apply + + + false + + + false + + + + + + + Qt::Horizontal + + + + 563 + 20 + + + + + @@ -442,6 +732,11 @@ + + lay::DecoratedLineEdit + QLineEdit +
layWidgets.h
+
lay::SaltGrainDetailsTextWidget QTextBrowser @@ -449,11 +744,18 @@
+ mode_tab + search_installed_edit salt_view - install_button - create_button - delete_button details_text + edit_button + search_new_edit + mark_button + salt_mine_view + details_new_text + toolButton + apply_button + scrollArea diff --git a/src/lay/lay.pro b/src/lay/lay.pro index 0d1251609..7d1bff20d 100644 --- a/src/lay/lay.pro +++ b/src/lay/lay.pro @@ -53,8 +53,7 @@ HEADERS = \ laySaltGrainDetailsTextWidget.h \ laySaltGrainPropertiesDialog.h \ laySaltDownloadManager.h \ - laySaltModel.h \ - laySaltGrainInstallationDialog.h + laySaltModel.h FORMS = \ ClipDialog.ui \ @@ -102,8 +101,7 @@ FORMS = \ MainConfigPage7.ui \ SaltManagerDialog.ui \ SaltGrainPropertiesDialog.ui \ - SaltGrainTemplateSelectionDialog.ui \ - SaltGrainInstallationDialog.ui + SaltGrainTemplateSelectionDialog.ui SOURCES = \ gsiDeclLayApplication.cc \ @@ -154,8 +152,7 @@ SOURCES = \ laySaltGrainDetailsTextWidget.cc \ laySaltGrainPropertiesDialog.cc \ laySaltDownloadManager.cc \ - laySaltModel.cc \ - laySaltGrainInstallationDialog.cc + laySaltModel.cc RESOURCES = layBuildInMacros.qrc \ layHelpResources.qrc \ diff --git a/src/lay/laySaltGrainInstallationDialog.cc b/src/lay/laySaltGrainInstallationDialog.cc deleted file mode 100644 index f12d7f576..000000000 --- a/src/lay/laySaltGrainInstallationDialog.cc +++ /dev/null @@ -1,101 +0,0 @@ - -/* - - 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 "laySaltGrainInstallationDialog.h" -#include "laySaltModel.h" -#include "laySaltGrainPropertiesDialog.h" -#include "laySalt.h" -#include "ui_SaltGrainTemplateSelectionDialog.h" -#include "tlString.h" -#include "tlExceptions.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace lay -{ - -// -------------------------------------------------------------------------------------- -// SaltGrainInstallation implementation - -SaltGrainInstallationDialog::SaltGrainInstallationDialog (QWidget *parent, lay::Salt *salt) - : QDialog (parent), mp_salt (salt) -{ - Ui::SaltGrainInstallationDialog::setupUi (this); - - // TODO: cache package list - // @@@ - m_salt_mine.load ("/home/matthias/salt.mine"); - // @@@ - - SaltModel *model = new SaltModel (this, &m_salt_mine); - salt_view->setModel (model); - salt_view->setItemDelegate (new SaltItemDelegate (this)); - salt_view->setCurrentIndex (model->index (0, 0, QModelIndex ())); - - connect (salt_view->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (current_changed ())); - connect (mark_button, SIGNAL (clicked ()), this, SLOT (mark ())); - connect (button_box->button (QDialogButtonBox::Apply), SIGNAL (clicked ()), this, SLOT (apply ())); - - current_changed (); -} - -void -SaltGrainInstallationDialog::current_changed () -{ - SaltGrain *g = current_grain (); - details_text->set_grain (g); - details_frame->setEnabled (g != 0); -} - -lay::SaltGrain * -SaltGrainInstallationDialog::current_grain () -{ - SaltModel *model = dynamic_cast (salt_view->model ()); - return model ? model->grain_from_index (salt_view->currentIndex ()) : 0; -} - -void -SaltGrainInstallationDialog::apply () -{ - - // @@@ - -} - -void -SaltGrainInstallationDialog::mark () -{ - - // @@@ - -} - -} diff --git a/src/lay/laySaltGrainInstallationDialog.h b/src/lay/laySaltGrainInstallationDialog.h deleted file mode 100644 index 9c7986fb2..000000000 --- a/src/lay/laySaltGrainInstallationDialog.h +++ /dev/null @@ -1,76 +0,0 @@ - -/* - - 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_laySaltGrainInstallationDialog -#define HDR_laySaltGrainInstallationDialog - -#include "laySalt.h" - -#include - -#include "ui_SaltGrainInstallationDialog.h" - -namespace lay -{ - -class Salt; - -/** - * @brief The dialog for managing the Salt ("Packages") - */ -class SaltGrainInstallationDialog - : public QDialog, private Ui::SaltGrainInstallationDialog -{ -Q_OBJECT - -public: - /** - * @brief Constructor - */ - SaltGrainInstallationDialog (QWidget *parent, lay::Salt *salt); - -private slots: - /** - * @brief Called when the currently selected package (grain) has changed - */ - void current_changed (); - - /** - * @brief Called when the Apply button is clicked - */ - void apply (); - - /** - * @brief Called when the Mark button is pressed - */ - void mark (); - -private: - lay::Salt *mp_salt; - lay::Salt m_salt_mine; - - lay::SaltGrain *current_grain (); -}; - -} - -#endif diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index c0d0934be..09d4a66ed 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -23,7 +23,6 @@ #include "laySaltManagerDialog.h" #include "laySaltModel.h" #include "laySaltGrainPropertiesDialog.h" -#include "laySaltGrainInstallationDialog.h" #include "laySalt.h" #include "ui_SaltGrainTemplateSelectionDialog.h" #include "tlString.h" @@ -119,6 +118,25 @@ void make_salt () salt.add_location (tl::to_string (QDir::homePath () + QString::fromUtf8("/.klayout/salt"))); } } +lay::Salt *get_salt () +{ + salt = lay::Salt (); salt_initialized = false; + make_salt (); + return &salt; +} +// @@@ + +// @@@ +lay::Salt salt_mine; +void make_salt_mine () +{ + salt_mine.load ("/home/matthias/salt.mine"); +} +lay::Salt *get_salt_mine () +{ + make_salt_mine(); + return &salt_mine; +} // @@@ SaltManagerDialog::SaltManagerDialog (QWidget *parent) @@ -131,27 +149,46 @@ SaltManagerDialog::SaltManagerDialog (QWidget *parent) connect (edit_button, SIGNAL (clicked ()), this, SLOT (edit_properties ())); connect (create_button, SIGNAL (clicked ()), this, SLOT (create_grain ())); connect (delete_button, SIGNAL (clicked ()), this, SLOT (delete_grain ())); - connect (install_button, SIGNAL (clicked ()), this, SLOT (install_grain ())); -// @@@ - salt = lay::Salt (); salt_initialized = false; - make_salt (); - mp_salt = &salt; -// @@@ + mp_salt = get_salt (); + mp_salt_mine = get_salt_mine (); - SaltModel *model = new SaltModel (this, mp_salt); + SaltModel *model; + + model = new SaltModel (this, mp_salt); salt_view->setModel (model); salt_view->setItemDelegate (new SaltItemDelegate (this)); + model = new SaltModel (this, mp_salt_mine); + salt_mine_view->setModel (model); + salt_mine_view->setItemDelegate (new SaltItemDelegate (this)); + + mode_tab->setCurrentIndex (mp_salt->is_empty () ? 1 : 0); + + connect (mode_tab, SIGNAL (currentChanged (int)), this, SLOT (mode_changed ())); connect (mp_salt, SIGNAL (collections_changed ()), this, SLOT (salt_changed ())); + connect (mp_salt_mine, SIGNAL (collections_changed ()), this, SLOT (salt_mine_changed ())); salt_changed (); + salt_mine_changed (); connect (salt_view->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (current_changed ())); + connect (salt_mine_view->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (mine_current_changed ())); // @@@ } +void +SaltManagerDialog::mode_changed () +{ + // keeps the splitters in sync + if (mode_tab->currentIndex () == 1) { + splitter_new->setSizes (splitter->sizes ()); + } else if (mode_tab->currentIndex () == 0) { + splitter->setSizes (splitter_new->sizes ()); + } +} + void SaltManagerDialog::edit_properties () { @@ -159,7 +196,6 @@ SaltManagerDialog::edit_properties () if (g) { if (mp_properties_dialog->exec_dialog (g, mp_salt)) { current_changed (); - // @@@ } } } @@ -218,18 +254,6 @@ BEGIN_PROTECTED END_PROTECTED } -void -SaltManagerDialog::install_grain () -{ -BEGIN_PROTECTED - - // @@@ TODO: cache this somewhere - don't recreate this dialog always - SaltGrainInstallationDialog inst_dialog (this, mp_salt); - inst_dialog.exec (); - -END_PROTECTED -} - void SaltManagerDialog::salt_changed () { @@ -286,4 +310,41 @@ SaltManagerDialog::current_grain () return model ? model->grain_from_index (salt_view->currentIndex ()) : 0; } +void +SaltManagerDialog::salt_mine_changed () +{ + SaltModel *model = dynamic_cast (salt_mine_view->model ()); + if (! model) { + return; + } + + // NOTE: the disabling of the event handler prevents us from + // letting the model connect to the salt's signal directly. + m_current_changed_enabled = false; + model->update (); + m_current_changed_enabled = true; + + // select the first grain + if (model->rowCount (QModelIndex ()) > 0) { + salt_mine_view->setCurrentIndex (model->index (0, 0, QModelIndex ())); + } + + mine_current_changed (); +} + +void +SaltManagerDialog::mine_current_changed () +{ + SaltGrain *g = mine_current_grain (); + details_new_text->set_grain (g); + details_new_frame->setEnabled (g != 0); +} + +lay::SaltGrain * +SaltManagerDialog::mine_current_grain () +{ + SaltModel *model = dynamic_cast (salt_mine_view->model ()); + return model ? model->grain_from_index (salt_mine_view->currentIndex ()) : 0; +} + } diff --git a/src/lay/laySaltManagerDialog.h b/src/lay/laySaltManagerDialog.h index 232d40403..305969f73 100644 --- a/src/lay/laySaltManagerDialog.h +++ b/src/lay/laySaltManagerDialog.h @@ -54,11 +54,21 @@ private slots: */ void salt_changed (); + /** + * @brief Called when the repository (salt mine) has changed + */ + void salt_mine_changed (); + /** * @brief Called when the currently selected package (grain) has changed */ void current_changed (); + /** + * @brief Called when the currently selected package from the salt mine has changed + */ + void mine_current_changed (); + /** * @brief Called when the "edit" button is pressed */ @@ -79,12 +89,18 @@ private slots: */ void install_grain (); + /** + * @brief Called when the mode tab changed + */ + void mode_changed (); + private: - lay::Salt *mp_salt; + lay::Salt *mp_salt, *mp_salt_mine; bool m_current_changed_enabled; lay::SaltGrainPropertiesDialog *mp_properties_dialog; lay::SaltGrain *current_grain (); + lay::SaltGrain *mine_current_grain (); }; } From b72655c94b675cc752bf4dd14a2ccb8398c8e7ca Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 25 Mar 2017 23:33:07 +0100 Subject: [PATCH 22/57] WIP: preview download and search feature in package manager --- src/lay/SaltManagerDialog.ui | 4 +- src/lay/laySaltManagerDialog.cc | 102 +++++++++++++++++++++++++++++++- src/lay/laySaltManagerDialog.h | 11 +++- 3 files changed, 111 insertions(+), 6 deletions(-) diff --git a/src/lay/SaltManagerDialog.ui b/src/lay/SaltManagerDialog.ui index 0a4bcf96c..9194d7715 100644 --- a/src/lay/SaltManagerDialog.ui +++ b/src/lay/SaltManagerDialog.ui @@ -21,7 +21,7 @@
- Installed Packages + Browse Installed Packages @@ -410,7 +410,7 @@ - Install New Packages + Install or Update Packages diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index 09d4a66ed..5ce7cd4eb 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -27,6 +27,7 @@ #include "ui_SaltGrainTemplateSelectionDialog.h" #include "tlString.h" #include "tlExceptions.h" +#include "tlHttpStream.h" #include #include @@ -130,6 +131,7 @@ lay::Salt *get_salt () lay::Salt salt_mine; void make_salt_mine () { + salt_mine = lay::Salt (); salt_mine.load ("/home/matthias/salt.mine"); } lay::Salt *get_salt_mine () @@ -175,7 +177,10 @@ SaltManagerDialog::SaltManagerDialog (QWidget *parent) connect (salt_view->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (current_changed ())); connect (salt_mine_view->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (mine_current_changed ())); - // @@@ + search_installed_edit->set_clear_button_enabled (true); + search_new_edit->set_clear_button_enabled (true); + connect (search_installed_edit, SIGNAL (textChanged (const QString &)), this, SLOT (search_text_changed (const QString &))); + connect (search_new_edit, SIGNAL (textChanged (const QString &)), this, SLOT (search_text_changed (const QString &))); } void @@ -189,6 +194,45 @@ SaltManagerDialog::mode_changed () } } +void +SaltManagerDialog::search_text_changed (const QString &text) +{ + QListView *view = 0; + if (sender () == search_installed_edit) { + view = salt_view; + } else if (sender () == search_new_edit) { + view = salt_mine_view; + } else { + return; + } + + SaltModel *model = dynamic_cast (view->model ()); + if (! model) { + return; + } + + if (text.isEmpty ()) { + + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + view->setRowHidden (i, false); + } + + } else { + + QRegExp re (text, Qt::CaseInsensitive); + + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + QModelIndex index = model->index (i, 0, QModelIndex ()); + SaltGrain *g = model->grain_from_index (index); + bool hidden = (!g || re.indexIn (tl::to_qstring (g->name ())) < 0); + view->setRowHidden (i, hidden); + } + + } +} + void SaltManagerDialog::edit_properties () { @@ -335,9 +379,63 @@ SaltManagerDialog::salt_mine_changed () void SaltManagerDialog::mine_current_changed () { +BEGIN_PROTECTED + SaltGrain *g = mine_current_grain (); - details_new_text->set_grain (g); details_new_frame->setEnabled (g != 0); + + if (! g) { + details_new_text->set_grain (0); + return; + } + + m_remote_grain.reset (0); + + // Download actual grain definition file + try { + + if (g->url ().empty ()) { + throw tl::Exception (tl::to_string (tr ("No download link available"))); + } + + tl::InputHttpStream http (SaltGrain::spec_url (g->url ())); + tl::InputStream stream (http); + + m_remote_grain.reset (new SaltGrain ()); + m_remote_grain->load (stream); + m_remote_grain->set_url (g->url ()); + + if (g->name () != m_remote_grain->name ()) { + throw tl::Exception (tl::to_string (tr ("Name mismatch between repository and actual package (repository: %1, package: %2)").arg (tl::to_qstring (g->name ())).arg (tl::to_qstring (m_remote_grain->name ())))); + } + if (SaltGrain::compare_versions (g->version (), m_remote_grain->version ()) != 0) { + throw tl::Exception (tl::to_string (tr ("Version mismatch between repository and actual package (repository: %1, package: %2)").arg (tl::to_qstring (g->version ())).arg (tl::to_qstring (m_remote_grain->version ())))); + } + + details_new_text->set_grain (m_remote_grain.get ()); + + } catch (tl::Exception &ex) { + + m_remote_grain.reset (0); + + QString text = tr ( + "" + "" + "" + "

Error Fetching Package Definition

" + "

URL: %1

" + "

Error: %2

" + "" + "" + ) + .arg (tl::to_qstring (SaltGrain::spec_url (g->url ()))) + .arg (tl::to_qstring (tl::escaped_to_html (ex.msg ()))); + + details_new_text->setHtml (text); + + } + +END_PROTECTED } lay::SaltGrain * diff --git a/src/lay/laySaltManagerDialog.h b/src/lay/laySaltManagerDialog.h index 305969f73..68576602a 100644 --- a/src/lay/laySaltManagerDialog.h +++ b/src/lay/laySaltManagerDialog.h @@ -23,10 +23,11 @@ #ifndef HDR_laySaltManagerDialog #define HDR_laySaltManagerDialog -#include - #include "ui_SaltManagerDialog.h" +#include +#include + namespace lay { @@ -94,8 +95,14 @@ private slots: */ void mode_changed (); + /** + * @brief Called when one search text changed + */ + void search_text_changed (const QString &text); + private: lay::Salt *mp_salt, *mp_salt_mine; + std::auto_ptr m_remote_grain; bool m_current_changed_enabled; lay::SaltGrainPropertiesDialog *mp_properties_dialog; From 9e2c4cb927b9fc9e6b1451c9f0fd63ae404f7a08 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 26 Mar 2017 01:02:40 +0100 Subject: [PATCH 23/57] WIP: marked mode, update required message --- src/lay/SaltManagerDialog.ui | 15 +++++-- src/lay/images/marked_16.png | Bin 0 -> 889 bytes src/lay/images/marked_24.png | Bin 0 -> 1452 bytes src/lay/images/marked_64.png | Bin 0 -> 3353 bytes src/lay/layResources.qrc | 3 ++ src/lay/laySaltManagerDialog.cc | 61 ++++++++++++++++++++++---- src/lay/laySaltManagerDialog.h | 5 +++ src/lay/laySaltModel.cc | 73 ++++++++++++++++++++++++++------ src/lay/laySaltModel.h | 17 ++++++++ 9 files changed, 147 insertions(+), 27 deletions(-) create mode 100644 src/lay/images/marked_16.png create mode 100644 src/lay/images/marked_24.png create mode 100644 src/lay/images/marked_64.png diff --git a/src/lay/SaltManagerDialog.ui b/src/lay/SaltManagerDialog.ui index 9194d7715..a51993a12 100644 --- a/src/lay/SaltManagerDialog.ui +++ b/src/lay/SaltManagerDialog.ui @@ -17,7 +17,7 @@ - 0 + 1 @@ -231,8 +231,8 @@ 0 0 - 343 - 207 + 537 + 284 @@ -456,11 +456,18 @@ - Mark for installation + Mark or unmark for installation Mark + + + :/marked_16.png:/marked_16.png + + + true + diff --git a/src/lay/images/marked_16.png b/src/lay/images/marked_16.png new file mode 100644 index 0000000000000000000000000000000000000000..aa152411be8cfc02a42015b1b194db9e08d7321e GIT binary patch literal 889 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 literal 0 HcmV?d00001 diff --git a/src/lay/layResources.qrc b/src/lay/layResources.qrc index 3ffd9e81e..52e7bc522 100644 --- a/src/lay/layResources.qrc +++ b/src/lay/layResources.qrc @@ -120,6 +120,9 @@ images/empty_16.png images/error_16.png images/info_16.png + images/marked_24.png + images/marked_64.png + images/marked_16.png
syntax/ruby.xml diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index 5ce7cd4eb..57ce3fbf5 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -155,19 +155,27 @@ SaltManagerDialog::SaltManagerDialog (QWidget *parent) mp_salt = get_salt (); mp_salt_mine = get_salt_mine (); - SaltModel *model; - - model = new SaltModel (this, mp_salt); + SaltModel *model = new SaltModel (this, mp_salt); salt_view->setModel (model); salt_view->setItemDelegate (new SaltItemDelegate (this)); - model = new SaltModel (this, mp_salt_mine); - salt_mine_view->setModel (model); + SaltModel *mine_model = new SaltModel (this, mp_salt_mine); + salt_mine_view->setModel (mine_model); salt_mine_view->setItemDelegate (new SaltItemDelegate (this)); + // Establish a message saying that an update is available + for (Salt::flat_iterator g = mp_salt->begin_flat (); g != mp_salt->end_flat (); ++g) { + SaltGrain *gm = mp_salt_mine->grain_by_name ((*g)->name ()); + if (gm && SaltGrain::compare_versions (gm->version (), (*g)->version ()) > 0) { + model->set_message ((*g)->name (), tl::to_string (tr ("An update to version %1 is available").arg (tl::to_qstring (gm->version ())))); + mine_model->set_message ((*g)->name (), tl::to_string (tr ("The installed version is outdated (%1)").arg (tl::to_qstring ((*g)->version ())))); + } + } + mode_tab->setCurrentIndex (mp_salt->is_empty () ? 1 : 0); connect (mode_tab, SIGNAL (currentChanged (int)), this, SLOT (mode_changed ())); + connect (mp_salt, SIGNAL (collections_changed ()), this, SLOT (salt_changed ())); connect (mp_salt_mine, SIGNAL (collections_changed ()), this, SLOT (salt_mine_changed ())); @@ -175,12 +183,14 @@ SaltManagerDialog::SaltManagerDialog (QWidget *parent) salt_mine_changed (); connect (salt_view->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (current_changed ())); - connect (salt_mine_view->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (mine_current_changed ())); + connect (salt_mine_view->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (mine_current_changed ()), Qt::QueuedConnection); search_installed_edit->set_clear_button_enabled (true); search_new_edit->set_clear_button_enabled (true); connect (search_installed_edit, SIGNAL (textChanged (const QString &)), this, SLOT (search_text_changed (const QString &))); connect (search_new_edit, SIGNAL (textChanged (const QString &)), this, SLOT (search_text_changed (const QString &))); + + connect (mark_button, SIGNAL (clicked ()), this, SLOT (mark_clicked ())); } void @@ -233,6 +243,21 @@ SaltManagerDialog::search_text_changed (const QString &text) } } +void +SaltManagerDialog::mark_clicked () +{ + SaltModel *model = dynamic_cast (salt_mine_view->model ()); + if (! model) { + return; + } + SaltGrain *g = mine_current_grain (); + if (! g) { + return; + } + + model->set_marked (g->name (), !model->is_marked (g->name ())); +} + void SaltManagerDialog::edit_properties () { @@ -379,6 +404,7 @@ SaltManagerDialog::salt_mine_changed () void SaltManagerDialog::mine_current_changed () { + BEGIN_PROTECTED SaltGrain *g = mine_current_grain (); @@ -398,6 +424,22 @@ BEGIN_PROTECTED throw tl::Exception (tl::to_string (tr ("No download link available"))); } + QString text = tr ( + "" + "" + "" + "

Fetching Package Definition ...

" + "

URL: %1

" + "
" + "" + "" + ) + .arg (tl::to_qstring (SaltGrain::spec_url (g->url ()))); + + details_new_text->setHtml (text); + + QApplication::processEvents (QEventLoop::ExcludeUserInputEvents); + tl::InputHttpStream http (SaltGrain::spec_url (g->url ())); tl::InputStream stream (http); @@ -422,9 +464,10 @@ BEGIN_PROTECTED "" "" "" - "

Error Fetching Package Definition

" - "

URL: %1

" - "

Error: %2

" + "

Error Fetching Package Definition

" + "

URL: %1

" + "

Error: %2

" + "
" "" "" ) diff --git a/src/lay/laySaltManagerDialog.h b/src/lay/laySaltManagerDialog.h index 68576602a..6a5068217 100644 --- a/src/lay/laySaltManagerDialog.h +++ b/src/lay/laySaltManagerDialog.h @@ -75,6 +75,11 @@ private slots: */ void edit_properties (); + /** + * @brief Called when the "mark" button is pressed + */ + void mark_clicked (); + /** * @brief Called when the "edit" button is pressed */ diff --git a/src/lay/laySaltModel.cc b/src/lay/laySaltModel.cc index ded796c86..57dd44212 100644 --- a/src/lay/laySaltModel.cc +++ b/src/lay/laySaltModel.cc @@ -120,6 +120,12 @@ SaltModel::data (const QModelIndex &index, int role) const text += tl::escaped_to_html (g->doc ()); text += "

"; } + + std::map::const_iterator m = m_messages.find (g->name ()); + if (m != m_messages.end ()) { + text += "

" + tl::escaped_to_html (m->second) + "

"; + } + text += ""; return tl::to_qstring (text); @@ -129,28 +135,39 @@ SaltModel::data (const QModelIndex &index, int role) const int icon_dim = 64; const lay::SaltGrain *g = mp_salt->begin_flat ()[index.row ()]; + + QImage img; if (g->icon ().isNull ()) { - return QIcon (":/salt_icon.png"); + img = QImage (":/salt_icon.png"); } else { + img = g->icon (); + } - QImage img = g->icon (); - if (img.width () == icon_dim && img.height () == icon_dim) { - return QPixmap::fromImage (img); - } else { + if (img.width () != icon_dim || img.height () != icon_dim) { - img = img.scaled (QSize (icon_dim, icon_dim), Qt::KeepAspectRatio, Qt::SmoothTransformation); + QImage scaled = img.scaled (QSize (icon_dim, icon_dim), Qt::KeepAspectRatio, Qt::SmoothTransformation); - QImage final_img (icon_dim, icon_dim, QImage::Format_ARGB32); - final_img.fill (QColor (0, 0, 0, 0)); - QPainter painter (&final_img); - painter.drawImage ((icon_dim - img.width ()) / 2, (icon_dim - img.height ()) / 2, img); - - return QPixmap::fromImage (final_img); - - } + img = QImage (icon_dim, icon_dim, QImage::Format_ARGB32); + img.fill (QColor (0, 0, 0, 0)); + QPainter painter (&img); + painter.drawImage ((icon_dim - scaled.width ()) / 2, (icon_dim - scaled.height ()) / 2, scaled); } + if (m_marked.find (g->name ()) != m_marked.end ()) { + QPainter painter (&img); + QImage warn (":/marked_64.png"); + painter.drawImage (0, 0, warn); + } + + if (m_messages.find (g->name ()) != m_messages.end ()) { + QPainter painter (&img); + QImage warn (":/warn_16.png"); + painter.drawImage (0, 0, warn); + } + + return QPixmap::fromImage (img); + } else { return QVariant (); } @@ -198,6 +215,34 @@ SaltModel::grain_from_index (const QModelIndex &index) const } } +bool +SaltModel::is_marked (const std::string &name) const +{ + return m_marked.find (name) != m_marked.end (); +} + +void +SaltModel::set_marked (const std::string &name, bool marked) +{ + if (! marked) { + m_marked.erase (name); + } else { + m_marked.insert (name); + } + emit dataChanged (index (0, 0, QModelIndex ()), index (rowCount (QModelIndex ()) - 1, 0, QModelIndex ())); +} + +void +SaltModel::set_message (const std::string &name, const std::string &message) +{ + if (message.empty ()) { + m_messages.erase (name); + } else { + m_messages.insert (std::make_pair (name, message)); + } + emit dataChanged (index (0, 0, QModelIndex ()), index (rowCount (QModelIndex ()) - 1, 0, QModelIndex ())); +} + void SaltModel::update () { diff --git a/src/lay/laySaltModel.h b/src/lay/laySaltModel.h index 1ffa5f86e..20a537e2c 100644 --- a/src/lay/laySaltModel.h +++ b/src/lay/laySaltModel.h @@ -29,6 +29,8 @@ #include #include #include +#include +#include namespace lay { @@ -86,8 +88,23 @@ public: */ void update (); + /** + * @brief Sets or resets the "marked" flag on the grain with the given name + */ + void set_marked (const std::string &name, bool marked); + + /** + * @brief Installs a message on the grain with the given name + * Installing an empty message basically removes the message. + */ + void set_message (const std::string &name, const std::string &message); + public: lay::Salt *mp_salt; + std::set m_marked; + std::map m_messages; + + bool is_marked (const std::string &name) const; }; // -------------------------------------------------------------------------------------- From a5d0461284f1c68270ce7a845155b6f1797e3b27 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 26 Mar 2017 23:26:45 +0200 Subject: [PATCH 24/57] WIP: removed obsolete method. --- src/lay/laySaltManagerDialog.h | 5 ----- src/unit_tests/tlHttpStream.cc | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 src/unit_tests/tlHttpStream.cc diff --git a/src/lay/laySaltManagerDialog.h b/src/lay/laySaltManagerDialog.h index 6a5068217..cbbfac0a4 100644 --- a/src/lay/laySaltManagerDialog.h +++ b/src/lay/laySaltManagerDialog.h @@ -90,11 +90,6 @@ private slots: */ void delete_grain (); - /** - * @brief Called when the "install" button is pressed - */ - void install_grain (); - /** * @brief Called when the mode tab changed */ diff --git a/src/unit_tests/tlHttpStream.cc b/src/unit_tests/tlHttpStream.cc new file mode 100644 index 000000000..184416f1f --- /dev/null +++ b/src/unit_tests/tlHttpStream.cc @@ -0,0 +1,33 @@ + +/* + + 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 "tlHttpStream.h" +#include "utHead.h" + +std::string test_url1 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text"); + +TEST(1) +{ + EXPECT_EQ (to_string (12.5), "12.5"); +} + From d98495c18a9c1884a865b1086ee2da50188c931c Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 26 Mar 2017 23:27:51 +0200 Subject: [PATCH 25/57] WIP: new features for HTTP streams. --- src/tl/tlHttpStream.cc | 39 ++++++++++++++++++++++++++++++---- src/tl/tlHttpStream.h | 38 +++++++++++++++++++++++++++------ src/unit_tests/tlHttpStream.cc | 7 +++++- src/unit_tests/unit_tests.pro | 3 ++- 4 files changed, 75 insertions(+), 12 deletions(-) diff --git a/src/tl/tlHttpStream.cc b/src/tl/tlHttpStream.cc index a709586e8..216e31c76 100644 --- a/src/tl/tlHttpStream.cc +++ b/src/tl/tlHttpStream.cc @@ -78,7 +78,7 @@ public: static QNetworkAccessManager *s_network_manager (0); InputHttpStream::InputHttpStream (const std::string &url) - : m_url (url) + : m_url (url), m_request ("GET"), mp_buffer (0) { if (! s_network_manager) { s_network_manager = new QNetworkAccessManager(0); @@ -87,7 +87,7 @@ InputHttpStream::InputHttpStream (const std::string &url) connect (s_network_manager, SIGNAL (finished (QNetworkReply *)), this, SLOT (finished (QNetworkReply *))); connect (s_network_manager, SIGNAL (authenticationRequired (QNetworkReply *, QAuthenticator *)), this, SLOT (authenticationRequired (QNetworkReply *, QAuthenticator *))); connect (s_network_manager, SIGNAL (proxyAuthenticationRequired (const QNetworkProxy &, QAuthenticator *)), this, SLOT (proxyAuthenticationRequired (const QNetworkProxy &, QAuthenticator *))); - s_network_manager->get (QNetworkRequest (QUrl (tl::to_qstring (url)))); + issue_request (QUrl (tl::to_qstring (url))); mp_reply = 0; } @@ -97,6 +97,24 @@ InputHttpStream::~InputHttpStream () mp_reply = 0; } +void +InputHttpStream::set_request (const char *r) +{ + m_request = QByteArray (r); +} + +void +InputHttpStream::set_data (const char *data) +{ + m_data = QByteArray (data); +} + +void +InputHttpStream::set_data (const char *data, size_t n) +{ + m_data = QByteArray (data, int (n)); +} + void InputHttpStream::authenticationRequired (QNetworkReply *reply, QAuthenticator *auth) { @@ -117,13 +135,27 @@ InputHttpStream::finished (QNetworkReply *reply) QVariant redirect_target = reply->attribute (QNetworkRequest::RedirectionTargetAttribute); if (reply->error () == QNetworkReply::NoError && ! redirect_target.isNull ()) { m_url = tl::to_string (redirect_target.toString ()); - s_network_manager->get (QNetworkRequest (QUrl (redirect_target.toString ()))); + issue_request (QUrl (redirect_target.toString ())); delete reply; } else { mp_reply = reply; } } +void +InputHttpStream::issue_request (const QUrl &url) +{ + delete mp_buffer; + mp_buffer = 0; + + if (m_data.isEmpty ()) { + s_network_manager->sendCustomRequest (QNetworkRequest (url), m_request); + } else { + mp_buffer = new QBuffer (&m_data); + s_network_manager->sendCustomRequest (QNetworkRequest (url), m_request, mp_buffer); + } +} + size_t InputHttpStream::read (char *b, size_t n) { @@ -160,4 +192,3 @@ InputHttpStream::filename () const } } - diff --git a/src/tl/tlHttpStream.h b/src/tl/tlHttpStream.h index 5a2da4d8a..7f20c0aad 100644 --- a/src/tl/tlHttpStream.h +++ b/src/tl/tlHttpStream.h @@ -27,6 +27,8 @@ #include "tlStream.h" #include +#include +#include class QNetworkAccessManager; class QNetworkReply; @@ -68,14 +70,33 @@ public: */ virtual ~InputHttpStream (); + /** + * @brief Sets the request verb + * The default verb is "GET" + */ + void set_request (const char *r); + + /** + * @brief Sets data to be sent with the request + * If data is given, it is sent along with the request. + * This version takes a null-terminated string. + */ + void set_data (const char *data); + + /** + * @brief Sets data to be sent with the request + * If data is given, it is sent along with the request. + * This version takes a data plus length. + */ + void set_data (const char *data, size_t n); + /** * @brief Read from the stream - * * Implements the basic read method. */ virtual size_t read (char *b, size_t n); - virtual void reset (); + virtual void reset (); virtual std::string source () const { @@ -89,14 +110,19 @@ public: virtual std::string filename () const; -private: - std::string m_url; - QNetworkReply *mp_reply; - private slots: void finished (QNetworkReply *); void authenticationRequired (QNetworkReply *, QAuthenticator *); void proxyAuthenticationRequired (const QNetworkProxy &, QAuthenticator *); + +private: + std::string m_url; + QNetworkReply *mp_reply; + QByteArray m_request; + QByteArray m_data; + QBuffer *mp_buffer; + + void issue_request (const QUrl &url); }; } diff --git a/src/unit_tests/tlHttpStream.cc b/src/unit_tests/tlHttpStream.cc index 184416f1f..1783b2ccb 100644 --- a/src/unit_tests/tlHttpStream.cc +++ b/src/unit_tests/tlHttpStream.cc @@ -28,6 +28,11 @@ std::string test_url1 ("http://www.klayout.org/svn-public/klayout-resources/trun TEST(1) { - EXPECT_EQ (to_string (12.5), "12.5"); + tl::InputHttpStream stream (test_url1); + + char b[100]; + size_t n = stream.read (b, sizeof (b)); + std::string res (b, n); + EXPECT_EQ (res, "hello, world.\n"); } diff --git a/src/unit_tests/unit_tests.pro b/src/unit_tests/unit_tests.pro index 31dd201a4..608f86786 100644 --- a/src/unit_tests/unit_tests.pro +++ b/src/unit_tests/unit_tests.pro @@ -97,7 +97,8 @@ SOURCES = \ gsiTest.cc \ tlFileSystemWatcher.cc \ laySalt.cc \ - tlFileUtils.cc + tlFileUtils.cc \ + tlHttpStream.cc # main components: SOURCES += \ From cb589dc2d3b690609918192b90bcac8c0d0ee9dd Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 27 Mar 2017 15:46:01 +0200 Subject: [PATCH 26/57] WIP: downloading of packages - Support for WebDAV - Download manager implemented - Apply button functionality implemented (needs testing) --- .../SaltManagerInstallConfirmationDialog.ui | 156 +++++++++ src/lay/lay.pro | 3 +- src/lay/laySalt.cc | 21 +- src/lay/laySalt.h | 22 +- src/lay/laySaltDownloadManager.cc | 175 +++++++++- src/lay/laySaltDownloadManager.h | 67 +++- src/lay/laySaltManagerDialog.cc | 38 +++ src/lay/laySaltManagerDialog.h | 5 + src/tl/tl.pro | 6 +- src/tl/tlHttpStream.cc | 20 +- src/tl/tlHttpStream.h | 6 + src/tl/tlWebDAV.cc | 316 ++++++++++++++++++ src/tl/tlWebDAV.h | 152 +++++++++ src/tl/tlXMLParser.h | 4 +- src/unit_tests/tlHttpStream.cc | 39 ++- src/unit_tests/tlWebDAV.cc | 129 +++++++ src/unit_tests/unit_tests.pro | 3 +- 17 files changed, 1130 insertions(+), 32 deletions(-) create mode 100644 src/lay/SaltManagerInstallConfirmationDialog.ui create mode 100644 src/tl/tlWebDAV.cc create mode 100644 src/tl/tlWebDAV.h create mode 100644 src/unit_tests/tlWebDAV.cc diff --git a/src/lay/SaltManagerInstallConfirmationDialog.ui b/src/lay/SaltManagerInstallConfirmationDialog.ui new file mode 100644 index 000000000..0920a619b --- /dev/null +++ b/src/lay/SaltManagerInstallConfirmationDialog.ui @@ -0,0 +1,156 @@ + + + SaltManagerInstallConfirmationDialog + + + + 0 + 0 + 495 + 478 + + + + Ready for Installation + + + + + + The following packages are now ready for installation or update: + + + true + + + + + + + + Package + + + + + Action + + + + + Version + + + + + Download link + + + + + + + + Press "Ok" to install or update these packages or "Cancel" to abort. + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 6 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + :/add.png:/add.png + + + New + + + New package + + + + + + :/clear.png:/clear.png + + + Delete + + + Delete package + + + + + + :/import.png:/import.png + + + Import + + + Import package + + + + + + + + + buttonBox + accepted() + SaltManagerInstallConfirmationDialog + accept() + + + 273 + 431 + + + 276 + 448 + + + + + buttonBox + rejected() + SaltManagerInstallConfirmationDialog + reject() + + + 351 + 426 + + + 363 + 445 + + + + + diff --git a/src/lay/lay.pro b/src/lay/lay.pro index 7d1bff20d..0daa9efc7 100644 --- a/src/lay/lay.pro +++ b/src/lay/lay.pro @@ -101,7 +101,8 @@ FORMS = \ MainConfigPage7.ui \ SaltManagerDialog.ui \ SaltGrainPropertiesDialog.ui \ - SaltGrainTemplateSelectionDialog.ui + SaltGrainTemplateSelectionDialog.ui \ + SaltManagerInstallConfirmationDialog.ui SOURCES = \ gsiDeclLayApplication.cc \ diff --git a/src/lay/laySalt.cc b/src/lay/laySalt.cc index c16bc8e56..2812d2095 100644 --- a/src/lay/laySalt.cc +++ b/src/lay/laySalt.cc @@ -21,11 +21,11 @@ */ #include "laySalt.h" -#include "laySaltDownloadManager.h" #include "tlString.h" #include "tlFileUtils.h" #include "tlLog.h" #include "tlInternational.h" +#include "tlWebDAV.h" #include #include @@ -307,12 +307,23 @@ public: } bool -Salt::create_grain (const SaltGrain &templ, SaltGrain &target, SaltDownloadManager *download_manager) +Salt::create_grain (const SaltGrain &templ, SaltGrain &target) { tl_assert (!m_root.is_empty ()); const SaltGrains *coll = m_root.begin_collections ().operator-> (); + if (target.name ().empty ()) { + target.set_name (templ.name ()); + } + + if (target.path ().empty ()) { + lay::SaltGrain *g = grain_by_name (target.name ()); + if (g) { + target.set_path (g->path ()); + } + } + std::string path = target.path (); if (! path.empty ()) { coll = 0; @@ -383,11 +394,11 @@ Salt::create_grain (const SaltGrain &templ, SaltGrain &target, SaltDownloadManag } else if (! templ.url ().empty ()) { - tl_assert (download_manager != 0); - // otherwise download from the URL tl::info << QObject::tr ("Downloading package from '%1' to '%2' ..").arg (tl::to_qstring (templ.url ())).arg (tl::to_qstring (target.path ())); - res = download_manager->download (templ.url (), target.path ()); + res = tl::WebDAVObject::download (templ.url (), target.path ()); + + target.set_url (templ.url ()); } diff --git a/src/lay/laySalt.h b/src/lay/laySalt.h index ff16c0280..d238476ae 100644 --- a/src/lay/laySalt.h +++ b/src/lay/laySalt.h @@ -34,8 +34,6 @@ namespace lay { -class SaltDownloadManager; - /** * @brief The global salt (package manager) object * This object can be configured to represent a couple of locations. @@ -123,6 +121,14 @@ public: */ SaltGrain *grain_by_name (const std::string &name); + /** + * @brief Gets the grain with the given name (const version) + */ + const SaltGrain *grain_by_name (const std::string &name) const + { + return const_cast (this)->grain_by_name (name); + } + /** * @brief Loads the salt from a "salt mine" file */ @@ -165,11 +171,11 @@ public: * all files related to this grain. It will copy the download URL from the template into the * new grain, so updates will come from the original location. * - * The target's name must be set. If a specific target location is desired, the target's - * path must be set too. - * - * This method refuses to overwrite existing grains, so an update needs to be performed by first - * deleting the grain and then re-installing it. + * If the target's name is not set, it will be taken from the template. + * If the target's path is not set and a grain with the given name already exists in + * the package, the path is taken from that grain. + * If no target path is set and no grain with this name exists yet, a new path will + * be constructed using the first location in the salt. * * The target grain will be updated with the installation information. If the target grain * contains an installation path prior to the installation, this path will be used for the @@ -177,7 +183,7 @@ public: * * Returns true, if the package could be created successfully. */ - bool create_grain (const SaltGrain &templ, SaltGrain &target, SaltDownloadManager *download_manager = 0); + bool create_grain (const SaltGrain &templ, SaltGrain &target); signals: /** diff --git a/src/lay/laySaltDownloadManager.cc b/src/lay/laySaltDownloadManager.cc index f6970b851..c3e587551 100644 --- a/src/lay/laySaltDownloadManager.cc +++ b/src/lay/laySaltDownloadManager.cc @@ -21,20 +21,189 @@ */ #include "laySaltDownloadManager.h" +#include "laySalt.h" +#include "tlFileUtils.h" +#include "tlWebDAV.h" + +#include "ui_SaltManagerInstallConfirmationDialog.h" + +#include namespace lay { +// ---------------------------------------------------------------------------------- + +class ConfirmationDialog + : public QDialog, private Ui::SaltManagerInstallConfirmationDialog +{ +public: + ConfirmationDialog (QWidget *parent) + : QDialog (parent) + { + Ui::SaltManagerInstallConfirmationDialog::setupUi (this); + } + + void add_info (const std::string &name, bool update, const std::string &version, const std::string &url) + { + QTreeWidgetItem *item = new QTreeWidgetItem (list); + item->setText (0, tl::to_qstring (name)); + item->setText (1, update ? tr ("UPDATE") : tr ("INSTALL")); + item->setText (2, tl::to_qstring (version)); + item->setText (3, tl::to_qstring (url)); + } +}; + +// ---------------------------------------------------------------------------------- + SaltDownloadManager::SaltDownloadManager () { // .. nothing yet .. } -bool -SaltDownloadManager::download (const std::string &url, const std::string &target_dir) +void +SaltDownloadManager::register_download (const std::string &name, const std::string &url, const std::string &version) { - // @@@ + m_registry.insert (std::make_pair (name, Descriptor (url, version))); +} + +void +SaltDownloadManager::compute_dependencies (const lay::Salt &salt, const lay::Salt &salt_mine) +{ + tl::AbsoluteProgress progress (tl::to_string (QObject::tr ("Computing package dependencies .."))); + + while (needs_iteration ()) { + + fetch_missing (salt_mine, progress); + + std::map registry = m_registry; + for (std::map::const_iterator p = registry.begin (); p != registry.end (); ++p) { + + for (std::vector::const_iterator d = p->second.grain.dependencies ().begin (); d != p->second.grain.dependencies ().end (); ++d) { + + std::map::iterator r = m_registry.find (d->name); + if (r != m_registry.end ()) { + + if (SaltGrain::compare_versions (r->second.version, d->version) < 0) { + + // Grain is present, but too old -> update version and reload in the next iteration + r->second.downloaded = false; + r->second.version = d->version; + r->second.url = d->url; + r->second.downloaded = false; + + } + + } else { + + const SaltGrain *g = salt.grain_by_name (d->name); + if (g) { + + // Grain is installed already, but too old -> register for update + if (SaltGrain::compare_versions (g->version (), d->version) < 0) { + register_download (d->name, d->url, d->version); + } + + } else { + register_download (d->name, d->url, d->version); + } + + } + + } + + } + + } +} + +bool +SaltDownloadManager::needs_iteration () +{ + for (std::map::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) { + if (! p->second.downloaded) { + return true; + } + } return false; } +void +SaltDownloadManager::fetch_missing (const lay::Salt &salt_mine, tl::AbsoluteProgress &progress) +{ + for (std::map::iterator p = m_registry.begin (); p != m_registry.end (); ++p) { + + if (! p->second.downloaded) { + + ++progress; + + // If no URL is given, utilize the salt mine to fetch it + if (p->second.url.empty ()) { + const lay::SaltGrain *g = salt_mine.grain_by_name (p->first); + if (SaltGrain::compare_versions (g->version (), p->second.version) < 0) { + throw tl::Exception (tl::to_string (QObject::tr ("Package '%1': package in repository is too old (%2) to satisfy requirements (%3)").arg (tl::to_qstring (p->first)).arg (tl::to_qstring (g->version ())).arg (tl::to_qstring (p->second.version)))); + } + p->second.version = g->version (); + p->second.url = g->url (); + } + + p->second.grain = SaltGrain::from_url (p->second.url); + p->second.downloaded = true; + + } + + } +} + +bool +SaltDownloadManager::show_confirmation_dialog (QWidget *parent, const lay::Salt &salt) +{ + lay::ConfirmationDialog dialog (parent); + + // First the packages to update + for (std::map::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) { + const lay::SaltGrain *g = salt.grain_by_name (p->first); + if (g) { + dialog.add_info (p->first, true, g->version () + "->" + p->second.version, p->second.url); + } + } + + // Then the packages to install + for (std::map::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) { + const lay::SaltGrain *g = salt.grain_by_name (p->first); + if (!g) { + dialog.add_info (p->first, false, p->second.version, p->second.url); + } + } + + return dialog.exec (); +} + +bool +SaltDownloadManager::execute (lay::Salt &salt) +{ + bool result = true; + + tl::RelativeProgress progress (tl::to_string (QObject::tr ("Downloading packages")), m_registry.size (), 1); + + for (std::map::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) { + + lay::SaltGrain target; + target.set_name (p->first); + lay::SaltGrain *g = salt.grain_by_name (p->first); + if (g) { + target.set_path (g->path ()); + } + + if (! salt.create_grain (p->second.grain, target)) { + result = false; + } + + ++progress; + + } + + return result; +} + } diff --git a/src/lay/laySaltDownloadManager.h b/src/lay/laySaltDownloadManager.h index 141df238a..89fd74620 100644 --- a/src/lay/laySaltDownloadManager.h +++ b/src/lay/laySaltDownloadManager.h @@ -24,17 +24,27 @@ #define HDR_laySaltDownloadManager #include "layCommon.h" +#include "laySaltGrain.h" +#include "tlProgress.h" #include #include +#include namespace lay { +class Salt; + /** * @brief The download manager + * * This class is responsible for handling the downloads for - * grains. + * grains. The basic sequence is: + * + "register_download" (multiple times) to register the packages intended for download + * + "compute_dependencies" to determine all related packages + * + (optional) "show_confirmation_dialog" + * + "execute" to actually execute the downloads */ class LAY_PUBLIC SaltDownloadManager : public QObject @@ -48,11 +58,58 @@ public: SaltDownloadManager (); /** - * @brief Downloads the files from the given URL to the given target location - * The target directory needs to exist. - * Returns true, if the download was successful, false otherwise. + * @brief Registers an URL (with version) for download in the given target directory + * + * The target directory can be empty. In this case, the downloader will pick an approriate one. */ - bool download (const std::string &url, const std::string &target_dir); + void register_download (const std::string &name, const std::string &url, const std::string &version); + + /** + * @brief Computes the dependencies after all required packages have been registered + * + * This method will compute the dependencies. Packages not present in the list of + * packages ("salt" argument), will be scheduled for download too. Dependency packages + * are looked up in "salt_mine" if no download URL is given. + */ + void compute_dependencies (const lay::Salt &salt, const Salt &salt_mine); + + /** + * @brief Presents a dialog showing the packages scheduled for download + * + * This method requires all dependencies to be computed. It will return false + * if the dialog is not confirmed. + * + * "salt" needs to be the currently installed packages so the dialog can + * indicate which packages will be updated. + */ + bool show_confirmation_dialog (QWidget *parent, const lay::Salt &salt); + + /** + * @brief Actually execute the downloads + * + * This method will return false if anything goes wrong. + * Failed packages will be removed entirely after they have been listed in + * an error dialog. + */ + bool execute (lay::Salt &salt); + +private: + struct Descriptor + { + Descriptor (const std::string &_url, const std::string &_version) + : url (_url), version (_version), downloaded (false) + { } + + std::string url; + std::string version; + bool downloaded; + lay::SaltGrain grain; + }; + + std::map m_registry; + + bool needs_iteration (); + void fetch_missing (const lay::Salt &salt_mine, tl::AbsoluteProgress &progress); }; } diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index 57ce3fbf5..978dc9c46 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -23,6 +23,7 @@ #include "laySaltManagerDialog.h" #include "laySaltModel.h" #include "laySaltGrainPropertiesDialog.h" +#include "laySaltDownloadManager.h" #include "laySalt.h" #include "ui_SaltGrainTemplateSelectionDialog.h" #include "tlString.h" @@ -151,6 +152,7 @@ SaltManagerDialog::SaltManagerDialog (QWidget *parent) connect (edit_button, SIGNAL (clicked ()), this, SLOT (edit_properties ())); connect (create_button, SIGNAL (clicked ()), this, SLOT (create_grain ())); connect (delete_button, SIGNAL (clicked ()), this, SLOT (delete_grain ())); + connect (apply_button, SIGNAL (clicked ()), this, SLOT (apply ())); mp_salt = get_salt (); mp_salt_mine = get_salt_mine (); @@ -258,6 +260,42 @@ SaltManagerDialog::mark_clicked () model->set_marked (g->name (), !model->is_marked (g->name ())); } +void +SaltManagerDialog::apply () +{ +BEGIN_PROTECTED + + lay::SaltDownloadManager manager; + + bool any = false; + + // fetch all marked grains and register for download + SaltModel *model = dynamic_cast (salt_mine_view->model ()); + if (model) { + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + QModelIndex index = model->index (i, 0, QModelIndex ()); + SaltGrain *g = model->grain_from_index (index); + if (g && model->is_marked (g->name ())) { + manager.register_download (g->name (), g->url (), g->version ()); + any = true; + } + } + } + + if (! any) { + throw tl::Exception (tl::to_string (tr ("No packages marked for installation or update"))); + } + + manager.compute_dependencies (*mp_salt, *mp_salt_mine); + + if (manager.show_confirmation_dialog (this, *mp_salt)) { + manager.execute (*mp_salt); + } + +END_PROTECTED +} + void SaltManagerDialog::edit_properties () { diff --git a/src/lay/laySaltManagerDialog.h b/src/lay/laySaltManagerDialog.h index cbbfac0a4..c6e4ff893 100644 --- a/src/lay/laySaltManagerDialog.h +++ b/src/lay/laySaltManagerDialog.h @@ -95,6 +95,11 @@ private slots: */ void mode_changed (); + /** + * @brief Called when the "apply" button is clicked + */ + void apply (); + /** * @brief Called when one search text changed */ diff --git a/src/tl/tl.pro b/src/tl/tl.pro index 39a9e5713..0a427e5e2 100644 --- a/src/tl/tl.pro +++ b/src/tl/tl.pro @@ -40,7 +40,8 @@ SOURCES = \ tlXMLParser.cc \ tlXMLWriter.cc \ tlFileSystemWatcher.cc \ - tlFileUtils.cc + tlFileUtils.cc \ + tlWebDAV.cc HEADERS = \ tlAlgorithm.h \ @@ -83,7 +84,8 @@ HEADERS = \ tlXMLWriter.h \ tlFileSystemWatcher.h \ tlCommon.h \ - tlFileUtils.h + tlFileUtils.h \ + tlWebDAV.h INCLUDEPATH = DEPENDPATH = diff --git a/src/tl/tlHttpStream.cc b/src/tl/tlHttpStream.cc index 216e31c76..d91bfb905 100644 --- a/src/tl/tlHttpStream.cc +++ b/src/tl/tlHttpStream.cc @@ -87,7 +87,6 @@ InputHttpStream::InputHttpStream (const std::string &url) connect (s_network_manager, SIGNAL (finished (QNetworkReply *)), this, SLOT (finished (QNetworkReply *))); connect (s_network_manager, SIGNAL (authenticationRequired (QNetworkReply *, QAuthenticator *)), this, SLOT (authenticationRequired (QNetworkReply *, QAuthenticator *))); connect (s_network_manager, SIGNAL (proxyAuthenticationRequired (const QNetworkProxy &, QAuthenticator *)), this, SLOT (proxyAuthenticationRequired (const QNetworkProxy &, QAuthenticator *))); - issue_request (QUrl (tl::to_qstring (url))); mp_reply = 0; } @@ -115,6 +114,12 @@ InputHttpStream::set_data (const char *data, size_t n) m_data = QByteArray (data, int (n)); } +void +InputHttpStream::add_header (const std::string &name, const std::string &value) +{ + m_headers.insert (std::make_pair (name, value)); +} + void InputHttpStream::authenticationRequired (QNetworkReply *reply, QAuthenticator *auth) { @@ -148,19 +153,26 @@ InputHttpStream::issue_request (const QUrl &url) delete mp_buffer; mp_buffer = 0; + QNetworkRequest request (url); + for (std::map::const_iterator h = m_headers.begin (); h != m_headers.end (); ++h) { + request.setRawHeader (QByteArray (h->first.c_str ()), QByteArray (h->second.c_str ())); + } if (m_data.isEmpty ()) { - s_network_manager->sendCustomRequest (QNetworkRequest (url), m_request); + s_network_manager->sendCustomRequest (request, m_request); } else { mp_buffer = new QBuffer (&m_data); - s_network_manager->sendCustomRequest (QNetworkRequest (url), m_request, mp_buffer); + s_network_manager->sendCustomRequest (request, m_request, mp_buffer); } } size_t InputHttpStream::read (char *b, size_t n) { + if (mp_reply == 0) { + issue_request (QUrl (tl::to_qstring (m_url))); + } while (mp_reply == 0) { - QCoreApplication::processEvents (QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents); + QCoreApplication::processEvents (QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents, 100); } if (mp_reply->error () != QNetworkReply::NoError) { diff --git a/src/tl/tlHttpStream.h b/src/tl/tlHttpStream.h index 7f20c0aad..acd3b8635 100644 --- a/src/tl/tlHttpStream.h +++ b/src/tl/tlHttpStream.h @@ -90,6 +90,11 @@ public: */ void set_data (const char *data, size_t n); + /** + * @brief Sets a header field + */ + void add_header (const std::string &name, const std::string &value); + /** * @brief Read from the stream * Implements the basic read method. @@ -121,6 +126,7 @@ private: QByteArray m_request; QByteArray m_data; QBuffer *mp_buffer; + std::map m_headers; void issue_request (const QUrl &url); }; diff --git a/src/tl/tlWebDAV.cc b/src/tl/tlWebDAV.cc new file mode 100644 index 000000000..19c2a004e --- /dev/null +++ b/src/tl/tlWebDAV.cc @@ -0,0 +1,316 @@ + +/* + + 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 "tlWebDAV.h" +#include "tlXMLParser.h" +#include "tlHttpStream.h" +#include "tlStream.h" +#include "tlInternational.h" +#include "tlProgress.h" +#include "tlLog.h" + +#include +#include + +namespace tl +{ + +// --------------------------------------------------------------- +// WebDAVCollection implementation + +WebDAVObject::WebDAVObject () +{ + // .. nothing yet .. +} + +namespace +{ + +/** + * @brief A dummy "DOM" for the WebDAV reply + */ +struct ResourceType +{ + ResourceType () : is_collection (false) { } + + const std::string &collection () const + { + static std::string empty; + return empty; + } + + void set_collection (const std::string &) + { + is_collection = true; + } + + bool is_collection; +}; + +/** + * @brief A dummy "DOM" for the WebDAV reply + */ +struct Prop +{ + ResourceType resourcetype; +}; + +/** + * @brief A dummy "DOM" for the WebDAV reply + */ +struct PropStat +{ + std::string status; + Prop prop; +}; + +/** + * @brief A dummy "DOM" for the WebDAV reply + */ +struct Response +{ + std::string href; + PropStat propstat; +}; + +/** + * @brief A dummy "DOM" for the WebDAV reply + */ +struct MultiStatus +{ + typedef std::list container; + typedef container::const_iterator iterator; + + iterator begin () const { return responses.begin (); } + iterator end () const { return responses.end (); } + void add (const Response &r) { responses.push_back (r); } + + container responses; +}; + +} + +tl::XMLStruct xml_struct ("multistatus", + tl::make_element (&MultiStatus::begin, &MultiStatus::end, &MultiStatus::add, "response", + tl::make_member (&Response::href, "href") + + tl::make_element (&Response::propstat, "propstat", + tl::make_member (&PropStat::status, "status") + + tl::make_element (&PropStat::prop, "prop", + tl::make_element (&Prop::resourcetype, "resourcetype", + tl::make_member (&ResourceType::collection, &ResourceType::set_collection, "collection") + ) + ) + ) + ) +); + +static std::string item_name (const QString &path1, const QString &path2) +{ + QStringList sl1 = path1.split (QChar ('/')); + if (! sl1.empty () && sl1.back ().isEmpty ()) { + sl1.pop_back (); + } + + QStringList sl2 = path2.split (QChar ('/')); + if (! sl2.empty () && sl2.back ().isEmpty ()) { + sl2.pop_back (); + } + + int i = 0; + for ( ; i < sl1.length () && i < sl2.length (); ++i) { + if (sl1 [i] != sl2 [i]) { + throw tl::Exception (tl::to_string (QObject::tr ("Invalid WebDAV response: %1 is not a collection item of %2").arg (path2).arg (path1))); + } + } + if (i == sl2.length ()) { + return std::string (); + } else if (i + 1 == sl2.length ()) { + return tl::to_string (sl2[i]); + } else { + throw tl::Exception (tl::to_string (QObject::tr ("Invalid WebDAV response: %1 is not a collection sub-item of %2").arg (path2).arg (path1))); + } +} + +void +WebDAVObject::read (const std::string &url, int depth) +{ + QUrl base_url = QUrl (tl::to_qstring (url)); + + tl::InputHttpStream http (url); + http.add_header ("User-Agent", "SVN"); + http.add_header ("Depth", tl::to_string (depth)); + http.set_request ("PROPFIND"); + http.set_data (""); + + MultiStatus multistatus; + tl::InputStream stream (http); + tl::XMLStreamSource source (stream); + xml_struct.parse (source, multistatus); + + // TODO: check status .. + + m_items.clear (); + for (MultiStatus::iterator r = multistatus.begin (); r != multistatus.end (); ++r) { + + bool is_collection = r->propstat.prop.resourcetype.is_collection; + QUrl item_url = base_url.resolved (QUrl (tl::to_qstring (r->href))); + + std::string n = item_name (base_url.path (), item_url.path ()); + std::string item_url_string = tl::to_string (item_url.toString ()); + + if (! n.empty ()) { + m_items.push_back (WebDAVItem (is_collection, item_url_string, n)); + } else { + m_is_collection = is_collection; + m_url = item_url_string; + } + + } +} + +namespace +{ + +struct DownloadItem +{ + DownloadItem (const std::string &u, const std::string &p) + { + url = u; + path = p; + } + + std::string url; + std::string path; +}; + +} + +static +void fetch_download_items (const std::string &url, const std::string &target, std::list &items, tl::AbsoluteProgress &progress) +{ + ++progress; + + WebDAVObject object; + object.read (url, 1); + + if (object.is_collection ()) { + + QDir dir (tl::to_qstring (target)); + if (! dir.exists ()) { + throw tl::Exception (tl::to_string (QObject::tr ("Download failed: target directory '%1' does not exists").arg (dir.path ()))); + } + + for (WebDAVObject::iterator i = object.begin (); i != object.end (); ++i) { + + QFileInfo new_item (dir.absoluteFilePath (tl::to_qstring (i->name ()))); + + if (i->is_collection ()) { + + if (! new_item.exists ()) { + if (! dir.mkdir (tl::to_qstring (i->name ()))) { + throw tl::Exception (tl::to_string (QObject::tr ("Download failed: unable to create subdirectory '%2' in '%1'").arg (dir.path ()).arg (tl::to_qstring (i->name ())))); + } + } else if (! new_item.isDir ()) { + throw tl::Exception (tl::to_string (QObject::tr ("Download failed: unable to create subdirectory '%2' in '%1' - is already a file").arg (dir.path ()).arg (tl::to_qstring (i->name ())))); + } else if (! new_item.isWritable ()) { + throw tl::Exception (tl::to_string (QObject::tr ("Download failed: unable to create subdirectory '%2' in '%1' - no write permissions").arg (dir.path ()).arg (tl::to_qstring (i->name ())))); + } + + fetch_download_items (i->url (), tl::to_string (new_item.filePath ()), items, progress); + + } else { + + if (new_item.exists () && ! new_item.isWritable ()) { + throw tl::Exception (tl::to_string (QObject::tr ("Download failed: file is '%2' in '%1' - already exists, but no write permissions").arg (dir.path ()).arg (tl::to_qstring (i->name ())))); + } + + items.push_back (DownloadItem (i->url (), tl::to_string (dir.absoluteFilePath (tl::to_qstring (i->name ()))))); + + } + } + + } else { + items.push_back (DownloadItem (url, target)); + } +} + +bool +WebDAVObject::download (const std::string &url, const std::string &target) +{ + std::list items; + + try { + + tl::info << QObject::tr ("Fetching file structure from ") << url; + tl::AbsoluteProgress progress (tl::to_string (QObject::tr ("Fetching directory structure from %1").arg (tl::to_qstring (url)))); + fetch_download_items (url, target, items, progress); + + } catch (tl::Exception &ex) { + tl::error << QObject::tr ("Error downloading file structure from '") << url << "':" << tl::endl << ex.msg (); + return false; + } + + bool has_errors = false; + + { + tl::info << tl::to_string (QObject::tr ("Downloading %1 files now").arg (items.size ())); + tl::RelativeProgress progress (tl::to_string (QObject::tr ("Downloading file(s) from %1").arg (tl::to_qstring (url))), items.size (), 1); + for (std::list::const_iterator i = items.begin (); i != items.end (); ++i) { + + tl::info << QObject::tr ("Downloading '%1' to '%2' ..").arg (tl::to_qstring (i->url)).arg (tl::to_qstring (i->path)); + + try { + + tl::InputHttpStream http (i->url); + + QFile file (tl::to_qstring (i->path)); + if (! file.open (QIODevice::WriteOnly)) { + has_errors = true; + tl::error << QObject::tr ("Unable to open file '%1' for writing").arg (tl::to_qstring (i->path)); + } + + const size_t chunk = 65536; + char b[chunk]; + size_t read; + while ((read = http.read (b, sizeof (b))) > 0) { + if (! file.write (b, read)) { + tl::error << QObject::tr ("Unable to write %2 bytes file '%1'").arg (tl::to_qstring (i->path)).arg (int (read)); + has_errors = true; + break; + } + } + + file.close (); + + } catch (tl::Exception &ex) { + tl::error << QObject::tr ("Error downloading file from '") << i->url << "':" << tl::endl << ex.msg (); + has_errors = true; + } + + } + } + + return ! has_errors; +} + +} diff --git a/src/tl/tlWebDAV.h b/src/tl/tlWebDAV.h new file mode 100644 index 000000000..9e82ffb8e --- /dev/null +++ b/src/tl/tlWebDAV.h @@ -0,0 +1,152 @@ + +/* + + 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_tlWebDAV +#define HDR_tlWebDAV + +#include "tlCommon.h" +#include +#include + +namespace tl +{ + +/** + * @brief Represents an item in a WebDAV collection + */ +class TL_PUBLIC WebDAVItem +{ +public: + /** + * @brief Default constructor + */ + WebDAVItem () + : m_is_collection (false) + { + // .. nothing yet .. + } + + /** + * @brief Constructor + */ + WebDAVItem (bool is_collection, const std::string &url, const std::string &name) + : m_is_collection (is_collection), m_url (url), m_name (name) + { + // .. nothing yet .. + } + + /** + * @brief Gets a value indicating whether this item is a collection + * If false, it's a file. + */ + bool is_collection () const + { + return m_is_collection; + } + + /** + * @brief Gets the URL of this item + */ + const std::string &url () const + { + return m_url; + } + + /** + * @brief Gets the name of this item + * The name is only valid for sub-items. + */ + const std::string &name () const + { + return m_name; + } + +protected: + bool m_is_collection; + std::string m_url; + std::string m_name; +}; + +/** + * @brief Represents an object from a WebDAV URL + * This object can be a file or collection + */ +class TL_PUBLIC WebDAVObject + : public WebDAVItem +{ +public: + typedef std::vector container; + typedef container::const_iterator iterator; + + /** + * @brief Open a stream with the given URL + */ + WebDAVObject (); + + /** + * @brief Populates the collection from the given URL + * The depth value can be 0 (self only) or 1 (self + collection members). + */ + void read (const std::string &url, int depth); + + /** + * @brief Gets the items of this collection (begin iterator) + */ + iterator begin () const + { + return m_items.begin (); + } + + /** + * @brief Gets the items of this collection (begin iterator) + */ + iterator end () const + { + return m_items.end (); + } + + /** + * @brief Downloads the collection or file with the given URL + * + * This method will download the WebDAV object from url to the file path + * given in "target". + * + * For file download, the target must be the path of the target file. + * For collection download, the target must be a directory path. In this + * case, the target directory must exist already. + * + * Sub-directories are created if required. + * + * This method throws an exception if the directory structure could + * not be obtained or downloading of one file failed. + */ + static bool download (const std::string &url, const std::string &target); + +private: + container m_items; +}; + +} + +#endif + diff --git a/src/tl/tlXMLParser.h b/src/tl/tlXMLParser.h index 2760df1f0..6a1ae3015 100644 --- a/src/tl/tlXMLParser.h +++ b/src/tl/tlXMLParser.h @@ -671,12 +671,12 @@ public: return m_name; } - bool check_name (const std::string &, const std::string &, const std::string &qname) const + bool check_name (const std::string & /*uri*/, const std::string &lname, const std::string & /*qname*/) const { if (m_name == "*") { return true; } else { - return m_name == qname; // no namespace currently + return m_name == lname; // no namespace currently } } diff --git a/src/unit_tests/tlHttpStream.cc b/src/unit_tests/tlHttpStream.cc index 1783b2ccb..2c493de12 100644 --- a/src/unit_tests/tlHttpStream.cc +++ b/src/unit_tests/tlHttpStream.cc @@ -24,7 +24,8 @@ #include "tlHttpStream.h" #include "utHead.h" -std::string test_url1 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text"); +static std::string test_url1 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text"); +static std::string test_url2 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/dir1"); TEST(1) { @@ -36,3 +37,39 @@ TEST(1) EXPECT_EQ (res, "hello, world.\n"); } +TEST(2) +{ + tl::InputHttpStream stream (test_url2); + stream.add_header ("User-Agent", "SVN"); + stream.add_header ("Depth", "1"); + stream.set_request ("PROPFIND"); + stream.set_data (""); + + char b[10000]; + size_t n = stream.read (b, sizeof (b)); + std::string res (b, n); + + EXPECT_EQ (res, + "\n" + "\n" + "\n" + "/svn-public/klayout-resources/trunk/testdata/dir1/\n" + "\n" + "\n" + "\n" + "\n" + "HTTP/1.1 200 OK\n" + "\n" + "\n" + "\n" + "/svn-public/klayout-resources/trunk/testdata/dir1/text\n" + "\n" + "\n" + "\n" + "\n" + "HTTP/1.1 200 OK\n" + "\n" + "\n" + "\n" + ); +} diff --git a/src/unit_tests/tlWebDAV.cc b/src/unit_tests/tlWebDAV.cc new file mode 100644 index 000000000..bba9764f8 --- /dev/null +++ b/src/unit_tests/tlWebDAV.cc @@ -0,0 +1,129 @@ + +/* + + 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 "tlWebDAV.h" +#include "utHead.h" + +#include + +static std::string test_url1 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata"); +static std::string test_url2 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text"); + +static std::string collection2string (const tl::WebDAVObject &coll) +{ + std::string s; + for (tl::WebDAVObject::iterator c = coll.begin (); c != coll.end (); ++c) { + if (!s.empty ()) { + s += "\n"; + } + if (c->is_collection ()) { + s += "[dir] "; + } + s += c->name (); + s += " "; + s += c->url (); + } + return s; +} + +TEST(1) +{ + tl::WebDAVObject collection; + collection.read (test_url1, 1); + + EXPECT_EQ (collection.is_collection (), true); + EXPECT_EQ (collection.url (), "http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/"); + + EXPECT_EQ (collection2string (collection), + "[dir] dir1 http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/dir1/\n" + "[dir] dir2 http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/dir2/\n" + "text http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text\n" + "text2 http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text2" + ); +} + +TEST(2) +{ + tl::WebDAVObject collection; + collection.read (test_url1, 0); + + EXPECT_EQ (collection.is_collection (), true); + EXPECT_EQ (collection.url (), "http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/"); + EXPECT_EQ (collection2string (collection), ""); +} + +TEST(3) +{ + tl::WebDAVObject collection; + collection.read (test_url2, 1); + + EXPECT_EQ (collection.is_collection (), false); + EXPECT_EQ (collection.url (), "http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text"); + EXPECT_EQ (collection2string (collection), ""); +} + +TEST(4) +{ + tl::WebDAVObject collection; + collection.read (test_url2, 0); + + EXPECT_EQ (collection.is_collection (), false); + EXPECT_EQ (collection.url (), "http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text"); + EXPECT_EQ (collection2string (collection), ""); +} + +TEST(5) +{ + tl::WebDAVObject collection; + + QDir tmp_dir (tl::to_qstring (tmp_file ("tmp"))); + EXPECT_EQ (tmp_dir.exists (), false); + + tmp_dir.cdUp (); + tmp_dir.mkdir (tl::to_qstring ("tmp")); + tmp_dir.cd (tl::to_qstring ("tmp")); + + bool res = collection.download (test_url1, tl::to_string (tmp_dir.absolutePath ())); + EXPECT_EQ (res, true); + + QDir dir1 (tmp_dir.absoluteFilePath (QString::fromUtf8 ("dir1"))); + QDir dir2 (tmp_dir.absoluteFilePath (QString::fromUtf8 ("dir2"))); + QDir dir21 (dir2.absoluteFilePath (QString::fromUtf8 ("dir21"))); + EXPECT_EQ (dir1.exists (), true); + EXPECT_EQ (dir2.exists (), true); + EXPECT_EQ (dir21.exists (), true); + + QByteArray ba; + + QFile text1 (dir1.absoluteFilePath (QString::fromUtf8 ("text"))); + text1.open (QIODevice::ReadOnly); + ba = text1.read (10000); + EXPECT_EQ (ba.constData (), "A text.\n"); + text1.close (); + + QFile text21 (dir21.absoluteFilePath (QString::fromUtf8 ("text"))); + text21.open (QIODevice::ReadOnly); + ba = text21.read (10000); + EXPECT_EQ (ba.constData (), "A text II.I.\n"); + text21.close (); +} diff --git a/src/unit_tests/unit_tests.pro b/src/unit_tests/unit_tests.pro index 608f86786..85b821dd9 100644 --- a/src/unit_tests/unit_tests.pro +++ b/src/unit_tests/unit_tests.pro @@ -98,7 +98,8 @@ SOURCES = \ tlFileSystemWatcher.cc \ laySalt.cc \ tlFileUtils.cc \ - tlHttpStream.cc + tlHttpStream.cc \ + tlWebDAV.cc # main components: SOURCES += \ From 7228efc7bd7ba2ea783a77ce789425a5ee437ae6 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 27 Mar 2017 23:55:26 +0200 Subject: [PATCH 27/57] WIP: various enhancements for salt manager * double-click * salt mine context menu * bug fixes * etc. --- src/lay/SaltManagerDialog.ui | 30 ++- .../SaltManagerInstallConfirmationDialog.ui | 9 + src/lay/images/marked_16.png | Bin 889 -> 336 bytes src/lay/images/marked_24.png | Bin 1452 -> 424 bytes src/lay/laySalt.cc | 1 + src/lay/laySaltDownloadManager.cc | 40 +++- src/lay/laySaltGrain.cc | 4 + src/lay/laySaltGrainDetailsTextWidget.cc | 7 +- src/lay/laySaltGrainPropertiesDialog.cc | 62 ++++-- src/lay/laySaltManagerDialog.cc | 188 ++++++++++++---- src/lay/laySaltManagerDialog.h | 33 ++- src/lay/laySaltModel.cc | 207 ++++++++++++++++-- src/lay/laySaltModel.h | 72 +++++- src/tl/tlString.cc | 14 ++ src/tl/tlWebDAV.cc | 6 +- 15 files changed, 566 insertions(+), 107 deletions(-) diff --git a/src/lay/SaltManagerDialog.ui b/src/lay/SaltManagerDialog.ui index a51993a12..77651bd95 100644 --- a/src/lay/SaltManagerDialog.ui +++ b/src/lay/SaltManagerDialog.ui @@ -231,8 +231,8 @@ 0 0 - 537 - 284 + 343 + 207 @@ -673,6 +673,13 @@ + + + + Set in code + + + @@ -725,16 +732,19 @@ Delete package - - - - :/import.png:/import.png - + - Import + Unmark all - - Import package + + + + Show marked only + + + + + Show all diff --git a/src/lay/SaltManagerInstallConfirmationDialog.ui b/src/lay/SaltManagerInstallConfirmationDialog.ui index 0920a619b..d8dcf20e4 100644 --- a/src/lay/SaltManagerInstallConfirmationDialog.ui +++ b/src/lay/SaltManagerInstallConfirmationDialog.ui @@ -26,6 +26,15 @@ + + Qt::NoFocus + + + false + + + true + Package diff --git a/src/lay/images/marked_16.png b/src/lay/images/marked_16.png index aa152411be8cfc02a42015b1b194db9e08d7321e..777a625a7262019d2b6404dad56282026f5a0bb1 100644 GIT binary patch delta 250 zcmV7#7)P> zwp?A>931==AK{PlxiJMD=Q#9CZxJNi#u3Wvo+_?(2hN6MF0Q*07*qoM6N<$f-19a A^Z)<= delta 808 zcmV+@1K0e}0{I4zI|~Av0007;0h?-OvXMq7e**SNL_t(IjeV0_Ow(r=hoApfdQhfP z+oIMeAVo7Og%%>{W>Y*QTegra1Kl(%CPpO-iDC1CY|Lmf*_an@alyF@v*;3)Y>P3A za}m>(8H~!*jzehcVYF3@mKBPO0&Txt(W&Tumv8dCzxSx{tlCgvS?V&W^;=XzWdTUY ze}q3WF>(^ez;7s|q?9GVkw2nWax94SFNw@=*JRlZPJLO{LX8oC2a^$}Cwi^= za<4a1f4}x1(EO#p*-*8(dO(?m^*z@2&6)-KKgp{z&dJD}Q5b+asii_Gmo0sJ6_Ylb z-35)mDAUgQvvN*jnGyow4pK;miP{@_$dEH z&hwd1DWx?1*}$>8t^D*#J0VqUWNZ6d27zU%YxTTh!~ek!>EMGKyEt~Yl}$@(DL1~# zx%dTGp5s*Y9J+cbYj&n-wpu#_Hetdp?KIrj!RgR(=48y}MEMy8GJ=eWF`YNoe?KY0 zBmPA-nktGYLd$jVJzR|Oa&X5}y zE($2XjG2|GmF(<#m!I$d2taXGDevX(q?cX*BoZm56rGdZUG+*7axWW?8`IRrmD;}cpkM=>2|QuQ_lGZ zXVdL$%dMkGZzn(?9qWj8MIR=jrxe;yS#9eB7ngmX_nNzc@Obd)L24J!xxSB}Iz*T- z0KA?*rMrjNjiVRLfn&OT?$BezN@mIYOjEARO zZo4#})kXEb_Eqs1;p@h=brqs1yF zlTkr13zt>=z#<}86v1FMh-k8jf`UQUFt@lYd&}Lq`-4q8^SraO?=X~`sooOtHN2Kl zpa)y{!DAWtHtta^1K&Y}nWFJN>^AX68F-_KzhSIMyn?+Z9)DrENPGYX3H(<0nnF^= zQ3C(QbgRLY4)GzJCGbe-o|aj}TnG4h3V%T@Z&E+5aDsZyxM5tR@CXa-tgMcAd}1Ag zsrMte&cH8)1J+iS5)&W%ZCedD8Tc0_+QsASr;uaZW#ADuI>liW&n>{@^CbL}t}-k1 zbzXt_Zt*yqIX(E{e{sS|&D#49j2BS=CWW?57FaC`kF%1%OS`2`m2^wR851t?uJQ{- W-)*78vH0=;0000>ZGQzRNklGwZePr??Q-Z zU?niHw6!=vx+AzWHbqmkrG8;JtcoTjX)#8$8l?hU(y!=s?sKv$a19xGkJs=nWJF`N z5TYlL4-8pzYB`RWoO)9SZ~-QBq-|vYkvl4Kn3xbf)|?U&k3`U3Oaj4~YYpscIXL-X z>#@gED<9k>gnzgfi9rkhm`AkQ6I*D%rr-%mY!Rv=@4d?sF>;}8mNh}^)2V|1mpoS( zEsqw7mRM>2?=MquaX*FG$Fa!j&YKeyU{X!ezP=#|8E@NWt09WkCAbwUIoepPSf$rb z`uQxcIqWXp5a71UDy8;g*j7&0- z12WH-On)Ws+}8jMjU3L?i8FEH#Lzs7AO9pOQZ4d@#G)gaq3LpRNL;7zZgeA)Or!wJ zI5vfE&+i66mSo;dTZbYkR8T>3NE}v0lhB=G)H}i>h`^2=!|*fh zsDtmD_qNxbNu0&-C_7Cwahc0VNF-`828h0%(lBk%k!>VI7|Oe=OUzSjhr+znLxeY`{Hvy-yzr=%4<33 zl~Hz+TH%!eJbwMn>9wy4zY<_=`lrZ}++oqJT9NhvAxh|NgROQqQh23Q^2)vi@PF@9 z!HC9cKgQae+F5#5bT%*8Ub-Xv;a(GYsFwpL&JMx43^&VaHhL6Ed7q*6wE)VEVqjeB zf#z=B`yxzXvGd~R%KI-0t>V9X~i2-g?SxJ)1QkJrn z;|WP!2H%hnLJ~quuW_C!eC@3Ux0000 +#include namespace lay { @@ -47,10 +48,17 @@ public: void add_info (const std::string &name, bool update, const std::string &version, const std::string &url) { QTreeWidgetItem *item = new QTreeWidgetItem (list); + + item->setFlags (item->flags () & ~Qt::ItemIsSelectable); + item->setText (0, tl::to_qstring (name)); item->setText (1, update ? tr ("UPDATE") : tr ("INSTALL")); item->setText (2, tl::to_qstring (version)); item->setText (3, tl::to_qstring (url)); + + for (int column = 0; column < list->colorCount (); ++column) { + item->setData (column, Qt::ForegroundRole, update ? Qt::blue : Qt::black); + } } }; @@ -72,11 +80,25 @@ SaltDownloadManager::compute_dependencies (const lay::Salt &salt, const lay::Sal { tl::AbsoluteProgress progress (tl::to_string (QObject::tr ("Computing package dependencies .."))); + std::map registry; + + // remove those registered entries which don't need to be updated + + registry = m_registry; + for (std::map::const_iterator p = registry.begin (); p != registry.end (); ++p) { + const SaltGrain *g = salt.grain_by_name (p->first); + if (g && SaltGrain::compare_versions (p->second.version, g->version ()) == 0 && p->second.url == g->url ()) { + m_registry.erase (p->first); + } + } + + // add further entries as derived from the dependencies + while (needs_iteration ()) { fetch_missing (salt_mine, progress); - std::map registry = m_registry; + registry = m_registry; for (std::map::const_iterator p = registry.begin (); p != registry.end (); ++p) { for (std::vector::const_iterator d = p->second.grain.dependencies ().begin (); d != p->second.grain.dependencies ().end (); ++d) { @@ -147,7 +169,12 @@ SaltDownloadManager::fetch_missing (const lay::Salt &salt_mine, tl::AbsoluteProg p->second.url = g->url (); } - p->second.grain = SaltGrain::from_url (p->second.url); + try { + p->second.grain = SaltGrain::from_url (p->second.url); + } catch (tl::Exception &ex) { + throw tl::Exception (tl::to_string (QObject::tr ("Error fetching spec file for package '%1': %2").arg (tl::to_qstring (p->first)).arg (tl::to_qstring (ex.msg ())))); + } + p->second.downloaded = true; } @@ -158,13 +185,20 @@ SaltDownloadManager::fetch_missing (const lay::Salt &salt_mine, tl::AbsoluteProg bool SaltDownloadManager::show_confirmation_dialog (QWidget *parent, const lay::Salt &salt) { + // Stop with a warning if there is nothing to do + if (m_registry.empty()) { + QMessageBox::warning (parent, tr ("Nothing to do"), tr ("No packages need update or are marked for installation")); + return false; + } + lay::ConfirmationDialog dialog (parent); // First the packages to update for (std::map::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) { const lay::SaltGrain *g = salt.grain_by_name (p->first); if (g) { - dialog.add_info (p->first, true, g->version () + "->" + p->second.version, p->second.url); + // \342\206\222 is UTF-8 "right arrow" + dialog.add_info (p->first, true, g->version () + " \342\206\222 " + p->second.version, p->second.url); } } diff --git a/src/lay/laySaltGrain.cc b/src/lay/laySaltGrain.cc index 61ceb8fef..63c7f5689 100644 --- a/src/lay/laySaltGrain.cc +++ b/src/lay/laySaltGrain.cc @@ -392,6 +392,10 @@ SaltGrain::from_path (const std::string &path) SaltGrain SaltGrain::from_url (const std::string &url) { + if (url.empty ()) { + throw tl::Exception (tl::to_string (QObject::tr ("No download link available"))); + } + tl::InputHttpStream http (SaltGrain::spec_url (url)); tl::InputStream stream (http); diff --git a/src/lay/laySaltGrainDetailsTextWidget.cc b/src/lay/laySaltGrainDetailsTextWidget.cc index f71bdda14..310a18a6e 100644 --- a/src/lay/laySaltGrainDetailsTextWidget.cc +++ b/src/lay/laySaltGrainDetailsTextWidget.cc @@ -226,8 +226,11 @@ SaltGrainDetailsTextWidget::details_text () stream << "

" << QObject::tr ("Depends on: ") << "
"; for (std::vector::const_iterator d = g->dependencies ().begin (); d != g->dependencies ().end (); ++d) { stream << "    " << tl::to_qstring (tl::escaped_to_html (d->name)) << " "; - stream << tl::to_qstring (tl::escaped_to_html (d->version)) << " - "; - stream << "[" << tl::to_qstring (tl::escaped_to_html (d->url)) << "]
"; + stream << tl::to_qstring (tl::escaped_to_html (d->version)); + if (! d->url.empty ()) { + stream << " - "; + stream << "[" << tl::to_qstring (tl::escaped_to_html (d->url)) << "]
"; + } } stream << "

"; } diff --git a/src/lay/laySaltGrainPropertiesDialog.cc b/src/lay/laySaltGrainPropertiesDialog.cc index 3059cb653..d78ee94bf 100644 --- a/src/lay/laySaltGrainPropertiesDialog.cc +++ b/src/lay/laySaltGrainPropertiesDialog.cc @@ -169,12 +169,19 @@ SaltGrainPropertiesDialog::update_controls () dependencies->clear (); for (std::vector::const_iterator d = m_grain.dependencies ().begin (); d != m_grain.dependencies ().end (); ++d) { + QTreeWidgetItem *item = new QTreeWidgetItem (dependencies); item->setFlags (item->flags () | Qt::ItemIsEditable); + item->setData (0, Qt::UserRole, tl::to_qstring (d->name)); + dependency_changed (item, 0); item->setData (1, Qt::UserRole, tl::to_qstring (d->version)); + dependency_changed (item, 1); item->setData (2, Qt::UserRole, tl::to_qstring (d->url)); + dependency_changed (item, 2); + dependencies->addTopLevelItem (item); + } update_icon (); @@ -249,9 +256,11 @@ SaltGrainPropertiesDialog::dependency_changed (QTreeWidgetItem *item, int column } m_update_enabled = false; + std::string name = tl::to_string (item->data (0, Qt::UserRole).toString ().simplified ()); + SaltGrain *g = mp_salt->grain_by_name (name); + if (column == 0 && mp_salt) { - std::string name = tl::to_string (item->data (0, Qt::UserRole).toString ().simplified ()); item->setData (0, Qt::EditRole, tl::to_qstring (name)); // set URL and version for known grains @@ -265,30 +274,48 @@ SaltGrainPropertiesDialog::dependency_changed (QTreeWidgetItem *item, int column } else { - SaltGrain *g = 0; - for (lay::Salt::flat_iterator i = mp_salt->begin_flat (); i != mp_salt->end_flat (); ++i) { - if ((*i)->name () == name) { - g = *i; - } - } if (g) { item->setData (1, Qt::UserRole, tl::to_qstring (g->version ())); - item->setData (2, Qt::UserRole, tl::to_qstring (g->url ())); + item->setData (2, Qt::UserRole, QString ()); // placeholder texts: item->setData (1, Qt::EditRole, tl::to_qstring (g->version ())); - item->setData (2, Qt::EditRole, tl::to_qstring (g->url ())); + if (! g->url ().empty ()) { + item->setData (2, Qt::EditRole, tl::to_qstring ("(" + g->url () + ")")); + } else { + item->setData (2, Qt::EditRole, tr ("(from repository)")); + } } else { item->setData (1, Qt::UserRole, QString ()); item->setData (2, Qt::UserRole, QString ()); // placeholder texts: item->setData (1, Qt::EditRole, QString ()); - item->setData (2, Qt::EditRole, tr ("(unknown packet)")); + item->setData (2, Qt::EditRole, tr ("(from repository)")); } } - } else if (column > 0) { - item->setData (column, Qt::EditRole, item->data (column, Qt::UserRole).toString ()); + } else if (column == 1) { + + QString text = item->data (column, Qt::UserRole).toString (); + if (! text.isEmpty ()) { + item->setData (1, Qt::EditRole, text); + } else if (g) { + item->setData (1, Qt::EditRole, tl::to_qstring (g->version ())); + } + + } else if (column == 2) { + + QString text = item->data (column, Qt::UserRole).toString (); + if (! text.isEmpty ()) { + item->setData (2, Qt::EditRole, text); + } else if (g) { + if (! g->url ().empty ()) { + item->setData (2, Qt::EditRole, tl::to_qstring ("(" + g->url () + ")")); + } else { + item->setData (2, Qt::EditRole, tr ("(from repository)")); + } + } + } m_update_enabled = true; @@ -531,10 +558,7 @@ SaltGrainPropertiesDialog::accept () } dep_seen.insert (d->name); - if (! dep.is_valid_name (d->name)) { - dependencies_alert->warn () << tr ("'%1' is not a name of a package loaded already").arg (tl::to_qstring (d->name)) << tl::endl - << tr ("You need to specify the details (version, URL) manually"); - } else { + if (dep.is_valid_name (d->name)) { try { dep.check_circular (dep.grain_for_name (m_grain.name ()), dep.grain_for_name (d->name)); } catch (tl::Exception &ex) { @@ -549,11 +573,7 @@ SaltGrainPropertiesDialog::accept () << tr ("If the dependency package has a version itself, the version is automatically set to it's current version."); } - if (d->url.empty ()) { - dependencies_alert->warn () << tr ("No download URL specified for dependency '%1'").arg (tl::to_qstring (d->name)) << tl::endl - << tr ("A download URL should be specified to ensure the package dependencies can be resolved.") << tl::endl - << tr ("If the dependency package was downloaded itself, the URL is automatically set to the download source."); - } else { + if (!d->url.empty ()) { SaltGrain gdep; try { gdep = SaltGrain::from_url (d->url); diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index 978dc9c46..aa3ecd745 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -144,7 +144,7 @@ lay::Salt *get_salt_mine () SaltManagerDialog::SaltManagerDialog (QWidget *parent) : QDialog (parent), - m_current_changed_enabled (true) + m_current_changed_enabled (true), dm_update_models (this, &SaltManagerDialog::update_models) { Ui::SaltManagerDialog::setupUi (this); mp_properties_dialog = new lay::SaltGrainPropertiesDialog (this); @@ -165,15 +165,6 @@ SaltManagerDialog::SaltManagerDialog (QWidget *parent) salt_mine_view->setModel (mine_model); salt_mine_view->setItemDelegate (new SaltItemDelegate (this)); - // Establish a message saying that an update is available - for (Salt::flat_iterator g = mp_salt->begin_flat (); g != mp_salt->end_flat (); ++g) { - SaltGrain *gm = mp_salt_mine->grain_by_name ((*g)->name ()); - if (gm && SaltGrain::compare_versions (gm->version (), (*g)->version ()) > 0) { - model->set_message ((*g)->name (), tl::to_string (tr ("An update to version %1 is available").arg (tl::to_qstring (gm->version ())))); - mine_model->set_message ((*g)->name (), tl::to_string (tr ("The installed version is outdated (%1)").arg (tl::to_qstring ((*g)->version ())))); - } - } - mode_tab->setCurrentIndex (mp_salt->is_empty () ? 1 : 0); connect (mode_tab, SIGNAL (currentChanged (int)), this, SLOT (mode_changed ())); @@ -181,11 +172,12 @@ SaltManagerDialog::SaltManagerDialog (QWidget *parent) connect (mp_salt, SIGNAL (collections_changed ()), this, SLOT (salt_changed ())); connect (mp_salt_mine, SIGNAL (collections_changed ()), this, SLOT (salt_mine_changed ())); - salt_changed (); - salt_mine_changed (); + update_models (); connect (salt_view->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (current_changed ())); + connect (salt_view, SIGNAL (doubleClicked (const QModelIndex &)), this, SLOT (edit_properties ())); connect (salt_mine_view->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (mine_current_changed ()), Qt::QueuedConnection); + connect (salt_mine_view, SIGNAL (doubleClicked (const QModelIndex &)), this, SLOT (mark_clicked ())); search_installed_edit->set_clear_button_enabled (true); search_new_edit->set_clear_button_enabled (true); @@ -193,6 +185,18 @@ SaltManagerDialog::SaltManagerDialog (QWidget *parent) connect (search_new_edit, SIGNAL (textChanged (const QString &)), this, SLOT (search_text_changed (const QString &))); connect (mark_button, SIGNAL (clicked ()), this, SLOT (mark_clicked ())); + + salt_mine_view->addAction (actionUnmarkAll); + QAction *a = new QAction (this); + a->setSeparator (true); + salt_mine_view->addAction (a); + salt_mine_view->addAction (actionShowMarkedOnly); + salt_mine_view->addAction (actionShowAll); + salt_mine_view->setContextMenuPolicy (Qt::ActionsContextMenu); + + connect (actionUnmarkAll, SIGNAL (triggered ()), this, SLOT (unmark_all ())); + connect (actionShowMarkedOnly, SIGNAL (triggered ()), this, SLOT (show_marked_only ())); + connect (actionShowAll, SIGNAL (triggered ()), this, SLOT (show_all ())); } void @@ -201,11 +205,55 @@ SaltManagerDialog::mode_changed () // keeps the splitters in sync if (mode_tab->currentIndex () == 1) { splitter_new->setSizes (splitter->sizes ()); + show_all (); } else if (mode_tab->currentIndex () == 0) { splitter->setSizes (splitter_new->sizes ()); } } +void +SaltManagerDialog::show_all () +{ + search_new_edit->clear (); + + SaltModel *model = dynamic_cast (salt_mine_view->model ()); + if (! model) { + return; + } + + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + salt_mine_view->setRowHidden (i, false); + } +} + +void +SaltManagerDialog::show_marked_only () +{ + search_new_edit->clear (); + + SaltModel *model = dynamic_cast (salt_mine_view->model ()); + if (! model) { + return; + } + + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + SaltGrain *g = model->grain_from_index (model->index (i, 0, QModelIndex ())); + salt_mine_view->setRowHidden (i, !(g && model->is_marked (g->name ()))); + } +} + +void +SaltManagerDialog::unmark_all () +{ + SaltModel *model = dynamic_cast (salt_mine_view->model ()); + if (model) { + model->clear_marked (); + update_apply_state (); + } +} + void SaltManagerDialog::search_text_changed (const QString &text) { @@ -258,6 +306,36 @@ SaltManagerDialog::mark_clicked () } model->set_marked (g->name (), !model->is_marked (g->name ())); + update_apply_state (); +} + +void +SaltManagerDialog::update_apply_state () +{ + int marked = 0; + + SaltModel *model = dynamic_cast (salt_mine_view->model ()); + if (! model) { + return; + } + + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + QModelIndex index = model->index (i, 0, QModelIndex ()); + SaltGrain *g = model->grain_from_index (index); + if (g && model->is_marked (g->name ())) { + marked += 1; + } + } + + apply_button->setEnabled (marked > 0); + if (marked == 0) { + apply_label->setText (QString ()); + } else if (marked == 1) { + apply_label->setText (tr ("One package selected")); + } else if (marked > 1) { + apply_label->setText (tr ("%1 packages selected").arg (marked)); + } } void @@ -290,6 +368,7 @@ BEGIN_PROTECTED manager.compute_dependencies (*mp_salt, *mp_salt_mine); if (manager.show_confirmation_dialog (this, *mp_salt)) { + unmark_all (); manager.execute (*mp_salt); } @@ -363,16 +442,38 @@ END_PROTECTED void SaltManagerDialog::salt_changed () +{ + dm_update_models (); +} + +void +SaltManagerDialog::salt_mine_changed () +{ + dm_update_models (); +} + +void +SaltManagerDialog::update_models () { SaltModel *model = dynamic_cast (salt_view->model ()); - if (! model) { - return; - } + tl_assert (model != 0); // NOTE: the disabling of the event handler prevents us from // letting the model connect to the salt's signal directly. m_current_changed_enabled = false; + + model->clear_messages (); + + // Establish a message saying that an update is available + for (Salt::flat_iterator g = mp_salt->begin_flat (); g != mp_salt->end_flat (); ++g) { + SaltGrain *gm = mp_salt_mine->grain_by_name ((*g)->name ()); + if (gm && SaltGrain::compare_versions (gm->version (), (*g)->version ()) > 0) { + model->set_message ((*g)->name (), SaltModel::Warning, tl::to_string (tr ("An update to version %1 is available").arg (tl::to_qstring (gm->version ())))); + } + } + model->update (); + m_current_changed_enabled = true; if (mp_salt->is_empty ()) { @@ -392,7 +493,42 @@ SaltManagerDialog::salt_changed () } + SaltModel *mine_model = dynamic_cast (salt_mine_view->model ()); + tl_assert (mine_model != 0); + + // NOTE: the disabling of the event handler prevents us from + // letting the model connect to the salt's signal directly. + m_current_changed_enabled = false; + + mine_model->clear_order (); + mine_model->clear_messages (); + mine_model->enable_all (); + + // Establish a message saying that an update is available + for (Salt::flat_iterator g = mp_salt->begin_flat (); g != mp_salt->end_flat (); ++g) { + SaltGrain *gm = mp_salt_mine->grain_by_name ((*g)->name ()); + if (gm && SaltGrain::compare_versions (gm->version (), (*g)->version ()) > 0) { + mine_model->set_message ((*g)->name (), SaltModel::Warning, tl::to_string (tr ("The installed version is outdated (%1)").arg (tl::to_qstring ((*g)->version ())))); + mine_model->set_order ((*g)->name (), -1); + } else if (gm) { + mine_model->set_message ((*g)->name (), SaltModel::None, tl::to_string (tr ("This package is already installed and up to date"))); + mine_model->set_order ((*g)->name (), 1); + mine_model->set_enabled ((*g)->name (), false); + } + } + + mine_model->update (); + + m_current_changed_enabled = true; + + // select the first grain + if (mine_model->rowCount (QModelIndex ()) > 0) { + salt_mine_view->setCurrentIndex (mine_model->index (0, 0, QModelIndex ())); + } + + mine_current_changed (); current_changed (); + update_apply_state (); } void @@ -417,28 +553,6 @@ SaltManagerDialog::current_grain () return model ? model->grain_from_index (salt_view->currentIndex ()) : 0; } -void -SaltManagerDialog::salt_mine_changed () -{ - SaltModel *model = dynamic_cast (salt_mine_view->model ()); - if (! model) { - return; - } - - // NOTE: the disabling of the event handler prevents us from - // letting the model connect to the salt's signal directly. - m_current_changed_enabled = false; - model->update (); - m_current_changed_enabled = true; - - // select the first grain - if (model->rowCount (QModelIndex ()) > 0) { - salt_mine_view->setCurrentIndex (model->index (0, 0, QModelIndex ())); - } - - mine_current_changed (); -} - void SaltManagerDialog::mine_current_changed () { diff --git a/src/lay/laySaltManagerDialog.h b/src/lay/laySaltManagerDialog.h index c6e4ff893..6e1f40cf4 100644 --- a/src/lay/laySaltManagerDialog.h +++ b/src/lay/laySaltManagerDialog.h @@ -24,6 +24,7 @@ #define HDR_laySaltManagerDialog #include "ui_SaltManagerDialog.h" +#include "tlDeferredExecution.h" #include #include @@ -105,14 +106,32 @@ private slots: */ void search_text_changed (const QString &text); -private: - lay::Salt *mp_salt, *mp_salt_mine; - std::auto_ptr m_remote_grain; - bool m_current_changed_enabled; - lay::SaltGrainPropertiesDialog *mp_properties_dialog; + /** + * @brief Called to show the marked items only + */ + void show_marked_only (); - lay::SaltGrain *current_grain (); - lay::SaltGrain *mine_current_grain (); + /** + * @brief Called to show all items again + */ + void show_all (); + + /** + * @brief Called to unmark all items + */ + void unmark_all (); + +private: + Salt *mp_salt, *mp_salt_mine; + std::auto_ptr m_remote_grain; + bool m_current_changed_enabled; + SaltGrainPropertiesDialog *mp_properties_dialog; + tl::DeferredMethod dm_update_models; + + SaltGrain *current_grain (); + SaltGrain *mine_current_grain (); + void update_models (); + void update_apply_state (); }; } diff --git a/src/lay/laySaltModel.cc b/src/lay/laySaltModel.cc index 57dd44212..fc1a44ef8 100644 --- a/src/lay/laySaltModel.cc +++ b/src/lay/laySaltModel.cc @@ -46,6 +46,9 @@ SaltItemDelegate::paint (QPainter *painter, const QStyleOptionViewItem &option, QStyleOptionViewItemV4 optionV4 = option; initStyleOption (&optionV4, index); + bool is_enabled = (optionV4.state & QStyle::State_Enabled); + optionV4.state |= QStyle::State_Enabled; + QStyle *style = optionV4.widget ? optionV4.widget->style () : QApplication::style (); QTextDocument doc; @@ -58,6 +61,8 @@ SaltItemDelegate::paint (QPainter *painter, const QStyleOptionViewItem &option, if (optionV4.state & QStyle::State_Selected) { ctx.palette.setColor (QPalette::Text, optionV4.palette.color (QPalette::Active, QPalette::HighlightedText)); + } else if (! is_enabled) { + ctx.palette.setColor (QPalette::Text, optionV4.palette.color (QPalette::Disabled, QPalette::Text)); } QRect textRect = style->subElementRect (QStyle::SE_ItemViewItemText, &optionV4); @@ -93,15 +98,32 @@ SaltItemDelegate::sizeHint (const QStyleOptionViewItem &option, const QModelInde SaltModel::SaltModel (QObject *parent, lay::Salt *salt) : QAbstractItemModel (parent), mp_salt (salt) { - // .. nothing yet .. + create_ordered_list (); } -QVariant +Qt::ItemFlags +SaltModel::flags (const QModelIndex &index) const +{ + Qt::ItemFlags f = QAbstractItemModel::flags (index); + + const lay::SaltGrain *g = grain_from_index (index); + if (g && ! is_enabled (g->name ())) { + f &= ~Qt::ItemIsSelectable; + f &= ~Qt::ItemIsEnabled; + } + + return f; +} + +QVariant SaltModel::data (const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { - const lay::SaltGrain *g = mp_salt->begin_flat ()[index.row ()]; + const lay::SaltGrain *g = grain_from_index (index); + if (!g) { + return QVariant (); + } std::string text = ""; text += "

"; @@ -121,9 +143,15 @@ SaltModel::data (const QModelIndex &index, int role) const text += "

"; } - std::map::const_iterator m = m_messages.find (g->name ()); + std::map >::const_iterator m = m_messages.find (g->name ()); if (m != m_messages.end ()) { - text += "

" + tl::escaped_to_html (m->second) + "

"; + if (m->second.first == Warning || m->second.first == Error) { + text += "

" + tl::escaped_to_html (m->second.second) + "

"; + } else if (m->second.first == Info) { + text += "

" + tl::escaped_to_html (m->second.second) + "

"; + } else { + text += "

" + tl::escaped_to_html (m->second.second) + "

"; + } } text += ""; @@ -134,7 +162,10 @@ SaltModel::data (const QModelIndex &index, int role) const int icon_dim = 64; - const lay::SaltGrain *g = mp_salt->begin_flat ()[index.row ()]; + const lay::SaltGrain *g = grain_from_index (index); + if (!g) { + return QVariant (); + } QImage img; if (g->icon ().isNull ()) { @@ -160,10 +191,21 @@ SaltModel::data (const QModelIndex &index, int role) const painter.drawImage (0, 0, warn); } - if (m_messages.find (g->name ()) != m_messages.end ()) { - QPainter painter (&img); - QImage warn (":/warn_16.png"); - painter.drawImage (0, 0, warn); + std::map >::const_iterator m = m_messages.find (g->name ()); + if (m != m_messages.end ()) { + if (m->second.first == Warning) { + QPainter painter (&img); + QImage warn (":/warn_16.png"); + painter.drawImage (0, 0, warn); + } else if (m->second.first == Error) { + QPainter painter (&img); + QImage warn (":/error_16.png"); + painter.drawImage (0, 0, warn); + } else if (m->second.first == Info) { + QPainter painter (&img); + QImage warn (":/info_16.png"); + painter.drawImage (0, 0, warn); + } } return QPixmap::fromImage (img); @@ -179,7 +221,7 @@ SaltModel::index (int row, int column, const QModelIndex &parent) const if (parent.isValid ()) { return QModelIndex (); } else { - return createIndex (row, column, mp_salt->begin_flat () [row]); + return createIndex (row, column, m_ordered_grains [row]); } } @@ -201,7 +243,7 @@ SaltModel::rowCount (const QModelIndex &parent) const if (parent.isValid ()) { return 0; } else { - return mp_salt->end_flat () - mp_salt->begin_flat (); + return int (m_ordered_grains.size ()); } } @@ -221,32 +263,149 @@ SaltModel::is_marked (const std::string &name) const return m_marked.find (name) != m_marked.end (); } +bool +SaltModel::is_enabled (const std::string &name) const +{ + return m_disabled.find (name) == m_disabled.end (); +} + void SaltModel::set_marked (const std::string &name, bool marked) { - if (! marked) { - m_marked.erase (name); - } else { - m_marked.insert (name); + if (marked != is_marked (name)) { + if (! marked) { + m_marked.erase (name); + } else { + m_marked.insert (name); + } + emit dataChanged (index (0, 0, QModelIndex ()), index (rowCount (QModelIndex ()) - 1, 0, QModelIndex ())); } - emit dataChanged (index (0, 0, QModelIndex ()), index (rowCount (QModelIndex ()) - 1, 0, QModelIndex ())); } void -SaltModel::set_message (const std::string &name, const std::string &message) +SaltModel::clear_marked () { - if (message.empty ()) { - m_messages.erase (name); - } else { - m_messages.insert (std::make_pair (name, message)); + if (! m_marked.empty ()) { + m_marked.clear (); + emit dataChanged (index (0, 0, QModelIndex ()), index (rowCount (QModelIndex ()) - 1, 0, QModelIndex ())); } - emit dataChanged (index (0, 0, QModelIndex ()), index (rowCount (QModelIndex ()) - 1, 0, QModelIndex ())); } -void +void +SaltModel::set_enabled (const std::string &name, bool enabled) +{ + if (enabled != is_enabled (name)) { + if (enabled) { + m_disabled.erase (name); + } else { + m_disabled.insert (name); + } + emit dataChanged (index (0, 0, QModelIndex ()), index (rowCount (QModelIndex ()) - 1, 0, QModelIndex ())); + } +} + +void +SaltModel::enable_all () +{ + if (! m_disabled.empty ()) { + m_disabled.clear (); + emit dataChanged (index (0, 0, QModelIndex ()), index (rowCount (QModelIndex ()) - 1, 0, QModelIndex ())); + } +} + +void +SaltModel::clear_order () +{ + m_display_order.clear (); +} + +void +SaltModel::reset_order (const std::string &name) +{ + m_display_order.erase (name); +} + +void +SaltModel::set_order (const std::string &name, int order) +{ + m_display_order[name] = order; +} + +void +SaltModel::set_message (const std::string &name, Severity severity, const std::string &message) +{ + bool needs_update = false; + if (message.empty ()) { + if (m_messages.find (name) != m_messages.end ()) { + m_messages.erase (name); + needs_update = true; + } + } else { + std::map >::iterator m = m_messages.find (name); + if (m == m_messages.end () || m->second.second != message || m->second.first != severity) { + m_messages.insert (std::make_pair (name, std::make_pair (severity, message))); + needs_update = true; + } + } + + if (needs_update) { + emit dataChanged (index (0, 0, QModelIndex ()), index (rowCount (QModelIndex ()) - 1, 0, QModelIndex ())); + } +} + +void +SaltModel::clear_messages () +{ + if (! m_messages.empty ()) { + m_messages.clear (); + emit dataChanged (index (0, 0, QModelIndex ()), index (rowCount (QModelIndex ()) - 1, 0, QModelIndex ())); + } +} + +void SaltModel::update () { + create_ordered_list (); reset (); } +void +SaltModel::create_ordered_list () +{ + m_ordered_grains.clear (); + + if (m_display_order.empty ()) { + + for (Salt::flat_iterator i = mp_salt->begin_flat (); i != mp_salt->end_flat (); ++i) { + m_ordered_grains.push_back (*i); + } + + } else { + + int min_order = m_display_order.begin ()->second; + int max_order = min_order; + min_order = std::min (min_order, 0); + max_order = std::max (max_order, 0); + + for (std::map::const_iterator i = m_display_order.begin (); i != m_display_order.end (); ++i) { + min_order = std::min (min_order, i->second); + max_order = std::max (max_order, i->second); + } + + for (int o = min_order; o <= max_order; ++o) { + for (Salt::flat_iterator i = mp_salt->begin_flat (); i != mp_salt->end_flat (); ++i) { + std::map::const_iterator d = m_display_order.find ((*i)->name ()); + int oi = 0; + if (d != m_display_order.end ()) { + oi = d->second; + } + if (oi == o) { + m_ordered_grains.push_back (*i); + } + } + } + + } +} + } diff --git a/src/lay/laySaltModel.h b/src/lay/laySaltModel.h index 20a537e2c..d7ba80d92 100644 --- a/src/lay/laySaltModel.h +++ b/src/lay/laySaltModel.h @@ -47,6 +47,17 @@ class SaltModel Q_OBJECT public: + /** + * @brief An enum describing the severity of a message + */ + enum Severity + { + None = 0, + Info = 1, + Warning = 2, + Error = 3 + }; + /** * @brief Constructor */ @@ -57,6 +68,11 @@ public: */ QVariant data (const QModelIndex &index, int role) const; + /** + * @brief Implementation of the QAbstractItemModel interface + */ + Qt::ItemFlags flags (const QModelIndex &index) const; + /** * @brief Implementation of the QAbstractItemModel interface */ @@ -93,18 +109,70 @@ public: */ void set_marked (const std::string &name, bool marked); + /** + * @brief Clears the marked state of all grains + */ + void clear_marked (); + + /** + * @brief Enables or disables the grain with the given name + */ + void set_enabled (const std::string &name, bool enabled); + + /** + * @brief Enables all grains + */ + void enable_all (); + /** * @brief Installs a message on the grain with the given name * Installing an empty message basically removes the message. */ - void set_message (const std::string &name, const std::string &message); + void set_message (const std::string &name, Severity severity, const std::string &message); + + /** + * @brief Removes a message + */ + void reset_message (const std::string &name) + { + set_message (name, None, std::string ()); + } + + /** + * @brief Clears all messages + */ + void clear_messages (); + + /** + * @brief Sets the display order + * Specifying a display order for a name will make the grain appear + * before or after other grains. + * "update" needs to be called before the order becomes active. + * Non-assigned items are considered to have order (0). + */ + void set_order (const std::string &name, int order); + + /** + * @brief Resets any display order + */ + void reset_order (const std::string &name); + + /** + * @brief Resets all display order specs + */ + void clear_order (); public: lay::Salt *mp_salt; std::set m_marked; - std::map m_messages; + std::set m_disabled; + std::map > m_messages; + std::map m_display_order; + std::vector m_ordered_grains; bool is_marked (const std::string &name) const; + bool is_enabled (const std::string &name) const; + void create_ordered_list (); }; // -------------------------------------------------------------------------------------- diff --git a/src/tl/tlString.cc b/src/tl/tlString.cc index 22d05402f..3179df4ac 100644 --- a/src/tl/tlString.cc +++ b/src/tl/tlString.cc @@ -416,6 +416,20 @@ tl::to_word_or_quoted_string (const std::string &s, const char *non_term) void tl::escape_to_html (std::string &out, const std::string &in, bool replace_newlines) { + + + + + + + + + + + + + + for (const char *cp = in.c_str (); *cp; ++cp) { if (*cp == '<') { out += "<"; diff --git a/src/tl/tlWebDAV.cc b/src/tl/tlWebDAV.cc index 19c2a004e..04706e78e 100644 --- a/src/tl/tlWebDAV.cc +++ b/src/tl/tlWebDAV.cc @@ -273,8 +273,10 @@ WebDAVObject::download (const std::string &url, const std::string &target) bool has_errors = false; { - tl::info << tl::to_string (QObject::tr ("Downloading %1 files now").arg (items.size ())); + tl::info << tl::to_string (QObject::tr ("Downloading %1 file(s) now ..").arg (items.size ())); + tl::RelativeProgress progress (tl::to_string (QObject::tr ("Downloading file(s) from %1").arg (tl::to_qstring (url))), items.size (), 1); + for (std::list::const_iterator i = items.begin (); i != items.end (); ++i) { tl::info << QObject::tr ("Downloading '%1' to '%2' ..").arg (tl::to_qstring (i->url)).arg (tl::to_qstring (i->path)); @@ -307,6 +309,8 @@ WebDAVObject::download (const std::string &url, const std::string &target) has_errors = true; } + ++progress; + } } From 9563d13bf36dd287d12569759a4604fe973f88cb Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 8 Apr 2017 22:32:08 +0200 Subject: [PATCH 28/57] WIP: introduced technology controller The technology controller is a further abstraction and is derived from the TechnologySelector. It will act together with the MacroController and supply technology specific information. Macros are part of that. --- src/lay/lay.pro | 8 +- src/lay/layMacroController.cc | 5 + src/lay/layMainWindow.cc | 28 +-- src/lay/layTechnologyController.cc | 295 +++++++++++++++++++++++++++++ src/lay/layTechnologyController.h | 109 +++++++++++ src/lay/layTechnologySelector.cc | 256 ------------------------- src/lay/layTechnologySelector.h | 35 ---- 7 files changed, 417 insertions(+), 319 deletions(-) create mode 100644 src/lay/layTechnologyController.cc create mode 100644 src/lay/layTechnologyController.h delete mode 100644 src/lay/layTechnologySelector.cc delete mode 100644 src/lay/layTechnologySelector.h diff --git a/src/lay/lay.pro b/src/lay/lay.pro index bd89c9af8..7e99a9574 100644 --- a/src/lay/lay.pro +++ b/src/lay/lay.pro @@ -41,13 +41,13 @@ HEADERS = \ laySelectCellViewForm.h \ laySession.h \ laySettingsForm.h \ - layTechnologySelector.h \ layTechSetupDialog.h \ layTextProgress.h \ layVersion.h \ layCommon.h \ layConfig.h \ - layMacroController.h + layMacroController.h \ + layTechnologyController.h FORMS = \ ClipDialog.ui \ @@ -132,11 +132,11 @@ SOURCES = \ laySelectCellViewForm.cc \ laySession.cc \ laySettingsForm.cc \ - layTechnologySelector.cc \ layTechSetupDialog.cc \ layTextProgress.cc \ layVersion.cc \ - layMacroController.cc + layMacroController.cc \ + layTechnologyController.cc RESOURCES = layBuildInMacros.qrc \ layHelpResources.qrc \ diff --git a/src/lay/layMacroController.cc b/src/lay/layMacroController.cc index 93f8e5920..a0c4ae858 100644 --- a/src/lay/layMacroController.cc +++ b/src/lay/layMacroController.cc @@ -21,6 +21,7 @@ */ #include "layMacroController.h" +#include "layTechnologyController.h" #include "layMacroEditorDialog.h" #include "layMacroInterpreter.h" #include "layMainWindow.h" @@ -54,6 +55,8 @@ MacroController::initialized (lay::PluginRoot *root) connect (&lay::MacroCollection::root (), SIGNAL (menu_needs_update ()), this, SLOT (update_menu_with_macros ())); connect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ())); + connect (lay::TechnologyController::instance (), SIGNAL (active_technology_changed ()), this, SLOT (update_menu_with_macros ())); + connect (lay::TechnologyController::instance (), SIGNAL (technologies_edited ()), this, SLOT (update_menu_with_macros ())); // update the menus with the macro menu bindings as late as possible (now we // can be sure that the menus are created propertly) @@ -65,6 +68,8 @@ MacroController::uninitialize (lay::PluginRoot * /*root*/) { disconnect (&lay::MacroCollection::root (), SIGNAL (menu_needs_update ()), this, SLOT (update_menu_with_macros ())); disconnect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ())); + disconnect (lay::TechnologyController::instance (), SIGNAL (active_technology_changed ()), this, SLOT (update_menu_with_macros ())); + disconnect (lay::TechnologyController::instance (), SIGNAL (technologies_edited ()), this, SLOT (update_menu_with_macros ())); delete mp_macro_editor; mp_macro_editor = 0; diff --git a/src/lay/layMainWindow.cc b/src/lay/layMainWindow.cc index ca633aaa5..7f95968f9 100644 --- a/src/lay/layMainWindow.cc +++ b/src/lay/layMainWindow.cc @@ -86,7 +86,7 @@ #include "layLayerToolbox.h" #include "laySettingsForm.h" #include "laySettingsForm.h" -#include "layTechSetupDialog.h" +#include "layTechnologyController.h" #include "layTipDialog.h" #include "laySelectCellViewForm.h" #include "layLayoutPropertiesForm.h" @@ -4507,29 +4507,9 @@ MainWindow::show_progress_bar (bool show) void MainWindow::cm_technologies () { - lay::TechSetupDialog dialog (this); - if (dialog.exec ()) { - - std::vector nm = lay::Application::instance ()->sync_tech_macro_locations (); - - bool has_autorun = false; - for (std::vector::const_iterator m = nm.begin (); m != nm.end () && ! has_autorun; ++m) { - has_autorun = (*m)->has_autorun (); - } - - if (has_autorun && QMessageBox::question (this, QObject::tr ("Run Macros"), QObject::tr ("Some macros associated with technologies now are configured to run automatically.\n\nChoose 'Yes' to run these macros now. Choose 'No' to not run them."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - for (std::vector::const_iterator m = nm.begin (); m != nm.end (); ++m) { - (*m)->autorun (); - } - } - - // because the macro-tech association might have changed, do this: - // TODO: let the macro controller monitor the technologies. - lay::MacroController *mc = lay::MacroController::instance (); - if (mc) { - mc->update_menu_with_macros (); - } - + lay::TechnologyController *tc = lay::TechnologyController::instance (); + if (tc) { + tc->show_editor (); } } diff --git a/src/lay/layTechnologyController.cc b/src/lay/layTechnologyController.cc new file mode 100644 index 000000000..26fff7611 --- /dev/null +++ b/src/lay/layTechnologyController.cc @@ -0,0 +1,295 @@ + +/* + + 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 "layTechnologyController.h" +#include "layTechSetupDialog.h" +#include "layMainWindow.h" +#include "layMacroController.h" +#include "layApplication.h" +#include "laybasicConfig.h" + +#include + +namespace lay +{ + +std::string tech_string_from_name (const std::string &tn) +{ + if (tn.empty ()) { + return tl::to_string (QObject::tr ("(Default)")); + } else { + return tn; + } +} + +TechnologyController::TechnologyController () + : PluginDeclaration (), mp_editor (0), mp_mw (0) +{ + m_current_technology_updated = false; +} + +TechnologyController * +TechnologyController::instance () +{ + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + TechnologyController *tc = dynamic_cast (cls.operator-> ()); + if (tc) { + return tc; + } + } + return 0; +} + +void +TechnologyController::initialized (lay::PluginRoot *root) +{ + mp_mw = dynamic_cast (root); + if (mp_mw) { + mp_editor = new lay::TechSetupDialog (mp_mw); + mp_editor->setModal (false); + } + + update_menu (); + update_after_change (); +} + +void +TechnologyController::uninitialize (lay::PluginRoot * /*root*/) +{ + m_tech_actions.clear (); + tl::Object::detach_from_all_events (); +} + +void +TechnologyController::get_options (std::vector < std::pair > &options) const +{ + options.push_back (std::pair (cfg_initial_technology, "")); +} + +void +TechnologyController::get_menu_entries (std::vector &menu_entries) const +{ + lay::PluginDeclaration::get_menu_entries (menu_entries); + menu_entries.push_back (lay::MenuEntry ("technology_selector:apply_technology", "technology_selector:tech_selector_group", "@toolbar.end", tl::to_string (QObject::tr ("Technology<:techs.png>{Select technology (click to apply)}")))); +} + +void +TechnologyController::update_after_change () +{ + // re-attach all events + tl::Object::detach_from_all_events (); + + lay::MainWindow::instance ()->current_view_changed_event.add (this, &TechnologyController::update_after_change); + lay::Technologies::instance ()->technology_changed_event.add (this, &TechnologyController::technology_changed); + lay::Technologies::instance ()->technologies_changed_event.add (this, &TechnologyController::technologies_changed); + + if (lay::LayoutView::current ()) { + lay::LayoutView::current ()->active_cellview_changed_event.add (this, &TechnologyController::update_after_change); + } + + std::string active_tech; + if (lay::LayoutView::current () && lay::LayoutView::current ()->active_cellview_index () >= 0 && lay::LayoutView::current ()->active_cellview_index () <= int (lay::LayoutView::current ()->cellviews ())) { + lay::LayoutView::current ()->active_cellview ()->technology_changed_event.add (this, &TechnologyController::update_after_change); + active_tech = lay::LayoutView::current ()->active_cellview ()->tech_name (); + } + + if (m_active_technology != active_tech) { + + m_active_technology = active_tech; + + lay::MainWindow *mw = lay::MainWindow::instance (); + if (mw) { + mw->tech_message (tech_string_from_name (active_tech)); + } + lay::MacroController *mc = lay::MacroController::instance (); + if (mc) { + // TODO: let the macro controller monitor the active technology + // need to do this since macros may be bound to the new technology + mc->update_menu_with_macros (); + } + + } + +#if 0 + // Hint with this implementation, the current technology follows the current layout. + // Although that's a nice way to display the current technology, it's pretty confusing + lay::PluginRoot *pr = lay::PluginRoot::instance (); + if (pr) { + pr->config_set (cfg_initial_technology, active_tech); + pr->config_finalize (); + } +#endif +} + +void +TechnologyController::technologies_changed () +{ + // delay actual update of menu so we can compress multiple events + update_menu (); +} + +void +TechnologyController::technology_changed (lay::Technology *) +{ + // delay actual update of menu so we can compress multiple events + update_menu (); +} + +bool +TechnologyController::configure (const std::string &name, const std::string &value) +{ + if (name == cfg_initial_technology) { + + if (value != m_current_technology) { + m_current_technology = value; + m_current_technology_updated = true; + } + + } + return false; +} + +void +TechnologyController::config_finalize () +{ + if (m_current_technology_updated) { + update_current_technology (); + m_current_technology_updated = false; + } +} + +bool +TechnologyController::menu_activated (const std::string &symbol) const +{ + if (symbol == "technology_selector:apply_technology") { + if (lay::LayoutView::current () && lay::LayoutView::current ()->active_cellview ().is_valid ()) { + lay::LayoutView::current ()->active_cellview ()->apply_technology (m_current_technology); + } + return true; + } else { + return lay::PluginDeclaration::menu_activated (symbol); + } +} + +void +TechnologyController::update_current_technology () +{ + lay::AbstractMenuProvider *pr = lay::AbstractMenuProvider::instance (); + if (! pr) { + return; + } + + std::string title = tech_string_from_name (m_current_technology); + + std::vector menu_entries = pr->menu ()->group ("tech_selector_group"); + for (std::vector::const_iterator m = menu_entries.begin (); m != menu_entries.end (); ++m) { + lay::Action action = pr->menu ()->action (*m); + action.set_title (title); + } + + size_t it = 0; + for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end () && it < m_tech_actions.size (); ++t, ++it) { + m_tech_actions[it].set_checked (t->name () == m_current_technology); + } +} + +void +TechnologyController::update_menu () +{ + lay::AbstractMenuProvider *pr = lay::AbstractMenuProvider::instance (); + if (! pr) { + return; + } + + if (lay::LayoutView::current () && lay::LayoutView::current ()->active_cellview ().is_valid ()) { + m_current_technology = lay::LayoutView::current ()->active_cellview ()->tech_name (); + } + + std::string title = tech_string_from_name (m_current_technology); + + size_t ntech = 0; + for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { + ++ntech; + } + + std::vector tech_group = pr->menu ()->group ("tech_selector_group"); + + for (std::vector::const_iterator t = tech_group.begin (); t != tech_group.end (); ++t) { + lay::Action action = pr->menu ()->action (*t); + action.set_title (title); + action.set_visible (ntech > 1); + std::vector items = pr->menu ()->items (*t); + for (std::vector::const_iterator i = items.begin (); i != items.end (); ++i) { + pr->menu ()->delete_item (*i); + } + } + + m_tech_actions.clear (); + + int it = 0; + for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t, ++it) { + + std::string title = tech_string_from_name (t->name ()); + + m_tech_actions.push_back (pr->create_config_action ("", cfg_initial_technology, t->name ())); + m_tech_actions.back ().set_title (title); // setting the title here avoids interpretation of '(...)' etc. + m_tech_actions.back ().set_checkable (true); + m_tech_actions.back ().set_checked (t->name () == m_current_technology); + for (std::vector::const_iterator t = tech_group.begin (); t != tech_group.end (); ++t) { + pr->menu ()->insert_item (*t + ".end", "technology_" + tl::to_string (it), m_tech_actions.back ()); + } + + } +} + +void +TechnologyController::show_editor () +{ + if (mp_editor && mp_editor->exec ()) { + + std::vector nm = lay::Application::instance ()->sync_tech_macro_locations (); + + bool has_autorun = false; + for (std::vector::const_iterator m = nm.begin (); m != nm.end () && ! has_autorun; ++m) { + has_autorun = (*m)->has_autorun (); + } + + if (has_autorun && QMessageBox::question (mp_mw, QObject::tr ("Run Macros"), QObject::tr ("Some macros associated with technologies now are configured to run automatically.\n\nChoose 'Yes' to run these macros now. Choose 'No' to not run them."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + for (std::vector::const_iterator m = nm.begin (); m != nm.end (); ++m) { + (*m)->autorun (); + } + } + + // because the macro-tech association might have changed, do this: + // TODO: let the macro controller monitor the technologies. + lay::MacroController *mc = lay::MacroController::instance (); + if (mc) { + mc->update_menu_with_macros (); + } + + } +} + +static tl::RegisteredClass config_decl (new TechnologyController (), 110, "TechnologyController"); + +} diff --git a/src/lay/layTechnologyController.h b/src/lay/layTechnologyController.h new file mode 100644 index 000000000..461fd354d --- /dev/null +++ b/src/lay/layTechnologyController.h @@ -0,0 +1,109 @@ + +/* + + 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_layTechnologyController +#define HDR_layTechnologyController + +#include "layCommon.h" +#include "layPlugin.h" +#include "layTechnology.h" +#include "layAbstractMenu.h" + +namespace lay +{ + +class TechSetupDialog; +class MainWindow; + +/** + * @brief A "controller" for the technologies + * The main task of the controller is to establish and manage the + * list of technologies and to manage the active technology. + */ +class LAY_PUBLIC TechnologyController + : public PluginDeclaration, + public tl::Object +{ +Q_OBJECT + +public: + /** + * @brief Constructor + */ + TechnologyController (); + + void initialized (lay::PluginRoot *root); + void uninitialize (lay::PluginRoot *root); + + void get_options (std::vector < std::pair > &options) const; + void get_menu_entries (std::vector &menu_entries) const; + + void show_editor (); + + /** + * @brief Gets the name of the active technology + * The active technology is the one the current cellview uses + */ + const std::string &active_technology () const + { + return m_active_technology; + } + + static TechnologyController *instance (); + +signals: + /** + * @brief This signal is emitted if the active technology has changed + */ + void active_technology_changed (); + + /** + * @brief This signal is emitted if the technology list has been edited + * This signal is emitted if either the list or one technology has been + * edited. It indicates the need for reflecting changes in the technology + * setup. + */ + void technologies_edited (); + +private: + tl::stable_vector m_tech_actions; + std::string m_current_technology; + std::string m_active_technology; + bool m_current_technology_updated; + lay::TechSetupDialog *mp_editor; + lay::MainWindow *mp_mw; + + void update_after_change (); + void technologies_changed (); + void technology_changed (lay::Technology *); + bool configure (const std::string &name, const std::string &value); + void config_finalize (); + bool menu_activated (const std::string &symbol) const; + void update_current_technology (); + void update_menu (); +}; + +} + +#endif + diff --git a/src/lay/layTechnologySelector.cc b/src/lay/layTechnologySelector.cc deleted file mode 100644 index e894c1f11..000000000 --- a/src/lay/layTechnologySelector.cc +++ /dev/null @@ -1,256 +0,0 @@ - -/* - - 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 "layTechnologySelector.h" -#include "layPlugin.h" -#include "laybasicConfig.h" -#include "layMainWindow.h" -#include "layMacroController.h" -#include "layTechnology.h" -#include "laybasicConfig.h" -#include "tlDeferredExecution.h" - -namespace lay -{ - -std::string tech_string_from_name (const std::string &tn) -{ - if (tn.empty ()) { - return tl::to_string (QObject::tr ("(Default)")); - } else { - return tn; - } -} - -class LAY_PUBLIC TechnologySelector - : public PluginDeclaration, - public tl::Object -{ -public: - TechnologySelector () - : PluginDeclaration () - { - m_current_technology_updated = false; - } - - void initialize (lay::PluginRoot * /*root*/) - { - // don't initialize in the -z case (no gui) - if (! lay::MainWindow::instance ()) { - return; - } - - update_menu (); - update_after_change (); - } - - void uninitialize (lay::PluginRoot * /*root*/) - { - m_tech_actions.clear (); - tl::Object::detach_from_all_events (); - } - - void get_options (std::vector < std::pair > &options) const - { - options.push_back (std::pair (cfg_initial_technology, "")); - } - - void get_menu_entries (std::vector &menu_entries) const - { - lay::PluginDeclaration::get_menu_entries (menu_entries); - menu_entries.push_back (lay::MenuEntry ("technology_selector:apply_technology", "technology_selector:tech_selector_group", "@toolbar.end", tl::to_string (QObject::tr ("Technology<:techs.png>{Select technology (click to apply)}")))); - } - -private: - tl::stable_vector m_tech_actions; - std::string m_current_technology; - std::string m_active_technology; - bool m_current_technology_updated; - - void update_after_change () - { - // re-attach all events - tl::Object::detach_from_all_events (); - - lay::MainWindow::instance ()->current_view_changed_event.add (this, &TechnologySelector::update_after_change); - lay::Technologies::instance ()->technology_changed_event.add (this, &TechnologySelector::technology_changed); - lay::Technologies::instance ()->technologies_changed_event.add (this, &TechnologySelector::technologies_changed); - - if (lay::LayoutView::current ()) { - lay::LayoutView::current ()->active_cellview_changed_event.add (this, &TechnologySelector::update_after_change); - } - - std::string active_tech; - if (lay::LayoutView::current () && lay::LayoutView::current ()->active_cellview_index () >= 0 && lay::LayoutView::current ()->active_cellview_index () <= int (lay::LayoutView::current ()->cellviews ())) { - lay::LayoutView::current ()->active_cellview ()->technology_changed_event.add (this, &TechnologySelector::update_after_change); - active_tech = lay::LayoutView::current ()->active_cellview ()->tech_name (); - } - - if (m_active_technology != active_tech) { - - m_active_technology = active_tech; - - lay::MainWindow *mw = lay::MainWindow::instance (); - if (mw) { - mw->tech_message (tech_string_from_name (active_tech)); - } - lay::MacroController *mc = lay::MacroController::instance (); - if (mc) { - // TODO: let the macro controller monitor the active technology - // need to do this since macros may be bound to the new technology - mc->update_menu_with_macros (); - } - - } - -#if 0 - // Hint with this implementation, the current technology follows the current layout. - // Although that's a nice way to display the current technology, it's pretty confusing - lay::PluginRoot *pr = lay::PluginRoot::instance (); - if (pr) { - pr->config_set (cfg_initial_technology, active_tech); - pr->config_finalize (); - } -#endif - } - - void technologies_changed () - { - // delay actual update of menu so we can compress multiple events - update_menu (); - } - - void technology_changed (lay::Technology *) - { - // delay actual update of menu so we can compress multiple events - update_menu (); - } - - bool configure (const std::string &name, const std::string &value) - { - if (name == cfg_initial_technology) { - - if (value != m_current_technology) { - m_current_technology = value; - m_current_technology_updated = true; - } - - } - return false; - } - - void config_finalize () - { - if (m_current_technology_updated) { - update_current_technology (); - m_current_technology_updated = false; - } - } - - bool menu_activated (const std::string &symbol) const - { - if (symbol == "technology_selector:apply_technology") { - if (lay::LayoutView::current () && lay::LayoutView::current ()->active_cellview ().is_valid ()) { - lay::LayoutView::current ()->active_cellview ()->apply_technology (m_current_technology); - } - return true; - } else { - return lay::PluginDeclaration::menu_activated (symbol); - } - } - - void update_current_technology () - { - lay::AbstractMenuProvider *pr = lay::AbstractMenuProvider::instance (); - if (! pr) { - return; - } - - std::string title = tech_string_from_name (m_current_technology); - - std::vector menu_entries = pr->menu ()->group ("tech_selector_group"); - for (std::vector::const_iterator m = menu_entries.begin (); m != menu_entries.end (); ++m) { - lay::Action action = pr->menu ()->action (*m); - action.set_title (title); - } - - size_t it = 0; - for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end () && it < m_tech_actions.size (); ++t, ++it) { - m_tech_actions[it].set_checked (t->name () == m_current_technology); - } - } - - void update_menu () - { - lay::AbstractMenuProvider *pr = lay::AbstractMenuProvider::instance (); - if (! pr) { - return; - } - - if (lay::LayoutView::current () && lay::LayoutView::current ()->active_cellview ().is_valid ()) { - m_current_technology = lay::LayoutView::current ()->active_cellview ()->tech_name (); - } - - std::string title = tech_string_from_name (m_current_technology); - - size_t ntech = 0; - for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { - ++ntech; - } - - std::vector tech_group = pr->menu ()->group ("tech_selector_group"); - - for (std::vector::const_iterator t = tech_group.begin (); t != tech_group.end (); ++t) { - lay::Action action = pr->menu ()->action (*t); - action.set_title (title); - action.set_visible (ntech > 1); - std::vector items = pr->menu ()->items (*t); - for (std::vector::const_iterator i = items.begin (); i != items.end (); ++i) { - pr->menu ()->delete_item (*i); - } - } - - m_tech_actions.clear (); - - int it = 0; - for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t, ++it) { - - std::string title = tech_string_from_name (t->name ()); - - m_tech_actions.push_back (pr->create_config_action ("", cfg_initial_technology, t->name ())); - m_tech_actions.back ().set_title (title); // setting the title here avoids interpretation of '(...)' etc. - m_tech_actions.back ().set_checkable (true); - m_tech_actions.back ().set_checked (t->name () == m_current_technology); - for (std::vector::const_iterator t = tech_group.begin (); t != tech_group.end (); ++t) { - pr->menu ()->insert_item (*t + ".end", "technology_" + tl::to_string (it), m_tech_actions.back ()); - } - - } - } -}; - -static tl::RegisteredClass config_decl (new TechnologySelector (), 9000, "TechnologySelector"); - -} - - diff --git a/src/lay/layTechnologySelector.h b/src/lay/layTechnologySelector.h deleted file mode 100644 index 0d6c8b1ad..000000000 --- a/src/lay/layTechnologySelector.h +++ /dev/null @@ -1,35 +0,0 @@ - -/* - - 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_layTechnologySelector -#define HDR_layTechnologySelector - -namespace lay -{ - - // no exposes classes. Everything is inside the plugin declaration singleton. - -} - -#endif - From 83b2c150d9fcd02cc54576e3904a1b02e550545c Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 11 Apr 2017 23:34:55 +0200 Subject: [PATCH 29/57] Fixed a segfault in batch mode. --- src/lay/layTechnologyController.cc | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/lay/layTechnologyController.cc b/src/lay/layTechnologyController.cc index 26fff7611..3f3c56b41 100644 --- a/src/lay/layTechnologyController.cc +++ b/src/lay/layTechnologyController.cc @@ -99,7 +99,13 @@ TechnologyController::update_after_change () // re-attach all events tl::Object::detach_from_all_events (); - lay::MainWindow::instance ()->current_view_changed_event.add (this, &TechnologyController::update_after_change); + lay::MainWindow *mw = lay::MainWindow::instance (); + lay::MacroController *mc = lay::MacroController::instance (); + + if (mw) { + mw->current_view_changed_event.add (this, &TechnologyController::update_after_change); + } + lay::Technologies::instance ()->technology_changed_event.add (this, &TechnologyController::technology_changed); lay::Technologies::instance ()->technologies_changed_event.add (this, &TechnologyController::technologies_changed); @@ -117,11 +123,10 @@ TechnologyController::update_after_change () m_active_technology = active_tech; - lay::MainWindow *mw = lay::MainWindow::instance (); if (mw) { mw->tech_message (tech_string_from_name (active_tech)); } - lay::MacroController *mc = lay::MacroController::instance (); + if (mc) { // TODO: let the macro controller monitor the active technology // need to do this since macros may be bound to the new technology From 5b422440a1f32fd91d960319b7e6cc0461266ee4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 15 Apr 2017 01:03:24 +0200 Subject: [PATCH 30/57] WIP: technologies are file based by default now * Introduced "refresh" method of tech setup dialog * Some refactoring -> tech management is part of tech controller * Macro category management moved to macro controller --- src/lay/layApplication.cc | 232 ++++------------------------- src/lay/layApplication.h | 8 - src/lay/layMacroController.cc | 40 ++++- src/lay/layMacroController.h | 20 +++ src/lay/layMacroEditorDialog.cc | 3 +- src/lay/layTechSetupDialog.cc | 168 +++++++++++++++++---- src/lay/layTechSetupDialog.h | 3 + src/lay/layTechnologyController.cc | 219 ++++++++++++++++++++++++++- src/lay/layTechnologyController.h | 44 ++++++ src/laybasic/layTechnology.cc | 54 ++++++- src/laybasic/layTechnology.h | 61 ++++++++ 11 files changed, 603 insertions(+), 249 deletions(-) diff --git a/src/lay/layApplication.cc b/src/lay/layApplication.cc index c74bb0f4c..058d52615 100644 --- a/src/lay/layApplication.cc +++ b/src/lay/layApplication.cc @@ -34,6 +34,7 @@ #include "layTextProgress.h" #include "layBackgroundAwareTreeStyle.h" #include "layMacroController.h" +#include "layTechnologyController.h" #include "gtf.h" #include "gsiDecl.h" #include "gsiInterpreter.h" @@ -55,7 +56,6 @@ #include #include -#include #include #include #include @@ -895,41 +895,26 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) } if (! m_no_macros) { + // Add the global ruby modules as the first ones. m_load_macros.insert (m_load_macros.begin (), global_modules.begin (), global_modules.end ()); - } - // Scan built-in macros - // These macros are always taken, even if there are no macros requested (they are required to - // fully form the API). - lay::MacroCollection::root ().add_folder (tl::to_string (QObject::tr ("Built-In")), ":/built-in-macros", "macros", true); - lay::MacroCollection::root ().add_folder (tl::to_string (QObject::tr ("Built-In")), ":/built-in-pymacros", "pymacros", true); - - m_macro_categories.push_back (std::pair ("macros", tl::to_string (QObject::tr ("Ruby")))); - m_macro_categories.push_back (std::pair ("pymacros", tl::to_string (QObject::tr ("Python")))); - m_macro_categories.push_back (std::pair ("drc", tl::to_string (QObject::tr ("DRC")))); - - // Scan for macros and set interpreter path - for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { - - for (size_t c = 0; c < m_macro_categories.size (); ++c) { - - std::string mp = tl::to_string (QDir (tl::to_qstring (*p)).filePath (tl::to_qstring (m_macro_categories [c].first))); - - // don't scan if macros are disabled - if (! m_no_macros) { + lay::MacroController *mc = lay::MacroController::instance (); + if (mc) { + for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { if (p == m_klayout_path.begin ()) { - lay::MacroCollection::root ().add_folder (tl::to_string (QObject::tr ("Local")), mp, m_macro_categories [c].first, false); + mc->add_path (*p, tl::to_string (QObject::tr ("Local")), std::string (), false); } else if (m_klayout_path.size () == 2) { - lay::MacroCollection::root ().add_folder (tl::to_string (QObject::tr ("Global")), mp, m_macro_categories [c].first, true); + mc->add_path (*p, tl::to_string (QObject::tr ("Global")), std::string (), true); } else { - lay::MacroCollection::root ().add_folder (tl::to_string (QObject::tr ("Global")) + " - " + *p, mp, m_macro_categories [c].first, true); + mc->add_path (*p, tl::to_string (QObject::tr ("Global")) + " - " + *p, std::string (), true); } } + } - ruby_interpreter ().add_path (mp); - python_interpreter ().add_path (mp); - + // Install the custom folders + for (std::vector >::const_iterator p = custom_macro_paths.begin (); p != custom_macro_paths.end (); ++p) { + mc->add_path (p->first, tl::to_string (QObject::tr ("Project")) + " - " + p->first, p->second, false); } } @@ -979,81 +964,43 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) } - // auto-import technologies - for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { + lay::TechnologyController *tc = lay::TechnologyController::instance (); - QDir inst_path_dir (tl::to_qstring (*p)); - if (! inst_path_dir.cd (QString::fromUtf8 ("tech"))) { - continue; + if (tc) { + + tc->enable_macros (! m_no_macros); + + // auto-import technologies + for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { + std::string tp = tl::to_string (QDir (tl::to_qstring (*p)).filePath (QString::fromUtf8 ("tech"))); + tc->add_path (tp); } - QStringList name_filters; - name_filters << QString::fromUtf8 ("*.lyt"); + // import technologies from the command line + for (std::vector > >::iterator f = m_files.begin (); f != m_files.end (); ++f) { - QStringList lyt_files; - - QDirIterator di (inst_path_dir.path (), name_filters, QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); - while (di.hasNext ()) { - lyt_files << di.next (); - } - - lyt_files.sort (); - - for (QStringList::const_iterator lf = lyt_files.begin (); lf != lyt_files.end (); ++lf) { - - try { + if (f->first == layout_file_with_tech_file) { if (tl::verbosity () >= 20) { - tl::info << "Auto-importing technology from " << tl::to_string (*lf); + tl::info << "Importing technology from " << f->second.second; } lay::Technology t; - t.load (tl::to_string (*lf)); - t.set_persisted (false); // don't save that one in the configuration - lay::Technologies::instance ()->add (new lay::Technology (t)); + t.load (f->second.second); + + tc->add_temp_tech (t); + + f->first = layout_file_with_tech; + f->second.second = t.name (); - } catch (tl::Exception &ex) { - tl::warn << tl::to_string (QObject::tr ("Unable to auto-import technology file ")) << tl::to_string (*lf) << ": " << ex.msg (); } } - } - - // import technologies from the command line - for (std::vector > >::iterator f = m_files.begin (); f != m_files.end (); ++f) { - - if (f->first == layout_file_with_tech_file) { - - if (tl::verbosity () >= 20) { - tl::info << "Importing technology from " << f->second.second; - } - - lay::Technology t; - t.load (f->second.second); - t.set_persisted (false); // don't save that one in the configuration - lay::Technologies::instance ()->add (new lay::Technology (t)); - - f->first = layout_file_with_tech; - f->second.second = t.name (); - - } + tc->refresh (); } - // Install the custom folders - if (! m_no_macros) { - for (std::vector >::const_iterator p = custom_macro_paths.begin (); p != custom_macro_paths.end (); ++p) { - lay::MacroCollection::root ().add_folder (tl::to_string (QObject::tr ("Project")) + " - " + p->first, p->first, p->second, false); - // TODO: put somewhere else: - ruby_interpreter ().add_path (p->first); - python_interpreter ().add_path (p->first); - } - } - - // Add locations defined by the technologies - sync_tech_macro_locations (); - // If the editable flag was not set, use it from the // configuration. Since it is too early now, we cannot use the // configuration once it is read @@ -1722,120 +1669,5 @@ Application::special_app_flag (const std::string &name) return (env && *env); } -std::vector -Application::sync_tech_macro_locations () -{ - if (m_no_macros) { - return std::vector (); - } - - std::set > tech_macro_paths; - std::map, std::string> tech_names_by_path; - - // Add additional places where the technologies define some macros - for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { - - if (t->base_path ().empty ()) { - continue; - } - - for (size_t c = 0; c < m_macro_categories.size (); ++c) { - - QDir base_dir (tl::to_qstring (t->base_path ())); - if (base_dir.exists ()) { - - QDir macro_dir (base_dir.filePath (tl::to_qstring (m_macro_categories [c].first))); - if (macro_dir.exists ()) { - - std::string mp = tl::to_string (macro_dir.path ()); - std::pair cp (m_macro_categories [c].first, mp); - tech_macro_paths.insert (cp); - std::string &tn = tech_names_by_path [cp]; - if (! tn.empty ()) { - tn += ","; - } - tn += t->name (); - - } - - } - - } - - } - - // delete macro collections which are no longer required or update description - std::vector folders_to_delete; - std::string desc_prefix = tl::to_string (QObject::tr ("Technology")) + " - "; - - lay::MacroCollection *root = &lay::MacroCollection::root (); - - for (lay::MacroCollection::child_iterator m = root->begin_children (); m != root->end_children (); ++m) { - - std::pair cp (m->second->category (), m->second->path ()); - if (m->second->virtual_mode () == lay::MacroCollection::TechFolder && m_tech_macro_paths.find (cp) != m_tech_macro_paths.end ()) { - - if (tech_macro_paths.find (cp) == tech_macro_paths.end ()) { - // no longer used - folders_to_delete.push_back (m->second); - } else { - // used: update description if required - std::string desc = desc_prefix + tech_names_by_path [cp]; - m->second->set_description (desc); - } - - } - - } - - for (std::vector::iterator m = folders_to_delete.begin (); m != folders_to_delete.end (); ++m) { - if (tl::verbosity () >= 20) { - tl::info << "Removing macro folder " << (*m)->path () << ", category '" << (*m)->category () << "' because no longer in use"; - } - root->erase (*m); - } - - // store new paths - m_tech_macro_paths = tech_macro_paths; - - // add new folders - for (lay::MacroCollection::child_iterator m = root->begin_children (); m != root->end_children (); ++m) { - if (m->second->virtual_mode () == lay::MacroCollection::TechFolder) { - std::pair cp (m->second->category (), m->second->path ()); - tech_macro_paths.erase (cp); - } - } - - std::vector new_folders; - - for (std::set >::const_iterator p = tech_macro_paths.begin (); p != tech_macro_paths.end (); ++p) { - - const std::string &tn = tech_names_by_path [*p]; - - // TODO: is it wise to make it writeable? - if (tl::verbosity () >= 20) { - tl::info << "Adding macro folder " << p->second << ", category '" << p->first << "' for technologies " << tn; - } - - // Add the folder. Note: it may happen that a macro folder for the tech specific macros already exists in - // a non-tech context. - // In that case, the add_folder method will return 0. - lay::MacroCollection *mc = lay::MacroCollection::root ().add_folder (desc_prefix + tn, p->second, p->first, false); - if (mc) { - - mc->set_virtual_mode (lay::MacroCollection::TechFolder); - new_folders.push_back (mc); - - // TODO: put somewhere else: - ruby_interpreter ().add_path (p->second); - python_interpreter ().add_path (p->second); - - } - - } - - return new_folders; -} - } diff --git a/src/lay/layApplication.h b/src/lay/layApplication.h index bdc319f7b..3cfdb8dd3 100644 --- a/src/lay/layApplication.h +++ b/src/lay/layApplication.h @@ -202,14 +202,6 @@ public: */ bool special_app_flag (const std::string &name); - /** - * @brief Obtain the list of macro categories - */ - const std::vector< std::pair > ¯o_categories () const - { - return m_macro_categories; - } - /** * @brief Return a reference to the Ruby interpreter */ diff --git a/src/lay/layMacroController.cc b/src/lay/layMacroController.cc index a0c4ae858..4f433910e 100644 --- a/src/lay/layMacroController.cc +++ b/src/lay/layMacroController.cc @@ -44,6 +44,38 @@ MacroController::MacroController () connect (&m_temp_macros, SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ())); } +void +MacroController::initialize (lay::PluginRoot * /*root*/) +{ + // Scan built-in macros + // These macros are always taken, even if there are no macros requested (they are required to + // fully form the API). + lay::MacroCollection::root ().add_folder (tl::to_string (QObject::tr ("Built-In")), ":/built-in-macros", "macros", true); + lay::MacroCollection::root ().add_folder (tl::to_string (QObject::tr ("Built-In")), ":/built-in-pymacros", "pymacros", true); + + // TODO: consider adding "drc" dynamically and allow more dynamic categories + m_macro_categories.push_back (std::pair ("macros", tl::to_string (QObject::tr ("Ruby")))); + m_macro_categories.push_back (std::pair ("pymacros", tl::to_string (QObject::tr ("Python")))); + m_macro_categories.push_back (std::pair ("drc", tl::to_string (QObject::tr ("DRC")))); + + // Scan for macros and set interpreter path + for (std::vector > > >::const_iterator p = m_paths.begin (); p != m_paths.end (); ++p) { + + std::string path = p->first; + std::string description = p->second.first; + std::string cat = p->second.second.first; + bool readonly = p->second.second.second; + + for (size_t c = 0; c < m_macro_categories.size (); ++c) { + if (cat.empty () || cat == m_macro_categories [c].first) { + std::string mp = tl::to_string (QDir (tl::to_qstring (p->first)).filePath (tl::to_qstring (m_macro_categories [c].first))); + lay::MacroCollection::root ().add_folder (description, mp, m_macro_categories [c].first, readonly); + } + } + + } +} + void MacroController::initialized (lay::PluginRoot *root) { @@ -236,6 +268,12 @@ MacroController::refresh () } } +void +MacroController::add_path (const std::string &path, const std::string &description, const std::string &category, bool readonly) +{ + m_paths.push_back (std::make_pair (path, std::make_pair (description, std::make_pair (category, readonly)))); +} + void MacroController::add_temp_macro (lay::Macro *m) { @@ -252,7 +290,7 @@ MacroController::add_macro_items_to_menu (lay::MacroCollection &collection, int if (! tech || c->second->virtual_mode () != lay::MacroCollection::TechFolder) { consider = true; } else { - const std::vector > &mc = lay::Application::instance ()->macro_categories (); + const std::vector > &mc = macro_categories (); for (std::vector >::const_iterator cc = mc.begin (); cc != mc.end () && !consider; ++cc) { consider = (c->second->path () == tl::to_string (QDir (tl::to_qstring (tech->base_path ())).filePath (tl::to_qstring (cc->first)))); } diff --git a/src/lay/layMacroController.h b/src/lay/layMacroController.h index 08f996c2d..c148c6265 100644 --- a/src/lay/layMacroController.h +++ b/src/lay/layMacroController.h @@ -68,6 +68,11 @@ public: */ MacroController (); + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void initialize (lay::PluginRoot *root); + /** * @brief Reimplementation of the PluginDeclaration interface */ @@ -117,6 +122,11 @@ public: */ void refresh (); + /** + * @brief Adds a search path to the macros + */ + void add_path (const std::string &path, const std::string &description, const std::string &category, bool readonly); + /** * @brief Adds a temporary macro * @@ -128,6 +138,14 @@ public: */ void add_temp_macro (lay::Macro *m); + /** + * @brief Obtain the list of macro categories + */ + const std::vector< std::pair > ¯o_categories () const + { + return m_macro_categories; + } + /** * @brief Gets the singleton instance for this object */ @@ -146,6 +164,8 @@ private: std::vector m_macro_actions; std::map m_action_to_macro; lay::MacroCollection m_temp_macros; + std::vector< std::pair > > > m_paths; + std::vector< std::pair > m_macro_categories; void add_macro_items_to_menu (lay::MacroCollection &collection, int &n, std::set &groups, const lay::Technology *tech, std::vector > *key_bindings); void do_update_menu_with_macros (); diff --git a/src/lay/layMacroEditorDialog.cc b/src/lay/layMacroEditorDialog.cc index 1f40e9b52..d1c67e288 100644 --- a/src/lay/layMacroEditorDialog.cc +++ b/src/lay/layMacroEditorDialog.cc @@ -22,6 +22,7 @@ #include "ui_MacroTemplateSelectionDialog.h" +#include "layMacroController.h" #include "layMacroEditorTree.h" #include "layMacroEditorDialog.h" #include "layMacroEditorSetupDialog.h" @@ -263,7 +264,7 @@ MacroEditorDialog::MacroEditorDialog (QWidget * /*parent*/, lay::MacroCollection connect (mp_root, SIGNAL (macro_changed (Macro *)), this, SLOT (macro_changed (Macro *))); connect (mp_root, SIGNAL (macro_deleted (Macro *)), this, SLOT (macro_deleted (Macro *))); - m_categories = lay::Application::instance ()->macro_categories (); + m_categories = lay::MacroController::instance ()->macro_categories (); treeTab->clear (); diff --git a/src/lay/layTechSetupDialog.cc b/src/lay/layTechSetupDialog.cc index aa392fa67..c563f1906 100644 --- a/src/lay/layTechSetupDialog.cc +++ b/src/lay/layTechSetupDialog.cc @@ -31,6 +31,8 @@ #include "layApplication.h" #include "layMacroEditorTree.h" #include "layMacro.h" +#include "layMacroController.h" +#include "layTechnologyController.h" #include "tlAssert.h" #include "tlStream.h" #include "dbStream.h" @@ -479,15 +481,23 @@ TechSetupDialog::TechSetupDialog (QWidget *parent) connect (import_action, SIGNAL (triggered ()), this, SLOT (import_clicked ())); QAction *export_action = new QAction (QObject::tr ("Export Technology"), this); connect (export_action, SIGNAL (triggered ()), this, SLOT (export_clicked ())); + QAction *refresh_action = new QAction (QObject::tr ("Refresh"), this); + connect (refresh_action, SIGNAL (triggered ()), this, SLOT (refresh_clicked ())); + + QAction *separator; tech_tree->addAction (add_action); tech_tree->addAction (delete_action); tech_tree->addAction (rename_action); - QAction *separator = new QAction (this); + separator = new QAction (this); separator->setSeparator (true); tech_tree->addAction (separator); tech_tree->addAction (import_action); tech_tree->addAction (export_action); + separator = new QAction (this); + separator->setSeparator (true); + tech_tree->addAction (separator); + tech_tree->addAction (refresh_action); tech_tree->header ()->hide (); connect (tech_tree, SIGNAL (currentItemChanged (QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT (current_tech_changed (QTreeWidgetItem *, QTreeWidgetItem *))); @@ -527,23 +537,87 @@ TechSetupDialog::clear_components () mp_current_tech_component = 0; } -int -TechSetupDialog::exec () +void +TechSetupDialog::refresh_clicked () +{ +BEGIN_PROTECTED + + commit (); + lay::TechnologyController::instance ()->refresh (); + update (); + +END_PROTECTED +} + +void +TechSetupDialog::update () { m_technologies = *lay::Technologies ().instance (); update_tech_tree (); tech_tree->setCurrentItem (tech_tree->topLevelItem (0)); update_tech (selected_tech ()); +} + +bool +TechSetupDialog::commit () +{ + std::string err_msg; + + // determine the technology files that need to be deleted and delete them + std::set files_before; + for (lay::Technologies::const_iterator t = m_technologies.begin (); t != m_technologies.end (); ++t) { + if (! ! t->tech_file_path ().empty () && ! t->is_persisted ()) { + files_before.insert (t->tech_file_path ()); + } + } + for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { + if (! t->tech_file_path ().empty () && ! t->is_persisted () && files_before.find (t->tech_file_path ()) == files_before.end ()) { + // TODO: issue an error if files could not be removed + QFile (tl::to_qstring (t->tech_file_path ())).remove (); + } + } + + *lay::Technologies ().instance () = m_technologies; + + // save the technologies that need to be saved + // TODO: save only the ones that really need saving + for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { + if (! t->tech_file_path ().empty () && ! t->is_persisted ()) { + try { + t->save (t->tech_file_path ()); + } catch (...) { + if (! err_msg.empty ()) { + err_msg += "\n"; + } + err_msg += t->tech_file_path (); + } + } + } + + if (! err_msg.empty ()) { + QMessageBox::critical (this, QObject::tr ("Error Saving Technology Files"), + QObject::tr ("The following files could not be saved:\n\n") + tl::to_qstring (err_msg), + QMessageBox::Ok); + return false; + } else { + return true; + } +} + +int +TechSetupDialog::exec () +{ + update (); + tc_stack->setMinimumSize (tc_stack->sizeHint ()); int ret = QDialog::exec (); if (ret) { - *lay::Technologies ().instance () = m_technologies; + commit (); } // clean up update_tech (0); - clear_components (); m_technologies = lay::Technologies (); update_tech_tree (); @@ -583,7 +657,19 @@ BEGIN_PROTECTED throw tl::Exception (tl::to_string (QObject::tr ("A technology with this name already exists"))); } + QDir root = QDir (tl::to_qstring (lay::TechnologyController::instance ()->default_root ())); + QDir tech_dir (root.filePath (tn)); + if (tech_dir.exists ()) { + throw tl::Exception (tl::to_string (QObject::tr ("A target folder with path '%1' already exists").arg (tech_dir.path ()))); + } + if (! root.mkdir (tn)) { + throw tl::Exception (tl::to_string (QObject::tr ("Unable to create target folder '%1'").arg (tech_dir.path ()))); + } + lay::Technology *nt = new lay::Technology (*t); + + nt->set_tech_file_path (tl::to_string (tech_dir.absoluteFilePath (tn + QString::fromUtf8 (".lyt")))); + nt->set_persisted (false); nt->set_name (tl::to_string (tn)); nt->set_description (std::string ()); m_technologies.add (nt); @@ -610,8 +696,8 @@ BEGIN_PROTECTED throw tl::Exception (tl::to_string (QObject::tr ("The default technology cannot be deleted"))); } - if (! t->is_persisted ()) { - throw tl::Exception (tl::to_string (QObject::tr ("Auto-imported technologies cannot be deleted"))); + if (t->is_readonly ()) { + throw tl::Exception (tl::to_string (QObject::tr ("This technology is read-only and cannot be deleted"))); } if (QMessageBox::question (this, QObject::tr ("Deleting Technology"), @@ -623,6 +709,7 @@ BEGIN_PROTECTED if (i->name () == t->name ()) { m_technologies.remove (i->name ()); + update_tech_tree (); select_tech (*m_technologies.technology_by_name (std::string ())); @@ -652,8 +739,8 @@ BEGIN_PROTECTED throw tl::Exception (tl::to_string (QObject::tr ("The default technology cannot be renamed"))); } - if (! t->is_persisted ()) { - throw tl::Exception (tl::to_string (QObject::tr ("Auto-imported technologies cannot be renamed"))); + if (t->is_readonly ()) { + throw tl::Exception (tl::to_string (QObject::tr ("This technology is read-only and cannot be renamed"))); } bool ok = false; @@ -670,10 +757,21 @@ BEGIN_PROTECTED throw tl::Exception (tl::to_string (QObject::tr ("A technology with this name already exists"))); } - t->set_name (tl::to_string (tn)); + if (t->name () != tl::to_string (tn)) { - update_tech_tree (); - select_tech (*t); + t->set_name (tl::to_string (tn)); + + if (! t->is_persisted () && ! t->tech_file_path().empty ()) { + TipDialog td (this, + tl::to_string (QObject::tr ("Renaming of a technology will neither rename the technology file or the folder the file is stored in.
The file or folder needs to be renamed manually.")), + "tech-manager-rename-tip"); + td.exec_dialog (); + } + + update_tech_tree (); + select_tech (*t); + + } } @@ -740,7 +838,7 @@ TechSetupDialog::update_tech_tree () for (std::map ::const_iterator t = tech_by_name.begin (); t != tech_by_name.end (); ++t) { QFont f (tech_tree->font ()); - f.setItalic (! t->second->is_persisted ()); + f.setItalic (t->second->is_readonly ()); std::string d; d += t->first; @@ -753,6 +851,9 @@ TechSetupDialog::update_tech_tree () ti->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (d))); ti->setData (0, Qt::UserRole, QVariant (tl::to_qstring (t->first))); ti->setData (0, Qt::FontRole, QVariant (f)); + if (! t->second->tech_file_path ().empty ()) { + ti->setData (0, Qt::ToolTipRole, QVariant (tl::to_qstring (t->second->tech_file_path ()))); + } std::vector tc_names = t->second->component_names (); std::map tc_by_name; @@ -775,12 +876,14 @@ TechSetupDialog::update_tech_tree () tci->setData (0, Qt::UserRole + 1, QVariant (tl::to_qstring ("_save_options"))); tci->setData (0, Qt::FontRole, QVariant (f)); - const std::vector > &mc = lay::Application::instance ()->macro_categories (); - for (std::vector >::const_iterator c = mc.begin (); c != mc.end (); ++c) { - tci = new QTreeWidgetItem (ti); - tci->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (c->second))); - tci->setData (0, Qt::UserRole + 1, QVariant (tl::to_qstring (std::string ("_macros_") + c->first))); - tci->setData (0, Qt::FontRole, QVariant (f)); + if (lay::MacroController::instance ()) { + const std::vector > &mc = lay::MacroController::instance ()->macro_categories (); + for (std::vector >::const_iterator c = mc.begin (); c != mc.end (); ++c) { + tci = new QTreeWidgetItem (ti); + tci->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (c->second))); + tci->setData (0, Qt::UserRole + 1, QVariant (tl::to_qstring (std::string ("_macros_") + c->first))); + tci->setData (0, Qt::FontRole, QVariant (f)); + } } for (std::map ::const_iterator c = tc_by_name.begin (); c != tc_by_name.end (); ++c) { @@ -791,7 +894,6 @@ TechSetupDialog::update_tech_tree () } } - } void @@ -808,28 +910,30 @@ TechSetupDialog::update_tech (lay::Technology *t) if (t) { lay::TechnologyComponentEditor *tce_widget = new TechBaseEditorPage (this); - tce_widget->setEnabled (t->is_persisted ()); + tce_widget->setEnabled (!t->is_readonly ()); tce_widget->set_technology (t, 0); tc_stack->addWidget (tce_widget); m_component_editors.insert (std::make_pair (std::string ("_general"), tce_widget)); - const std::vector > &mc = lay::Application::instance ()->macro_categories (); - for (std::vector >::const_iterator c = mc.begin (); c != mc.end (); ++c) { - tce_widget = new TechMacrosPage (this, c->first, c->second); - tce_widget->setEnabled (t->is_persisted ()); - tce_widget->set_technology (t, 0); - tc_stack->addWidget (tce_widget); - m_component_editors.insert (std::make_pair (std::string ("_macros_") + c->first, tce_widget)); + if (lay::MacroController::instance ()) { + const std::vector > &mc = lay::MacroController::instance ()->macro_categories (); + for (std::vector >::const_iterator c = mc.begin (); c != mc.end (); ++c) { + tce_widget = new TechMacrosPage (this, c->first, c->second); + tce_widget->setEnabled (!t->is_readonly ()); + tce_widget->set_technology (t, 0); + tc_stack->addWidget (tce_widget); + m_component_editors.insert (std::make_pair (std::string ("_macros_") + c->first, tce_widget)); + } } tce_widget = new TechLoadOptionsEditorPage (this); - tce_widget->setEnabled (t->is_persisted ()); + tce_widget->setEnabled (!t->is_readonly ()); tce_widget->set_technology (t, 0); tc_stack->addWidget (tce_widget); m_component_editors.insert (std::make_pair (std::string ("_load_options"), tce_widget)); tce_widget = new TechSaveOptionsEditorPage (this); - tce_widget->setEnabled (t->is_persisted ()); + tce_widget->setEnabled (!t->is_readonly ()); tce_widget->set_technology (t, 0); tc_stack->addWidget (tce_widget); m_component_editors.insert (std::make_pair (std::string ("_save_options"), tce_widget)); @@ -842,7 +946,7 @@ TechSetupDialog::update_tech (lay::Technology *t) tce_widget = tc->create_editor (this); if (tce_widget) { - tce_widget->setEnabled (t->is_persisted ()); + tce_widget->setEnabled (!t->is_readonly ()); tce_widget->set_technology (t, tc); tc_stack->addWidget (tce_widget); m_component_editors.insert (std::make_pair (tc->name (), tce_widget)); @@ -936,7 +1040,7 @@ TechSetupDialog::commit_tech_component () mp_current_editor->commit (); } - if (mp_current_tech && mp_current_tech_component && mp_current_tech->is_persisted ()) { + if (mp_current_tech && mp_current_tech_component && !mp_current_tech->is_readonly ()) { mp_current_tech->set_component (mp_current_tech_component->clone ()); diff --git a/src/lay/layTechSetupDialog.h b/src/lay/layTechSetupDialog.h index 1fddac714..ada25e6a2 100644 --- a/src/lay/layTechSetupDialog.h +++ b/src/lay/layTechSetupDialog.h @@ -137,6 +137,7 @@ protected slots: void rename_clicked (); void import_clicked (); void export_clicked (); + void refresh_clicked (); private: void update_tech_tree (); @@ -148,6 +149,8 @@ private: std::string selected_tech_component_name (); void commit_tech_component (); void clear_components (); + bool commit (); + void update (); lay::Technologies m_technologies; lay::Technology *mp_current_tech; diff --git a/src/lay/layTechnologyController.cc b/src/lay/layTechnologyController.cc index 3f3c56b41..b3d22d2cc 100644 --- a/src/lay/layTechnologyController.cc +++ b/src/lay/layTechnologyController.cc @@ -29,6 +29,9 @@ #include "laybasicConfig.h" #include +#include +#include +#include namespace lay { @@ -43,11 +46,17 @@ std::string tech_string_from_name (const std::string &tn) } TechnologyController::TechnologyController () - : PluginDeclaration (), mp_editor (0), mp_mw (0) + : PluginDeclaration (), mp_editor (0), mp_mw (0), m_no_macros (false) { m_current_technology_updated = false; } +void +TechnologyController::enable_macros (bool enable) +{ + m_no_macros = !enable; +} + TechnologyController * TechnologyController::instance () { @@ -60,9 +69,17 @@ TechnologyController::instance () return 0; } +void +TechnologyController::initialize (lay::PluginRoot * /*root*/) +{ + // .. nothing yet .. +} + void TechnologyController::initialized (lay::PluginRoot *root) { + sync_tech_macro_locations (); + mp_mw = dynamic_cast (root); if (mp_mw) { mp_editor = new lay::TechSetupDialog (mp_mw); @@ -272,7 +289,7 @@ TechnologyController::show_editor () { if (mp_editor && mp_editor->exec ()) { - std::vector nm = lay::Application::instance ()->sync_tech_macro_locations (); + std::vector nm = sync_tech_macro_locations (); bool has_autorun = false; for (std::vector::const_iterator m = nm.begin (); m != nm.end () && ! has_autorun; ++m) { @@ -295,6 +312,204 @@ TechnologyController::show_editor () } } +const std::string & +TechnologyController::default_root () +{ + tl_assert (!m_paths.empty ()); + return m_paths.front (); +} + +void +TechnologyController::refresh () +{ + try { + + lay::Technologies::instance ()->begin_updates (); + lay::Technologies::instance ()->clear (); + + for (std::vector::const_iterator p = m_paths.begin (); p != m_paths.end (); ++p) { + + QDir dir (tl::to_qstring (*p)); + if (! dir.exists ()) { + continue; + } + + QStringList name_filters; + name_filters << QString::fromUtf8 ("*.lyt"); + + QStringList lyt_files; + + QDirIterator di (dir.path (), name_filters, QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); + while (di.hasNext ()) { + lyt_files << di.next (); + } + + lyt_files.sort (); + + for (QStringList::const_iterator lf = lyt_files.begin (); lf != lyt_files.end (); ++lf) { + + try { + + if (tl::verbosity () >= 20) { + tl::info << "Auto-importing technology from " << tl::to_string (*lf); + } + + lay::Technology t; + t.load (tl::to_string (*lf)); + t.set_persisted (false); // don't save that one in the configuration + t.set_readonly (! QFileInfo (dir.filePath (*lf)).isWritable ()); + lay::Technologies::instance ()->add (new lay::Technology (t)); + + } catch (tl::Exception &ex) { + tl::warn << tl::to_string (QObject::tr ("Unable to auto-import technology file ")) << tl::to_string (*lf) << ": " << ex.msg (); + } + + } + + } + + for (std::vector::const_iterator t = m_temp_tech.begin (); t != m_temp_tech.end (); ++t) { + + lay::Technology *tech = new lay::Technology (*t); + tech->set_persisted (false); // don't save that one in the configuration + tech->set_readonly (true); + lay::Technologies::instance ()->add (tech); + + } + + lay::Technologies::instance ()->end_updates (); + + } catch (...) { + lay::Technologies::instance ()->end_updates (); + throw; + } +} + +void +TechnologyController::add_temp_tech (const lay::Technology &t) +{ + m_temp_tech.push_back (t); +} + +void +TechnologyController::add_path (const std::string &p) +{ + m_paths.push_back (p); +} + +std::vector +TechnologyController::sync_tech_macro_locations () +{ + lay::MacroController *mc = lay::MacroController::instance (); + + if (! mc || m_no_macros) { + return std::vector (); + } + + std::set > tech_macro_paths; + std::map, std::string> tech_names_by_path; + + // Add additional places where the technologies define some macros + for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { + + if (t->base_path ().empty ()) { + continue; + } + + for (size_t c = 0; c < mc->macro_categories ().size (); ++c) { + + QDir base_dir (tl::to_qstring (t->base_path ())); + if (base_dir.exists ()) { + + QDir macro_dir (base_dir.filePath (tl::to_qstring (mc->macro_categories () [c].first))); + if (macro_dir.exists ()) { + + std::string mp = tl::to_string (macro_dir.path ()); + std::pair cp (mc->macro_categories () [c].first, mp); + tech_macro_paths.insert (cp); + std::string &tn = tech_names_by_path [cp]; + if (! tn.empty ()) { + tn += ","; + } + tn += t->name (); + + } + + } + + } + + } + + // delete macro collections which are no longer required or update description + std::vector folders_to_delete; + std::string desc_prefix = tl::to_string (QObject::tr ("Technology")) + " - "; + + lay::MacroCollection *root = &lay::MacroCollection::root (); + + for (lay::MacroCollection::child_iterator m = root->begin_children (); m != root->end_children (); ++m) { + + std::pair cp (m->second->category (), m->second->path ()); + if (m->second->virtual_mode () == lay::MacroCollection::TechFolder && m_tech_macro_paths.find (cp) != m_tech_macro_paths.end ()) { + + if (tech_macro_paths.find (cp) == tech_macro_paths.end ()) { + // no longer used + folders_to_delete.push_back (m->second); + } else { + // used: update description if required + std::string desc = desc_prefix + tech_names_by_path [cp]; + m->second->set_description (desc); + } + + } + + } + + for (std::vector::iterator m = folders_to_delete.begin (); m != folders_to_delete.end (); ++m) { + if (tl::verbosity () >= 20) { + tl::info << "Removing macro folder " << (*m)->path () << ", category '" << (*m)->category () << "' because no longer in use"; + } + root->erase (*m); + } + + // store new paths + m_tech_macro_paths = tech_macro_paths; + + // add new folders + for (lay::MacroCollection::child_iterator m = root->begin_children (); m != root->end_children (); ++m) { + if (m->second->virtual_mode () == lay::MacroCollection::TechFolder) { + std::pair cp (m->second->category (), m->second->path ()); + tech_macro_paths.erase (cp); + } + } + + std::vector new_folders; + + for (std::set >::const_iterator p = tech_macro_paths.begin (); p != tech_macro_paths.end (); ++p) { + + const std::string &tn = tech_names_by_path [*p]; + + // TODO: is it wise to make it writeable? + if (tl::verbosity () >= 20) { + tl::info << "Adding macro folder " << p->second << ", category '" << p->first << "' for technologies " << tn; + } + + // Add the folder. Note: it may happen that a macro folder for the tech specific macros already exists in + // a non-tech context. + // In that case, the add_folder method will return 0. + lay::MacroCollection *mc = lay::MacroCollection::root ().add_folder (desc_prefix + tn, p->second, p->first, false); + if (mc) { + + mc->set_virtual_mode (lay::MacroCollection::TechFolder); + new_folders.push_back (mc); + + } + + } + + return new_folders; +} + static tl::RegisteredClass config_decl (new TechnologyController (), 110, "TechnologyController"); } diff --git a/src/lay/layTechnologyController.h b/src/lay/layTechnologyController.h index 461fd354d..c6bf7ab57 100644 --- a/src/lay/layTechnologyController.h +++ b/src/lay/layTechnologyController.h @@ -29,11 +29,14 @@ #include "layTechnology.h" #include "layAbstractMenu.h" +#include + namespace lay { class TechSetupDialog; class MainWindow; +class MacroCollection; /** * @brief A "controller" for the technologies @@ -52,6 +55,7 @@ public: */ TechnologyController (); + void initialize (lay::PluginRoot *root); void initialized (lay::PluginRoot *root); void uninitialize (lay::PluginRoot *root); @@ -69,6 +73,41 @@ public: return m_active_technology; } + /** + * @brief Enables or disables macros + * If macros are enabled, the macro tree contains the macros defined within the technologies. + * This flag needs to be set initially and before the technology tree is updated. + */ + void enable_macros (bool enable); + + /** + * @brief Adds a path as a search path for technologies + * "refresh" needs to be called after search paths have been added. + */ + void add_path (const std::string &path); + + /** + * @brief Adds a temporary technology + * Temporary technologies are additional technologies which are added to the list of technologies + * but are not persisted or editable. + * "refresh" needs to be called after temp technologies have been added. + */ + void add_temp_tech (const lay::Technology &t); + + /** + * @brief Updates the technology collection with the technologies from the search path and teh temp technologies + */ + void refresh (); + + /** + * @brief Gets the default root folder + * The default root is the first one of the paths added with add_path. + */ + const std::string &default_root (); + + /** + * @brief Gets the singleton instance of the controller + */ static TechnologyController *instance (); signals: @@ -92,6 +131,10 @@ private: bool m_current_technology_updated; lay::TechSetupDialog *mp_editor; lay::MainWindow *mp_mw; + bool m_no_macros; + std::vector m_paths; + std::vector m_temp_tech; + std::set > m_tech_macro_paths; void update_after_change (); void technologies_changed (); @@ -101,6 +144,7 @@ private: bool menu_activated (const std::string &symbol) const; void update_current_technology (); void update_menu (); + std::vector sync_tech_macro_locations (); }; } diff --git a/src/laybasic/layTechnology.cc b/src/laybasic/layTechnology.cc index 9750e2995..e3a10a54d 100644 --- a/src/laybasic/layTechnology.cc +++ b/src/laybasic/layTechnology.cc @@ -38,6 +38,8 @@ namespace lay Technologies::Technologies () { m_technologies.push_back (new Technology (std::string (""), "(Default)")); + m_changed = false; + m_in_update = false; } Technologies::Technologies (const Technologies &other) @@ -128,7 +130,7 @@ Technologies::add (Technology *technology) technology->technology_changed_with_sender_event.add (this, &Technologies::technology_changed); } - technologies_changed_event (); + technologies_changed (); } void @@ -137,12 +139,52 @@ Technologies::remove (const std::string &name) for (tl::stable_vector::iterator t = m_technologies.begin (); t != m_technologies.end (); ++t) { if (t->name () == name) { m_technologies.erase (t); - technologies_changed_event (); + technologies_changed (); break; } } } +void +Technologies::clear () +{ + if (! m_technologies.empty ()) { + m_technologies.clear (); + technologies_changed (); + } +} + +void +Technologies::technologies_changed () +{ + if (m_in_update) { + m_changed = true; + } else { + technologies_changed_event (); + } +} + +void +Technologies::begin_updates () +{ + tl_assert (! m_in_update); + m_in_update = true; + m_changed = false; +} + +void +Technologies::end_updates () +{ + if (m_in_update) { + m_in_update = false; + if (m_changed) { + m_changed = false; + technologies_changed (); + } + } +} + + bool Technologies::has_technology (const std::string &name) const { @@ -171,13 +213,13 @@ Technologies::technology_by_name (const std::string &name) // Technology implementation Technology::Technology () - : m_name (), m_description (), m_dbu (0.001), m_persisted (true) + : m_name (), m_description (), m_dbu (0.001), m_persisted (true), m_readonly (false) { init (); } Technology::Technology (const std::string &name, const std::string &description) - : m_name (name), m_description (description), m_dbu (0.001), m_persisted (true) + : m_name (name), m_description (description), m_dbu (0.001), m_persisted (true), m_readonly (false) { init (); } @@ -350,7 +392,9 @@ Technology::load (const std::string &fn) xml_struct.parse (source, *this); // use the tech file's path as the default base path - set_default_base_path (tl::to_string (QFileInfo (tl::to_qstring (fn)).absoluteDir ().path ())); + std::string lyt_file = tl::to_string (QFileInfo (tl::to_qstring (fn)).absoluteDir ().path ()); + set_default_base_path (lyt_file); + set_tech_file_path (lyt_file); } void diff --git a/src/laybasic/layTechnology.h b/src/laybasic/layTechnology.h index 82f4fd028..ac78b593d 100644 --- a/src/laybasic/layTechnology.h +++ b/src/laybasic/layTechnology.h @@ -133,6 +133,22 @@ public: */ void remove (const std::string &name); + /** + * @brief Clears the list of technologies + */ + void clear (); + + /** + * @brief Begins a bulk operation + * This method will disable "technologies_changed" events until (later) end_updates () is called. + */ + void begin_updates (); + + /** + * @brief Ends a bulk operation + */ + void end_updates (); + /** * @brief Checks, if a technology with the given name exists */ @@ -191,8 +207,15 @@ protected: technology_changed_event (t); } + /** + * @brief Sends the technologies_changed event + */ + void technologies_changed (); + private: tl::stable_vector m_technologies; + bool m_changed; + bool m_in_update; }; /** @@ -308,6 +331,23 @@ public: } } + /** + * @brief Gets the path of the tech file if the technology was loaded from a tech file + */ + const std::string &tech_file_path () const + { + return m_lyt_file; + } + + /** + * @brief Sets the path of the tech file + * This method is intended for internal use only. + */ + void set_tech_file_path (const std::string &lyt_file) + { + m_lyt_file = lyt_file; + } + /** * @brief Gets the description */ @@ -497,6 +537,24 @@ public: m_persisted = f; } + /** + * @brief Returns a flag indicating whether the technology is readonly + * + * If the flag is false, the technology can be edited. Otherwise it's locked for editing. + */ + bool is_readonly () const + { + return m_readonly; + } + + /** + * @brief Sets a flag indicating whether the technology is readonly + */ + void set_readonly (bool f) + { + m_readonly = f; + } + /** * @brief An event indicating that the technology has changed */ @@ -514,9 +572,12 @@ private: db::LoadLayoutOptions m_load_layout_options; db::SaveLayoutOptions m_save_layout_options; std::string m_lyp_path; + std::string m_lyt_path; bool m_add_other_layers; std::vector m_components; bool m_persisted; + bool m_readonly; + std::string m_lyt_file; void init (); From e6f696b8dc3e2fc0353d31e9f4a8574def4b87d3 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 15 Apr 2017 17:51:04 +0200 Subject: [PATCH 31/57] WIP: bugfixed tech controller, more refactoring Resolved the dependencies between MainWindow, TechController and MacroController somewhat more. * The macro controller now listens to the tech controller for the active technology * The tech controller has more responsibility now * Some functionality has been taken out of the MainWindow and put into the controller's implementation --- src/edt/edtPlugin.cc | 2 +- src/lay/layApplication.cc | 107 ++++------ src/lay/layMacroController.cc | 20 +- src/lay/layMacroController.h | 12 +- src/lay/layMainWindow.cc | 5 + src/lay/layTechSetupDialog.cc | 58 +----- src/lay/layTechSetupDialog.h | 3 +- src/lay/layTechnologyController.cc | 320 +++++++++++++++++++++-------- src/lay/layTechnologyController.h | 20 +- src/laybasic/layAbstractMenu.cc | 11 +- src/laybasic/layPlugin.cc | 2 +- src/laybasic/layTechnology.cc | 6 + src/laybasic/layTechnology.h | 8 + src/tl/tlClassRegistry.h | 5 + 14 files changed, 335 insertions(+), 244 deletions(-) diff --git a/src/edt/edtPlugin.cc b/src/edt/edtPlugin.cc index 1021c61c5..8e56b8e94 100644 --- a/src/edt/edtPlugin.cc +++ b/src/edt/edtPlugin.cc @@ -239,7 +239,7 @@ public: menu_entries.push_back (lay::MenuEntry ("edt::sel_convert_to_pcell", "convert_to_pcell:edit_mode", "edit_menu.selection_menu.end", tl::to_string (QObject::tr ("Convert To PCell")))); menu_entries.push_back (lay::MenuEntry ("edt::sel_convert_to_cell", "convert_to_cell:edit_mode", "edit_menu.selection_menu.end", tl::to_string (QObject::tr ("Convert To Static Cell")))); - menu_entries.push_back (lay::MenuEntry ("edt::combine_mode", "combine_mode:edit_mode", "@toolbar.end", tl::to_string (QObject::tr ("Combine{Select background combination mode}")))); + menu_entries.push_back (lay::MenuEntry ("edt::combine_mode", "combine_mode:edit_mode", "@toolbar.end_modes", tl::to_string (QObject::tr ("Combine{Select background combination mode}")))); } bool configure (const std::string &name, const std::string &value) diff --git a/src/lay/layApplication.cc b/src/lay/layApplication.cc index 058d52615..bf4b65abf 100644 --- a/src/lay/layApplication.cc +++ b/src/lay/layApplication.cc @@ -888,37 +888,6 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) mp_ruby_interpreter = new rba::RubyInterpreter (); mp_python_interpreter = new pya::PythonInterpreter (); - if (! m_no_gui) { - // Install the signal handlers after the interpreters, so we can be sure we - // installed our handler. - install_signal_handlers (); - } - - if (! m_no_macros) { - - // Add the global ruby modules as the first ones. - m_load_macros.insert (m_load_macros.begin (), global_modules.begin (), global_modules.end ()); - - lay::MacroController *mc = lay::MacroController::instance (); - if (mc) { - for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { - if (p == m_klayout_path.begin ()) { - mc->add_path (*p, tl::to_string (QObject::tr ("Local")), std::string (), false); - } else if (m_klayout_path.size () == 2) { - mc->add_path (*p, tl::to_string (QObject::tr ("Global")), std::string (), true); - } else { - mc->add_path (*p, tl::to_string (QObject::tr ("Global")) + " - " + *p, std::string (), true); - } - } - } - - // Install the custom folders - for (std::vector >::const_iterator p = custom_macro_paths.begin (); p != custom_macro_paths.end (); ++p) { - mc->add_path (p->first, tl::to_string (QObject::tr ("Project")) + " - " + p->first, p->second, false); - } - - } - // Read some configuration values that we need early bool editable_from_config = false; @@ -950,18 +919,39 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) } } catch (...) { } - try { - std::string s; - cfg.config_get (cfg_technologies, s); - lay::Technologies tt; - if (! s.empty ()) { - tt.load_from_xml (s); + } + + if (! m_no_gui) { + // Install the signal handlers after the interpreters, so we can be sure we + // installed our handler. + install_signal_handlers (); + } + + lay::MacroController *mc = lay::MacroController::instance (); + + if (mc && ! m_no_macros) { + + // Add the global ruby modules as the first ones. + m_load_macros.insert (m_load_macros.begin (), global_modules.begin (), global_modules.end ()); + + for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { + if (p == m_klayout_path.begin ()) { + mc->add_path (*p, tl::to_string (QObject::tr ("Local")), std::string (), false); + } else if (m_klayout_path.size () == 2) { + mc->add_path (*p, tl::to_string (QObject::tr ("Global")), std::string (), true); + } else { + mc->add_path (*p, tl::to_string (QObject::tr ("Global")) + " - " + *p, std::string (), true); } - *lay::Technologies::instance () = tt; - } catch (tl::Exception &ex) { - tl::warn << tl::to_string (QObject::tr ("Unable to restore technologies: ")) << ex.msg (); } + // Install the custom folders + for (std::vector >::const_iterator p = custom_macro_paths.begin (); p != custom_macro_paths.end (); ++p) { + mc->add_path (p->first, tl::to_string (QObject::tr ("Project")) + " - " + p->first, p->second, false); + } + + // Actually load the macros + mc->load (); + } lay::TechnologyController *tc = lay::TechnologyController::instance (); @@ -1050,8 +1040,14 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) } // initialize the plugins for the first time + if (tl::verbosity () >= 20) { + tl::info << "Initializing plugins:"; + } for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { lay::PluginDeclaration *pd = const_cast (&*cls); + if (tl::verbosity () >= 20) { + tl::info << " " << cls.current_name () << " [" << cls.current_position () << "]"; + } pd->initialize (mp_plugin_root); } @@ -1126,9 +1122,6 @@ Application::finish () if (mp_plugin_root && m_write_config_file) { - // save the technology setup in the configuration - mp_plugin_root->config_set (cfg_technologies, lay::Technologies::instance ()->to_xml ()); - if (! m_config_file_to_write.empty ()) { if (tl::verbosity () >= 20) { tl::info << tl::to_string (QObject::tr ("Updating configuration file ")) << m_config_file_to_write; @@ -1605,22 +1598,7 @@ void Application::set_config (const std::string &name, const std::string &value) { if (mp_plugin_root) { - - if (name == cfg_technologies) { - - // HACK: cfg_technologies is not a real configuration parameter currently. Hence we emulate that - // behavior. But currently this is the only way to access technology data indirectly from a script. - // Note that this method will set only the technologies accessible through the configuration parameter. - // I.e. the ones not auto-imported. - // TODO: rework this one. This is only half-hearted. - if (! value.empty ()) { - lay::Technologies::instance ()->load_from_xml (value); - } - - } else { - mp_plugin_root->config_set (name, value); - } - + mp_plugin_root->config_set (name, value); } } @@ -1636,16 +1614,7 @@ std::string Application::get_config (const std::string &name) const { if (mp_plugin_root) { - if (name == cfg_technologies) { - // HACK: cfg_technologies is not a real configuration parameter currently. Hence we emulate that - // behavior. But currently this is the only way to access technology data indirectly from a script. - // Note that this method will return only the technologies accessible through the configuration parameter. - // I.e. the ones not auto-imported. - // TODO: rework this one. - return lay::Technologies::instance ()->to_xml (); - } else { - return mp_plugin_root->config_get (name); - } + return mp_plugin_root->config_get (name); } else { return std::string (); } diff --git a/src/lay/layMacroController.cc b/src/lay/layMacroController.cc index 4f433910e..fe220ca71 100644 --- a/src/lay/layMacroController.cc +++ b/src/lay/layMacroController.cc @@ -45,7 +45,7 @@ MacroController::MacroController () } void -MacroController::initialize (lay::PluginRoot * /*root*/) +MacroController::load () { // Scan built-in macros // These macros are always taken, even if there are no macros requested (they are required to @@ -87,8 +87,10 @@ MacroController::initialized (lay::PluginRoot *root) connect (&lay::MacroCollection::root (), SIGNAL (menu_needs_update ()), this, SLOT (update_menu_with_macros ())); connect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ())); - connect (lay::TechnologyController::instance (), SIGNAL (active_technology_changed ()), this, SLOT (update_menu_with_macros ())); - connect (lay::TechnologyController::instance (), SIGNAL (technologies_edited ()), this, SLOT (update_menu_with_macros ())); + if (lay::TechnologyController::instance ()) { + connect (lay::TechnologyController::instance (), SIGNAL (active_technology_changed ()), this, SLOT (update_menu_with_macros ())); + connect (lay::TechnologyController::instance (), SIGNAL (technologies_edited ()), this, SLOT (update_menu_with_macros ())); + } // update the menus with the macro menu bindings as late as possible (now we // can be sure that the menus are created propertly) @@ -100,8 +102,10 @@ MacroController::uninitialize (lay::PluginRoot * /*root*/) { disconnect (&lay::MacroCollection::root (), SIGNAL (menu_needs_update ()), this, SLOT (update_menu_with_macros ())); disconnect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ())); - disconnect (lay::TechnologyController::instance (), SIGNAL (active_technology_changed ()), this, SLOT (update_menu_with_macros ())); - disconnect (lay::TechnologyController::instance (), SIGNAL (technologies_edited ()), this, SLOT (update_menu_with_macros ())); + if (lay::TechnologyController::instance ()) { + disconnect (lay::TechnologyController::instance (), SIGNAL (active_technology_changed ()), this, SLOT (update_menu_with_macros ())); + disconnect (lay::TechnologyController::instance (), SIGNAL (technologies_edited ()), this, SLOT (update_menu_with_macros ())); + } delete mp_macro_editor; mp_macro_editor = 0; @@ -380,11 +384,9 @@ MacroController::do_update_menu_with_macros () return; } - // TODO: implement this by asking the technology manager for the active technology const lay::Technology *tech = 0; - if (mp_mw->current_view () && mp_mw->current_view ()->active_cellview_index () >= 0 && mp_mw->current_view ()->active_cellview_index () <= int (mp_mw->current_view ()->cellviews ())) { - std::string active_tech = mp_mw->current_view ()->active_cellview ()->tech_name (); - tech = lay::Technologies::instance ()->technology_by_name (active_tech); + if (lay::TechnologyController::instance ()) { + tech = lay::TechnologyController::instance ()->active_technology (); } std::vector > key_bindings = unpack_key_binding (mp_mw->config_get (cfg_key_bindings)); diff --git a/src/lay/layMacroController.h b/src/lay/layMacroController.h index c148c6265..90b683358 100644 --- a/src/lay/layMacroController.h +++ b/src/lay/layMacroController.h @@ -68,11 +68,6 @@ public: */ MacroController (); - /** - * @brief Reimplementation of the PluginDeclaration interface - */ - virtual void initialize (lay::PluginRoot *root); - /** * @brief Reimplementation of the PluginDeclaration interface */ @@ -124,9 +119,16 @@ public: /** * @brief Adds a search path to the macros + * After adding the paths, "load" needs to be called to actually load the macros. */ void add_path (const std::string &path, const std::string &description, const std::string &category, bool readonly); + /** + * @brief Loads the macros from the predefined paths + * This method will also establish the macro categories. + */ + void load (); + /** * @brief Adds a temporary macro * diff --git a/src/lay/layMainWindow.cc b/src/lay/layMainWindow.cc index 7f95968f9..d0c98f402 100644 --- a/src/lay/layMainWindow.cc +++ b/src/lay/layMainWindow.cc @@ -997,6 +997,11 @@ MainWindow::init_menu () } + // Add a hook for inserting new items after the modes + Action end_modes; + end_modes.set_separator (true); + mp_menu->insert_item ("@toolbar.end", "end_modes", end_modes); + // make the plugins create their menu items for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { // TODO: get rid of the const_cast hack diff --git a/src/lay/layTechSetupDialog.cc b/src/lay/layTechSetupDialog.cc index c563f1906..3f94132bf 100644 --- a/src/lay/layTechSetupDialog.cc +++ b/src/lay/layTechSetupDialog.cc @@ -542,8 +542,7 @@ TechSetupDialog::refresh_clicked () { BEGIN_PROTECTED - commit (); - lay::TechnologyController::instance ()->refresh (); + lay::TechnologyController::instance ()->refresh (m_technologies); update (); END_PROTECTED @@ -552,68 +551,22 @@ END_PROTECTED void TechSetupDialog::update () { - m_technologies = *lay::Technologies ().instance (); update_tech_tree (); tech_tree->setCurrentItem (tech_tree->topLevelItem (0)); update_tech (selected_tech ()); } -bool -TechSetupDialog::commit () -{ - std::string err_msg; - - // determine the technology files that need to be deleted and delete them - std::set files_before; - for (lay::Technologies::const_iterator t = m_technologies.begin (); t != m_technologies.end (); ++t) { - if (! ! t->tech_file_path ().empty () && ! t->is_persisted ()) { - files_before.insert (t->tech_file_path ()); - } - } - for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { - if (! t->tech_file_path ().empty () && ! t->is_persisted () && files_before.find (t->tech_file_path ()) == files_before.end ()) { - // TODO: issue an error if files could not be removed - QFile (tl::to_qstring (t->tech_file_path ())).remove (); - } - } - - *lay::Technologies ().instance () = m_technologies; - - // save the technologies that need to be saved - // TODO: save only the ones that really need saving - for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { - if (! t->tech_file_path ().empty () && ! t->is_persisted ()) { - try { - t->save (t->tech_file_path ()); - } catch (...) { - if (! err_msg.empty ()) { - err_msg += "\n"; - } - err_msg += t->tech_file_path (); - } - } - } - - if (! err_msg.empty ()) { - QMessageBox::critical (this, QObject::tr ("Error Saving Technology Files"), - QObject::tr ("The following files could not be saved:\n\n") + tl::to_qstring (err_msg), - QMessageBox::Ok); - return false; - } else { - return true; - } -} - int -TechSetupDialog::exec () +TechSetupDialog::exec (lay::Technologies &technologies) { + m_technologies = technologies; update (); tc_stack->setMinimumSize (tc_stack->sizeHint ()); int ret = QDialog::exec (); if (ret) { - commit (); + technologies = m_technologies; } // clean up @@ -662,9 +615,6 @@ BEGIN_PROTECTED if (tech_dir.exists ()) { throw tl::Exception (tl::to_string (QObject::tr ("A target folder with path '%1' already exists").arg (tech_dir.path ()))); } - if (! root.mkdir (tn)) { - throw tl::Exception (tl::to_string (QObject::tr ("Unable to create target folder '%1'").arg (tech_dir.path ()))); - } lay::Technology *nt = new lay::Technology (*t); diff --git a/src/lay/layTechSetupDialog.h b/src/lay/layTechSetupDialog.h index ada25e6a2..0c65840ae 100644 --- a/src/lay/layTechSetupDialog.h +++ b/src/lay/layTechSetupDialog.h @@ -128,7 +128,7 @@ public: TechSetupDialog (QWidget *parent); ~TechSetupDialog (); - int exec (); + int exec (lay::Technologies &technologies); protected slots: void current_tech_changed (QTreeWidgetItem *current, QTreeWidgetItem *previous); @@ -149,7 +149,6 @@ private: std::string selected_tech_component_name (); void commit_tech_component (); void clear_components (); - bool commit (); void update (); lay::Technologies m_technologies; diff --git a/src/lay/layTechnologyController.cc b/src/lay/layTechnologyController.cc index b3d22d2cc..f4b753c9d 100644 --- a/src/lay/layTechnologyController.cc +++ b/src/lay/layTechnologyController.cc @@ -26,6 +26,7 @@ #include "layMainWindow.h" #include "layMacroController.h" #include "layApplication.h" +#include "layConfig.h" #include "laybasicConfig.h" #include @@ -46,9 +47,10 @@ std::string tech_string_from_name (const std::string &tn) } TechnologyController::TechnologyController () - : PluginDeclaration (), mp_editor (0), mp_mw (0), m_no_macros (false) + : PluginDeclaration (), mp_editor (0), mp_mw (0), m_no_macros (false), mp_active_technology (0) { m_current_technology_updated = false; + m_technologies_configured = false; } void @@ -87,7 +89,7 @@ TechnologyController::initialized (lay::PluginRoot *root) } update_menu (); - update_after_change (); + connect_events (); } void @@ -111,54 +113,80 @@ TechnologyController::get_menu_entries (std::vector &menu_entrie } void -TechnologyController::update_after_change () +TechnologyController::connect_events () { - // re-attach all events + // NOTE: the whole concept is a but strange here: the goal is to + // connect to the current view's active_cellview_changed event and + // the active cellview's technology_changed event. We could register + // events tracking the current view and active cellview which detach + // and attach event handlers. This is more tedious than doing this: + // we detach and re-attach the events whenever something changes. + // The event system supports this case, hence we do so. + tl::Object::detach_from_all_events (); - lay::MainWindow *mw = lay::MainWindow::instance (); - lay::MacroController *mc = lay::MacroController::instance (); - - if (mw) { - mw->current_view_changed_event.add (this, &TechnologyController::update_after_change); - } - lay::Technologies::instance ()->technology_changed_event.add (this, &TechnologyController::technology_changed); lay::Technologies::instance ()->technologies_changed_event.add (this, &TechnologyController::technologies_changed); - if (lay::LayoutView::current ()) { - lay::LayoutView::current ()->active_cellview_changed_event.add (this, &TechnologyController::update_after_change); - } + if (mp_mw) { - std::string active_tech; - if (lay::LayoutView::current () && lay::LayoutView::current ()->active_cellview_index () >= 0 && lay::LayoutView::current ()->active_cellview_index () <= int (lay::LayoutView::current ()->cellviews ())) { - lay::LayoutView::current ()->active_cellview ()->technology_changed_event.add (this, &TechnologyController::update_after_change); - active_tech = lay::LayoutView::current ()->active_cellview ()->tech_name (); - } + // NOTE: the "real" call needs to come before the re-connect handler because + // the latter will remove the update call + mp_mw->current_view_changed_event.add (this, &TechnologyController::update_active_technology); + mp_mw->current_view_changed_event.add (this, &TechnologyController::connect_events); - if (m_active_technology != active_tech) { + if (mp_mw->current_view ()) { - m_active_technology = active_tech; + // NOTE: the "real" call needs to come before the re-connect handler because + // the latter will remove the update call + mp_mw->current_view ()->active_cellview_changed_event.add (this, &TechnologyController::update_active_technology); + mp_mw->current_view ()->active_cellview_changed_event.add (this, &TechnologyController::connect_events); + + if (mp_mw->current_view ()->active_cellview_index () >= 0 && mp_mw->current_view ()->active_cellview_index () <= int (mp_mw->current_view ()->cellviews ())) { + mp_mw->current_view ()->active_cellview ()->technology_changed_event.add (this, &TechnologyController::update_active_technology); + } - if (mw) { - mw->tech_message (tech_string_from_name (active_tech)); } - if (mc) { - // TODO: let the macro controller monitor the active technology - // need to do this since macros may be bound to the new technology - mc->update_menu_with_macros (); + } +} + +lay::Technology * +TechnologyController::active_technology () const +{ + return mp_active_technology; +} + +void +TechnologyController::update_active_technology () +{ + lay::Technology *active_tech = 0; + if (mp_mw && mp_mw->current_view () && mp_mw->current_view ()->active_cellview_index () >= 0 && mp_mw->current_view ()->active_cellview_index () <= int (mp_mw->current_view ()->cellviews ())) { + active_tech = lay::Technologies::instance ()->technology_by_name (mp_mw->current_view ()->active_cellview ()->tech_name ()); + } + + if (mp_active_technology != active_tech) { + + mp_active_technology = active_tech; + + if (mp_mw) { + if (active_tech) { + mp_mw->tech_message (tech_string_from_name (active_tech->name ())); + } else { + mp_mw->tech_message (std::string ()); + } } + emit active_technology_changed (); + } #if 0 // Hint with this implementation, the current technology follows the current layout. // Although that's a nice way to display the current technology, it's pretty confusing - lay::PluginRoot *pr = lay::PluginRoot::instance (); + lay::PluginRoot *pr = mp_mw; if (pr) { pr->config_set (cfg_initial_technology, active_tech); - pr->config_finalize (); } #endif } @@ -166,14 +194,18 @@ TechnologyController::update_after_change () void TechnologyController::technologies_changed () { - // delay actual update of menu so we can compress multiple events + // update the configuration to reflect the persisted technologies + lay::PluginRoot *pr = mp_mw; + if (pr) { + pr->config_set (cfg_technologies, lay::Technologies::instance ()->to_xml ()); + } + update_menu (); } void TechnologyController::technology_changed (lay::Technology *) { - // delay actual update of menu so we can compress multiple events update_menu (); } @@ -187,6 +219,23 @@ TechnologyController::configure (const std::string &name, const std::string &val m_current_technology_updated = true; } + } else if (name == cfg_technologies) { + + if (! value.empty ()) { + + // sync the non-persisted technologies with the configuration + lay::Technologies::instance ()->begin_updates (); + try { + lay::Technologies::instance ()->load_from_xml (value); + } catch (...) { + } + lay::Technologies::instance ()->end_updates_no_event (); + + // explicitly handle changes -> no recursion + m_technologies_configured = true; + + } + } return false; } @@ -198,6 +247,12 @@ TechnologyController::config_finalize () update_current_technology (); m_current_technology_updated = false; } + + if (m_technologies_configured) { + update_menu (); + emit technologies_edited (); + m_technologies_configured = false; + } } bool @@ -282,12 +337,82 @@ TechnologyController::update_menu () } } + + update_active_technology (); } void TechnologyController::show_editor () { - if (mp_editor && mp_editor->exec ()) { + lay::Technologies new_tech = *lay::Technologies ().instance (); + + if (mp_editor && mp_editor->exec (new_tech)) { + + std::string err_msg; + + // determine the technology files that need to be deleted and delete them + std::set files_before; + for (lay::Technologies::const_iterator t = new_tech.begin (); t != new_tech.end (); ++t) { + if (! ! t->tech_file_path ().empty () && ! t->is_persisted ()) { + files_before.insert (t->tech_file_path ()); + } + } + for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { + if (! t->tech_file_path ().empty () && ! t->is_persisted () && files_before.find (t->tech_file_path ()) == files_before.end ()) { + // TODO: issue an error if files could not be removed + QFile (tl::to_qstring (t->tech_file_path ())).remove (); + } + } + + lay::Technologies ().instance ()->begin_updates (); + *lay::Technologies ().instance () = new_tech; + lay::Technologies ().instance ()->end_updates_no_event (); + + // save the technologies that need to be saved + // TODO: save only the ones that really need saving + for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { + + if (! t->tech_file_path ().empty () && ! t->is_persisted ()) { + + // create the tech folder if required + + try { + + QDir dir = QFileInfo (tl::to_qstring (t->tech_file_path ())).absoluteDir (); + QStringList to_create; + while (! dir.isRoot() && ! dir.exists ()) { + to_create << dir.dirName (); + dir = QFileInfo (dir.path ()).absoluteDir (); + } + + while (! to_create.empty ()) { + if (! dir.mkdir (to_create.back ())) { + throw tl::CancelException (); + } + if (! dir.cd (to_create.back ())) { + throw tl::CancelException (); + } + to_create.pop_back (); + } + + t->save (t->tech_file_path ()); + + } catch (...) { + if (! err_msg.empty ()) { + err_msg += "\n"; + } + err_msg += t->tech_file_path (); + } + + } + + } + + if (! err_msg.empty ()) { + QMessageBox::critical (mp_mw, QObject::tr ("Error Saving Technology Files"), + QObject::tr ("The following files could not be saved:\n\n") + tl::to_qstring (err_msg), + QMessageBox::Ok); + } std::vector nm = sync_tech_macro_locations (); @@ -302,12 +427,8 @@ TechnologyController::show_editor () } } - // because the macro-tech association might have changed, do this: - // TODO: let the macro controller monitor the technologies. - lay::MacroController *mc = lay::MacroController::instance (); - if (mc) { - mc->update_menu_with_macros (); - } + update_menu (); + emit technologies_edited (); } } @@ -325,66 +446,83 @@ TechnologyController::refresh () try { lay::Technologies::instance ()->begin_updates (); - lay::Technologies::instance ()->clear (); + refresh (*lay::Technologies::instance ()); + lay::Technologies::instance ()->end_updates_no_event (); - for (std::vector::const_iterator p = m_paths.begin (); p != m_paths.end (); ++p) { - - QDir dir (tl::to_qstring (*p)); - if (! dir.exists ()) { - continue; - } - - QStringList name_filters; - name_filters << QString::fromUtf8 ("*.lyt"); - - QStringList lyt_files; - - QDirIterator di (dir.path (), name_filters, QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); - while (di.hasNext ()) { - lyt_files << di.next (); - } - - lyt_files.sort (); - - for (QStringList::const_iterator lf = lyt_files.begin (); lf != lyt_files.end (); ++lf) { - - try { - - if (tl::verbosity () >= 20) { - tl::info << "Auto-importing technology from " << tl::to_string (*lf); - } - - lay::Technology t; - t.load (tl::to_string (*lf)); - t.set_persisted (false); // don't save that one in the configuration - t.set_readonly (! QFileInfo (dir.filePath (*lf)).isWritable ()); - lay::Technologies::instance ()->add (new lay::Technology (t)); - - } catch (tl::Exception &ex) { - tl::warn << tl::to_string (QObject::tr ("Unable to auto-import technology file ")) << tl::to_string (*lf) << ": " << ex.msg (); - } - - } - - } - - for (std::vector::const_iterator t = m_temp_tech.begin (); t != m_temp_tech.end (); ++t) { - - lay::Technology *tech = new lay::Technology (*t); - tech->set_persisted (false); // don't save that one in the configuration - tech->set_readonly (true); - lay::Technologies::instance ()->add (tech); - - } - - lay::Technologies::instance ()->end_updates (); + update_menu (); + emit technologies_edited (); } catch (...) { - lay::Technologies::instance ()->end_updates (); + lay::Technologies::instance ()->end_updates_no_event (); throw; } } +void +TechnologyController::refresh (lay::Technologies &technologies) +{ + lay::Technologies current = technologies; + + // start with all persisted technologies (at least "default") + technologies.clear (); + for (lay::Technologies::const_iterator t = current.begin (); t != current.end (); ++t) { + if (t->is_persisted ()) { + technologies.add (new lay::Technology (*t)); + } + } + + for (std::vector::const_iterator p = m_paths.begin (); p != m_paths.end (); ++p) { + + QDir dir (tl::to_qstring (*p)); + if (! dir.exists ()) { + continue; + } + + QStringList name_filters; + name_filters << QString::fromUtf8 ("*.lyt"); + + QStringList lyt_files; + + QDirIterator di (dir.path (), name_filters, QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); + while (di.hasNext ()) { + lyt_files << di.next (); + } + + lyt_files.sort (); + + for (QStringList::const_iterator lf = lyt_files.begin (); lf != lyt_files.end (); ++lf) { + + try { + + if (tl::verbosity () >= 20) { + tl::info << "Auto-importing technology from " << tl::to_string (*lf); + } + + lay::Technology t; + t.load (tl::to_string (*lf)); + t.set_persisted (false); // don't save that one in the configuration + t.set_readonly (! QFileInfo (dir.filePath (*lf)).isWritable ()); + technologies.add (new lay::Technology (t)); + + } catch (tl::Exception &ex) { + tl::warn << tl::to_string (QObject::tr ("Unable to auto-import technology file ")) << tl::to_string (*lf) << ": " << ex.msg (); + } + + } + + } + + for (std::vector::const_iterator t = m_temp_tech.begin (); t != m_temp_tech.end (); ++t) { + + lay::Technology *tech = new lay::Technology (*t); + tech->set_persisted (false); // don't save that one in the configuration + tech->set_tech_file_path (std::string ()); // don't save to a file either + tech->set_readonly (true); // don't edit + technologies.add (tech); + + } +} + void TechnologyController::add_temp_tech (const lay::Technology &t) { diff --git a/src/lay/layTechnologyController.h b/src/lay/layTechnologyController.h index c6bf7ab57..ea5d7f00f 100644 --- a/src/lay/layTechnologyController.h +++ b/src/lay/layTechnologyController.h @@ -65,13 +65,10 @@ public: void show_editor (); /** - * @brief Gets the name of the active technology + * @brief Gets the active technology object or 0 if none is active * The active technology is the one the current cellview uses */ - const std::string &active_technology () const - { - return m_active_technology; - } + lay::Technology *active_technology () const; /** * @brief Enables or disables macros @@ -95,7 +92,12 @@ public: void add_temp_tech (const lay::Technology &t); /** - * @brief Updates the technology collection with the technologies from the search path and teh temp technologies + * @brief Updates the given technology collection with the technologies from the search path and teh temp technologies + */ + void refresh (lay::Technologies &technologies); + + /** + * @brief Refreshes the global list of technologies */ void refresh (); @@ -127,16 +129,18 @@ signals: private: tl::stable_vector m_tech_actions; std::string m_current_technology; - std::string m_active_technology; bool m_current_technology_updated; + bool m_technologies_configured; lay::TechSetupDialog *mp_editor; lay::MainWindow *mp_mw; bool m_no_macros; std::vector m_paths; std::vector m_temp_tech; std::set > m_tech_macro_paths; + lay::Technology *mp_active_technology; - void update_after_change (); + void update_active_technology (); + void connect_events (); void technologies_changed (); void technology_changed (lay::Technology *); bool configure (const std::string &name, const std::string &value); diff --git a/src/laybasic/layAbstractMenu.cc b/src/laybasic/layAbstractMenu.cc index fac446f05..c0516fa87 100644 --- a/src/laybasic/layAbstractMenu.cc +++ b/src/laybasic/layAbstractMenu.cc @@ -1123,14 +1123,17 @@ AbstractMenu::find_item (const std::string &path) return std::make_pair ((AbstractMenuItem *) 0, m_root.children.end ()); } - } else if (extr.test ("begin")) { - return std::make_pair (parent, parent->children.begin ()); - } else if (extr.test ("end")) { - return std::make_pair (parent, parent->children.end ()); } else { std::string n; extr.read (n, ".+"); + + if (n == "begin") { + return std::make_pair (parent, parent->children.begin ()); + } else if (n == "end") { + return std::make_pair (parent, parent->children.end ()); + } + std::string name (parent->name ()); if (! name.empty ()) { name += "."; diff --git a/src/laybasic/layPlugin.cc b/src/laybasic/layPlugin.cc index ea2858034..9596d5873 100644 --- a/src/laybasic/layPlugin.cc +++ b/src/laybasic/layPlugin.cc @@ -197,7 +197,7 @@ PluginDeclaration::init_menu () m_mouse_mode_action.qaction ()->setData (QVariant (id ())); menu.insert_item ("edit_menu.mode_menu.end", name, m_mouse_mode_action); - menu.insert_item ("@toolbar.end", name, m_mouse_mode_action); + menu.insert_item ("@toolbar.end_modes", name, m_mouse_mode_action); gtf::action_connect (m_mouse_mode_action.qaction (), SIGNAL (triggered ()), this, SLOT (mode_triggered ())); diff --git a/src/laybasic/layTechnology.cc b/src/laybasic/layTechnology.cc index e3a10a54d..5cc9434e7 100644 --- a/src/laybasic/layTechnology.cc +++ b/src/laybasic/layTechnology.cc @@ -184,6 +184,12 @@ Technologies::end_updates () } } +void +Technologies::end_updates_no_event () +{ + m_in_update = false; + m_changed = false; +} bool Technologies::has_technology (const std::string &name) const diff --git a/src/laybasic/layTechnology.h b/src/laybasic/layTechnology.h index ac78b593d..50fc61c1e 100644 --- a/src/laybasic/layTechnology.h +++ b/src/laybasic/layTechnology.h @@ -149,6 +149,14 @@ public: */ void end_updates (); + /** + * @brief Ends a bulk operation + * This version does not send an technologies_changed event but just cancels the bulk + * operation. begin_updates/end_updates_no_event is essentially equivalent to blocking + * signals. + */ + void end_updates_no_event (); + /** * @brief Checks, if a technology with the given name exists */ diff --git a/src/tl/tlClassRegistry.h b/src/tl/tlClassRegistry.h index b03eebfe0..059d76200 100644 --- a/src/tl/tlClassRegistry.h +++ b/src/tl/tlClassRegistry.h @@ -163,6 +163,11 @@ public: return mp_pos->m_name; } + int current_position () const + { + return mp_pos->m_position; + } + X &operator* () const { return *(mp_pos->mp_object); From 430d14001663ae3b5f446f02358620372ec4ef39 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 15 Apr 2017 18:50:51 +0200 Subject: [PATCH 32/57] WIP: some bug fixes. --- src/lay/layTechnologyController.cc | 30 ++++++++++++++++++++---------- src/laybasic/layAbstractMenu.cc | 1 - src/laybasic/layTechnology.cc | 8 ++++++-- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/lay/layTechnologyController.cc b/src/lay/layTechnologyController.cc index f4b753c9d..6f0b26c4f 100644 --- a/src/lay/layTechnologyController.cc +++ b/src/lay/layTechnologyController.cc @@ -284,9 +284,14 @@ TechnologyController::update_current_technology () action.set_title (title); } - size_t it = 0; - for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end () && it < m_tech_actions.size (); ++t, ++it) { - m_tech_actions[it].set_checked (t->name () == m_current_technology); + std::map tech_by_name; + for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { + tech_by_name.insert (std::make_pair (t->name (), t.operator-> ())); + } + + int it = 0; + for (std::map::const_iterator t = tech_by_name.begin (); t != tech_by_name.end (); ++t, ++it) { + m_tech_actions[it].set_checked (t->second->name () == m_current_technology); } } @@ -323,17 +328,22 @@ TechnologyController::update_menu () m_tech_actions.clear (); + std::map tech_by_name; + for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { + tech_by_name.insert (std::make_pair (t->name (), t.operator-> ())); + } + int it = 0; - for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t, ++it) { + for (std::map::const_iterator t = tech_by_name.begin (); t != tech_by_name.end (); ++t, ++it) { - std::string title = tech_string_from_name (t->name ()); + std::string title = tech_string_from_name (t->first); - m_tech_actions.push_back (pr->create_config_action ("", cfg_initial_technology, t->name ())); + m_tech_actions.push_back (pr->create_config_action ("", cfg_initial_technology, t->first)); m_tech_actions.back ().set_title (title); // setting the title here avoids interpretation of '(...)' etc. m_tech_actions.back ().set_checkable (true); - m_tech_actions.back ().set_checked (t->name () == m_current_technology); - for (std::vector::const_iterator t = tech_group.begin (); t != tech_group.end (); ++t) { - pr->menu ()->insert_item (*t + ".end", "technology_" + tl::to_string (it), m_tech_actions.back ()); + m_tech_actions.back ().set_checked (t->first == m_current_technology); + for (std::vector::const_iterator tg = tech_group.begin (); tg != tech_group.end (); ++tg) { + pr->menu ()->insert_item (*tg + ".end", "technology_" + tl::to_string (it), m_tech_actions.back ()); } } @@ -353,7 +363,7 @@ TechnologyController::show_editor () // determine the technology files that need to be deleted and delete them std::set files_before; for (lay::Technologies::const_iterator t = new_tech.begin (); t != new_tech.end (); ++t) { - if (! ! t->tech_file_path ().empty () && ! t->is_persisted ()) { + if (! t->tech_file_path ().empty () && ! t->is_persisted ()) { files_before.insert (t->tech_file_path ()); } } diff --git a/src/laybasic/layAbstractMenu.cc b/src/laybasic/layAbstractMenu.cc index c0516fa87..3c62f6bb9 100644 --- a/src/laybasic/layAbstractMenu.cc +++ b/src/laybasic/layAbstractMenu.cc @@ -635,7 +635,6 @@ ConfigureAction::triggered () } m_pr->config_set (m_cname, m_cvalue); - m_pr->config_end (); } void diff --git a/src/laybasic/layTechnology.cc b/src/laybasic/layTechnology.cc index 5cc9434e7..09e596bdd 100644 --- a/src/laybasic/layTechnology.cc +++ b/src/laybasic/layTechnology.cc @@ -256,7 +256,8 @@ Technology::Technology (const Technology &d) m_explicit_base_path (d.m_explicit_base_path), m_default_base_path (d.m_default_base_path), m_load_layout_options (d.m_load_layout_options), m_save_layout_options (d.m_save_layout_options), - m_lyp_path (d.m_lyp_path), m_add_other_layers (d.m_add_other_layers), m_persisted (d.m_persisted) + m_lyp_path (d.m_lyp_path), m_add_other_layers (d.m_add_other_layers), m_persisted (d.m_persisted), + m_readonly (d.m_readonly), m_lyt_file (d.m_lyt_file) { for (std::vector ::const_iterator c = d.m_components.begin (); c != d.m_components.end (); ++c) { m_components.push_back ((*c)->clone ()); @@ -277,6 +278,8 @@ Technology &Technology::operator= (const Technology &d) m_lyp_path = d.m_lyp_path; m_add_other_layers = d.m_add_other_layers; m_persisted = d.m_persisted; + m_readonly = d.m_readonly; + m_lyt_file = d.m_lyt_file; for (std::vector ::const_iterator c = m_components.begin (); c != m_components.end (); ++c) { delete *c; @@ -400,7 +403,8 @@ Technology::load (const std::string &fn) // use the tech file's path as the default base path std::string lyt_file = tl::to_string (QFileInfo (tl::to_qstring (fn)).absoluteDir ().path ()); set_default_base_path (lyt_file); - set_tech_file_path (lyt_file); + + set_tech_file_path (fn); } void From 6b6b45714fc03f3b1c62388a5e184ecfc6b99fe5 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 15 Apr 2017 23:07:40 +0200 Subject: [PATCH 33/57] WIP: controllers (tech + macro) The tech controller now is indenpendent from the macro controller. The macro controller is responsible for pulling it's data from the tech controller. It's initialized after the tech controller so it can do so. The macro controller is also responsible to building the menus. The tech controller issues events that the macro controller can listen to. --- src/lay/layApplication.cc | 60 ++++----- src/lay/layMacro.cc | 2 +- src/lay/layMacroController.cc | 146 +++++++++++++++++++++- src/lay/layMacroController.h | 16 +++ src/lay/layMacroEditorDialog.cc | 51 +++++--- src/lay/layMacroEditorDialog.h | 1 + src/lay/layTechSetupDialog.cc | 2 +- src/lay/layTechnologyController.cc | 188 +++++------------------------ src/lay/layTechnologyController.h | 23 ++-- 9 files changed, 260 insertions(+), 229 deletions(-) diff --git a/src/lay/layApplication.cc b/src/lay/layApplication.cc index bf4b65abf..d53df08fb 100644 --- a/src/lay/layApplication.cc +++ b/src/lay/layApplication.cc @@ -929,37 +929,10 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) lay::MacroController *mc = lay::MacroController::instance (); - if (mc && ! m_no_macros) { - - // Add the global ruby modules as the first ones. - m_load_macros.insert (m_load_macros.begin (), global_modules.begin (), global_modules.end ()); - - for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { - if (p == m_klayout_path.begin ()) { - mc->add_path (*p, tl::to_string (QObject::tr ("Local")), std::string (), false); - } else if (m_klayout_path.size () == 2) { - mc->add_path (*p, tl::to_string (QObject::tr ("Global")), std::string (), true); - } else { - mc->add_path (*p, tl::to_string (QObject::tr ("Global")) + " - " + *p, std::string (), true); - } - } - - // Install the custom folders - for (std::vector >::const_iterator p = custom_macro_paths.begin (); p != custom_macro_paths.end (); ++p) { - mc->add_path (p->first, tl::to_string (QObject::tr ("Project")) + " - " + p->first, p->second, false); - } - - // Actually load the macros - mc->load (); - - } - lay::TechnologyController *tc = lay::TechnologyController::instance (); if (tc) { - tc->enable_macros (! m_no_macros); - // auto-import technologies for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { std::string tp = tl::to_string (QDir (tl::to_qstring (*p)).filePath (QString::fromUtf8 ("tech"))); @@ -987,7 +960,38 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) } - tc->refresh (); + tc->load (); + + } + + if (mc) { + + mc->enable_implicit_macros (! m_no_macros); + + if (! m_no_macros) { + + // Add the global ruby modules as the first ones. + m_load_macros.insert (m_load_macros.begin (), global_modules.begin (), global_modules.end ()); + + for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { + if (p == m_klayout_path.begin ()) { + mc->add_path (*p, tl::to_string (QObject::tr ("Local")), std::string (), false); + } else if (m_klayout_path.size () == 2) { + mc->add_path (*p, tl::to_string (QObject::tr ("Global")), std::string (), true); + } else { + mc->add_path (*p, tl::to_string (QObject::tr ("Global")) + " - " + *p, std::string (), true); + } + } + + // Install the custom folders + for (std::vector >::const_iterator p = custom_macro_paths.begin (); p != custom_macro_paths.end (); ++p) { + mc->add_path (p->first, tl::to_string (QObject::tr ("Project")) + " - " + p->first, p->second, false); + } + + } + + // Actually load the macros + mc->load (); } diff --git a/src/lay/layMacro.cc b/src/lay/layMacro.cc index 848fea380..2a062b540 100644 --- a/src/lay/layMacro.cc +++ b/src/lay/layMacro.cc @@ -1083,7 +1083,7 @@ void MacroCollection::on_macro_changed (Macro *macro) } } -void MacroCollection::collect_used_nodes(std::set ¯os, std::set ¯o_collections) +void MacroCollection::collect_used_nodes (std::set ¯os, std::set ¯o_collections) { for (MacroCollection::child_iterator c = begin_children (); c != end_children (); ++c) { macro_collections.insert (c->second); diff --git a/src/lay/layMacroController.cc b/src/lay/layMacroController.cc index fe220ca71..ddc68d15e 100644 --- a/src/lay/layMacroController.cc +++ b/src/lay/layMacroController.cc @@ -37,7 +37,7 @@ namespace lay { MacroController::MacroController () - : mp_macro_editor (0), mp_mw (0), + : mp_macro_editor (0), mp_mw (0), m_no_implicit_macros (false), dm_do_update_menu_with_macros (this, &MacroController::do_update_menu_with_macros) { connect (&m_temp_macros, SIGNAL (menu_needs_update ()), this, SLOT (update_menu_with_macros ())); @@ -74,6 +74,8 @@ MacroController::load () } } + + sync_implicit_macros (false); } void @@ -89,7 +91,7 @@ MacroController::initialized (lay::PluginRoot *root) connect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ())); if (lay::TechnologyController::instance ()) { connect (lay::TechnologyController::instance (), SIGNAL (active_technology_changed ()), this, SLOT (update_menu_with_macros ())); - connect (lay::TechnologyController::instance (), SIGNAL (technologies_edited ()), this, SLOT (update_menu_with_macros ())); + connect (lay::TechnologyController::instance (), SIGNAL (technologies_edited ()), this, SLOT (technologies_edited ())); } // update the menus with the macro menu bindings as late as possible (now we @@ -104,7 +106,7 @@ MacroController::uninitialize (lay::PluginRoot * /*root*/) disconnect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ())); if (lay::TechnologyController::instance ()) { disconnect (lay::TechnologyController::instance (), SIGNAL (active_technology_changed ()), this, SLOT (update_menu_with_macros ())); - disconnect (lay::TechnologyController::instance (), SIGNAL (technologies_edited ()), this, SLOT (update_menu_with_macros ())); + disconnect (lay::TechnologyController::instance (), SIGNAL (technologies_edited ()), this, SLOT (technologies_edited ())); } delete mp_macro_editor; @@ -264,6 +266,136 @@ MacroController::show_editor (const std::string &cat, bool force_add) } } +void +MacroController::enable_implicit_macros (bool enable) +{ + m_no_implicit_macros = !enable; +} + +void +MacroController::sync_implicit_macros (bool check_autorun) +{ + if (m_no_implicit_macros) { + return; + } + + std::set > tech_macro_paths; + std::map, std::string> tech_names_by_path; + + // Add additional places where the technologies define some macros + for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { + + if (t->base_path ().empty ()) { + continue; + } + + for (size_t c = 0; c < macro_categories ().size (); ++c) { + + QDir base_dir (tl::to_qstring (t->base_path ())); + if (base_dir.exists ()) { + + QDir macro_dir (base_dir.filePath (tl::to_qstring (macro_categories () [c].first))); + if (macro_dir.exists ()) { + + std::string mp = tl::to_string (macro_dir.path ()); + std::pair cp (macro_categories () [c].first, mp); + tech_macro_paths.insert (cp); + std::string &tn = tech_names_by_path [cp]; + if (! tn.empty ()) { + tn += ","; + } + tn += t->name (); + + } + + } + + } + + } + + // delete macro collections which are no longer required or update description + std::vector folders_to_delete; + std::string desc_prefix = tl::to_string (QObject::tr ("Technology")) + " - "; + + lay::MacroCollection *root = &lay::MacroCollection::root (); + + for (lay::MacroCollection::child_iterator m = root->begin_children (); m != root->end_children (); ++m) { + + std::pair cp (m->second->category (), m->second->path ()); + if (m->second->virtual_mode () == lay::MacroCollection::TechFolder && m_tech_macro_paths.find (cp) != m_tech_macro_paths.end ()) { + + if (tech_macro_paths.find (cp) == tech_macro_paths.end ()) { + // no longer used + folders_to_delete.push_back (m->second); + } else { + // used: update description if required + std::string desc = desc_prefix + tech_names_by_path [cp]; + m->second->set_description (desc); + } + + } + + } + + for (std::vector::iterator m = folders_to_delete.begin (); m != folders_to_delete.end (); ++m) { + if (tl::verbosity () >= 20) { + tl::info << "Removing macro folder " << (*m)->path () << ", category '" << (*m)->category () << "' because no longer in use"; + } + root->erase (*m); + } + + // store new paths + m_tech_macro_paths = tech_macro_paths; + + // add new folders + for (lay::MacroCollection::child_iterator m = root->begin_children (); m != root->end_children (); ++m) { + if (m->second->virtual_mode () == lay::MacroCollection::TechFolder) { + std::pair cp (m->second->category (), m->second->path ()); + tech_macro_paths.erase (cp); + } + } + + std::vector new_folders; + + for (std::set >::const_iterator p = tech_macro_paths.begin (); p != tech_macro_paths.end (); ++p) { + + const std::string &tn = tech_names_by_path [*p]; + + // TODO: is it wise to make it writeable? + if (tl::verbosity () >= 20) { + tl::info << "Adding macro folder " << p->second << ", category '" << p->first << "' for technologies " << tn; + } + + // Add the folder. Note: it may happen that a macro folder for the tech specific macros already exists in + // a non-tech context. + // In that case, the add_folder method will return 0. + lay::MacroCollection *mc = lay::MacroCollection::root ().add_folder (desc_prefix + tn, p->second, p->first, false); + if (mc) { + + mc->set_virtual_mode (lay::MacroCollection::TechFolder); + new_folders.push_back (mc); + + } + + } + + if (check_autorun) { + + bool has_autorun = false; + for (std::vector::const_iterator m = new_folders.begin (); m != new_folders.end () && ! has_autorun; ++m) { + has_autorun = (*m)->has_autorun (); + } + + if (has_autorun && QMessageBox::question (mp_mw, QObject::tr ("Run Macros"), QObject::tr ("Some macros associated with new items are configured to run automatically.\n\nChoose 'Yes' to run these macros now. Choose 'No' to not run them."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + for (std::vector::const_iterator m = new_folders.begin (); m != new_folders.end (); ++m) { + (*m)->autorun (); + } + } + + } +} + void MacroController::refresh () { @@ -369,6 +501,14 @@ MacroController::add_macro_items_to_menu (lay::MacroCollection &collection, int } } +void +MacroController::technologies_edited () +{ + sync_implicit_macros (true); + refresh (); + update_menu_with_macros (); +} + void MacroController::update_menu_with_macros () { diff --git a/src/lay/layMacroController.h b/src/lay/layMacroController.h index 90b683358..5d6e53697 100644 --- a/src/lay/layMacroController.h +++ b/src/lay/layMacroController.h @@ -103,6 +103,14 @@ public: */ virtual void drop_url (const std::string &path_or_url); + /** + * @brief Enables or disables implicit macros + * If implicit macros are enabled, the macro tree contains the macros defined within the technologies + * and other implicit sources. + * This flag needs to be set initially and before the technology tree is updated. + */ + void enable_implicit_macros (bool enable); + /** * @brief Shows the macro editor * @@ -159,16 +167,24 @@ public slots: */ void update_menu_with_macros (); + /** + * @brief Called when the technologies got changed + */ + void technologies_edited (); + private: lay::MacroEditorDialog *mp_macro_editor; lay::MainWindow *mp_mw; + bool m_no_implicit_macros; tl::DeferredMethod dm_do_update_menu_with_macros; std::vector m_macro_actions; std::map m_action_to_macro; lay::MacroCollection m_temp_macros; std::vector< std::pair > > > m_paths; std::vector< std::pair > m_macro_categories; + std::set > m_tech_macro_paths; + void sync_implicit_macros (bool check_autorun); void add_macro_items_to_menu (lay::MacroCollection &collection, int &n, std::set &groups, const lay::Technology *tech, std::vector > *key_bindings); void do_update_menu_with_macros (); }; diff --git a/src/lay/layMacroEditorDialog.cc b/src/lay/layMacroEditorDialog.cc index d1c67e288..58edc972f 100644 --- a/src/lay/layMacroEditorDialog.cc +++ b/src/lay/layMacroEditorDialog.cc @@ -263,6 +263,7 @@ MacroEditorDialog::MacroEditorDialog (QWidget * /*parent*/, lay::MacroCollection connect (mp_root, SIGNAL (macro_changed (Macro *)), this, SLOT (macro_changed (Macro *))); connect (mp_root, SIGNAL (macro_deleted (Macro *)), this, SLOT (macro_deleted (Macro *))); + connect (mp_root, SIGNAL (macro_collection_deleted (MacroCollection *)), this, SLOT (macro_collection_deleted (MacroCollection *))); m_categories = lay::MacroController::instance ()->macro_categories (); @@ -1534,6 +1535,35 @@ MacroEditorDialog::commit () } } +void +MacroEditorDialog::macro_collection_deleted (lay::MacroCollection *collection) +{ + // close the tab pages related to the collection we want to delete + std::set used_macros; + std::set used_collections; + collection->collect_used_nodes (used_macros, used_collections); + + for (std::set ::iterator mc = used_macros.begin (); mc != used_macros.end (); ++mc) { + + if (mp_run_macro == *mc) { + mp_run_macro = 0; + } + + std::map ::iterator p = m_tab_widgets.find (*mc); + if (p != m_tab_widgets.end ()) { + p->second->connect_macro (0); + tabWidget->blockSignals (true); // blockSignals prevents a reentrant call into set_current of the tree + tabWidget->removeTab (tabWidget->indexOf (p->second)); + tabWidget->blockSignals (false); + delete p->second; + m_tab_widgets.erase (p); + } + + } + + refresh_file_watcher (); +} + void MacroEditorDialog::macro_deleted (lay::Macro *macro) { @@ -2594,30 +2624,13 @@ BEGIN_PROTECTED throw tl::Exception (tl::to_string (QObject::tr ("Unable to remove that location"))); } - // close the tab pages related to the collection we want to delete - std::set used_macros; - std::set used_collections; - collection->collect_used_nodes (used_macros, used_collections); - - for (std::set ::iterator mc = used_macros.begin (); mc != used_macros.end (); ++mc) { - - std::map ::iterator p = m_tab_widgets.find (*mc); - if (p != m_tab_widgets.end ()) { - p->second->connect_macro (0); - delete p->second; - m_tab_widgets.erase (p); - } - - } - - // actually remove the collection + // actually remove the collection (update is done through the + // macro_collection_deleted signal handler). mp_root->erase (collection); // save the new paths set_custom_paths (paths); - refresh_file_watcher (); - END_PROTECTED } diff --git a/src/lay/layMacroEditorDialog.h b/src/lay/layMacroEditorDialog.h index 476baa492..2fc328e10 100644 --- a/src/lay/layMacroEditorDialog.h +++ b/src/lay/layMacroEditorDialog.h @@ -192,6 +192,7 @@ private slots: void help_requested (const QString &s); void macro_changed (Macro *macro); void macro_deleted (Macro *macro); + void macro_collection_deleted (MacroCollection *collection); void add_watch (); void edit_watch (); void del_watches (); diff --git a/src/lay/layTechSetupDialog.cc b/src/lay/layTechSetupDialog.cc index 3f94132bf..c143043ef 100644 --- a/src/lay/layTechSetupDialog.cc +++ b/src/lay/layTechSetupDialog.cc @@ -542,7 +542,7 @@ TechSetupDialog::refresh_clicked () { BEGIN_PROTECTED - lay::TechnologyController::instance ()->refresh (m_technologies); + lay::TechnologyController::instance ()->rescan (m_technologies); update (); END_PROTECTED diff --git a/src/lay/layTechnologyController.cc b/src/lay/layTechnologyController.cc index 6f0b26c4f..d10aa2340 100644 --- a/src/lay/layTechnologyController.cc +++ b/src/lay/layTechnologyController.cc @@ -24,7 +24,6 @@ #include "layTechnologyController.h" #include "layTechSetupDialog.h" #include "layMainWindow.h" -#include "layMacroController.h" #include "layApplication.h" #include "layConfig.h" #include "laybasicConfig.h" @@ -47,18 +46,12 @@ std::string tech_string_from_name (const std::string &tn) } TechnologyController::TechnologyController () - : PluginDeclaration (), mp_editor (0), mp_mw (0), m_no_macros (false), mp_active_technology (0) + : PluginDeclaration (), mp_editor (0), mp_mw (0), mp_active_technology (0) { m_current_technology_updated = false; m_technologies_configured = false; } -void -TechnologyController::enable_macros (bool enable) -{ - m_no_macros = !enable; -} - TechnologyController * TechnologyController::instance () { @@ -80,8 +73,6 @@ TechnologyController::initialize (lay::PluginRoot * /*root*/) void TechnologyController::initialized (lay::PluginRoot *root) { - sync_tech_macro_locations (); - mp_mw = dynamic_cast (root); if (mp_mw) { mp_editor = new lay::TechSetupDialog (mp_mw); @@ -223,16 +214,13 @@ TechnologyController::configure (const std::string &name, const std::string &val if (! value.empty ()) { - // sync the non-persisted technologies with the configuration - lay::Technologies::instance ()->begin_updates (); try { - lay::Technologies::instance ()->load_from_xml (value); + lay::Technologies new_tech = *lay::Technologies::instance (); + new_tech.load_from_xml (value); + replace_technologies (new_tech); + m_technologies_configured = true; } catch (...) { } - lay::Technologies::instance ()->end_updates_no_event (); - - // explicitly handle changes -> no recursion - m_technologies_configured = true; } @@ -351,6 +339,24 @@ TechnologyController::update_menu () update_active_technology (); } +void +TechnologyController::replace_technologies (const lay::Technologies &technologies) +{ + bool has_active_tech = (mp_active_technology != 0); + std::string active_tech_name; + if (mp_active_technology) { + active_tech_name = mp_active_technology->name (); + } + + lay::Technologies ().instance ()->begin_updates (); + *lay::Technologies ().instance () = technologies; + lay::Technologies ().instance ()->end_updates_no_event (); + + if (has_active_tech) { + mp_active_technology = lay::Technologies::instance ()->technology_by_name (active_tech_name); + } +} + void TechnologyController::show_editor () { @@ -374,9 +380,7 @@ TechnologyController::show_editor () } } - lay::Technologies ().instance ()->begin_updates (); - *lay::Technologies ().instance () = new_tech; - lay::Technologies ().instance ()->end_updates_no_event (); + replace_technologies (new_tech); // save the technologies that need to be saved // TODO: save only the ones that really need saving @@ -424,19 +428,6 @@ TechnologyController::show_editor () QMessageBox::Ok); } - std::vector nm = sync_tech_macro_locations (); - - bool has_autorun = false; - for (std::vector::const_iterator m = nm.begin (); m != nm.end () && ! has_autorun; ++m) { - has_autorun = (*m)->has_autorun (); - } - - if (has_autorun && QMessageBox::question (mp_mw, QObject::tr ("Run Macros"), QObject::tr ("Some macros associated with technologies now are configured to run automatically.\n\nChoose 'Yes' to run these macros now. Choose 'No' to not run them."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - for (std::vector::const_iterator m = nm.begin (); m != nm.end (); ++m) { - (*m)->autorun (); - } - } - update_menu (); emit technologies_edited (); @@ -451,25 +442,13 @@ TechnologyController::default_root () } void -TechnologyController::refresh () +TechnologyController::load () { - try { - - lay::Technologies::instance ()->begin_updates (); - refresh (*lay::Technologies::instance ()); - lay::Technologies::instance ()->end_updates_no_event (); - - update_menu (); - emit technologies_edited (); - - } catch (...) { - lay::Technologies::instance ()->end_updates_no_event (); - throw; - } + rescan (*lay::Technologies::instance ()); } void -TechnologyController::refresh (lay::Technologies &technologies) +TechnologyController::rescan (lay::Technologies &technologies) { lay::Technologies current = technologies; @@ -545,119 +524,6 @@ TechnologyController::add_path (const std::string &p) m_paths.push_back (p); } -std::vector -TechnologyController::sync_tech_macro_locations () -{ - lay::MacroController *mc = lay::MacroController::instance (); - - if (! mc || m_no_macros) { - return std::vector (); - } - - std::set > tech_macro_paths; - std::map, std::string> tech_names_by_path; - - // Add additional places where the technologies define some macros - for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { - - if (t->base_path ().empty ()) { - continue; - } - - for (size_t c = 0; c < mc->macro_categories ().size (); ++c) { - - QDir base_dir (tl::to_qstring (t->base_path ())); - if (base_dir.exists ()) { - - QDir macro_dir (base_dir.filePath (tl::to_qstring (mc->macro_categories () [c].first))); - if (macro_dir.exists ()) { - - std::string mp = tl::to_string (macro_dir.path ()); - std::pair cp (mc->macro_categories () [c].first, mp); - tech_macro_paths.insert (cp); - std::string &tn = tech_names_by_path [cp]; - if (! tn.empty ()) { - tn += ","; - } - tn += t->name (); - - } - - } - - } - - } - - // delete macro collections which are no longer required or update description - std::vector folders_to_delete; - std::string desc_prefix = tl::to_string (QObject::tr ("Technology")) + " - "; - - lay::MacroCollection *root = &lay::MacroCollection::root (); - - for (lay::MacroCollection::child_iterator m = root->begin_children (); m != root->end_children (); ++m) { - - std::pair cp (m->second->category (), m->second->path ()); - if (m->second->virtual_mode () == lay::MacroCollection::TechFolder && m_tech_macro_paths.find (cp) != m_tech_macro_paths.end ()) { - - if (tech_macro_paths.find (cp) == tech_macro_paths.end ()) { - // no longer used - folders_to_delete.push_back (m->second); - } else { - // used: update description if required - std::string desc = desc_prefix + tech_names_by_path [cp]; - m->second->set_description (desc); - } - - } - - } - - for (std::vector::iterator m = folders_to_delete.begin (); m != folders_to_delete.end (); ++m) { - if (tl::verbosity () >= 20) { - tl::info << "Removing macro folder " << (*m)->path () << ", category '" << (*m)->category () << "' because no longer in use"; - } - root->erase (*m); - } - - // store new paths - m_tech_macro_paths = tech_macro_paths; - - // add new folders - for (lay::MacroCollection::child_iterator m = root->begin_children (); m != root->end_children (); ++m) { - if (m->second->virtual_mode () == lay::MacroCollection::TechFolder) { - std::pair cp (m->second->category (), m->second->path ()); - tech_macro_paths.erase (cp); - } - } - - std::vector new_folders; - - for (std::set >::const_iterator p = tech_macro_paths.begin (); p != tech_macro_paths.end (); ++p) { - - const std::string &tn = tech_names_by_path [*p]; - - // TODO: is it wise to make it writeable? - if (tl::verbosity () >= 20) { - tl::info << "Adding macro folder " << p->second << ", category '" << p->first << "' for technologies " << tn; - } - - // Add the folder. Note: it may happen that a macro folder for the tech specific macros already exists in - // a non-tech context. - // In that case, the add_folder method will return 0. - lay::MacroCollection *mc = lay::MacroCollection::root ().add_folder (desc_prefix + tn, p->second, p->first, false); - if (mc) { - - mc->set_virtual_mode (lay::MacroCollection::TechFolder); - new_folders.push_back (mc); - - } - - } - - return new_folders; -} - static tl::RegisteredClass config_decl (new TechnologyController (), 110, "TechnologyController"); } diff --git a/src/lay/layTechnologyController.h b/src/lay/layTechnologyController.h index ea5d7f00f..69fd5da90 100644 --- a/src/lay/layTechnologyController.h +++ b/src/lay/layTechnologyController.h @@ -70,16 +70,9 @@ public: */ lay::Technology *active_technology () const; - /** - * @brief Enables or disables macros - * If macros are enabled, the macro tree contains the macros defined within the technologies. - * This flag needs to be set initially and before the technology tree is updated. - */ - void enable_macros (bool enable); - /** * @brief Adds a path as a search path for technologies - * "refresh" needs to be called after search paths have been added. + * "load" needs to be called after search paths have been added. */ void add_path (const std::string &path); @@ -87,19 +80,19 @@ public: * @brief Adds a temporary technology * Temporary technologies are additional technologies which are added to the list of technologies * but are not persisted or editable. - * "refresh" needs to be called after temp technologies have been added. + * "load" needs to be called after temp technologies have been added. */ void add_temp_tech (const lay::Technology &t); /** - * @brief Updates the given technology collection with the technologies from the search path and teh temp technologies + * @brief Updates the given technology collection with the technologies from the search path and the temp technologies */ - void refresh (lay::Technologies &technologies); + void rescan (lay::Technologies &technologies); /** - * @brief Refreshes the global list of technologies + * @brief Loads the global list of technologies */ - void refresh (); + void load (); /** * @brief Gets the default root folder @@ -133,10 +126,8 @@ private: bool m_technologies_configured; lay::TechSetupDialog *mp_editor; lay::MainWindow *mp_mw; - bool m_no_macros; std::vector m_paths; std::vector m_temp_tech; - std::set > m_tech_macro_paths; lay::Technology *mp_active_technology; void update_active_technology (); @@ -148,7 +139,7 @@ private: bool menu_activated (const std::string &symbol) const; void update_current_technology (); void update_menu (); - std::vector sync_tech_macro_locations (); + void replace_technologies (const lay::Technologies &technologies); }; } From 516c3ff9250affb1ad294c4f6618cb80aeb36ec4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 16 Apr 2017 00:27:28 +0200 Subject: [PATCH 34/57] WIP: tech controller and tech setup dialog Some bug fixes (description was not updated in tree, base path of new tech was not set, dirs were not reused for new tech). Some enhancements (tech setup dialog's window state is persisted etc.) --- src/lay/layTechSetupDialog.cc | 29 +++++++++++++-------- src/lay/layTechnologyController.cc | 41 ++++++++++++++++++++++-------- src/laybasic/layAbstractMenu.cc | 1 - src/laybasic/layQtTools.cc | 4 +++ 4 files changed, 52 insertions(+), 23 deletions(-) diff --git a/src/lay/layTechSetupDialog.cc b/src/lay/layTechSetupDialog.cc index c143043ef..996f69a76 100644 --- a/src/lay/layTechSetupDialog.cc +++ b/src/lay/layTechSetupDialog.cc @@ -504,14 +504,6 @@ TechSetupDialog::TechSetupDialog (QWidget *parent) connect (add_pb, SIGNAL (clicked ()), this, SLOT (add_clicked ())); connect (delete_pb, SIGNAL (clicked ()), this, SLOT (delete_clicked ())); connect (rename_pb, SIGNAL (clicked ()), this, SLOT (rename_clicked ())); - - if (s_first_show) { - TipDialog td (this, - tl::to_string (QObject::tr ("To get started with the technology manager, read the documentation provided: About Technology Management.")), - "tech-manager-basic-tips"); - td.exec_dialog (); - s_first_show = false; - } } TechSetupDialog::~TechSetupDialog () @@ -559,6 +551,14 @@ TechSetupDialog::update () int TechSetupDialog::exec (lay::Technologies &technologies) { + if (s_first_show) { + TipDialog td (this, + tl::to_string (QObject::tr ("To get started with the technology manager, read the documentation provided: About Technology Management.")), + "tech-manager-basic-tips"); + td.exec_dialog (); + s_first_show = false; + } + m_technologies = technologies; update (); @@ -613,12 +613,17 @@ BEGIN_PROTECTED QDir root = QDir (tl::to_qstring (lay::TechnologyController::instance ()->default_root ())); QDir tech_dir (root.filePath (tn)); if (tech_dir.exists ()) { - throw tl::Exception (tl::to_string (QObject::tr ("A target folder with path '%1' already exists").arg (tech_dir.path ()))); + if (QMessageBox::question (this, QObject::tr ("Creating Technology"), + QObject::tr ("A target folder with path '%1' already exists\nUse this directory for the new technology?").arg (tech_dir.path ()), + QMessageBox::No | QMessageBox::Yes) == QMessageBox::No) { + throw tl::CancelException (); + } } lay::Technology *nt = new lay::Technology (*t); nt->set_tech_file_path (tl::to_string (tech_dir.absoluteFilePath (tn + QString::fromUtf8 (".lyt")))); + nt->set_default_base_path (tl::to_string (tech_dir.absolutePath ())); nt->set_persisted (false); nt->set_name (tl::to_string (tn)); nt->set_description (std::string ()); @@ -990,9 +995,11 @@ TechSetupDialog::commit_tech_component () mp_current_editor->commit (); } - if (mp_current_tech && mp_current_tech_component && !mp_current_tech->is_readonly ()) { + if (mp_current_tech && !mp_current_tech->is_readonly ()) { - mp_current_tech->set_component (mp_current_tech_component->clone ()); + if (mp_current_tech_component) { + mp_current_tech->set_component (mp_current_tech_component->clone ()); + } // because commit may have changed the description text, update the technology titles for (int i = tech_tree->topLevelItemCount (); i > 0; --i) { diff --git a/src/lay/layTechnologyController.cc b/src/lay/layTechnologyController.cc index d10aa2340..6178f0944 100644 --- a/src/lay/layTechnologyController.cc +++ b/src/lay/layTechnologyController.cc @@ -26,6 +26,7 @@ #include "layMainWindow.h" #include "layApplication.h" #include "layConfig.h" +#include "layQtTools.h" #include "laybasicConfig.h" #include @@ -36,6 +37,8 @@ namespace lay { +static const std::string cfg_tech_editor_window_state ("tech-editor-window-state"); + std::string tech_string_from_name (const std::string &tn) { if (tn.empty ()) { @@ -65,20 +68,18 @@ TechnologyController::instance () } void -TechnologyController::initialize (lay::PluginRoot * /*root*/) -{ - // .. nothing yet .. -} - -void -TechnologyController::initialized (lay::PluginRoot *root) +TechnologyController::initialize (lay::PluginRoot *root) { mp_mw = dynamic_cast (root); if (mp_mw) { mp_editor = new lay::TechSetupDialog (mp_mw); mp_editor->setModal (false); } +} +void +TechnologyController::initialized (lay::PluginRoot * /*root*/) +{ update_menu (); connect_events (); } @@ -94,6 +95,7 @@ void TechnologyController::get_options (std::vector < std::pair > &options) const { options.push_back (std::pair (cfg_initial_technology, "")); + options.push_back (std::pair (cfg_tech_editor_window_state, "")); } void @@ -153,7 +155,12 @@ TechnologyController::update_active_technology () { lay::Technology *active_tech = 0; if (mp_mw && mp_mw->current_view () && mp_mw->current_view ()->active_cellview_index () >= 0 && mp_mw->current_view ()->active_cellview_index () <= int (mp_mw->current_view ()->cellviews ())) { - active_tech = lay::Technologies::instance ()->technology_by_name (mp_mw->current_view ()->active_cellview ()->tech_name ()); + + std::string tn = mp_mw->current_view ()->active_cellview ()->tech_name (); + if (lay::Technologies::instance ()->has_technology (tn)) { + active_tech = lay::Technologies::instance ()->technology_by_name (tn); + } + } if (mp_active_technology != active_tech) { @@ -192,6 +199,7 @@ TechnologyController::technologies_changed () } update_menu (); + emit technologies_edited (); } void @@ -210,6 +218,10 @@ TechnologyController::configure (const std::string &name, const std::string &val m_current_technology_updated = true; } + } else if (name == cfg_tech_editor_window_state) { + + lay::restore_dialog_state (mp_editor, value); + } else if (name == cfg_technologies) { if (! value.empty ()) { @@ -295,6 +307,10 @@ TechnologyController::update_menu () m_current_technology = lay::LayoutView::current ()->active_cellview ()->tech_name (); } + if (! lay::Technologies::instance()->has_technology (m_current_technology)) { + m_current_technology = std::string (); + } + std::string title = tech_string_from_name (m_current_technology); size_t ntech = 0; @@ -307,7 +323,7 @@ TechnologyController::update_menu () for (std::vector::const_iterator t = tech_group.begin (); t != tech_group.end (); ++t) { lay::Action action = pr->menu ()->action (*t); action.set_title (title); - action.set_visible (ntech > 1); + action.set_enabled (ntech > 1); std::vector items = pr->menu ()->items (*t); for (std::vector::const_iterator i = items.begin (); i != items.end (); ++i) { pr->menu ()->delete_item (*i); @@ -428,10 +444,13 @@ TechnologyController::show_editor () QMessageBox::Ok); } - update_menu (); - emit technologies_edited (); + technologies_changed (); } + + if (mp_mw) { + mp_mw->config_set (cfg_tech_editor_window_state, lay::save_dialog_state (mp_editor)); + } } const std::string & diff --git a/src/laybasic/layAbstractMenu.cc b/src/laybasic/layAbstractMenu.cc index 3c62f6bb9..6e5fd7c32 100644 --- a/src/laybasic/layAbstractMenu.cc +++ b/src/laybasic/layAbstractMenu.cc @@ -873,7 +873,6 @@ AbstractMenu::build (QToolBar *t, std::list &items) } } - } QMenu * diff --git a/src/laybasic/layQtTools.cc b/src/laybasic/layQtTools.cc index 6d395e8e9..0ed979770 100644 --- a/src/laybasic/layQtTools.cc +++ b/src/laybasic/layQtTools.cc @@ -103,6 +103,10 @@ save_dialog_state (QWidget *w) void restore_dialog_state (QWidget *dialog, const std::string &s) { + if (! dialog) { + return; + } + tl::Extractor ex (s.c_str ()); while (! ex.at_end ()) { From 88487a001f005e74fe3d3d75bd77da3e5acf74ce Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 16 Apr 2017 23:03:33 +0200 Subject: [PATCH 35/57] Integration of Salt with another controller --- src/lay/gsiDeclLayApplication.cc | 3 +- src/lay/lay.pro | 8 +- src/lay/layApplication.cc | 422 ++++++------------------------- src/lay/layApplication.h | 6 +- src/lay/layMainWindow.cc | 8 +- src/lay/laySaltController.cc | 172 +++++++++++++ src/lay/laySaltController.h | 148 +++++++++++ src/lay/laySaltManagerDialog.cc | 38 +-- src/lay/laySaltManagerDialog.h | 2 +- src/lay/laySignalHandler.cc | 336 ++++++++++++++++++++++++ src/lay/laySignalHandler.h | 43 ++++ src/tl/tlSystemPaths.cc | 22 ++ src/tl/tlSystemPaths.h | 5 + 13 files changed, 817 insertions(+), 396 deletions(-) create mode 100644 src/lay/laySaltController.cc create mode 100644 src/lay/laySaltController.h create mode 100644 src/lay/laySignalHandler.cc create mode 100644 src/lay/laySignalHandler.h diff --git a/src/lay/gsiDeclLayApplication.cc b/src/lay/gsiDeclLayApplication.cc index 6c4321d24..e5c89415d 100644 --- a/src/lay/gsiDeclLayApplication.cc +++ b/src/lay/gsiDeclLayApplication.cc @@ -23,6 +23,7 @@ #include "layApplication.h" #include "layMainWindow.h" +#include "laySignalHandler.h" #include "gsiDecl.h" #include "gsiQtExternals.h" @@ -68,7 +69,7 @@ Class decl_Application (QT_EXTERNAL_BASE (QApplication) "Appli "method." ) + method ("crash_me", &crash_me, "@hide") + - method ("symname", &lay::Application::symbol_name_from_address, "@hide") + + method ("symname", &lay::get_symbol_name_from_address, "@hide") + method ("is_editable?", &lay::Application::is_editable, "@brief Returns true if the application is in editable mode\n" ) + diff --git a/src/lay/lay.pro b/src/lay/lay.pro index d6b1a048a..f076212b6 100644 --- a/src/lay/lay.pro +++ b/src/lay/lay.pro @@ -55,7 +55,9 @@ HEADERS = \ laySaltGrainDetailsTextWidget.h \ laySaltGrainPropertiesDialog.h \ laySaltDownloadManager.h \ - laySaltModel.h + laySaltModel.h \ + laySaltController.h \ + laySignalHandler.h FORMS = \ ClipDialog.ui \ @@ -156,7 +158,9 @@ SOURCES = \ laySaltGrainDetailsTextWidget.cc \ laySaltGrainPropertiesDialog.cc \ laySaltDownloadManager.cc \ - laySaltModel.cc + laySaltModel.cc \ + laySaltController.cc \ + laySignalHandler.cc RESOURCES = layBuildInMacros.qrc \ layHelpResources.qrc \ diff --git a/src/lay/layApplication.cc b/src/lay/layApplication.cc index d53df08fb..ccd1906c2 100644 --- a/src/lay/layApplication.cc +++ b/src/lay/layApplication.cc @@ -22,19 +22,21 @@ #include "layApplication.h" +#include "laySignalHandler.h" #include "laybasicConfig.h" #include "layConfig.h" #include "layMainWindow.h" #include "layMacroEditorDialog.h" #include "layVersion.h" #include "layMacro.h" -#include "layCrashMessage.h" +#include "laySignalHandler.h" #include "layRuntimeErrorForm.h" #include "layProgress.h" #include "layTextProgress.h" #include "layBackgroundAwareTreeStyle.h" #include "layMacroController.h" #include "layTechnologyController.h" +#include "laySaltController.h" #include "gtf.h" #include "gsiDecl.h" #include "gsiInterpreter.h" @@ -61,26 +63,15 @@ #include #include -#ifdef _WIN32 -# include -# include -# include -// get rid of these - we have std::min/max .. -# ifdef min -# undef min -# endif -# ifdef max -# undef max -# endif -#else -# include -# include -#endif - #include #include #include -#include + +#ifdef _WIN32 +# include +#else +# include +#endif namespace lay { @@ -163,292 +154,6 @@ static void ui_exception_handler_def (QWidget *parent) static Application *ms_instance = 0; -#if defined(WIN32) - -static QString -addr2symname (DWORD64 addr) -{ - const int max_symbol_length = 255; - - SYMBOL_INFO *symbol = (SYMBOL_INFO *) calloc (sizeof (SYMBOL_INFO) + (max_symbol_length + 1) * sizeof (char), 1); - symbol->MaxNameLen = max_symbol_length; - symbol->SizeOfStruct = sizeof (SYMBOL_INFO); - - HANDLE process = GetCurrentProcess (); - - QString sym_name; - DWORD64 d; - bool has_symbol = false; - DWORD64 disp = addr; - if (SymFromAddr(process, addr, &d, symbol)) { - // Symbols taken from the export table seem to be unreliable - skip these - // and report the module name + offset. - if (! (symbol->Flags & SYMFLAG_EXPORT)) { - sym_name = QString::fromLocal8Bit (symbol->Name); - disp = d; - has_symbol = true; - } - } - - // find the module name from the module base address - - HMODULE modules[1024]; - DWORD modules_size = 0; - if (! EnumProcessModules (process, modules, sizeof (modules), &modules_size)) { - modules_size = 0; - } - - QString mod_name; - for (unsigned int i = 0; i < (modules_size / sizeof (HMODULE)); i++) { - TCHAR mn[MAX_PATH]; - if (GetModuleFileName (modules[i], mn, sizeof (mn) / sizeof (TCHAR))) { - MODULEINFO mi; - if (GetModuleInformation (process, modules[i], &mi, sizeof (mi))) { - if ((DWORD64) mi.lpBaseOfDll <= addr && (DWORD64) mi.lpBaseOfDll + mi.SizeOfImage > addr) { - mod_name = QFileInfo (QString::fromUtf16 ((unsigned short *) mn)).fileName (); - if (! has_symbol) { - disp -= (DWORD64) mi.lpBaseOfDll; - } - break; - } - } - } - } - - if (! mod_name.isNull ()) { - mod_name = QString::fromUtf8 ("(") + mod_name + QString::fromUtf8 (") "); - } - - free (symbol); - - return QString::fromUtf8 ("0x%1 - %2%3+%4"). - arg (addr, 0, 16). - arg (mod_name). - arg (sym_name). - arg (disp); -} - -static QString -get_symbol_name_from_address (const QString &mod_name, size_t addr) -{ - HANDLE process = GetCurrentProcess (); - - DWORD64 mod_base = 0; - if (! mod_name.isEmpty ()) { - - // find the module name from the module base address - HMODULE modules[1024]; - DWORD modules_size = 0; - if (! EnumProcessModules (process, modules, sizeof (modules), &modules_size)) { - modules_size = 0; - } - - for (unsigned int i = 0; i < (modules_size / sizeof (HMODULE)); i++) { - TCHAR mn[MAX_PATH]; - if (GetModuleFileName (modules[i], mn, sizeof (mn) / sizeof (TCHAR))) { - if (mod_name == QFileInfo (QString::fromUtf16 ((unsigned short *) mn)).fileName ()) { - MODULEINFO mi; - if (GetModuleInformation (process, modules[i], &mi, sizeof (mi))) { - mod_base = (DWORD64) mi.lpBaseOfDll; - } - } - } - } - - if (mod_base == 0) { - throw tl::Exception (tl::to_string (QObject::tr ("Unknown module name: ") + mod_name)); - } - - } - - SymInitialize (process, NULL, TRUE); - QString res = addr2symname (mod_base + (DWORD64) addr); - SymCleanup (process); - - return res; -} - -LONG WINAPI ExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) -{ - HANDLE process = GetCurrentProcess (); - SymInitialize (process, NULL, TRUE); - - QString text; - text += QObject::tr ("Exception code: 0x%1\n").arg (pExceptionInfo->ExceptionRecord->ExceptionCode, 0, 16); - text += QObject::tr ("Program Version: ") + - QString::fromUtf8 (lay::Version::name ()) + - QString::fromUtf8 (" ") + - QString::fromUtf8 (lay::Version::version ()) + - QString::fromUtf8 (" (") + - QString::fromUtf8 (lay::Version::subversion ()) + - QString::fromUtf8 (")"); -#if defined(_WIN64) - text += QString::fromUtf8 (" AMD64"); -#else - text += QString::fromUtf8 (" x86"); -#endif - text += QString::fromUtf8 ("\n"); - text += QObject::tr ("\nBacktrace:\n"); - - CONTEXT context_record = *pExceptionInfo->ContextRecord; - - // Initialize stack walking. - STACKFRAME64 stack_frame; - memset(&stack_frame, 0, sizeof(stack_frame)); - -#if defined(_WIN64) - int machine_type = IMAGE_FILE_MACHINE_AMD64; - stack_frame.AddrPC.Offset = context_record.Rip; - stack_frame.AddrFrame.Offset = context_record.Rbp; - stack_frame.AddrStack.Offset = context_record.Rsp; -#else - int machine_type = IMAGE_FILE_MACHINE_I386; - stack_frame.AddrPC.Offset = context_record.Eip; - stack_frame.AddrFrame.Offset = context_record.Ebp; - stack_frame.AddrStack.Offset = context_record.Esp; -#endif - stack_frame.AddrPC.Mode = AddrModeFlat; - stack_frame.AddrFrame.Mode = AddrModeFlat; - stack_frame.AddrStack.Mode = AddrModeFlat; - - while (StackWalk64 (machine_type, - GetCurrentProcess(), - GetCurrentThread(), - &stack_frame, - &context_record, - NULL, - &SymFunctionTableAccess64, - &SymGetModuleBase64, - NULL)) { - text += addr2symname (stack_frame.AddrPC.Offset); - text += QString::fromUtf8 ("\n"); - } - - SymCleanup (process); - - // YES! I! KNOW! - // In a signal handler you shall not do fancy stuff (in particular not - // open dialogs) nor shall you throw exceptions! But that scheme appears to - // be working since in most cases the signal is raised from our code (hence - // from our stack frames) and everything is better than just showing - // the "application stopped working" dialog. - // Isn't it? - - CrashMessage msg (0, true, text); - if (! msg.exec ()) { - // terminate unconditionally - return EXCEPTION_EXECUTE_HANDLER; - } else { - throw tl::CancelException (); - } -} - -static void handle_signal (int signo) -{ - signal (signo, handle_signal); - int user_base = (1 << 29); - RaiseException(signo + user_base, 0, 0, NULL); -} - -static void install_signal_handlers () -{ - // disable any signal handlers that Ruby might have installed. - signal (SIGSEGV, SIG_DFL); - signal (SIGILL, SIG_DFL); - signal (SIGFPE, SIG_DFL); - - signal (SIGABRT, handle_signal); - -#if 0 - // TODO: not available to MinGW - linking against msvc100 would help - // but then the app crashes. - _set_abort_behavior( 0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT ); -#endif - - SetUnhandledExceptionFilter(ExceptionHandler); -} - -#else - -QString get_symbol_name_from_address (const QString &, size_t) -{ - return QString::fromUtf8 ("n/a"); -} - -void signal_handler (int signo, siginfo_t *si, void *) -{ - void *array [100]; - - bool can_resume = (signo != SIGILL); - - size_t nptrs = backtrace (array, sizeof (array) / sizeof (array[0])); - - QString text; - text += QObject::tr ("Signal number: %1\n").arg (signo); - text += QObject::tr ("Address: 0x%1\n").arg ((size_t) si->si_addr, 0, 16); - text += QObject::tr ("Program Version: ") + - QString::fromUtf8 (lay::Version::name ()) + - QString::fromUtf8 (" ") + - QString::fromUtf8 (lay::Version::version ()) + - QString::fromUtf8 (" (") + - QString::fromUtf8 (lay::Version::subversion ()) + - QString::fromUtf8 (")"); - text += QString::fromUtf8 ("\n"); - text += QObject::tr ("Backtrace:\n"); - - char **symbols = backtrace_symbols (array, nptrs); - if (symbols == NULL) { - text += QObject::tr ("-- Unable to obtain stack trace --"); - } else { - for (size_t i = 2; i < nptrs; i++) { - text += QString::fromUtf8 (symbols [i]) + QString::fromUtf8 ("\n"); - } - } - free(symbols); - - // YES! I! KNOW! - // In a signal handler you shall not do fancy stuff (in particular not - // open dialogs) nor shall you throw exceptions! But that scheme appears to - // be working since in most cases the signal is raised from our code (hence - // from our stack frames) and everything is better than just core dumping. - // Isn't it? - - CrashMessage msg (0, can_resume, text); - if (! msg.exec ()) { - - _exit (signo); - - } else { - - sigset_t x; - sigemptyset (&x); - sigaddset(&x, signo); - sigprocmask(SIG_UNBLOCK, &x, NULL); - - throw tl::CancelException (); - - } -} - -static void install_signal_handlers () -{ - struct sigaction act; - act.sa_sigaction = signal_handler; - sigemptyset (&act.sa_mask); - act.sa_flags = SA_SIGINFO; -#if !defined(__APPLE__) - act.sa_restorer = 0; -#endif - - sigaction (SIGSEGV, &act, NULL); - sigaction (SIGILL, &act, NULL); - sigaction (SIGFPE, &act, NULL); - sigaction (SIGABRT, &act, NULL); - sigaction (SIGBUS, &act, NULL); -} - -#endif - static void load_plugin (const std::string &pp) { #ifdef _WIN32 @@ -577,47 +282,6 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) } - // try to locate a global rbainit file and rbm modules - std::vector global_modules; - std::set modules; - - // try to locate a global plugins - for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { - -#if 0 - // deprecated functionality - QFileInfo rbainit_file (tl::to_qstring (*p), QString::fromUtf8 ("rbainit")); - if (rbainit_file.exists () && rbainit_file.isReadable ()) { - std::string m = tl::to_string (rbainit_file.absoluteFilePath ()); - if (modules.find (m) == modules.end ()) { - global_modules.push_back (m); - modules.insert (m); - } - } -#endif - - QDir inst_path_dir (tl::to_qstring (*p)); - - QStringList name_filters; - name_filters << QString::fromUtf8 ("*.rbm"); - name_filters << QString::fromUtf8 ("*.pym"); - - QStringList inst_modules = inst_path_dir.entryList (name_filters); - inst_modules.sort (); - - for (QStringList::const_iterator im = inst_modules.begin (); im != inst_modules.end (); ++im) { - QFileInfo rbm_file (tl::to_qstring (*p), *im); - if (rbm_file.exists () && rbm_file.isReadable ()) { - std::string m = tl::to_string (rbm_file.absoluteFilePath ()); - if (modules.find (m) == modules.end ()) { - global_modules.push_back (m); - modules.insert (m); - } - } - } - - } - // try to locate the global plugins for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { @@ -927,9 +591,21 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) install_signal_handlers (); } + lay::SaltController *sc = lay::SaltController::instance (); + lay::TechnologyController *tc = lay::TechnologyController::instance (); lay::MacroController *mc = lay::MacroController::instance (); - lay::TechnologyController *tc = lay::TechnologyController::instance (); + if (sc) { + + // auto-import technologies + for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { + std::string tp = tl::to_string (QDir (tl::to_qstring (*p)).filePath (QString::fromUtf8 ("salt"))); + sc->add_path (tp); + } + + sc->set_salt_mine_url (tl::salt_mine_url ()); + + } if (tc) { @@ -971,6 +647,8 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) if (! m_no_macros) { // Add the global ruby modules as the first ones. + // TODO: this is a deprecated feature. + std::vector global_modules = scan_global_modules (); m_load_macros.insert (m_load_macros.begin (), global_modules.begin (), global_modules.end ()); for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { @@ -1085,10 +763,56 @@ Application::~Application () shutdown (); } -QString -Application::symbol_name_from_address (const QString &mod_name, size_t addr) +std::vector +Application::scan_global_modules () { - return get_symbol_name_from_address (mod_name, addr); + // NOTE: + // this is deprecated functionality - for backward compatibility, global "*.rbm" and "*.pym" modules + // are still considered. The desired solution is autorun macros. + + // try to locate a global rbainit file and rbm modules + std::vector global_modules; + std::set modules; + + // try to locate a global plugins + for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { + +#if 0 + // deprecated functionality + QFileInfo rbainit_file (tl::to_qstring (*p), QString::fromUtf8 ("rbainit")); + if (rbainit_file.exists () && rbainit_file.isReadable ()) { + std::string m = tl::to_string (rbainit_file.absoluteFilePath ()); + if (modules.find (m) == modules.end ()) { + global_modules.push_back (m); + modules.insert (m); + } + } +#endif + + QDir inst_path_dir (tl::to_qstring (*p)); + + QStringList name_filters; + name_filters << QString::fromUtf8 ("*.rbm"); + name_filters << QString::fromUtf8 ("*.pym"); + + QStringList inst_modules = inst_path_dir.entryList (name_filters); + inst_modules.sort (); + + for (QStringList::const_iterator im = inst_modules.begin (); im != inst_modules.end (); ++im) { + QFileInfo rbm_file (tl::to_qstring (*p), *im); + if (rbm_file.exists () && rbm_file.isReadable ()) { + std::string m = tl::to_string (rbm_file.absoluteFilePath ()); + if (modules.find (m) == modules.end ()) { + tl::warn << tl::to_string (tr ("Global modules are deprecated. Turn '%1'' into an autorun macro instead and put it into 'macros' or 'pymacros'.").arg (tl::to_qstring (m))); + global_modules.push_back (m); + modules.insert (m); + } + } + } + + } + + return global_modules; } bool diff --git a/src/lay/layApplication.h b/src/lay/layApplication.h index 3cfdb8dd3..3186c7c56 100644 --- a/src/lay/layApplication.h +++ b/src/lay/layApplication.h @@ -257,11 +257,6 @@ public: return ! m_no_gui; } - /** - * @brief For debugging purposes: get a symbol name (a description actually) from an address - */ - static QString symbol_name_from_address (const QString &mod_name, size_t addr); - /** * @brief Reset config to global configuration */ @@ -301,6 +296,7 @@ public: private: void shutdown (); void finish (); + std::vector scan_global_modules (); enum file_type { layout_file, diff --git a/src/lay/layMainWindow.cc b/src/lay/layMainWindow.cc index 465821e34..f94f059a1 100644 --- a/src/lay/layMainWindow.cc +++ b/src/lay/layMainWindow.cc @@ -86,7 +86,7 @@ #include "layLayerToolbox.h" #include "laySettingsForm.h" #include "layTechnologyController.h" -#include "laySaltManagerDialog.h" +#include "laySaltController.h" #include "layTipDialog.h" #include "laySelectCellViewForm.h" #include "layLayoutPropertiesForm.h" @@ -4513,8 +4513,10 @@ MainWindow::show_progress_bar (bool show) void MainWindow::cm_packages () { - lay::SaltManagerDialog dialog (this); - dialog.exec (); + lay::SaltController *sc = lay::SaltController::instance (); + if (sc) { + sc->show_editor (); + } } void diff --git a/src/lay/laySaltController.cc b/src/lay/laySaltController.cc new file mode 100644 index 000000000..6b799f835 --- /dev/null +++ b/src/lay/laySaltController.cc @@ -0,0 +1,172 @@ + +/* + + 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 "laySaltController.h" +#include "laySaltManagerDialog.h" +#include "layConfig.h" +#include "layMainWindow.h" +#include "layQtTools.h" +#include "tlLog.h" + +namespace lay +{ + +static const std::string cfg_salt_manager_window_state ("salt-manager-window-state"); + +SaltController::SaltController () + : mp_salt_dialog (0), mp_mw (0) +{ + // .. nothing yet .. +} + +void +SaltController::initialized (lay::PluginRoot *root) +{ + mp_mw = dynamic_cast (root); + + connect (&m_salt, SIGNAL (collections_changed ()), this, SIGNAL (salt_changed ())); +} + +void +SaltController::uninitialize (lay::PluginRoot * /*root*/) +{ + disconnect (&m_salt, SIGNAL (collections_changed ()), this, SIGNAL (salt_changed ())); + + delete mp_salt_dialog; + mp_salt_dialog = 0; + mp_mw = 0; +} + +void +SaltController::get_options (std::vector < std::pair > &options) const +{ + options.push_back (std::pair (cfg_salt_manager_window_state, "")); +} + +void +SaltController::get_menu_entries (std::vector & /*menu_entries*/) const +{ + // .. nothing yet .. +} + +bool +SaltController::configure (const std::string & /*name*/, const std::string & /*value*/) +{ + return false; +} + +void +SaltController::config_finalize() +{ + // .. nothing yet .. +} + +bool +SaltController::can_exit (lay::PluginRoot * /*root*/) const +{ + // .. nothing yet .. + return true; +} + +bool +SaltController::accepts_drop (const std::string & /*path_or_url*/) const +{ + // .. nothing yet .. + return false; +} + +void +SaltController::drop_url (const std::string & /*path_or_url*/) +{ + // .. nothing yet .. +} + +void +SaltController::show_editor () +{ + if (mp_mw && !mp_salt_dialog) { + + try { + if (! m_salt_mine_url.empty ()) { + tl::log << tl::to_string (tr ("Downloading package repository from %1").arg (tl::to_qstring (m_salt_mine_url))); + m_salt_mine.load (m_salt_mine_url); + } + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + } + + mp_salt_dialog = new lay::SaltManagerDialog (mp_mw, &m_salt, &m_salt_mine); + + } + + if (mp_salt_dialog) { + + if (mp_mw) { + std::string s = mp_mw->config_get (cfg_salt_manager_window_state); + if (! s.empty ()) { + lay::restore_dialog_state (mp_salt_dialog, s); + } + } + + mp_salt_dialog->exec (); + + if (mp_mw) { + mp_mw->config_set (cfg_salt_manager_window_state, lay::save_dialog_state (mp_salt_dialog)); + } + + } +} + +void +SaltController::add_path (const std::string &path) +{ + try { + tl::log << tl::to_string (tr ("Scanning %1 for packages").arg (tl::to_qstring (path))); + m_salt.add_location (path); + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + } +} + +void +SaltController::set_salt_mine_url (const std::string &url) +{ + m_salt_mine_url = url; +} + +SaltController * +SaltController::instance () +{ + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + SaltController *sc = dynamic_cast (cls.operator-> ()); + if (sc) { + return sc; + } + } + return 0; +} + +// The singleton instance of the salt controller +static tl::RegisteredClass salt_controller_decl (new lay::SaltController (), 100, "SaltController"); + +} + diff --git a/src/lay/laySaltController.h b/src/lay/laySaltController.h new file mode 100644 index 000000000..2e310f31a --- /dev/null +++ b/src/lay/laySaltController.h @@ -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 + +*/ + + +#ifndef HDR_laySaltController +#define HDR_laySaltController + +#include "layCommon.h" +#include "layPlugin.h" +#include "laySalt.h" + +#include +#include + +#include + +namespace lay +{ + +class SaltManagerDialog; +class MainWindow; + +/** + * @brief A controller for the salt package manager + * + * This object is a singleton that acts as a controller + * for the package management. The controller is responsible + * to managing the packages and notifying package consumers + * of changes. + * + * It interacts with the SaltManagerDialog which basically + * is the view for the packages. + * + * By making the controller a PluginDeclaration it will receive + * initialization and configuration calls. + */ +class SaltController + : public lay::PluginDeclaration, public tl::Object +{ +Q_OBJECT + +public: + /** + * @brief Default constructor + */ + SaltController (); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void initialized (lay::PluginRoot *root); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void uninitialize (lay::PluginRoot *root); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void get_options (std::vector < std::pair > &options) const; + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void get_menu_entries (std::vector &menu_entries) const; + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + bool configure (const std::string &key, const std::string &value); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void config_finalize(); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + bool can_exit (lay::PluginRoot *root) const; + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + bool accepts_drop (const std::string &path_or_url) const; + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void drop_url (const std::string &path_or_url); + + /** + * @brief Shows the package editor + */ + void show_editor (); + + /** + * @brief Adds a search path to the package manager + */ + void add_path (const std::string &path); + + /** + * @brief Specifies the salt mine (package repository) URL + */ + void set_salt_mine_url (const std::string &url); + + /** + * @brief Gets the singleton instance for this object + */ + static SaltController *instance (); + +signals: + /** + * @brief This signal is emitted if the salt changed + */ + void salt_changed (); + +private: + lay::SaltManagerDialog *mp_salt_dialog; + lay::MainWindow *mp_mw; + std::string m_salt_mine_url; + lay::Salt m_salt, m_salt_mine; +}; + +} + +#endif + diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index aa3ecd745..b12e8b11d 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -110,39 +110,7 @@ private: // -------------------------------------------------------------------------------------- // SaltManager implementation -// @@@ -lay::Salt salt; -static bool salt_initialized = false; -void make_salt () -{ - if (!salt_initialized) { - salt_initialized = true; - salt.add_location (tl::to_string (QDir::homePath () + QString::fromUtf8("/.klayout/salt"))); - } -} -lay::Salt *get_salt () -{ - salt = lay::Salt (); salt_initialized = false; - make_salt (); - return &salt; -} -// @@@ - -// @@@ -lay::Salt salt_mine; -void make_salt_mine () -{ - salt_mine = lay::Salt (); - salt_mine.load ("/home/matthias/salt.mine"); -} -lay::Salt *get_salt_mine () -{ - make_salt_mine(); - return &salt_mine; -} -// @@@ - -SaltManagerDialog::SaltManagerDialog (QWidget *parent) +SaltManagerDialog::SaltManagerDialog (QWidget *parent, lay::Salt *salt, lay::Salt *salt_mine) : QDialog (parent), m_current_changed_enabled (true), dm_update_models (this, &SaltManagerDialog::update_models) { @@ -154,8 +122,8 @@ SaltManagerDialog::SaltManagerDialog (QWidget *parent) connect (delete_button, SIGNAL (clicked ()), this, SLOT (delete_grain ())); connect (apply_button, SIGNAL (clicked ()), this, SLOT (apply ())); - mp_salt = get_salt (); - mp_salt_mine = get_salt_mine (); + mp_salt = salt; + mp_salt_mine = salt_mine; SaltModel *model = new SaltModel (this, mp_salt); salt_view->setModel (model); diff --git a/src/lay/laySaltManagerDialog.h b/src/lay/laySaltManagerDialog.h index 6e1f40cf4..feee212b9 100644 --- a/src/lay/laySaltManagerDialog.h +++ b/src/lay/laySaltManagerDialog.h @@ -48,7 +48,7 @@ public: /** * @brief Constructor */ - SaltManagerDialog (QWidget *parent); + SaltManagerDialog (QWidget *parent, lay::Salt *salt, lay::Salt *salt_mine); private slots: /** diff --git a/src/lay/laySignalHandler.cc b/src/lay/laySignalHandler.cc new file mode 100644 index 000000000..9b65614c2 --- /dev/null +++ b/src/lay/laySignalHandler.cc @@ -0,0 +1,336 @@ + +/* + + 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 "laySignalHandler.h" +#include "layCrashMessage.h" +#include "layVersion.h" +#include "tlException.h" + +#ifdef _WIN32 +# include +# include +# include +// get rid of these - we have std::min/max .. +# ifdef min +# undef min +# endif +# ifdef max +# undef max +# endif +#else +# include +# include +# include +#endif + +#include + +namespace lay +{ + +#if defined(WIN32) + +static QString +addr2symname (DWORD64 addr) +{ + const int max_symbol_length = 255; + + SYMBOL_INFO *symbol = (SYMBOL_INFO *) calloc (sizeof (SYMBOL_INFO) + (max_symbol_length + 1) * sizeof (char), 1); + symbol->MaxNameLen = max_symbol_length; + symbol->SizeOfStruct = sizeof (SYMBOL_INFO); + + HANDLE process = GetCurrentProcess (); + + QString sym_name; + DWORD64 d; + bool has_symbol = false; + DWORD64 disp = addr; + if (SymFromAddr(process, addr, &d, symbol)) { + // Symbols taken from the export table seem to be unreliable - skip these + // and report the module name + offset. + if (! (symbol->Flags & SYMFLAG_EXPORT)) { + sym_name = QString::fromLocal8Bit (symbol->Name); + disp = d; + has_symbol = true; + } + } + + // find the module name from the module base address + + HMODULE modules[1024]; + DWORD modules_size = 0; + if (! EnumProcessModules (process, modules, sizeof (modules), &modules_size)) { + modules_size = 0; + } + + QString mod_name; + for (unsigned int i = 0; i < (modules_size / sizeof (HMODULE)); i++) { + TCHAR mn[MAX_PATH]; + if (GetModuleFileName (modules[i], mn, sizeof (mn) / sizeof (TCHAR))) { + MODULEINFO mi; + if (GetModuleInformation (process, modules[i], &mi, sizeof (mi))) { + if ((DWORD64) mi.lpBaseOfDll <= addr && (DWORD64) mi.lpBaseOfDll + mi.SizeOfImage > addr) { + mod_name = QFileInfo (QString::fromUtf16 ((unsigned short *) mn)).fileName (); + if (! has_symbol) { + disp -= (DWORD64) mi.lpBaseOfDll; + } + break; + } + } + } + } + + if (! mod_name.isNull ()) { + mod_name = QString::fromUtf8 ("(") + mod_name + QString::fromUtf8 (") "); + } + + free (symbol); + + return QString::fromUtf8 ("0x%1 - %2%3+%4"). + arg (addr, 0, 16). + arg (mod_name). + arg (sym_name). + arg (disp); +} + +QString +get_symbol_name_from_address (const QString &mod_name, size_t addr) +{ + HANDLE process = GetCurrentProcess (); + + DWORD64 mod_base = 0; + if (! mod_name.isEmpty ()) { + + // find the module name from the module base address + HMODULE modules[1024]; + DWORD modules_size = 0; + if (! EnumProcessModules (process, modules, sizeof (modules), &modules_size)) { + modules_size = 0; + } + + for (unsigned int i = 0; i < (modules_size / sizeof (HMODULE)); i++) { + TCHAR mn[MAX_PATH]; + if (GetModuleFileName (modules[i], mn, sizeof (mn) / sizeof (TCHAR))) { + if (mod_name == QFileInfo (QString::fromUtf16 ((unsigned short *) mn)).fileName ()) { + MODULEINFO mi; + if (GetModuleInformation (process, modules[i], &mi, sizeof (mi))) { + mod_base = (DWORD64) mi.lpBaseOfDll; + } + } + } + } + + if (mod_base == 0) { + throw tl::Exception (tl::to_string (QObject::tr ("Unknown module name: ") + mod_name)); + } + + } + + SymInitialize (process, NULL, TRUE); + QString res = addr2symname (mod_base + (DWORD64) addr); + SymCleanup (process); + + return res; +} + +LONG WINAPI ExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) +{ + HANDLE process = GetCurrentProcess (); + SymInitialize (process, NULL, TRUE); + + QString text; + text += QObject::tr ("Exception code: 0x%1\n").arg (pExceptionInfo->ExceptionRecord->ExceptionCode, 0, 16); + text += QObject::tr ("Program Version: ") + + QString::fromUtf8 (lay::Version::name ()) + + QString::fromUtf8 (" ") + + QString::fromUtf8 (lay::Version::version ()) + + QString::fromUtf8 (" (") + + QString::fromUtf8 (lay::Version::subversion ()) + + QString::fromUtf8 (")"); +#if defined(_WIN64) + text += QString::fromUtf8 (" AMD64"); +#else + text += QString::fromUtf8 (" x86"); +#endif + text += QString::fromUtf8 ("\n"); + text += QObject::tr ("\nBacktrace:\n"); + + CONTEXT context_record = *pExceptionInfo->ContextRecord; + + // Initialize stack walking. + STACKFRAME64 stack_frame; + memset(&stack_frame, 0, sizeof(stack_frame)); + +#if defined(_WIN64) + int machine_type = IMAGE_FILE_MACHINE_AMD64; + stack_frame.AddrPC.Offset = context_record.Rip; + stack_frame.AddrFrame.Offset = context_record.Rbp; + stack_frame.AddrStack.Offset = context_record.Rsp; +#else + int machine_type = IMAGE_FILE_MACHINE_I386; + stack_frame.AddrPC.Offset = context_record.Eip; + stack_frame.AddrFrame.Offset = context_record.Ebp; + stack_frame.AddrStack.Offset = context_record.Esp; +#endif + stack_frame.AddrPC.Mode = AddrModeFlat; + stack_frame.AddrFrame.Mode = AddrModeFlat; + stack_frame.AddrStack.Mode = AddrModeFlat; + + while (StackWalk64 (machine_type, + GetCurrentProcess(), + GetCurrentThread(), + &stack_frame, + &context_record, + NULL, + &SymFunctionTableAccess64, + &SymGetModuleBase64, + NULL)) { + text += addr2symname (stack_frame.AddrPC.Offset); + text += QString::fromUtf8 ("\n"); + } + + SymCleanup (process); + + // YES! I! KNOW! + // In a signal handler you shall not do fancy stuff (in particular not + // open dialogs) nor shall you throw exceptions! But that scheme appears to + // be working since in most cases the signal is raised from our code (hence + // from our stack frames) and everything is better than just showing + // the "application stopped working" dialog. + // Isn't it? + + CrashMessage msg (0, true, text); + if (! msg.exec ()) { + // terminate unconditionally + return EXCEPTION_EXECUTE_HANDLER; + } else { + throw tl::CancelException (); + } +} + +static void handle_signal (int signo) +{ + signal (signo, handle_signal); + int user_base = (1 << 29); + RaiseException(signo + user_base, 0, 0, NULL); +} + +static void install_signal_handlers () +{ + // disable any signal handlers that Ruby might have installed. + signal (SIGSEGV, SIG_DFL); + signal (SIGILL, SIG_DFL); + signal (SIGFPE, SIG_DFL); + + signal (SIGABRT, handle_signal); + +#if 0 + // TODO: not available to MinGW - linking against msvc100 would help + // but then the app crashes. + _set_abort_behavior( 0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT ); +#endif + + SetUnhandledExceptionFilter(ExceptionHandler); +} + +#else + +QString get_symbol_name_from_address (const QString &, size_t) +{ + return QString::fromUtf8 ("n/a"); +} + +void signal_handler (int signo, siginfo_t *si, void *) +{ + void *array [100]; + + bool can_resume = (signo != SIGILL); + + size_t nptrs = backtrace (array, sizeof (array) / sizeof (array[0])); + + QString text; + text += QObject::tr ("Signal number: %1\n").arg (signo); + text += QObject::tr ("Address: 0x%1\n").arg ((size_t) si->si_addr, 0, 16); + text += QObject::tr ("Program Version: ") + + QString::fromUtf8 (lay::Version::name ()) + + QString::fromUtf8 (" ") + + QString::fromUtf8 (lay::Version::version ()) + + QString::fromUtf8 (" (") + + QString::fromUtf8 (lay::Version::subversion ()) + + QString::fromUtf8 (")"); + text += QString::fromUtf8 ("\n"); + text += QObject::tr ("Backtrace:\n"); + + char **symbols = backtrace_symbols (array, nptrs); + if (symbols == NULL) { + text += QObject::tr ("-- Unable to obtain stack trace --"); + } else { + for (size_t i = 2; i < nptrs; i++) { + text += QString::fromUtf8 (symbols [i]) + QString::fromUtf8 ("\n"); + } + } + free(symbols); + + // YES! I! KNOW! + // In a signal handler you shall not do fancy stuff (in particular not + // open dialogs) nor shall you throw exceptions! But that scheme appears to + // be working since in most cases the signal is raised from our code (hence + // from our stack frames) and everything is better than just core dumping. + // Isn't it? + + CrashMessage msg (0, can_resume, text); + if (! msg.exec ()) { + + _exit (signo); + + } else { + + sigset_t x; + sigemptyset (&x); + sigaddset(&x, signo); + sigprocmask(SIG_UNBLOCK, &x, NULL); + + throw tl::CancelException (); + + } +} + +void install_signal_handlers () +{ + struct sigaction act; + act.sa_sigaction = signal_handler; + sigemptyset (&act.sa_mask); + act.sa_flags = SA_SIGINFO; +#if !defined(__APPLE__) + act.sa_restorer = 0; +#endif + + sigaction (SIGSEGV, &act, NULL); + sigaction (SIGILL, &act, NULL); + sigaction (SIGFPE, &act, NULL); + sigaction (SIGABRT, &act, NULL); + sigaction (SIGBUS, &act, NULL); +} + +#endif + +} diff --git a/src/lay/laySignalHandler.h b/src/lay/laySignalHandler.h new file mode 100644 index 000000000..9b9c528f9 --- /dev/null +++ b/src/lay/laySignalHandler.h @@ -0,0 +1,43 @@ + +/* + + 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_laySignalHandler +#define HDR_laySignalHandler + +#include + +namespace lay +{ + +/** + * @brief Installs global signal handlers for SIGSEGV and similar + */ +void install_signal_handlers (); + +/** + * @brief For debugging purposes: get the symbol name from a memory address + */ +QString get_symbol_name_from_address (const QString &mod_name, size_t addr); + +} + +#endif diff --git a/src/tl/tlSystemPaths.cc b/src/tl/tlSystemPaths.cc index d1847b853..53a9c5615 100644 --- a/src/tl/tlSystemPaths.cc +++ b/src/tl/tlSystemPaths.cc @@ -164,5 +164,27 @@ get_klayout_path () } } +std::string +salt_mine_url () +{ + const std::string default_url ("https://www.klayout.org/salt.mine"); + +#ifdef _WIN32 + wchar_t *env = _wgetenv (L"KLAYOUT_SALT_MINE"); + if (env) { + return tl::to_string (QString ((const QChar *) env))); + } else { + return default_url; + } +#else + char *env = getenv ("KLAYOUT_SALT_MINE"); + if (env) { + return (tl::system_to_string (env)); + } else { + return default_url; + } +#endif +} + } diff --git a/src/tl/tlSystemPaths.h b/src/tl/tlSystemPaths.h index 16187c8a3..84b83bf86 100644 --- a/src/tl/tlSystemPaths.h +++ b/src/tl/tlSystemPaths.h @@ -66,6 +66,11 @@ TL_PUBLIC void set_klayout_path (const std::vector &path); */ TL_PUBLIC void reset_klayout_path (); +/** + * @brief Gets the package manager URL + */ +TL_PUBLIC std::string salt_mine_url (); + } #endif From 59dadcdd38c6f82b3852ad0beed08585580824b7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 17 Apr 2017 18:22:36 +0200 Subject: [PATCH 36/57] WIP: small bug fixes - LayoutView should accept technology names that are valid only - CellView now also sends an "active cellview changed" event when loading a layout in "replace layout" mode. --- src/laybasic/layCellView.cc | 6 +++++- src/laybasic/layLayoutView.cc | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/laybasic/layCellView.cc b/src/laybasic/layCellView.cc index 86e4bd46d..0736fdb6f 100644 --- a/src/laybasic/layCellView.cc +++ b/src/laybasic/layCellView.cc @@ -217,7 +217,11 @@ void LayoutHandle::set_tech_name (const std::string &tn) { if (tn != m_tech_name) { - m_tech_name = tn; + if (lay::Technologies::instance ()->has_technology (tn)) { + m_tech_name = tn; + } else { + m_tech_name = std::string (); + } technology_changed_event (); } } diff --git a/src/laybasic/layLayoutView.cc b/src/laybasic/layLayoutView.cc index 06c674f3c..c5aa79673 100644 --- a/src/laybasic/layLayoutView.cc +++ b/src/laybasic/layLayoutView.cc @@ -3124,6 +3124,9 @@ LayoutView::load_layout (const std::string &filename, const db::LoadLayoutOption } update_content (); + // this event may not be generated otherwise: + active_cellview_changed (cv_index); + return cv_index; } From b3abb276a47efc35dfb7accc3bd5d7f3fdb0e243 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 17 Apr 2017 18:24:59 +0200 Subject: [PATCH 37/57] WIP: integration of salt controller for macros This enables defintion of macros within salt packages (this is probably the most important application). --- src/lay/layMacro.h | 5 +- src/lay/layMacroController.cc | 138 +++++++++++++++--------- src/lay/layMacroController.h | 43 +++++++- src/lay/laySalt.cc | 23 +++- src/lay/laySaltController.h | 16 +++ src/lay/laySaltGrain.cc | 4 +- src/lay/laySaltGrainPropertiesDialog.cc | 7 +- src/lay/laySaltManagerDialog.cc | 4 +- 8 files changed, 174 insertions(+), 66 deletions(-) diff --git a/src/lay/layMacro.h b/src/lay/layMacro.h index 63114f6b9..0b6d0b9a7 100644 --- a/src/lay/layMacro.h +++ b/src/lay/layMacro.h @@ -643,10 +643,11 @@ public: /** * @brief Some constants for virtual_mode */ - enum { + enum FolderType { NotVirtual = 0, ProjectFolder = 1, - TechFolder = 2 + TechFolder = 2, + SaltFolder = 3 }; /** diff --git a/src/lay/layMacroController.cc b/src/lay/layMacroController.cc index ddc68d15e..564591c53 100644 --- a/src/lay/layMacroController.cc +++ b/src/lay/layMacroController.cc @@ -22,6 +22,7 @@ #include "layMacroController.h" #include "layTechnologyController.h" +#include "laySaltController.h" #include "layMacroEditorDialog.h" #include "layMacroInterpreter.h" #include "layMainWindow.h" @@ -59,17 +60,12 @@ MacroController::load () m_macro_categories.push_back (std::pair ("drc", tl::to_string (QObject::tr ("DRC")))); // Scan for macros and set interpreter path - for (std::vector > > >::const_iterator p = m_paths.begin (); p != m_paths.end (); ++p) { - - std::string path = p->first; - std::string description = p->second.first; - std::string cat = p->second.second.first; - bool readonly = p->second.second.second; + for (std::vector ::const_iterator p = m_internal_paths.begin (); p != m_internal_paths.end (); ++p) { for (size_t c = 0; c < m_macro_categories.size (); ++c) { - if (cat.empty () || cat == m_macro_categories [c].first) { - std::string mp = tl::to_string (QDir (tl::to_qstring (p->first)).filePath (tl::to_qstring (m_macro_categories [c].first))); - lay::MacroCollection::root ().add_folder (description, mp, m_macro_categories [c].first, readonly); + if (p->cat.empty () || p->cat == m_macro_categories [c].first) { + std::string mp = tl::to_string (QDir (tl::to_qstring (p->path)).absoluteFilePath (tl::to_qstring (m_macro_categories [c].first))); + lay::MacroCollection::root ().add_folder (p->description, mp, m_macro_categories [c].first, p->readonly); } } @@ -93,6 +89,9 @@ MacroController::initialized (lay::PluginRoot *root) connect (lay::TechnologyController::instance (), SIGNAL (active_technology_changed ()), this, SLOT (update_menu_with_macros ())); connect (lay::TechnologyController::instance (), SIGNAL (technologies_edited ()), this, SLOT (technologies_edited ())); } + if (lay::SaltController::instance ()) { + connect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (salt_changed ())); + } // update the menus with the macro menu bindings as late as possible (now we // can be sure that the menus are created propertly) @@ -108,6 +107,9 @@ MacroController::uninitialize (lay::PluginRoot * /*root*/) disconnect (lay::TechnologyController::instance (), SIGNAL (active_technology_changed ()), this, SLOT (update_menu_with_macros ())); disconnect (lay::TechnologyController::instance (), SIGNAL (technologies_edited ()), this, SLOT (technologies_edited ())); } + if (lay::SaltController::instance ()) { + disconnect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (salt_changed ())); + } delete mp_macro_editor; mp_macro_editor = 0; @@ -279,32 +281,62 @@ MacroController::sync_implicit_macros (bool check_autorun) return; } - std::set > tech_macro_paths; - std::map, std::string> tech_names_by_path; + std::vector external_paths; // Add additional places where the technologies define some macros - for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { - if (t->base_path ().empty ()) { - continue; + std::map > tech_names_by_path; + + for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { + if (! t->base_path ().empty ()) { + QDir base_dir (tl::to_qstring (t->base_path ())); + if (base_dir.exists ()) { + tech_names_by_path [tl::to_string (base_dir.absolutePath ())].push_back (t->name ()); + } } + } + + for (std::map >::const_iterator t = tech_names_by_path.begin (); t != tech_names_by_path.end (); ++t) { for (size_t c = 0; c < macro_categories ().size (); ++c) { - QDir base_dir (tl::to_qstring (t->base_path ())); - if (base_dir.exists ()) { + QDir base_dir (tl::to_qstring (t->first)); + QDir macro_dir (base_dir.filePath (tl::to_qstring (macro_categories () [c].first))); + if (macro_dir.exists ()) { + std::string description; + if (t->second.size () == 1) { + description = tl::to_string (tr ("Technology %1").arg (tl::to_qstring (t->second.front ()))); + } else { + description = tl::to_string (tr ("Technologies %1").arg (tl::to_qstring (tl::join (t->second, ",")))); + } + + external_paths.push_back (ExternalPathDescriptor (tl::to_string (macro_dir.path ()), description, macro_categories () [c].first, lay::MacroCollection::TechFolder)); + + } + + } + + } + + // Add additional places where the salt defines macros + + lay::SaltController *sc = lay::SaltController::instance (); + if (sc) { + + lay::Salt &salt = sc->salt (); + for (lay::Salt::flat_iterator i = salt.begin_flat (); i != salt.end_flat (); ++i) { + + const lay::SaltGrain *g = *i; + + for (size_t c = 0; c < macro_categories ().size (); ++c) { + + QDir base_dir (tl::to_qstring (g->path ())); QDir macro_dir (base_dir.filePath (tl::to_qstring (macro_categories () [c].first))); if (macro_dir.exists ()) { - std::string mp = tl::to_string (macro_dir.path ()); - std::pair cp (macro_categories () [c].first, mp); - tech_macro_paths.insert (cp); - std::string &tn = tech_names_by_path [cp]; - if (! tn.empty ()) { - tn += ","; - } - tn += t->name (); + std::string description = tl::to_string (tr ("Package %1").arg (tl::to_qstring (g->name ()))); + external_paths.push_back (ExternalPathDescriptor (tl::to_string (macro_dir.path ()), description, macro_categories () [c].first, lay::MacroCollection::SaltFolder)); } @@ -315,27 +347,28 @@ MacroController::sync_implicit_macros (bool check_autorun) } // delete macro collections which are no longer required or update description + std::vector folders_to_delete; - std::string desc_prefix = tl::to_string (QObject::tr ("Technology")) + " - "; + + // determine the paths currently in use + std::map used_folders_by_path; + for (std::vector::const_iterator p = m_external_paths.begin (); p != m_external_paths.end (); ++p) { + used_folders_by_path.insert (std::make_pair (p->path, p.operator-> ())); + } lay::MacroCollection *root = &lay::MacroCollection::root (); for (lay::MacroCollection::child_iterator m = root->begin_children (); m != root->end_children (); ++m) { - - std::pair cp (m->second->category (), m->second->path ()); - if (m->second->virtual_mode () == lay::MacroCollection::TechFolder && m_tech_macro_paths.find (cp) != m_tech_macro_paths.end ()) { - - if (tech_macro_paths.find (cp) == tech_macro_paths.end ()) { + if (m->second->virtual_mode () == lay::MacroCollection::TechFolder || + m->second->virtual_mode () == lay::MacroCollection::SaltFolder) { + std::map::const_iterator u = used_folders_by_path.find (m->second->path ()); + if (u == used_folders_by_path.end ()) { // no longer used folders_to_delete.push_back (m->second); } else { - // used: update description if required - std::string desc = desc_prefix + tech_names_by_path [cp]; - m->second->set_description (desc); + m->second->set_description (u->second->description); } - } - } for (std::vector::iterator m = folders_to_delete.begin (); m != folders_to_delete.end (); ++m) { @@ -346,36 +379,31 @@ MacroController::sync_implicit_macros (bool check_autorun) } // store new paths - m_tech_macro_paths = tech_macro_paths; + m_external_paths = external_paths; // add new folders - for (lay::MacroCollection::child_iterator m = root->begin_children (); m != root->end_children (); ++m) { - if (m->second->virtual_mode () == lay::MacroCollection::TechFolder) { - std::pair cp (m->second->category (), m->second->path ()); - tech_macro_paths.erase (cp); - } - } std::vector new_folders; - for (std::set >::const_iterator p = tech_macro_paths.begin (); p != tech_macro_paths.end (); ++p) { + for (std::vector::const_iterator p = m_external_paths.begin (); p != m_external_paths.end (); ++p) { - const std::string &tn = tech_names_by_path [*p]; + if (used_folders_by_path.find (p->path) != used_folders_by_path.end ()) { + continue; + } - // TODO: is it wise to make it writeable? if (tl::verbosity () >= 20) { - tl::info << "Adding macro folder " << p->second << ", category '" << p->first << "' for technologies " << tn; + tl::info << "Adding macro folder " << p->path << ", category '" << p->cat << "' for '" << p->description << "'"; } // Add the folder. Note: it may happen that a macro folder for the tech specific macros already exists in // a non-tech context. // In that case, the add_folder method will return 0. - lay::MacroCollection *mc = lay::MacroCollection::root ().add_folder (desc_prefix + tn, p->second, p->first, false); + + // TODO: is it wise to make this writeable? + lay::MacroCollection *mc = lay::MacroCollection::root ().add_folder (p->description, p->path, p->cat, false); if (mc) { - - mc->set_virtual_mode (lay::MacroCollection::TechFolder); + mc->set_virtual_mode (p->type); new_folders.push_back (mc); - } } @@ -407,7 +435,7 @@ MacroController::refresh () void MacroController::add_path (const std::string &path, const std::string &description, const std::string &category, bool readonly) { - m_paths.push_back (std::make_pair (path, std::make_pair (description, std::make_pair (category, readonly)))); + m_internal_paths.push_back (InternalPathDescriptor (path, description, category, readonly)); } void @@ -501,6 +529,14 @@ MacroController::add_macro_items_to_menu (lay::MacroCollection &collection, int } } +void +MacroController::salt_changed () +{ + sync_implicit_macros (true); + refresh (); + update_menu_with_macros (); +} + void MacroController::technologies_edited () { diff --git a/src/lay/layMacroController.h b/src/lay/layMacroController.h index 5d6e53697..8f0bf1dde 100644 --- a/src/lay/layMacroController.h +++ b/src/lay/layMacroController.h @@ -172,7 +172,46 @@ public slots: */ void technologies_edited (); + /** + * @brief Called when the salt (packages) got changed + */ + void salt_changed (); + private: + /** + * @brief A structure describing an external macro location + */ + struct ExternalPathDescriptor + { + ExternalPathDescriptor (const std::string &_path, const std::string &_description, const std::string &_cat, lay::MacroCollection::FolderType _type) + : path (_path), description (_description), cat (_cat), type (_type) + { + // .. nothing yet .. + } + + std::string path; + std::string description; + std::string cat; + lay::MacroCollection::FolderType type; + }; + + /** + * @brief A structure describing an internal macro location + */ + struct InternalPathDescriptor + { + InternalPathDescriptor (const std::string &_path, const std::string &_description, const std::string &_cat, bool _readonly) + : path (_path), description (_description), cat (_cat), readonly (_readonly) + { + // .. nothing yet .. + } + + std::string path; + std::string description; + std::string cat; + bool readonly; + }; + lay::MacroEditorDialog *mp_macro_editor; lay::MainWindow *mp_mw; bool m_no_implicit_macros; @@ -180,9 +219,9 @@ private: std::vector m_macro_actions; std::map m_action_to_macro; lay::MacroCollection m_temp_macros; - std::vector< std::pair > > > m_paths; std::vector< std::pair > m_macro_categories; - std::set > m_tech_macro_paths; + std::vector m_internal_paths; + std::vector m_external_paths; void sync_implicit_macros (bool check_autorun); void add_macro_items_to_menu (lay::MacroCollection &collection, int &n, std::set &groups, const lay::Technology *tech, std::vector > *key_bindings); diff --git a/src/lay/laySalt.cc b/src/lay/laySalt.cc index 4b1b2174d..259965378 100644 --- a/src/lay/laySalt.cc +++ b/src/lay/laySalt.cc @@ -29,6 +29,7 @@ #include #include +#include #include namespace lay @@ -395,9 +396,25 @@ Salt::create_grain (const SaltGrain &templ, SaltGrain &target) } else if (! templ.url ().empty ()) { - // otherwise download from the URL - tl::info << QObject::tr ("Downloading package from '%1' to '%2' ..").arg (tl::to_qstring (templ.url ())).arg (tl::to_qstring (target.path ())); - res = tl::WebDAVObject::download (templ.url (), target.path ()); + if (templ.url ().find ("http:") == 0 || templ.url ().find ("https:") == 0) { + + // otherwise download from the URL + tl::info << QObject::tr ("Downloading package from '%1' to '%2' ..").arg (tl::to_qstring (templ.url ())).arg (tl::to_qstring (target.path ())); + res = tl::WebDAVObject::download (templ.url (), target.path ()); + + } else { + + // or copy from a file path for "file" URL's + std::string src = templ.url (); + if (src.find ("file:") == 0) { + QUrl url (tl::to_qstring (src)); + src = tl::to_string (QFileInfo (url.toLocalFile ()).absoluteFilePath ()); + } + + tl::info << QObject::tr ("Copying package from '%1' to '%2' ..").arg (tl::to_qstring (src)).arg (tl::to_qstring (target.path ())); + res = tl::cp_dir_recursive (src, target.path ()); + + } target.set_url (templ.url ()); diff --git a/src/lay/laySaltController.h b/src/lay/laySaltController.h index 2e310f31a..10f44648f 100644 --- a/src/lay/laySaltController.h +++ b/src/lay/laySaltController.h @@ -124,6 +124,22 @@ public: */ void set_salt_mine_url (const std::string &url); + /** + * @brief Gets the salt + */ + lay::Salt &salt () + { + return m_salt; + } + + /** + * @brief Gets the salt (const version) + */ + const lay::Salt &salt () const + { + return m_salt; + } + /** * @brief Gets the singleton instance for this object */ diff --git a/src/lay/laySaltGrain.cc b/src/lay/laySaltGrain.cc index 63c7f5689..e3fe54985 100644 --- a/src/lay/laySaltGrain.cc +++ b/src/lay/laySaltGrain.cc @@ -192,6 +192,7 @@ SaltGrain::spec_url (const std::string &url) { std::string res = url; if (! res.empty()) { + // TODO: use system path separator unless this is a URL if (res [res.size () - 1] != '/') { res += "/"; } @@ -396,8 +397,7 @@ SaltGrain::from_url (const std::string &url) throw tl::Exception (tl::to_string (QObject::tr ("No download link available"))); } - tl::InputHttpStream http (SaltGrain::spec_url (url)); - tl::InputStream stream (http); + tl::InputStream stream (SaltGrain::spec_url (url)); SaltGrain g; g.load (stream); diff --git a/src/lay/laySaltGrainPropertiesDialog.cc b/src/lay/laySaltGrainPropertiesDialog.cc index d78ee94bf..c3ce4244c 100644 --- a/src/lay/laySaltGrainPropertiesDialog.cc +++ b/src/lay/laySaltGrainPropertiesDialog.cc @@ -527,10 +527,11 @@ SaltGrainPropertiesDialog::accept () // doc URL doc_url_alert->clear (); if (! m_grain.doc_url ().empty ()) { - tl::InputHttpStream stream (m_grain.doc_url ()); + tl::InputStream stream (m_grain.doc_url ()); try { - char b; - stream.read (&b, 1); + if (! stream.get (1)) { + throw tl::Exception (tl::to_string (tr ("Empty document"))); + } } catch (tl::Exception &ex) { doc_url_alert->error () << tr ("Attempt to read documentation URL failed. Error details follow.") << tl::endl << tr ("URL: ") << m_grain.doc_url () << tl::endl diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index b12e8b11d..346d5adcc 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -28,7 +28,6 @@ #include "ui_SaltGrainTemplateSelectionDialog.h" #include "tlString.h" #include "tlExceptions.h" -#include "tlHttpStream.h" #include #include @@ -560,8 +559,7 @@ BEGIN_PROTECTED QApplication::processEvents (QEventLoop::ExcludeUserInputEvents); - tl::InputHttpStream http (SaltGrain::spec_url (g->url ())); - tl::InputStream stream (http); + tl::InputStream stream (SaltGrain::spec_url (g->url ())); m_remote_grain.reset (new SaltGrain ()); m_remote_grain->load (stream); From 4460819a6c0525d91820e9eac2627f7a607bdf56 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 18 Apr 2017 01:07:10 +0200 Subject: [PATCH 38/57] Macro editor now acts on more file changes - Adding/removing folders to packages now will make them appear/disappear automatically in the macro editor. - Adding and removing files from folders will make them appear/disappear in the macro tree. --- src/lay/layMacro.cc | 111 +++++++++++++++++++++-- src/lay/layMacro.h | 12 ++- src/lay/layMacroController.cc | 131 +++++++++++++++++---------- src/lay/layMacroController.h | 26 +++--- src/lay/layMacroEditorDialog.cc | 156 ++++++++++---------------------- src/lay/layMacroEditorDialog.h | 4 +- src/lay/laySaltController.cc | 45 ++++++++- src/lay/laySaltController.h | 14 +++ 8 files changed, 321 insertions(+), 178 deletions(-) diff --git a/src/lay/layMacro.cc b/src/lay/layMacro.cc index 2a062b540..7c180cdaf 100644 --- a/src/lay/layMacro.cc +++ b/src/lay/layMacro.cc @@ -1173,7 +1173,7 @@ MacroCollection::make_readonly (bool f) } MacroCollection * -MacroCollection::add_folder (const std::string &description, const std::string &path, const std::string &cat, bool readonly) +MacroCollection::add_folder (const std::string &description, const std::string &path, const std::string &cat, bool readonly, bool force_create) { if (! path.empty () && path[0] == ':') { readonly = true; @@ -1183,15 +1183,25 @@ MacroCollection::add_folder (const std::string &description, const std::string & if (! file_info.exists ()) { - // Try to create the folder since it does not exist yet - if (tl::verbosity () >= 20) { - tl::log << "Folder does not exist yet - trying to create it: " << path; - } - if (! QDir::root ().mkpath (file_info.absoluteFilePath ())) { - if (tl::verbosity () >= 10) { - tl::error << "Unable to create folder path: " << path; + // Try to create the folder since it does not exist yet or skip that one + if (! force_create) { + + if (tl::verbosity () >= 20) { + tl::log << "Folder does not exist - skipping: " << path; } return 0; + + } else { + + if (tl::verbosity () >= 20) { + tl::log << "Folder does not exist yet - trying to create it: " << path; + } + if (! QDir::root ().mkpath (file_info.absoluteFilePath ())) { + if (tl::verbosity () >= 10) { + tl::error << "Unable to create folder path: " << path; + } + return 0; + } } file_info.refresh (); @@ -1666,6 +1676,91 @@ MacroCollection &MacroCollection::root () return ms_root; } +static bool sync_macros (lay::MacroCollection *current, lay::MacroCollection *actual) +{ + bool ret = false; + + if (actual) { + current->make_readonly (actual->is_readonly ()); + } + + std::vector folders_to_delete; + + for (lay::MacroCollection::child_iterator m = current->begin_children (); m != current->end_children (); ++m) { + lay::MacroCollection *cm = actual ? actual->folder_by_name (m->first) : 0; + if (! cm) { + folders_to_delete.push_back (m->second); + } + } + + if (actual) { + for (lay::MacroCollection::child_iterator m = actual->begin_children (); m != actual->end_children (); ++m) { + lay::MacroCollection *cm = current->folder_by_name (m->first); + if (! cm) { + cm = current->create_folder (m->first.c_str (), false); + ret = true; + } + if (sync_macros(cm, m->second)) { + ret = true; + } + } + } + + // delete folders which do no longer exist + for (std::vector::iterator m = folders_to_delete.begin (); m != folders_to_delete.end (); ++m) { + ret = true; + sync_macros (*m, 0); + current->erase (*m); + } + + std::vector macros_to_delete; + + for (lay::MacroCollection::iterator m = current->begin (); m != current->end (); ++m) { + lay::Macro *cm = actual ? actual->macro_by_name (m->first, m->second->format ()) : 0; + if (! cm) { + macros_to_delete.push_back (m->second); + } + } + + if (actual) { + for (lay::MacroCollection::iterator m = actual->begin (); m != actual->end (); ++m) { + lay::Macro *cm = current->macro_by_name (m->first, m->second->format ()); + if (cm) { + if (*cm != *m->second) { + cm->assign (*m->second); + } + cm->set_readonly (m->second->is_readonly ()); + } else { + cm = current->create (m->first.c_str (), m->second->format ()); + cm->assign (*m->second); + cm->set_readonly (m->second->is_readonly ()); + ret = true; + } + } + } + + // erase macros from collection which are no longer used + for (std::vector::const_iterator m = macros_to_delete.begin (); m != macros_to_delete.end (); ++m) { + current->erase (*m); + ret = true; + } + + return ret; +} + +void MacroCollection::reload () +{ + // create a new collection and synchronize + + lay::MacroCollection new_collection; + for (lay::MacroCollection::child_iterator c = begin_children (); c != end_children (); ++c) { + new_collection.add_folder (c->second->description (), c->second->path (), c->second->category (), c->second->is_readonly (), false /* don't force to create */); + } + + // and synchronize current with the actual one + sync_macros (this, &new_collection); +} + static bool has_autorun_for (const lay::MacroCollection &collection, bool early) { for (lay::MacroCollection::const_child_iterator c = collection.begin_children (); c != collection.end_children (); ++c) { diff --git a/src/lay/layMacro.h b/src/lay/layMacro.h index 0b6d0b9a7..46b7c6982 100644 --- a/src/lay/layMacro.h +++ b/src/lay/layMacro.h @@ -666,8 +666,11 @@ public: * @brief Add a folder (will also scan the folder) * * @return A pointer to the new collection if successful + * + * If force_create is true (the default), the folder will be created if it does not + * exist yet. On error, 0 is returned. */ - MacroCollection *add_folder (const std::string &description, const std::string &path, const std::string &category, bool readonly); + MacroCollection *add_folder (const std::string &description, const std::string &path, const std::string &category, bool readonly, bool force_create = true); /** * @brief Gets the category tag of the collection @@ -998,6 +1001,13 @@ public: */ void rescan (); + /** + * @brief Reloads the macro collection + * + * This method is similar to rescan, but it will also remove folders and macros. + */ + void reload (); + /** * @brief Gets the root of the macro hierarchy corresponding to the configuration space */ diff --git a/src/lay/layMacroController.cc b/src/lay/layMacroController.cc index 564591c53..12c44901f 100644 --- a/src/lay/layMacroController.cc +++ b/src/lay/layMacroController.cc @@ -38,11 +38,13 @@ namespace lay { MacroController::MacroController () - : mp_macro_editor (0), mp_mw (0), m_no_implicit_macros (false), - dm_do_update_menu_with_macros (this, &MacroController::do_update_menu_with_macros) + : mp_macro_editor (0), mp_mw (0), m_no_implicit_macros (false), m_file_watcher (0), + dm_do_update_menu_with_macros (this, &MacroController::do_update_menu_with_macros), + dm_sync_file_watcher (this, &MacroController::sync_file_watcher), + dm_sync_files (this, &MacroController::sync_files) { - connect (&m_temp_macros, SIGNAL (menu_needs_update ()), this, SLOT (update_menu_with_macros ())); - connect (&m_temp_macros, SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ())); + connect (&m_temp_macros, SIGNAL (menu_needs_update ()), this, SLOT (macro_collection_changed ())); + connect (&m_temp_macros, SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (macro_collection_changed ())); } void @@ -70,8 +72,6 @@ MacroController::load () } } - - sync_implicit_macros (false); } void @@ -83,32 +83,48 @@ MacroController::initialized (lay::PluginRoot *root) mp_macro_editor->setModal (false); } - connect (&lay::MacroCollection::root (), SIGNAL (menu_needs_update ()), this, SLOT (update_menu_with_macros ())); - connect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ())); + if (! m_file_watcher) { + m_file_watcher = new tl::FileSystemWatcher (this); + connect (m_file_watcher, SIGNAL (fileChanged (const QString &)), this, SLOT (file_watcher_triggered ())); + connect (m_file_watcher, SIGNAL (fileRemoved (const QString &)), this, SLOT (file_watcher_triggered ())); + } + + connect (&lay::MacroCollection::root (), SIGNAL (menu_needs_update ()), this, SLOT (macro_collection_changed ())); + connect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (macro_collection_changed ())); if (lay::TechnologyController::instance ()) { - connect (lay::TechnologyController::instance (), SIGNAL (active_technology_changed ()), this, SLOT (update_menu_with_macros ())); - connect (lay::TechnologyController::instance (), SIGNAL (technologies_edited ()), this, SLOT (technologies_edited ())); + connect (lay::TechnologyController::instance (), SIGNAL (active_technology_changed ()), this, SLOT (macro_collection_changed ())); + connect (lay::TechnologyController::instance (), SIGNAL (technologies_edited ()), this, SLOT (sync_with_external_sources ())); } if (lay::SaltController::instance ()) { - connect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (salt_changed ())); + connect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (sync_with_external_sources ())); } + // synchronize the macro collection with all external sources + sync_implicit_macros (false); + // update the menus with the macro menu bindings as late as possible (now we // can be sure that the menus are created propertly) - do_update_menu_with_macros (); + macro_collection_changed (); } void MacroController::uninitialize (lay::PluginRoot * /*root*/) { - disconnect (&lay::MacroCollection::root (), SIGNAL (menu_needs_update ()), this, SLOT (update_menu_with_macros ())); - disconnect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ())); + disconnect (&lay::MacroCollection::root (), SIGNAL (menu_needs_update ()), this, SLOT (macro_collection_changed ())); + disconnect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (macro_collection_changed ())); if (lay::TechnologyController::instance ()) { - disconnect (lay::TechnologyController::instance (), SIGNAL (active_technology_changed ()), this, SLOT (update_menu_with_macros ())); - disconnect (lay::TechnologyController::instance (), SIGNAL (technologies_edited ()), this, SLOT (technologies_edited ())); + disconnect (lay::TechnologyController::instance (), SIGNAL (active_technology_changed ()), this, SLOT (macro_collection_changed ())); + disconnect (lay::TechnologyController::instance (), SIGNAL (technologies_edited ()), this, SLOT (sync_with_external_sources ())); } if (lay::SaltController::instance ()) { - disconnect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (salt_changed ())); + disconnect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (sync_with_external_sources ())); + } + + if (m_file_watcher) { + disconnect (m_file_watcher, SIGNAL (fileChanged (const QString &)), this, SLOT (file_watcher_triggered ())); + disconnect (m_file_watcher, SIGNAL (fileRemoved (const QString &)), this, SLOT (file_watcher_triggered ())); + delete m_file_watcher; + m_file_watcher = 0; } delete mp_macro_editor; @@ -238,9 +254,6 @@ MacroController::drop_url (const std::string &path_or_url) macro->save (); - // refresh macro editor to show new macro plus to install the menus - refresh (); - } } else { @@ -350,10 +363,16 @@ MacroController::sync_implicit_macros (bool check_autorun) std::vector folders_to_delete; + // determine the paths that will be in use + std::map new_folders_by_path; + for (std::vector::const_iterator p = external_paths.begin (); p != external_paths.end (); ++p) { + new_folders_by_path.insert (std::make_pair (p->path, p.operator-> ())); + } + // determine the paths currently in use - std::map used_folders_by_path; + std::map prev_folders_by_path; for (std::vector::const_iterator p = m_external_paths.begin (); p != m_external_paths.end (); ++p) { - used_folders_by_path.insert (std::make_pair (p->path, p.operator-> ())); + prev_folders_by_path.insert (std::make_pair (p->path, p.operator-> ())); } lay::MacroCollection *root = &lay::MacroCollection::root (); @@ -361,8 +380,8 @@ MacroController::sync_implicit_macros (bool check_autorun) for (lay::MacroCollection::child_iterator m = root->begin_children (); m != root->end_children (); ++m) { if (m->second->virtual_mode () == lay::MacroCollection::TechFolder || m->second->virtual_mode () == lay::MacroCollection::SaltFolder) { - std::map::const_iterator u = used_folders_by_path.find (m->second->path ()); - if (u == used_folders_by_path.end ()) { + std::map::const_iterator u = new_folders_by_path.find (m->second->path ()); + if (u == new_folders_by_path.end ()) { // no longer used folders_to_delete.push_back (m->second); } else { @@ -387,7 +406,7 @@ MacroController::sync_implicit_macros (bool check_autorun) for (std::vector::const_iterator p = m_external_paths.begin (); p != m_external_paths.end (); ++p) { - if (used_folders_by_path.find (p->path) != used_folders_by_path.end ()) { + if (prev_folders_by_path.find (p->path) != prev_folders_by_path.end ()) { continue; } @@ -424,14 +443,6 @@ MacroController::sync_implicit_macros (bool check_autorun) } } -void -MacroController::refresh () -{ - if (mp_macro_editor) { - mp_macro_editor->refresh (); - } -} - void MacroController::add_path (const std::string &path, const std::string &description, const std::string &category, bool readonly) { @@ -530,27 +541,22 @@ MacroController::add_macro_items_to_menu (lay::MacroCollection &collection, int } void -MacroController::salt_changed () +MacroController::sync_with_external_sources () { - sync_implicit_macros (true); - refresh (); - update_menu_with_macros (); + try { + sync_implicit_macros (true); + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + } } void -MacroController::technologies_edited () -{ - sync_implicit_macros (true); - refresh (); - update_menu_with_macros (); -} - -void -MacroController::update_menu_with_macros () +MacroController::macro_collection_changed () { // empty action to macro table now we know it's invalid m_action_to_macro.clear (); dm_do_update_menu_with_macros (); + dm_sync_file_watcher (); } void @@ -597,6 +603,39 @@ MacroController::do_update_menu_with_macros () } } +void +MacroController::file_watcher_triggered () +{ + dm_sync_files (); +} + +static void +add_collections_to_file_watcher (const lay::MacroCollection &collection, tl::FileSystemWatcher *watcher) +{ + for (lay::MacroCollection::const_child_iterator c = collection.begin_children (); c != collection.end_children (); ++c) { + if (! c->second->path ().empty () && c->second->path ()[0] != ':') { + watcher->add_file (c->second->path ()); + add_collections_to_file_watcher (*c->second, watcher); + } + } +} + +void +MacroController::sync_file_watcher () +{ + m_file_watcher->clear (); + m_file_watcher->enable (false); + add_collections_to_file_watcher (lay::MacroCollection::root (), m_file_watcher); + m_file_watcher->enable (true); +} + +void +MacroController::sync_files () +{ + tl::log << tl::to_string (tr ("Detected file system change in macro folders - updating")); + lay::MacroCollection::root ().reload (); +} + MacroController * MacroController::instance () { diff --git a/src/lay/layMacroController.h b/src/lay/layMacroController.h index 8f0bf1dde..f888f983e 100644 --- a/src/lay/layMacroController.h +++ b/src/lay/layMacroController.h @@ -29,6 +29,7 @@ #include "layMacro.h" #include "tlObject.h" #include "tlDeferredExecution.h" +#include "tlFileSystemWatcher.h" #include #include @@ -120,11 +121,6 @@ public: */ void show_editor (const std::string &cat = std::string (), bool force_add = false); - /** - * @brief Reloads all macros from the paths registered - */ - void refresh (); - /** * @brief Adds a search path to the macros * After adding the paths, "load" needs to be called to actually load the macros. @@ -163,19 +159,20 @@ public: public slots: /** - * @brief Update the menu with macros bound to a menu + * @brief Updates the menu with macros bound to a menu */ - void update_menu_with_macros (); + void macro_collection_changed (); /** - * @brief Called when the technologies got changed + * @brief Called when the technologies or the salt got changed */ - void technologies_edited (); + void sync_with_external_sources (); +private slots: /** - * @brief Called when the salt (packages) got changed + * @brief Called when the file watcher detects a change in the file system */ - void salt_changed (); + void file_watcher_triggered (); private: /** @@ -215,17 +212,22 @@ private: lay::MacroEditorDialog *mp_macro_editor; lay::MainWindow *mp_mw; bool m_no_implicit_macros; - tl::DeferredMethod dm_do_update_menu_with_macros; std::vector m_macro_actions; std::map m_action_to_macro; lay::MacroCollection m_temp_macros; std::vector< std::pair > m_macro_categories; std::vector m_internal_paths; std::vector m_external_paths; + tl::FileSystemWatcher *m_file_watcher; + tl::DeferredMethod dm_do_update_menu_with_macros; + tl::DeferredMethod dm_sync_file_watcher; + tl::DeferredMethod dm_sync_files; void sync_implicit_macros (bool check_autorun); void add_macro_items_to_menu (lay::MacroCollection &collection, int &n, std::set &groups, const lay::Technology *tech, std::vector > *key_bindings); void do_update_menu_with_macros (); + void sync_file_watcher (); + void sync_files (); }; } diff --git a/src/lay/layMacroEditorDialog.cc b/src/lay/layMacroEditorDialog.cc index 58edc972f..3702b2592 100644 --- a/src/lay/layMacroEditorDialog.cc +++ b/src/lay/layMacroEditorDialog.cc @@ -253,7 +253,8 @@ MacroEditorDialog::MacroEditorDialog (QWidget * /*parent*/, lay::MacroCollection m_font_size (0), m_edit_trace_index (-1), m_add_edit_trace_enabled (true), - dm_refresh_file_watcher (this, &MacroEditorDialog::do_refresh_file_watcher) + dm_refresh_file_watcher (this, &MacroEditorDialog::do_refresh_file_watcher), + dm_update_ui_to_run_mode (this, &MacroEditorDialog::do_update_ui_to_run_mode) { // Makes this dialog receive events while progress bars are on - this way we can set breakpoints // during execution of a macro even if anything lengthy is running. @@ -264,6 +265,7 @@ MacroEditorDialog::MacroEditorDialog (QWidget * /*parent*/, lay::MacroCollection connect (mp_root, SIGNAL (macro_changed (Macro *)), this, SLOT (macro_changed (Macro *))); connect (mp_root, SIGNAL (macro_deleted (Macro *)), this, SLOT (macro_deleted (Macro *))); connect (mp_root, SIGNAL (macro_collection_deleted (MacroCollection *)), this, SLOT (macro_collection_deleted (MacroCollection *))); + connect (mp_root, SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (macro_collection_changed (MacroCollection *))); m_categories = lay::MacroController::instance ()->macro_categories (); @@ -391,7 +393,6 @@ MacroEditorDialog::MacroEditorDialog (QWidget * /*parent*/, lay::MacroCollection watchList->addAction (actionDeleteWatches); watchList->addAction (actionClearWatches); - connect (actionAddWatch, SIGNAL (triggered ()), this, SLOT (add_watch ())); connect (actionAddWatch, SIGNAL (triggered ()), this, SLOT (add_watch ())); connect (actionEditWatch, SIGNAL (triggered ()), this, SLOT (edit_watch ())); connect (actionDeleteWatches, SIGNAL (triggered ()), this, SLOT (del_watches ())); @@ -1025,7 +1026,7 @@ MacroEditorDialog::add_edit_trace (bool compress) } MacroEditorPage *page = dynamic_cast (tabWidget->currentWidget ()); - if (! page) { + if (! page || ! page->macro ()) { return; } @@ -1551,17 +1552,16 @@ MacroEditorDialog::macro_collection_deleted (lay::MacroCollection *collection) std::map ::iterator p = m_tab_widgets.find (*mc); if (p != m_tab_widgets.end ()) { + // disable the macro on the page - we'll ask for updates when the file + // watcher becomes active. So long, the macro is "zombie". p->second->connect_macro (0); - tabWidget->blockSignals (true); // blockSignals prevents a reentrant call into set_current of the tree - tabWidget->removeTab (tabWidget->indexOf (p->second)); - tabWidget->blockSignals (false); - delete p->second; m_tab_widgets.erase (p); } } refresh_file_watcher (); + update_ui_to_run_mode (); } void @@ -1573,15 +1573,20 @@ MacroEditorDialog::macro_deleted (lay::Macro *macro) std::map ::iterator page = m_tab_widgets.find (macro); if (page != m_tab_widgets.end ()) { + // disable the macro on the page - we'll ask for updates when the file + // watcher becomes active. So long, the macro is "zombie". page->second->connect_macro (0); - tabWidget->blockSignals (true); // blockSignals prevents a reentrant call into set_current of the tree - tabWidget->removeTab (tabWidget->indexOf (page->second)); - tabWidget->blockSignals (false); - delete page->second; m_tab_widgets.erase (page); } refresh_file_watcher (); + update_ui_to_run_mode (); +} + +void +MacroEditorDialog::macro_collection_changed (lay::MacroCollection * /*collection*/) +{ + refresh_file_watcher (); } void @@ -1624,7 +1629,7 @@ MacroEditorDialog::current_tab_changed (int index) update_ui_to_run_mode (); } -lay::Macro *MacroEditorDialog::create_macro_here(const char *prefix) +lay::Macro *MacroEditorDialog::create_macro_here (const char *prefix) { lay::MacroEditorTree *mt = current_macro_tree (); MacroCollection *collection = mt->current_macro_collection (); @@ -1954,11 +1959,14 @@ MacroEditorDialog::setup_button_clicked () m_save_all_on_run = data.save_all_on_run; - for (std::map::const_iterator f = m_tab_widgets.begin (); f != m_tab_widgets.end (); ++f) { - f->second->set_ntab (m_ntab); - f->second->set_nindent (m_nindent); - f->second->apply_attributes (); - f->second->set_font (m_font_family, m_font_size); + for (int i = 0; i < tabWidget->count (); ++i) { + MacroEditorPage *page = dynamic_cast (tabWidget->widget (i)); + if (page) { + page->set_ntab (m_ntab); + page->set_nindent (m_nindent); + page->apply_attributes (); + page->set_font (m_font_family, m_font_size); + } } // write configuration @@ -2090,14 +2098,16 @@ BEGIN_PROTECTED } MacroEditorPage *page = dynamic_cast (tabWidget->widget (index)); - if (! page || ! page->macro ()) { + if (! page) { delete tabWidget->currentWidget (); return; } - std::map ::iterator p = m_tab_widgets.find (page->macro ()); - if (p != m_tab_widgets.end ()) { - m_tab_widgets.erase (p); + for (std::map ::iterator p = m_tab_widgets.begin (); p != m_tab_widgets.end (); ++p) { + if (p->second == page) { + m_tab_widgets.erase (p); + break; + } } page->connect_macro (0); @@ -2374,7 +2384,7 @@ MacroEditorDialog::file_changed (const QString &path) { m_changed_files.push_back (path); - // Wait a little to let more to allow for more reload requests to collect + // Wait a little to allow for more reload requests to collect m_file_changed_timer->setInterval (300); m_file_changed_timer->start (); } @@ -2419,79 +2429,6 @@ MacroEditorDialog::sync_file_watcher (lay::MacroCollection * /*collection*/) #endif } -bool -MacroEditorDialog::sync_macros (lay::MacroCollection *current, lay::MacroCollection *actual) -{ - bool ret = false; - - if (actual) { - current->make_readonly (actual->is_readonly ()); - } - - std::vector folders_to_delete; - - for (lay::MacroCollection::child_iterator m = current->begin_children (); m != current->end_children (); ++m) { - lay::MacroCollection *cm = actual ? actual->folder_by_name (m->first) : 0; - if (! cm) { - folders_to_delete.push_back (m->second); - } - } - - if (actual) { - for (lay::MacroCollection::child_iterator m = actual->begin_children (); m != actual->end_children (); ++m) { - lay::MacroCollection *cm = current->folder_by_name (m->first); - if (! cm) { - cm = current->create_folder (m->first.c_str (), false); - ret = true; - } - if (sync_macros(cm, m->second)) { - ret = true; - } - } - } - - // delete folders which do no longer exist - for (std::vector::iterator m = folders_to_delete.begin (); m != folders_to_delete.end (); ++m) { - ret = true; - sync_macros (*m, 0); - current->erase (*m); - } - - std::vector macros_to_delete; - - for (lay::MacroCollection::iterator m = current->begin (); m != current->end (); ++m) { - lay::Macro *cm = actual ? actual->macro_by_name (m->first, m->second->format ()) : 0; - if (! cm) { - macros_to_delete.push_back (m->second); - } - } - - if (actual) { - for (lay::MacroCollection::iterator m = actual->begin (); m != actual->end (); ++m) { - lay::Macro *cm = current->macro_by_name (m->first, m->second->format ()); - if (cm) { - if (*cm != *m->second) { - cm->assign (*m->second); - } - cm->set_readonly (m->second->is_readonly ()); - } else { - cm = current->create (m->first.c_str (), m->second->format ()); - cm->assign (*m->second); - cm->set_readonly (m->second->is_readonly ()); - ret = true; - } - } - } - - // erase macros from collection which are no longer used - for (std::vector::const_iterator m = macros_to_delete.begin (); m != macros_to_delete.end (); ++m) { - current->erase (*m); - ret = true; - } - - return ret; -} - void MacroEditorDialog::refresh_file_watcher () { @@ -2519,18 +2456,13 @@ void MacroEditorDialog::reload_macros () { m_file_watcher->clear (); - - lay::MacroCollection new_root; - - // create a new root - for (lay::MacroCollection::child_iterator c = mp_root->begin_children (); c != mp_root->end_children (); ++c) { - new_root.add_folder (c->second->description (), c->second->path (), c->second->category (), c->second->is_readonly ()); + try { + mp_root->reload (); + refresh_file_watcher (); + } catch (...) { + refresh_file_watcher (); + throw; } - - // and synchronize current with the actual one - sync_macros (mp_root, &new_root); - - refresh_file_watcher (); } void @@ -3058,18 +2990,26 @@ MacroEditorDialog::leave_breakpoint_mode () void MacroEditorDialog::update_ui_to_run_mode () +{ + dm_update_ui_to_run_mode (); +} + +void +MacroEditorDialog::do_update_ui_to_run_mode () { double alpha = 0.95; MacroEditorPage *page = dynamic_cast (tabWidget->currentWidget ()); dbgOn->setEnabled (! m_in_exec); - runButton->setEnabled ((! m_in_exec && (mp_run_macro || (page && page->macro ()->interpreter () != lay::Macro::None))) || m_in_breakpoint); - runThisButton->setEnabled ((! m_in_exec && page && page->macro ()->interpreter () != lay::Macro::None) || m_in_breakpoint); + runButton->setEnabled ((! m_in_exec && (mp_run_macro || (page && page->macro () && page->macro ()->interpreter () != lay::Macro::None))) || m_in_breakpoint); + runThisButton->setEnabled ((! m_in_exec && page && page->macro () && page->macro ()->interpreter () != lay::Macro::None) || m_in_breakpoint); singleStepButton->setEnabled (! m_in_exec || m_in_breakpoint); nextStepButton->setEnabled (! m_in_exec || m_in_breakpoint); stopButton->setEnabled (m_in_exec); pauseButton->setEnabled (m_in_exec && ! m_in_breakpoint); + breakpointButton->setEnabled (page && page->macro ()); + clearBreakpointsButton->setEnabled (page && page->macro ()); for (std::vector::const_iterator mt = m_macro_trees.begin (); mt != m_macro_trees.end (); ++mt) { (*mt)->setEditTriggers (m_in_exec ? QAbstractItemView::NoEditTriggers : QAbstractItemView::SelectedClicked); diff --git a/src/lay/layMacroEditorDialog.h b/src/lay/layMacroEditorDialog.h index 2fc328e10..b89615caf 100644 --- a/src/lay/layMacroEditorDialog.h +++ b/src/lay/layMacroEditorDialog.h @@ -193,6 +193,7 @@ private slots: void macro_changed (Macro *macro); void macro_deleted (Macro *macro); void macro_collection_deleted (MacroCollection *collection); + void macro_collection_changed (MacroCollection *collection); void add_watch (); void edit_watch (); void del_watches (); @@ -249,10 +250,10 @@ private: void run (int stop_stack_depth, lay::Macro *macro); lay::Macro *current_run_macro (); void update_ui_to_run_mode (); + void do_update_ui_to_run_mode (); void set_run_macro (lay::Macro *m); void apply_search (bool if_needed); void process_events (QEventLoop::ProcessEventsFlags flags = QEventLoop::AllEvents); - bool sync_macros (lay::MacroCollection *current, lay::MacroCollection *actual); void sync_file_watcher (lay::MacroCollection *current); void do_refresh_file_watcher (); void refresh_file_watcher (); @@ -310,6 +311,7 @@ private: QTimer *m_file_changed_timer; std::vector m_changed_files, m_removed_files; tl::DeferredMethod dm_refresh_file_watcher; + tl::DeferredMethod dm_update_ui_to_run_mode; }; } diff --git a/src/lay/laySaltController.cc b/src/lay/laySaltController.cc index 6b799f835..e0330bd5d 100644 --- a/src/lay/laySaltController.cc +++ b/src/lay/laySaltController.cc @@ -33,14 +33,21 @@ namespace lay static const std::string cfg_salt_manager_window_state ("salt-manager-window-state"); SaltController::SaltController () - : mp_salt_dialog (0), mp_mw (0) + : mp_salt_dialog (0), mp_mw (0), m_file_watcher (0), + dm_sync_file_watcher (this, &SaltController::sync_file_watcher), + dm_sync_files (this, &SaltController::sync_files) { - // .. nothing yet .. } void SaltController::initialized (lay::PluginRoot *root) { + if (! m_file_watcher) { + m_file_watcher = new tl::FileSystemWatcher (this); + connect (m_file_watcher, SIGNAL (fileChanged (const QString &)), this, SLOT (file_watcher_triggered ())); + connect (m_file_watcher, SIGNAL (fileRemoved (const QString &)), this, SLOT (file_watcher_triggered ())); + } + mp_mw = dynamic_cast (root); connect (&m_salt, SIGNAL (collections_changed ()), this, SIGNAL (salt_changed ())); @@ -51,6 +58,13 @@ SaltController::uninitialize (lay::PluginRoot * /*root*/) { disconnect (&m_salt, SIGNAL (collections_changed ()), this, SIGNAL (salt_changed ())); + if (m_file_watcher) { + disconnect (m_file_watcher, SIGNAL (fileChanged (const QString &)), this, SLOT (file_watcher_triggered ())); + disconnect (m_file_watcher, SIGNAL (fileRemoved (const QString &)), this, SLOT (file_watcher_triggered ())); + delete m_file_watcher; + m_file_watcher = 0; + } + delete mp_salt_dialog; mp_salt_dialog = 0; mp_mw = 0; @@ -133,20 +147,47 @@ SaltController::show_editor () mp_mw->config_set (cfg_salt_manager_window_state, lay::save_dialog_state (mp_salt_dialog)); } + sync_file_watcher (); + } } +void +SaltController::sync_file_watcher () +{ + m_file_watcher->clear (); + m_file_watcher->enable (false); + for (lay::Salt::flat_iterator g = m_salt.begin_flat (); g != m_salt.end_flat (); ++g) { + m_file_watcher->add_file ((*g)->path ()); + } + m_file_watcher->enable (true); +} + +void +SaltController::sync_files () +{ + tl::log << tl::to_string (tr ("Detected file system change in packages - updating")); + emit salt_changed (); +} + void SaltController::add_path (const std::string &path) { try { tl::log << tl::to_string (tr ("Scanning %1 for packages").arg (tl::to_qstring (path))); m_salt.add_location (path); + dm_sync_file_watcher (); } catch (tl::Exception &ex) { tl::error << ex.msg (); } } +void +SaltController::file_watcher_triggered () +{ + dm_sync_files (); +} + void SaltController::set_salt_mine_url (const std::string &url) { diff --git a/src/lay/laySaltController.h b/src/lay/laySaltController.h index 10f44648f..e1687f667 100644 --- a/src/lay/laySaltController.h +++ b/src/lay/laySaltController.h @@ -27,6 +27,8 @@ #include "layCommon.h" #include "layPlugin.h" #include "laySalt.h" +#include "tlFileSystemWatcher.h" +#include "tlDeferredExecution.h" #include #include @@ -145,6 +147,12 @@ public: */ static SaltController *instance (); +private slots: + /** + * @brief Called when the file watcher detects a change in the file system + */ + void file_watcher_triggered (); + signals: /** * @brief This signal is emitted if the salt changed @@ -156,6 +164,12 @@ private: lay::MainWindow *mp_mw; std::string m_salt_mine_url; lay::Salt m_salt, m_salt_mine; + tl::FileSystemWatcher *m_file_watcher; + tl::DeferredMethod dm_sync_file_watcher; + tl::DeferredMethod dm_sync_files; + + void sync_file_watcher (); + void sync_files (); }; } From d5bf24666fcfefcabb4f7b62893a646f8fa8e9ba Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 18 Apr 2017 23:58:24 +0200 Subject: [PATCH 39/57] WIP: technologies now are synced with salt - The technology list now is synched with the salt package manager so it's basically possible to include technologies into packages. This checkin also contains: - A "NoDeferredMethods" class that blocks execution of deferred methods in a region of code (preferably around message boxes). This prevents side effects when message boxes are shown and deferred methods are processed because of this. That prevention method is used in the macro controller when asking whether to execute autorun macros (that happens in slots and must not interfere with deferred methods). It's also used to protect the exception handlers. - The tech manager dialog's refresh function used to crash because of an invalid tech pointer. --- src/lay/layApplication.cc | 17 ++++++--- src/lay/layMacroController.cc | 11 ++++-- src/lay/laySaltController.cc | 15 ++++++-- src/lay/layTechSetupDialog.cc | 56 ++++++++++++++++++++++++++++-- src/lay/layTechSetupDialog.h | 1 + src/lay/layTechnologyController.cc | 30 ++++++++++++++-- src/lay/layTechnologyController.h | 6 ++++ src/tl/tlDeferredExecution.h | 34 ++++++++++++++++++ 8 files changed, 156 insertions(+), 14 deletions(-) diff --git a/src/lay/layApplication.cc b/src/lay/layApplication.cc index ccd1906c2..3ab77f072 100644 --- a/src/lay/layApplication.cc +++ b/src/lay/layApplication.cc @@ -81,6 +81,9 @@ namespace lay static void ui_exception_handler_tl (const tl::Exception &ex, QWidget *parent) { + // Prevents severe side effects if there are pending deferred methods + tl::NoDeferredMethods silent; + // if any transaction is pending (this may happen when an operation threw an exception) // close transactions. if (lay::MainWindow::instance () && lay::MainWindow::instance ()->manager ().transacting ()) { @@ -123,6 +126,9 @@ static void ui_exception_handler_tl (const tl::Exception &ex, QWidget *parent) static void ui_exception_handler_std (const std::exception &ex, QWidget *parent) { + // Prevents severe side effects if there are pending deferred methods + tl::NoDeferredMethods silent; + // if any transaction is pending (this may happen when an operation threw an exception) // close transactions. if (lay::MainWindow::instance () && lay::MainWindow::instance ()->manager ().transacting ()) { @@ -138,6 +144,9 @@ static void ui_exception_handler_std (const std::exception &ex, QWidget *parent) static void ui_exception_handler_def (QWidget *parent) { + // Prevents severe side effects if there are pending deferred methods + tl::NoDeferredMethods silent; + // if any transaction is pending (this may happen when an operation threw an exception) // close transactions. if (lay::MainWindow::instance () && lay::MainWindow::instance ()->manager ().transacting ()) { @@ -597,10 +606,9 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) if (sc) { - // auto-import technologies + // auto-import salt grains for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { - std::string tp = tl::to_string (QDir (tl::to_qstring (*p)).filePath (QString::fromUtf8 ("salt"))); - sc->add_path (tp); + sc->add_path (*p); } sc->set_salt_mine_url (tl::salt_mine_url ()); @@ -611,8 +619,7 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) // auto-import technologies for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { - std::string tp = tl::to_string (QDir (tl::to_qstring (*p)).filePath (QString::fromUtf8 ("tech"))); - tc->add_path (tp); + tc->add_path (*p); } // import technologies from the command line diff --git a/src/lay/layMacroController.cc b/src/lay/layMacroController.cc index 12c44901f..1b3e3f9f1 100644 --- a/src/lay/layMacroController.cc +++ b/src/lay/layMacroController.cc @@ -429,14 +429,19 @@ MacroController::sync_implicit_macros (bool check_autorun) if (check_autorun) { + // This prevents the message dialog below to issue deferred methods + tl::NoDeferredMethods silent; + bool has_autorun = false; for (std::vector::const_iterator m = new_folders.begin (); m != new_folders.end () && ! has_autorun; ++m) { has_autorun = (*m)->has_autorun (); } - if (has_autorun && QMessageBox::question (mp_mw, QObject::tr ("Run Macros"), QObject::tr ("Some macros associated with new items are configured to run automatically.\n\nChoose 'Yes' to run these macros now. Choose 'No' to not run them."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - for (std::vector::const_iterator m = new_folders.begin (); m != new_folders.end (); ++m) { - (*m)->autorun (); + if (has_autorun) { + if (QMessageBox::question (mp_mw, QObject::tr ("Run Macros"), QObject::tr ("Some macros associated with new items are configured to run automatically.\n\nChoose 'Yes' to run these macros now. Choose 'No' to not run them."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + for (std::vector::const_iterator m = new_folders.begin (); m != new_folders.end (); ++m) { + (*m)->autorun (); + } } } diff --git a/src/lay/laySaltController.cc b/src/lay/laySaltController.cc index e0330bd5d..866e0b00e 100644 --- a/src/lay/laySaltController.cc +++ b/src/lay/laySaltController.cc @@ -27,6 +27,8 @@ #include "layQtTools.h" #include "tlLog.h" +#include + namespace lay { @@ -141,7 +143,11 @@ SaltController::show_editor () } } + // while running the dialog, don't watch file events - that would interfere with + // the changes applied by the dialog itself. + m_file_watcher->enable (false); mp_salt_dialog->exec (); + m_file_watcher->enable (true); if (mp_mw) { mp_mw->config_set (cfg_salt_manager_window_state, lay::save_dialog_state (mp_salt_dialog)); @@ -174,9 +180,14 @@ void SaltController::add_path (const std::string &path) { try { - tl::log << tl::to_string (tr ("Scanning %1 for packages").arg (tl::to_qstring (path))); - m_salt.add_location (path); + + std::string tp = tl::to_string (QDir (tl::to_qstring (path)).filePath (QString::fromUtf8 ("salt"))); + + tl::log << tl::to_string (tr ("Scanning %1 for packages").arg (tl::to_qstring (tp))); + m_salt.add_location (tp); + dm_sync_file_watcher (); + } catch (tl::Exception &ex) { tl::error << ex.msg (); } diff --git a/src/lay/layTechSetupDialog.cc b/src/lay/layTechSetupDialog.cc index 996f69a76..405274266 100644 --- a/src/lay/layTechSetupDialog.cc +++ b/src/lay/layTechSetupDialog.cc @@ -465,7 +465,7 @@ TechMacrosPage::commit () static bool s_first_show = true; TechSetupDialog::TechSetupDialog (QWidget *parent) - : QDialog (parent), mp_current_tech (0), mp_current_editor (0), mp_current_tech_component (0) + : QDialog (parent), mp_current_tech (0), mp_current_editor (0), mp_current_tech_component (0), m_current_tech_changed_enabled (true) { setObjectName (QString::fromUtf8 ("tech_setup_dialog")); @@ -532,12 +532,60 @@ TechSetupDialog::clear_components () void TechSetupDialog::refresh_clicked () { + m_current_tech_changed_enabled = false; + BEGIN_PROTECTED + commit_tech_component (); + update_tech (0); + + std::string tech_name; + if (selected_tech ()) { + tech_name = selected_tech ()->name (); + } + + // Save the expanded state of the items + std::set expanded_techs; + for (int i = 0; i < tech_tree->topLevelItemCount (); ++i) { + QTreeWidgetItem *item = tech_tree->topLevelItem (i); + if (item && item->isExpanded ()) { + QVariant d = item->data (0, Qt::UserRole); + if (d != QVariant ()) { + expanded_techs.insert (tl::to_string (d.toString ())); + } + } + } + lay::TechnologyController::instance ()->rescan (m_technologies); - update (); + + update_tech_tree (); + + QTreeWidgetItem *new_item = 0; + + for (int i = 0; i < tech_tree->topLevelItemCount () && !new_item; ++i) { + QTreeWidgetItem *item = tech_tree->topLevelItem (i); + QVariant d = item->data (0, Qt::UserRole); + if (d != QVariant () && tech_name == tl::to_string (d.toString ())) { + new_item = item; + } + } + + tech_tree->setCurrentItem (new_item); + + // restore the expanded state + for (int i = 0; i < tech_tree->topLevelItemCount (); ++i) { + QTreeWidgetItem *item = tech_tree->topLevelItem (i); + QVariant d = item->data (0, Qt::UserRole); + bool expand = (d != QVariant () && expanded_techs.find (tl::to_string (d.toString ())) != expanded_techs.end ()); + item->setExpanded (expand); + } + + update_tech (selected_tech ()); + update_tech_component (); END_PROTECTED + + m_current_tech_changed_enabled = true; } void @@ -971,6 +1019,10 @@ END_PROTECTED void TechSetupDialog::current_tech_changed (QTreeWidgetItem *current, QTreeWidgetItem *previous) { + if (! m_current_tech_changed_enabled) { + return; + } + BEGIN_PROTECTED try { if (current) { diff --git a/src/lay/layTechSetupDialog.h b/src/lay/layTechSetupDialog.h index 0c65840ae..c4c503899 100644 --- a/src/lay/layTechSetupDialog.h +++ b/src/lay/layTechSetupDialog.h @@ -157,6 +157,7 @@ private: std::map m_technology_components; lay::TechnologyComponentEditor *mp_current_editor; lay::TechnologyComponent *mp_current_tech_component; + bool m_current_tech_changed_enabled; }; class LAY_PUBLIC TechComponentSetupDialog diff --git a/src/lay/layTechnologyController.cc b/src/lay/layTechnologyController.cc index 6178f0944..51f7228b6 100644 --- a/src/lay/layTechnologyController.cc +++ b/src/lay/layTechnologyController.cc @@ -25,6 +25,7 @@ #include "layTechSetupDialog.h" #include "layMainWindow.h" #include "layApplication.h" +#include "laySaltController.h" #include "layConfig.h" #include "layQtTools.h" #include "laybasicConfig.h" @@ -82,6 +83,10 @@ TechnologyController::initialized (lay::PluginRoot * /*root*/) { update_menu (); connect_events (); + + if (lay::SaltController::instance ()) { + connect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (sync_with_external_sources ())); + } } void @@ -89,6 +94,10 @@ TechnologyController::uninitialize (lay::PluginRoot * /*root*/) { m_tech_actions.clear (); tl::Object::detach_from_all_events (); + + if (lay::SaltController::instance ()) { + disconnect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (sync_with_external_sources ())); + } } void @@ -466,6 +475,12 @@ TechnologyController::load () rescan (*lay::Technologies::instance ()); } +void +TechnologyController::sync_with_external_sources () +{ + rescan (*lay::Technologies::instance ()); +} + void TechnologyController::rescan (lay::Technologies &technologies) { @@ -479,7 +494,17 @@ TechnologyController::rescan (lay::Technologies &technologies) } } - for (std::vector::const_iterator p = m_paths.begin (); p != m_paths.end (); ++p) { + std::vector paths = m_paths; + + // add the salt grains as potential sources for tech definitions + lay::SaltController *sc = lay::SaltController::instance (); + if (sc) { + for (lay::Salt::flat_iterator g = sc->salt ().begin_flat (); g != sc->salt ().end_flat (); ++g) { + paths.push_back ((*g)->path ()); + } + } + + for (std::vector::const_iterator p = paths.begin (); p != paths.end (); ++p) { QDir dir (tl::to_qstring (*p)); if (! dir.exists ()) { @@ -540,7 +565,8 @@ TechnologyController::add_temp_tech (const lay::Technology &t) void TechnologyController::add_path (const std::string &p) { - m_paths.push_back (p); + std::string tp = tl::to_string (QDir (tl::to_qstring (p)).filePath (QString::fromUtf8 ("tech"))); + m_paths.push_back (tp); } static tl::RegisteredClass config_decl (new TechnologyController (), 110, "TechnologyController"); diff --git a/src/lay/layTechnologyController.h b/src/lay/layTechnologyController.h index 69fd5da90..1845f08f1 100644 --- a/src/lay/layTechnologyController.h +++ b/src/lay/layTechnologyController.h @@ -119,6 +119,12 @@ signals: */ void technologies_edited (); +private slots: + /** + * @brief Called when the salt got changed + */ + void sync_with_external_sources (); + private: tl::stable_vector m_tech_actions; std::string m_current_technology; diff --git a/src/tl/tlDeferredExecution.h b/src/tl/tlDeferredExecution.h index 2a8c9537f..7e1136cee 100644 --- a/src/tl/tlDeferredExecution.h +++ b/src/tl/tlDeferredExecution.h @@ -140,6 +140,40 @@ private: void do_execute (); }; +/** + * @brief A protected region that ensures that deferred methods are not executed + * + * This class employs the RAII pattern to block a region of code for execution of + * deferred methods. This is useful to protect message boxes against having a side + * effects of issuing deferred method calls: + * + * @code + * { + * tl::NoDeferredMethods block; + * QMessageBox::warning (...); + * } + * @endcode + */ +class TL_PUBLIC NoDeferredMethods +{ +public: + /** + * @brief Constructor + */ + NoDeferredMethods () + { + DeferredMethodScheduler::enable (false); + } + + /** + * @brief Destructor + */ + ~NoDeferredMethods () + { + DeferredMethodScheduler::enable (true); + } +}; + /** * @brief Deferred execution of a const method * From 2dd03c0e37843a75ccb8b62a168314a5a38b2613 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 20 Apr 2017 00:08:31 +0200 Subject: [PATCH 40/57] Removed some artifical styling. --- src/tl/tlString.cc | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/tl/tlString.cc b/src/tl/tlString.cc index 3179df4ac..22d05402f 100644 --- a/src/tl/tlString.cc +++ b/src/tl/tlString.cc @@ -416,20 +416,6 @@ tl::to_word_or_quoted_string (const std::string &s, const char *non_term) void tl::escape_to_html (std::string &out, const std::string &in, bool replace_newlines) { - - - - - - - - - - - - - - for (const char *cp = in.c_str (); *cp; ++cp) { if (*cp == '<') { out += "<"; From 9c2c3301cf14c61c8b22e17699880a27ded043a2 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 20 Apr 2017 00:09:03 +0200 Subject: [PATCH 41/57] File watcher: ignore missing files/folders to prevent 'deleted' events later. --- src/tl/tlFileSystemWatcher.cc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tl/tlFileSystemWatcher.cc b/src/tl/tlFileSystemWatcher.cc index 17c262e0e..58f52eec1 100644 --- a/src/tl/tlFileSystemWatcher.cc +++ b/src/tl/tlFileSystemWatcher.cc @@ -85,11 +85,13 @@ FileSystemWatcher::add_file (const std::string &path) QDateTime time; QFileInfo fi (tl::to_qstring (path)); - if (fi.exists ()) { - size = size_t (fi.size ()); - time = fi.lastModified (); + if (! fi.exists () || ! fi.isReadable ()) { + return; } + size = size_t (fi.size ()); + time = fi.lastModified (); + std::map::iterator i = m_files.find (path); if (i != m_files.end ()) { i->second.refcount += 1; From a7038282cedee16147c8134ca092096815647125 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 20 Apr 2017 00:09:53 +0200 Subject: [PATCH 42/57] WIP: Library controller plus a bugfix The bug was that a crash happened when a package was deleted. Essentially that was an effect of too many message boxes asking to run autorun macros (also a bug - solved by deferred execution of the macro update). This is solved by putting the salt model into a "under construction" state while the model is not updated yet. --- src/lay/lay.pro | 6 +- src/lay/layApplication.cc | 42 ----- src/lay/layLibraryController.cc | 261 ++++++++++++++++++++++++++++++++ src/lay/layLibraryController.h | 132 ++++++++++++++++ src/lay/layMacroController.cc | 13 +- src/lay/layMacroController.h | 4 +- src/lay/laySalt.cc | 21 +-- src/lay/laySalt.h | 5 + src/lay/laySaltManagerDialog.cc | 18 +++ src/lay/laySaltManagerDialog.h | 10 ++ src/lay/laySaltModel.cc | 22 ++- src/lay/laySaltModel.h | 8 + 12 files changed, 479 insertions(+), 63 deletions(-) create mode 100644 src/lay/layLibraryController.cc create mode 100644 src/lay/layLibraryController.h diff --git a/src/lay/lay.pro b/src/lay/lay.pro index f076212b6..ff6ebd3d3 100644 --- a/src/lay/lay.pro +++ b/src/lay/lay.pro @@ -57,7 +57,8 @@ HEADERS = \ laySaltDownloadManager.h \ laySaltModel.h \ laySaltController.h \ - laySignalHandler.h + laySignalHandler.h \ + layLibraryController.h FORMS = \ ClipDialog.ui \ @@ -160,7 +161,8 @@ SOURCES = \ laySaltDownloadManager.cc \ laySaltModel.cc \ laySaltController.cc \ - laySignalHandler.cc + laySignalHandler.cc \ + layLibraryController.cc RESOURCES = layBuildInMacros.qrc \ layHelpResources.qrc \ diff --git a/src/lay/layApplication.cc b/src/lay/layApplication.cc index 3ab77f072..896f28e49 100644 --- a/src/lay/layApplication.cc +++ b/src/lay/layApplication.cc @@ -1055,48 +1055,6 @@ Application::run () } - // scan for libraries - for (std::vector ::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { - - QDir lp = QDir (tl::to_qstring (*p)).filePath (tl::to_qstring ("libraries")); - - QStringList name_filters; - name_filters << QString::fromUtf8 ("*"); - - QStringList libs = lp.entryList (name_filters, QDir::Files); - for (QStringList::const_iterator im = libs.begin (); im != libs.end (); ++im) { - - std::string filename = tl::to_string (*im); - - try { - - std::auto_ptr lib (new db::Library ()); - lib->set_description (filename); - lib->set_name (tl::to_string (QFileInfo (*im).baseName ())); - - tl::log << "Reading library '" << filename << "'"; - tl::InputStream stream (tl::to_string (lp.filePath (*im))); - db::Reader reader (stream); - reader.read (lib->layout ()); - - // Use the libname if there is one - for (db::Layout::meta_info_iterator m = lib->layout ().begin_meta (); m != lib->layout ().end_meta (); ++m) { - if (m->name == "libname" && ! m->value.empty ()) { - lib->set_name (m->value); - break; - } - } - - db::LibraryManager::instance ().register_lib (lib.release ()); - - } catch (tl::Exception &ex) { - tl::error << ex.msg (); - } - - } - - } - // run all autorun macros lay::MacroCollection::root ().autorun (); diff --git a/src/lay/layLibraryController.cc b/src/lay/layLibraryController.cc new file mode 100644 index 000000000..0548c2c49 --- /dev/null +++ b/src/lay/layLibraryController.cc @@ -0,0 +1,261 @@ + +/* + + 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 "layLibraryController.h" +#include "layApplication.h" +#include "laySaltController.h" +#include "layConfig.h" +#include "layMainWindow.h" +#include "layQtTools.h" +#include "dbLibraryManager.h" +#include "dbLibrary.h" +#include "dbReader.h" +#include "tlLog.h" +#include "tlStream.h" + +#include + +namespace lay +{ + +LibraryController::LibraryController () + : m_file_watcher (0), + dm_sync_files (this, &LibraryController::sync_files) +{ +} + +void +LibraryController::initialize (lay::PluginRoot * /*root*/) +{ + // NOTE: we initialize the libraries in the stage once to have them available for the autorun + // macros. We'll do that later again in order to pull in the libraries from the packages. + sync_files (); +} + +void +LibraryController::initialized (lay::PluginRoot * /*root*/) +{ + if (lay::SaltController::instance ()) { + connect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (sync_with_external_sources ())); + } + + if (! m_file_watcher) { + m_file_watcher = new tl::FileSystemWatcher (this); + connect (m_file_watcher, SIGNAL (fileChanged (const QString &)), this, SLOT (file_watcher_triggered ())); + connect (m_file_watcher, SIGNAL (fileRemoved (const QString &)), this, SLOT (file_watcher_triggered ())); + } + + sync_files (); +} + +void +LibraryController::uninitialize (lay::PluginRoot * /*root*/) +{ + if (m_file_watcher) { + disconnect (m_file_watcher, SIGNAL (fileChanged (const QString &)), this, SLOT (file_watcher_triggered ())); + disconnect (m_file_watcher, SIGNAL (fileRemoved (const QString &)), this, SLOT (file_watcher_triggered ())); + delete m_file_watcher; + m_file_watcher = 0; + } + + if (lay::SaltController::instance ()) { + disconnect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (sync_with_external_sources ())); + } +} + +void +LibraryController::get_options (std::vector < std::pair > & /*options*/) const +{ + // .. nothing yet .. +} + +void +LibraryController::get_menu_entries (std::vector & /*menu_entries*/) const +{ + // .. nothing yet .. +} + +bool +LibraryController::configure (const std::string & /*name*/, const std::string & /*value*/) +{ + return false; +} + +void +LibraryController::config_finalize() +{ + // .. nothing yet .. +} + +bool +LibraryController::can_exit (lay::PluginRoot * /*root*/) const +{ + // .. nothing yet .. + return true; +} + +void +LibraryController::sync_files () +{ + if (! m_file_watcher) { + return; + } + + m_file_watcher->clear (); + m_file_watcher->enable (false); + + std::map > new_lib_files; + + std::vector paths = lay::Application::instance ()->klayout_path (); + + // add the salt grains as potential sources for library definitions + + lay::SaltController *sc = lay::SaltController::instance (); + if (sc) { + for (lay::Salt::flat_iterator g = sc->salt ().begin_flat (); g != sc->salt ().end_flat (); ++g) { + paths.push_back ((*g)->path ()); + } + } + + // scan for libraries + + for (std::vector ::const_iterator p = paths.begin (); p != paths.end (); ++p) { + + QDir lp = QDir (tl::to_qstring (*p)).filePath (tl::to_qstring ("libraries")); + m_file_watcher->add_file (tl::to_string (lp.absolutePath ())); + + QStringList name_filters; + name_filters << QString::fromUtf8 ("*"); + + QStringList libs = lp.entryList (name_filters, QDir::Files); + for (QStringList::const_iterator im = libs.begin (); im != libs.end (); ++im) { + + std::string filename = tl::to_string (*im); + std::string lib_path = tl::to_string (lp.absoluteFilePath (*im)); + + try { + + QFileInfo fi (tl::to_qstring (lib_path)); + + bool needs_load = false; + std::map >::iterator ll = m_lib_files.find (lib_path); + if (ll == m_lib_files.end ()) { + needs_load = true; + } else { + if (fi.lastModified () > ll->second.second) { + needs_load = true; + } else { + new_lib_files.insert (*ll); + } + } + + if (needs_load) { + + std::auto_ptr lib (new db::Library ()); + lib->set_description (filename); + lib->set_name (tl::to_string (QFileInfo (*im).baseName ())); + + tl::log << "Reading library '" << filename << "'"; + tl::InputStream stream (lib_path); + db::Reader reader (stream); + reader.read (lib->layout ()); + + // Use the libname if there is one + for (db::Layout::meta_info_iterator m = lib->layout ().begin_meta (); m != lib->layout ().end_meta (); ++m) { + if (m->name == "libname" && ! m->value.empty ()) { + lib->set_name (m->value); + break; + } + } + + new_lib_files.insert (std::make_pair (lib_path, std::make_pair (lib->get_name (), fi.lastModified ()))); + + db::LibraryManager::instance ().register_lib (lib.release ()); + + } + + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + } + + } + + } + + m_file_watcher->enable (true); + + // remove libraries which are no longer present + + std::set new_names; + + for (std::map >::const_iterator lf = new_lib_files.begin (); lf != new_lib_files.end (); ++lf) { + new_names.insert (lf->second.first); + } + + for (std::map >::const_iterator lf = m_lib_files.begin (); lf != m_lib_files.end (); ++lf) { + if (new_names.find (lf->second.first) == new_names.end ()) { + try { + std::pair li = db::LibraryManager::instance ().lib_by_name (lf->second.first); + if (li.first) { + db::LibraryManager::instance ().delete_lib (db::LibraryManager::instance ().lib (li.second)); + } + } catch (...) { + } + } + } + + // establish the new libraries + + m_lib_files = new_lib_files; +} + +void +LibraryController::sync_with_external_sources () +{ + tl::log << tl::to_string (tr ("Package updates - updating libraries")); + dm_sync_files (); +} + +void +LibraryController::file_watcher_triggered () +{ + tl::log << tl::to_string (tr ("Detected file system change in libraries - updating")); + dm_sync_files (); +} + +LibraryController * +LibraryController::instance () +{ + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + LibraryController *sc = dynamic_cast (cls.operator-> ()); + if (sc) { + return sc; + } + } + return 0; +} + +// The singleton instance of the library controller +static tl::RegisteredClass salt_controller_decl (new lay::LibraryController (), 150, "LibraryController"); + +} + diff --git a/src/lay/layLibraryController.h b/src/lay/layLibraryController.h new file mode 100644 index 000000000..1792bd1ef --- /dev/null +++ b/src/lay/layLibraryController.h @@ -0,0 +1,132 @@ + +/* + + 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_layLibraryController +#define HDR_layLibraryController + +#include "layCommon.h" +#include "layPlugin.h" +#include "tlFileSystemWatcher.h" +#include "tlDeferredExecution.h" + +#include +#include + +#include + +namespace lay +{ + +class LibraryManagerDialog; +class MainWindow; + +/** + * @brief A controller for the libraries + * + * This object is a singleton that acts as a controller + * for the library management. The controller is responsible + * to managing the libraries and notifying library consumers + * of changes. + * + * By making the controller a PluginDeclaration it will receive + * initialization and configuration calls. + */ +class LibraryController + : public lay::PluginDeclaration, public tl::Object +{ +Q_OBJECT + +public: + /** + * @brief Default constructor + */ + LibraryController (); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void initialize (lay::PluginRoot *root); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void initialized (lay::PluginRoot *root); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void uninitialize (lay::PluginRoot *root); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void get_options (std::vector < std::pair > &options) const; + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void get_menu_entries (std::vector &menu_entries) const; + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + bool configure (const std::string &key, const std::string &value); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void config_finalize(); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + bool can_exit (lay::PluginRoot *root) const; + + /** + * @brief Gets the singleton instance for this object + */ + static LibraryController *instance (); + +private slots: + /** + * @brief Called when the file watcher detects a change in the file system + */ + void file_watcher_triggered (); + + /** + * @brief Called when the salt (packages) has changed + */ + void sync_with_external_sources (); + +private: + tl::FileSystemWatcher *m_file_watcher; + tl::DeferredMethod dm_sync_files; + std::map > m_lib_files; + + void sync_files (); +}; + +} + +#endif + diff --git a/src/lay/layMacroController.cc b/src/lay/layMacroController.cc index 1b3e3f9f1..a069e6d1f 100644 --- a/src/lay/layMacroController.cc +++ b/src/lay/layMacroController.cc @@ -40,6 +40,7 @@ namespace lay MacroController::MacroController () : mp_macro_editor (0), mp_mw (0), m_no_implicit_macros (false), m_file_watcher (0), dm_do_update_menu_with_macros (this, &MacroController::do_update_menu_with_macros), + dm_do_sync_with_external_sources (this, &MacroController::do_sync_with_external_sources), dm_sync_file_watcher (this, &MacroController::sync_file_watcher), dm_sync_files (this, &MacroController::sync_files) { @@ -288,7 +289,7 @@ MacroController::enable_implicit_macros (bool enable) } void -MacroController::sync_implicit_macros (bool check_autorun) +MacroController::sync_implicit_macros (bool ask_before_autorun) { if (m_no_implicit_macros) { return; @@ -427,7 +428,7 @@ MacroController::sync_implicit_macros (bool check_autorun) } - if (check_autorun) { + { // This prevents the message dialog below to issue deferred methods tl::NoDeferredMethods silent; @@ -438,7 +439,7 @@ MacroController::sync_implicit_macros (bool check_autorun) } if (has_autorun) { - if (QMessageBox::question (mp_mw, QObject::tr ("Run Macros"), QObject::tr ("Some macros associated with new items are configured to run automatically.\n\nChoose 'Yes' to run these macros now. Choose 'No' to not run them."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (! ask_before_autorun || QMessageBox::question (mp_mw, QObject::tr ("Run Macros"), QObject::tr ("Some macros associated with new items are configured to run automatically.\n\nChoose 'Yes' to run these macros now. Choose 'No' to not run them."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { for (std::vector::const_iterator m = new_folders.begin (); m != new_folders.end (); ++m) { (*m)->autorun (); } @@ -547,6 +548,12 @@ MacroController::add_macro_items_to_menu (lay::MacroCollection &collection, int void MacroController::sync_with_external_sources () +{ + dm_do_sync_with_external_sources (); +} + +void +MacroController::do_sync_with_external_sources () { try { sync_implicit_macros (true); diff --git a/src/lay/layMacroController.h b/src/lay/layMacroController.h index f888f983e..0eaf18d8d 100644 --- a/src/lay/layMacroController.h +++ b/src/lay/layMacroController.h @@ -220,12 +220,14 @@ private: std::vector m_external_paths; tl::FileSystemWatcher *m_file_watcher; tl::DeferredMethod dm_do_update_menu_with_macros; + tl::DeferredMethod dm_do_sync_with_external_sources; tl::DeferredMethod dm_sync_file_watcher; tl::DeferredMethod dm_sync_files; - void sync_implicit_macros (bool check_autorun); + void sync_implicit_macros (bool ask_before_autorun); void add_macro_items_to_menu (lay::MacroCollection &collection, int &n, std::set &groups, const lay::Technology *tech, std::vector > *key_bindings); void do_update_menu_with_macros (); + void do_sync_with_external_sources (); void sync_file_watcher (); void sync_files (); }; diff --git a/src/lay/laySalt.cc b/src/lay/laySalt.cc index 259965378..9def56e49 100644 --- a/src/lay/laySalt.cc +++ b/src/lay/laySalt.cc @@ -49,6 +49,7 @@ Salt::Salt (const Salt &other) Salt &Salt::operator= (const Salt &other) { if (this != &other) { + emit collections_about_to_change (); m_root = other.m_root; invalidate (); } @@ -97,6 +98,7 @@ Salt::add_location (const std::string &path) } lay::SaltGrains gg = lay::SaltGrains::from_path (path); + emit collections_about_to_change (); m_root.add_collection (gg); invalidate (); } @@ -107,6 +109,7 @@ Salt::remove_location (const std::string &path) QFileInfo fi (tl::to_qstring (path)); for (lay::SaltGrains::collection_iterator g = m_root.begin_collections (); g != m_root.end_collections (); ++g) { if (QFileInfo (tl::to_qstring (g->path ())) == fi) { + emit collections_about_to_change (); m_root.remove_collection (g, false); invalidate (); return; @@ -122,6 +125,7 @@ Salt::refresh () new_root.add_collection (lay::SaltGrains::from_path (g->path ())); } if (new_root != m_root) { + emit collections_about_to_change (); m_root = new_root; invalidate (); } @@ -208,21 +212,18 @@ bool remove_from_collection (SaltGrains &collection, const std::string &name) bool Salt::remove_grain (const SaltGrain &grain) { + emit collections_about_to_change (); + tl::info << QObject::tr ("Removing package '%1' ..").arg (tl::to_qstring (grain.name ())); - if (remove_from_collection (m_root, grain.name ())) { - + bool res = remove_from_collection (m_root, grain.name ()); + if (res) { tl::info << QObject::tr ("Package '%1' removed.").arg (tl::to_qstring (grain.name ())); - - // NOTE: this is a bit brute force .. we could as well try to insert the new grain into the existing structure - refresh (); - - invalidate (); - return true; - } else { tl::warn << QObject::tr ("Failed to remove package '%1'.").arg (tl::to_qstring (grain.name ())); - return false; } + + invalidate (); + return res; } namespace diff --git a/src/lay/laySalt.h b/src/lay/laySalt.h index d238476ae..c37766bfc 100644 --- a/src/lay/laySalt.h +++ b/src/lay/laySalt.h @@ -186,6 +186,11 @@ public: bool create_grain (const SaltGrain &templ, SaltGrain &target); signals: + /** + * @brief A signal triggered before one of the collections changed + */ + void collections_about_to_change (); + /** * @brief A signal triggered when one of the collections changed */ diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index 346d5adcc..5c191b2bb 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -137,7 +137,9 @@ SaltManagerDialog::SaltManagerDialog (QWidget *parent, lay::Salt *salt, lay::Sal connect (mode_tab, SIGNAL (currentChanged (int)), this, SLOT (mode_changed ())); connect (mp_salt, SIGNAL (collections_changed ()), this, SLOT (salt_changed ())); + connect (mp_salt, SIGNAL (collections_about_to_change ()), this, SLOT (salt_about_to_change ())); connect (mp_salt_mine, SIGNAL (collections_changed ()), this, SLOT (salt_mine_changed ())); + connect (mp_salt_mine, SIGNAL (collections_about_to_change ()), this, SLOT (salt_mine_about_to_change ())); update_models (); @@ -407,12 +409,28 @@ BEGIN_PROTECTED END_PROTECTED } +void +SaltManagerDialog::salt_about_to_change () +{ + SaltModel *model = dynamic_cast (salt_view->model ()); + tl_assert (model != 0); + model->begin_update (); +} + void SaltManagerDialog::salt_changed () { dm_update_models (); } +void +SaltManagerDialog::salt_mine_about_to_change () +{ + SaltModel *mine_model = dynamic_cast (salt_mine_view->model ()); + tl_assert (mine_model != 0); + mine_model->begin_update (); +} + void SaltManagerDialog::salt_mine_changed () { diff --git a/src/lay/laySaltManagerDialog.h b/src/lay/laySaltManagerDialog.h index feee212b9..4455ef9a3 100644 --- a/src/lay/laySaltManagerDialog.h +++ b/src/lay/laySaltManagerDialog.h @@ -51,11 +51,21 @@ public: SaltManagerDialog (QWidget *parent, lay::Salt *salt, lay::Salt *salt_mine); private slots: + /** + * @brief Called when the list of packages (grains) is about to change + */ + void salt_about_to_change (); + /** * @brief Called when the list of packages (grains) has changed */ void salt_changed (); + /** + * @brief Called when the repository (salt mine) is about to change + */ + void salt_mine_about_to_change (); + /** * @brief Called when the repository (salt mine) has changed */ diff --git a/src/lay/laySaltModel.cc b/src/lay/laySaltModel.cc index fc1a44ef8..661b0934f 100644 --- a/src/lay/laySaltModel.cc +++ b/src/lay/laySaltModel.cc @@ -96,7 +96,7 @@ SaltItemDelegate::sizeHint (const QStyleOptionViewItem &option, const QModelInde // -------------------------------------------------------------------------------------- SaltModel::SaltModel (QObject *parent, lay::Salt *salt) - : QAbstractItemModel (parent), mp_salt (salt) + : QAbstractItemModel (parent), mp_salt (salt), m_in_update (false) { create_ordered_list (); } @@ -221,7 +221,7 @@ SaltModel::index (int row, int column, const QModelIndex &parent) const if (parent.isValid ()) { return QModelIndex (); } else { - return createIndex (row, column, m_ordered_grains [row]); + return createIndex (row, column); } } @@ -250,8 +250,8 @@ SaltModel::rowCount (const QModelIndex &parent) const SaltGrain * SaltModel::grain_from_index (const QModelIndex &index) const { - if (index.isValid ()) { - return static_cast (index.internalPointer ()); + if (index.isValid () && index.row () >= 0 && index.row () < int (m_ordered_grains.size ())) { + return m_ordered_grains [index.row ()]; } else { return 0; } @@ -362,11 +362,23 @@ SaltModel::clear_messages () } } +void +SaltModel::begin_update () +{ + if (! m_in_update) { + m_ordered_grains.clear (); + beginResetModel (); + m_in_update = true; + } +} + void SaltModel::update () { + begin_update (); create_ordered_list (); - reset (); + endResetModel (); + m_in_update = false; } void diff --git a/src/lay/laySaltModel.h b/src/lay/laySaltModel.h index d7ba80d92..b95824665 100644 --- a/src/lay/laySaltModel.h +++ b/src/lay/laySaltModel.h @@ -98,6 +98,13 @@ public: */ SaltGrain *grain_from_index (const QModelIndex &index) const; + /** + * @brief Marks the model as "under construction" + * This method can be called (multiple times) before update to mark the model + * as being under construction. update() will end this state. + */ + void begin_update (); + /** * @brief Updates the model * Needs to be called when the salt has changed. @@ -169,6 +176,7 @@ public: std::map > m_messages; std::map m_display_order; std::vector m_ordered_grains; + bool m_in_update; bool is_marked (const std::string &name) const; bool is_enabled (const std::string &name) const; From 6ba251cf2a049cf57f55338306048db87fd646b7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 22 Apr 2017 00:03:58 +0200 Subject: [PATCH 43/57] WIP: Added font controller This enables font packages: packages can now provide fonts by featuring a "fonts" directory with the font layouts. This commit also adds the capability to dynamically add fonts. The Glyphs system is decoupled from the application's system path and the BASIC.TEXT PCell parameters are non-cached to enable dynamic updates. --- src/db/db.pro | 3 + src/db/dbGlyphs.cc | 58 ++++++---- src/db/dbGlyphs.h | 6 ++ src/db/dbPCellDeclaration.h | 14 ++- src/db/dbResources.qrc | 5 + src/{lib => db}/std_font.gds | Bin src/lay/lay.pro | 6 +- src/lay/layFontController.cc | 180 ++++++++++++++++++++++++++++++++ src/lay/layFontController.h | 131 +++++++++++++++++++++++ src/lay/layLibraryController.cc | 2 +- src/lib/libBasicText.h | 11 ++ src/lib/libResources.qrc | 3 - 12 files changed, 392 insertions(+), 27 deletions(-) create mode 100644 src/db/dbResources.qrc rename src/{lib => db}/std_font.gds (100%) create mode 100644 src/lay/layFontController.cc create mode 100644 src/lay/layFontController.h diff --git a/src/db/db.pro b/src/db/db.pro index 18190dbba..b43b57f12 100644 --- a/src/db/db.pro +++ b/src/db/db.pro @@ -230,6 +230,9 @@ HEADERS = \ dbGlyphs.h \ dbCommon.h +RESOURCES = \ + dbResources.qrc + INCLUDEPATH += ../tl ../gsi DEPENDPATH += ../tl ../gsi LIBS += -L$$DESTDIR -lklayout_tl -lklayout_gsi diff --git a/src/db/dbGlyphs.cc b/src/db/dbGlyphs.cc index 4954e2bea..112caaa28 100644 --- a/src/db/dbGlyphs.cc +++ b/src/db/dbGlyphs.cc @@ -299,12 +299,25 @@ TextGenerator::default_generator () return fonts.empty () ? 0 : &fonts [0]; } + +static std::vector s_font_paths; +static std::vector s_fonts; +static bool s_fonts_loaded = false; + +void +TextGenerator::set_font_paths (const std::vector &paths) +{ + s_font_paths = paths; + s_fonts.clear (); + s_fonts_loaded = false; +} + const std::vector & TextGenerator::generators () { - static std::vector m_fonts; + if (! s_fonts_loaded) { - if (m_fonts.empty ()) { + s_fonts.clear (); const char *resources[] = { ":/fonts/std_font.gds" @@ -312,42 +325,47 @@ TextGenerator::generators () for (size_t i = 0 ; i < sizeof (resources) / sizeof (resources [0]); ++i) { try { - m_fonts.push_back (TextGenerator ()); - m_fonts.back ().load_from_resource (resources [i]); + tl::log << "Loading font from resource " << resources [i] << " .."; + s_fonts.push_back (TextGenerator ()); + s_fonts.back ().load_from_resource (resources [i]); } catch (tl::Exception &ex) { tl::error << ex.msg (); - m_fonts.pop_back (); + s_fonts.pop_back (); } } - std::vector system_path = tl::get_klayout_path (); - // scan for font files - for (std::vector::const_iterator p = system_path.begin (); p != system_path.end (); ++p) { + for (std::vector::const_iterator p = s_font_paths.begin (); p != s_font_paths.end (); ++p) { - QDir fp = QDir (tl::to_qstring (*p)).filePath (tl::to_qstring ("fonts")); + QDir fp = QDir (tl::to_qstring (*p)); + if (fp.exists ()) { - QStringList name_filters; - name_filters << QString::fromUtf8 ("*"); + QStringList name_filters; + name_filters << QString::fromUtf8 ("*"); - QStringList font_files = fp.entryList (name_filters, QDir::Files); - for (QStringList::const_iterator ff = font_files.begin (); ff != font_files.end (); ++ff) { + QStringList font_files = fp.entryList (name_filters, QDir::Files); + for (QStringList::const_iterator ff = font_files.begin (); ff != font_files.end (); ++ff) { + + try { + tl::log << "Loading font from " << tl::to_string (fp.filePath (*ff)) << " .."; + s_fonts.push_back (TextGenerator ()); + s_fonts.back ().load_from_file (tl::to_string (fp.filePath (*ff))); + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + s_fonts.pop_back (); + } - try { - m_fonts.push_back (TextGenerator ()); - m_fonts.back ().load_from_file (tl::to_string (fp.filePath (*ff))); - } catch (tl::Exception &ex) { - tl::error << ex.msg (); - m_fonts.pop_back (); } } } + s_fonts_loaded = true; + } - return m_fonts; + return s_fonts; } } diff --git a/src/db/dbGlyphs.h b/src/db/dbGlyphs.h index 18d9be721..73a6d3864 100644 --- a/src/db/dbGlyphs.h +++ b/src/db/dbGlyphs.h @@ -188,6 +188,12 @@ public: */ static const std::vector &generators (); + /** + * @brief Sets the search path for font files + * The given folders are scanned for font files. + */ + static void set_font_paths (const std::vector &paths); + /** * @brief Returns the font with the given name * If no font with that name exsist, 0 is returned. diff --git a/src/db/dbPCellDeclaration.h b/src/db/dbPCellDeclaration.h index ea669a50a..f957e1868 100644 --- a/src/db/dbPCellDeclaration.h +++ b/src/db/dbPCellDeclaration.h @@ -453,7 +453,7 @@ public: */ const std::vector ¶meter_declarations () const { - if (! m_has_parameter_declarations) { + if (! m_has_parameter_declarations || ! wants_parameter_declaration_caching ()) { m_parameter_declarations = get_parameter_declarations (); m_has_parameter_declarations = true; } @@ -495,6 +495,18 @@ public: */ pcell_parameters_type map_parameters (const std::map &named_parameters) const; +protected: + /** + * @brief Gets a value indicating whether the PCell wants caching of the parameter declarations + * + * Some PCells with a dynamic parameter definition may not want paramater declaration caching. These + * PCells can override this method and return false. + */ + virtual bool wants_parameter_declaration_caching () const + { + return true; + } + private: int m_ref_count; pcell_id_type m_id; diff --git a/src/db/dbResources.qrc b/src/db/dbResources.qrc new file mode 100644 index 000000000..92058dab4 --- /dev/null +++ b/src/db/dbResources.qrc @@ -0,0 +1,5 @@ + + + std_font.gds + + diff --git a/src/lib/std_font.gds b/src/db/std_font.gds similarity index 100% rename from src/lib/std_font.gds rename to src/db/std_font.gds diff --git a/src/lay/lay.pro b/src/lay/lay.pro index ff6ebd3d3..079155bfd 100644 --- a/src/lay/lay.pro +++ b/src/lay/lay.pro @@ -58,7 +58,8 @@ HEADERS = \ laySaltModel.h \ laySaltController.h \ laySignalHandler.h \ - layLibraryController.h + layLibraryController.h \ + layFontController.h FORMS = \ ClipDialog.ui \ @@ -162,7 +163,8 @@ SOURCES = \ laySaltModel.cc \ laySaltController.cc \ laySignalHandler.cc \ - layLibraryController.cc + layLibraryController.cc \ + layFontController.cc RESOURCES = layBuildInMacros.qrc \ layHelpResources.qrc \ diff --git a/src/lay/layFontController.cc b/src/lay/layFontController.cc new file mode 100644 index 000000000..de36f4bca --- /dev/null +++ b/src/lay/layFontController.cc @@ -0,0 +1,180 @@ + +/* + + 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 "layFontController.h" +#include "layApplication.h" +#include "laySaltController.h" +#include "layConfig.h" +#include "layMainWindow.h" +#include "dbGlyphs.h" +#include "tlLog.h" + +#include + +namespace lay +{ + +FontController::FontController () + : m_file_watcher (0), + dm_sync_dirs (this, &FontController::sync_dirs) +{ +} + +void +FontController::initialize (lay::PluginRoot * /*root*/) +{ + // NOTE: we initialize the dirs in the stage once to have them available for the autorun + // macros. We'll do that later again in order to pull in the dirs from the packages. + sync_dirs (); +} + +void +FontController::initialized (lay::PluginRoot * /*root*/) +{ + if (lay::SaltController::instance ()) { + connect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (sync_with_external_sources ())); + } + + if (! m_file_watcher) { + m_file_watcher = new tl::FileSystemWatcher (this); + connect (m_file_watcher, SIGNAL (fileChanged (const QString &)), this, SLOT (file_watcher_triggered ())); + connect (m_file_watcher, SIGNAL (fileRemoved (const QString &)), this, SLOT (file_watcher_triggered ())); + } + + sync_dirs (); +} + +void +FontController::uninitialize (lay::PluginRoot * /*root*/) +{ + if (m_file_watcher) { + disconnect (m_file_watcher, SIGNAL (fileChanged (const QString &)), this, SLOT (file_watcher_triggered ())); + disconnect (m_file_watcher, SIGNAL (fileRemoved (const QString &)), this, SLOT (file_watcher_triggered ())); + delete m_file_watcher; + m_file_watcher = 0; + } + + if (lay::SaltController::instance ()) { + disconnect (lay::SaltController::instance (), SIGNAL (salt_changed ()), this, SLOT (sync_with_external_sources ())); + } +} + +void +FontController::get_options (std::vector < std::pair > & /*options*/) const +{ + // .. nothing yet .. +} + +void +FontController::get_menu_entries (std::vector & /*menu_entries*/) const +{ + // .. nothing yet .. +} + +bool +FontController::configure (const std::string & /*name*/, const std::string & /*value*/) +{ + return false; +} + +void +FontController::config_finalize() +{ + // .. nothing yet .. +} + +bool +FontController::can_exit (lay::PluginRoot * /*root*/) const +{ + // .. nothing yet .. + return true; +} + +void +FontController::sync_dirs () +{ + if (! m_file_watcher) { + return; + } + + m_file_watcher->clear (); + m_file_watcher->enable (false); + + std::vector paths = lay::Application::instance ()->klayout_path (); + + // add the salt grains as potential sources for library definitions + + lay::SaltController *sc = lay::SaltController::instance (); + if (sc) { + for (lay::Salt::flat_iterator g = sc->salt ().begin_flat (); g != sc->salt ().end_flat (); ++g) { + paths.push_back ((*g)->path ()); + } + } + + // scan for font directories + + std::vector font_paths; + + for (std::vector ::const_iterator p = paths.begin (); p != paths.end (); ++p) { + QDir fp = QDir (tl::to_qstring (*p)).filePath (tl::to_qstring ("fonts")); + if (fp.exists ()) { + m_file_watcher->add_file (tl::to_string (fp.absolutePath ())); + font_paths.push_back (tl::to_string (fp.absolutePath ())); + } + } + + db::TextGenerator::set_font_paths (font_paths); + + m_file_watcher->enable (true); +} + +void +FontController::sync_with_external_sources () +{ + tl::log << tl::to_string (tr ("Package updates - updating fonts")); + dm_sync_dirs (); +} + +void +FontController::file_watcher_triggered () +{ + tl::log << tl::to_string (tr ("Detected file system change in fonts - updating")); + dm_sync_dirs (); +} + +FontController * +FontController::instance () +{ + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + FontController *sc = dynamic_cast (cls.operator-> ()); + if (sc) { + return sc; + } + } + return 0; +} + +// The singleton instance of the library controller +static tl::RegisteredClass font_controller_decl (new lay::FontController (), 160, "FontController"); + +} + diff --git a/src/lay/layFontController.h b/src/lay/layFontController.h new file mode 100644 index 000000000..5c1e48e28 --- /dev/null +++ b/src/lay/layFontController.h @@ -0,0 +1,131 @@ + +/* + + 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_layFontController +#define HDR_layFontController + +#include "layCommon.h" +#include "layPlugin.h" +#include "tlFileSystemWatcher.h" +#include "tlDeferredExecution.h" + +#include +#include + +#include + +namespace lay +{ + +class FontManagerDialog; +class MainWindow; + +/** + * @brief A controller for the fonts + * + * This object is a singleton that acts as a controller + * for the font management for the Glyphs. The controller is responsible + * to managing the fonts and notifying library consumers + * of changes. + * + * By making the controller a PluginDeclaration it will receive + * initialization and configuration calls. + */ +class FontController + : public lay::PluginDeclaration, public tl::Object +{ +Q_OBJECT + +public: + /** + * @brief Default constructor + */ + FontController (); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void initialize (lay::PluginRoot *root); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void initialized (lay::PluginRoot *root); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + virtual void uninitialize (lay::PluginRoot *root); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void get_options (std::vector < std::pair > &options) const; + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void get_menu_entries (std::vector &menu_entries) const; + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + bool configure (const std::string &key, const std::string &value); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + void config_finalize(); + + /** + * @brief Reimplementation of the PluginDeclaration interface + */ + bool can_exit (lay::PluginRoot *root) const; + + /** + * @brief Gets the singleton instance for this object + */ + static FontController *instance (); + +private slots: + /** + * @brief Called when the file watcher detects a change in the file system + */ + void file_watcher_triggered (); + + /** + * @brief Called when the salt (packages) has changed + */ + void sync_with_external_sources (); + +private: + tl::FileSystemWatcher *m_file_watcher; + tl::DeferredMethod dm_sync_dirs; + + void sync_dirs (); +}; + +} + +#endif + diff --git a/src/lay/layLibraryController.cc b/src/lay/layLibraryController.cc index 0548c2c49..88334f7be 100644 --- a/src/lay/layLibraryController.cc +++ b/src/lay/layLibraryController.cc @@ -255,7 +255,7 @@ LibraryController::instance () } // The singleton instance of the library controller -static tl::RegisteredClass salt_controller_decl (new lay::LibraryController (), 150, "LibraryController"); +static tl::RegisteredClass library_controller_decl (new lay::LibraryController (), 150, "LibraryController"); } diff --git a/src/lib/libBasicText.h b/src/lib/libBasicText.h index b0ef3dfa0..8f36f4662 100644 --- a/src/lib/libBasicText.h +++ b/src/lib/libBasicText.h @@ -83,6 +83,17 @@ public: */ virtual std::vector get_parameter_declarations () const; +protected: + /** + * @brief Returns a value indicating that this PCell wants to update it's parameter declarations dynamically + * + * This is be required because the fonts can be updated dynamically when new packages are installed. + */ + virtual bool wants_parameter_declaration_caching () const + { + return false; + } + public: int get_font_index (const db::pcell_parameters_type ¶meters) const; }; diff --git a/src/lib/libResources.qrc b/src/lib/libResources.qrc index d98df72d0..03585ec03 100644 --- a/src/lib/libResources.qrc +++ b/src/lib/libResources.qrc @@ -1,5 +1,2 @@ - - std_font.gds - From 7e1e75ebf0bc4049df156275eec5078bc4962c99 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 22 Apr 2017 18:09:12 +0200 Subject: [PATCH 44/57] Salt manager dialog: UI enhancements * refresh function for salt mine * Split "new" tab into "updates" and "new installations" * A warning icon in the tab when updates are available --- src/lay/SaltManagerDialog.ui | 335 ++++++++++++++++++++++- src/lay/laySaltController.cc | 11 +- src/lay/laySaltManagerDialog.cc | 465 +++++++++++++++++++++++--------- src/lay/laySaltManagerDialog.h | 57 ++-- src/lay/laySaltModel.cc | 14 +- src/lay/laySaltModel.h | 5 +- 6 files changed, 725 insertions(+), 162 deletions(-) diff --git a/src/lay/SaltManagerDialog.ui b/src/lay/SaltManagerDialog.ui index 77651bd95..b0cd99823 100644 --- a/src/lay/SaltManagerDialog.ui +++ b/src/lay/SaltManagerDialog.ui @@ -408,9 +408,299 @@ + + + Update Packages + + + + + + + 0 + 1 + + + + Qt::Horizontal + + + + + 4 + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 2 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Mark or unmark for installation + + + Mark + + + + :/marked_16.png:/marked_16.png + + + true + + + + + + + Qt::Horizontal + + + + 126 + 20 + + + + + + + + + + + :/find.png + + + + + + + + 0 + 0 + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + 64 + 64 + + + + false + + + + + + + + + + + + 1 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 4 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 75 + true + + + + Details + + + + + + + false + + + ... + + + + :/empty_16.png:/empty_16.png + + + true + + + + + + + + + + true + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Apply + + + false + + + false + + + + + + + Set in code + + + + + + + Qt::Horizontal + + + + 563 + 20 + + + + + + + + + - Install or Update Packages + Install New Packages @@ -454,7 +744,7 @@ 0 - + Mark or unmark for installation @@ -528,7 +818,7 @@ 0 - + true @@ -661,7 +951,7 @@ 0 - + Apply @@ -674,7 +964,7 @@ - + Set in code @@ -732,21 +1022,44 @@ Delete package - + Unmark all - + Show marked only - + Show all + + + Refresh + + + Reload package repository + + + + + Show all + + + + + Show marked only + + + + + Unmark all + + @@ -767,11 +1080,11 @@ details_text edit_button search_new_edit - mark_button - salt_mine_view + mark_new_button + salt_mine_view_new details_new_text toolButton - apply_button + apply_new_button scrollArea diff --git a/src/lay/laySaltController.cc b/src/lay/laySaltController.cc index 866e0b00e..98e5e0174 100644 --- a/src/lay/laySaltController.cc +++ b/src/lay/laySaltController.cc @@ -121,16 +121,7 @@ SaltController::show_editor () { if (mp_mw && !mp_salt_dialog) { - try { - if (! m_salt_mine_url.empty ()) { - tl::log << tl::to_string (tr ("Downloading package repository from %1").arg (tl::to_qstring (m_salt_mine_url))); - m_salt_mine.load (m_salt_mine_url); - } - } catch (tl::Exception &ex) { - tl::error << ex.msg (); - } - - mp_salt_dialog = new lay::SaltManagerDialog (mp_mw, &m_salt, &m_salt_mine); + mp_salt_dialog = new lay::SaltManagerDialog (mp_mw, &m_salt, m_salt_mine_url); } diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index 5c191b2bb..295b0fa71 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -109,9 +109,10 @@ private: // -------------------------------------------------------------------------------------- // SaltManager implementation -SaltManagerDialog::SaltManagerDialog (QWidget *parent, lay::Salt *salt, lay::Salt *salt_mine) +SaltManagerDialog::SaltManagerDialog (QWidget *parent, lay::Salt *salt, const std::string &salt_mine_url) : QDialog (parent), - m_current_changed_enabled (true), dm_update_models (this, &SaltManagerDialog::update_models) + m_salt_mine_url (salt_mine_url), + dm_update_models (this, &SaltManagerDialog::update_models), m_current_tab (-1) { Ui::SaltManagerDialog::setupUi (this); mp_properties_dialog = new lay::SaltGrainPropertiesDialog (this); @@ -119,106 +120,213 @@ SaltManagerDialog::SaltManagerDialog (QWidget *parent, lay::Salt *salt, lay::Sal connect (edit_button, SIGNAL (clicked ()), this, SLOT (edit_properties ())); connect (create_button, SIGNAL (clicked ()), this, SLOT (create_grain ())); connect (delete_button, SIGNAL (clicked ()), this, SLOT (delete_grain ())); - connect (apply_button, SIGNAL (clicked ()), this, SLOT (apply ())); + connect (apply_new_button, SIGNAL (clicked ()), this, SLOT (apply ())); + connect (apply_update_button, SIGNAL (clicked ()), this, SLOT (apply ())); mp_salt = salt; - mp_salt_mine = salt_mine; + + try { + if (! m_salt_mine_url.empty ()) { + tl::log << tl::to_string (tr ("Downloading package repository from %1").arg (tl::to_qstring (m_salt_mine_url))); + m_salt_mine.load (m_salt_mine_url); + } + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + } SaltModel *model = new SaltModel (this, mp_salt); salt_view->setModel (model); salt_view->setItemDelegate (new SaltItemDelegate (this)); - SaltModel *mine_model = new SaltModel (this, mp_salt_mine); - salt_mine_view->setModel (mine_model); - salt_mine_view->setItemDelegate (new SaltItemDelegate (this)); + SaltModel *mine_model; + + // This model will show only the grains of mp_salt_mine which are not present in mp_salt yet. + mine_model = new SaltModel (this, &m_salt_mine, mp_salt, true); + salt_mine_view_new->setModel (mine_model); + salt_mine_view_new->setItemDelegate (new SaltItemDelegate (this)); + + // This model will show only the grains of mp_salt_mine which are present in mp_salt already. + mine_model = new SaltModel (this, &m_salt_mine, mp_salt, false); + salt_mine_view_update->setModel (mine_model); + salt_mine_view_update->setItemDelegate (new SaltItemDelegate (this)); mode_tab->setCurrentIndex (mp_salt->is_empty () ? 1 : 0); connect (mode_tab, SIGNAL (currentChanged (int)), this, SLOT (mode_changed ())); + m_current_tab = mode_tab->currentIndex (); connect (mp_salt, SIGNAL (collections_changed ()), this, SLOT (salt_changed ())); connect (mp_salt, SIGNAL (collections_about_to_change ()), this, SLOT (salt_about_to_change ())); - connect (mp_salt_mine, SIGNAL (collections_changed ()), this, SLOT (salt_mine_changed ())); - connect (mp_salt_mine, SIGNAL (collections_about_to_change ()), this, SLOT (salt_mine_about_to_change ())); + connect (&m_salt_mine, SIGNAL (collections_changed ()), this, SLOT (salt_mine_changed ())); + connect (&m_salt_mine, SIGNAL (collections_about_to_change ()), this, SLOT (salt_mine_about_to_change ())); update_models (); connect (salt_view->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (current_changed ())); connect (salt_view, SIGNAL (doubleClicked (const QModelIndex &)), this, SLOT (edit_properties ())); - connect (salt_mine_view->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (mine_current_changed ()), Qt::QueuedConnection); - connect (salt_mine_view, SIGNAL (doubleClicked (const QModelIndex &)), this, SLOT (mark_clicked ())); + connect (salt_mine_view_new->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (mine_new_current_changed ()), Qt::QueuedConnection); + connect (salt_mine_view_update->selectionModel (), SIGNAL (currentChanged (const QModelIndex &, const QModelIndex &)), this, SLOT (mine_update_current_changed ()), Qt::QueuedConnection); + connect (salt_mine_view_new, SIGNAL (doubleClicked (const QModelIndex &)), this, SLOT (mark_clicked ())); + connect (salt_mine_view_update, SIGNAL (doubleClicked (const QModelIndex &)), this, SLOT (mark_clicked ())); search_installed_edit->set_clear_button_enabled (true); search_new_edit->set_clear_button_enabled (true); + search_update_edit->set_clear_button_enabled (true); connect (search_installed_edit, SIGNAL (textChanged (const QString &)), this, SLOT (search_text_changed (const QString &))); connect (search_new_edit, SIGNAL (textChanged (const QString &)), this, SLOT (search_text_changed (const QString &))); + connect (search_update_edit, SIGNAL (textChanged (const QString &)), this, SLOT (search_text_changed (const QString &))); - connect (mark_button, SIGNAL (clicked ()), this, SLOT (mark_clicked ())); + connect (mark_new_button, SIGNAL (clicked ()), this, SLOT (mark_clicked ())); + connect (mark_update_button, SIGNAL (clicked ()), this, SLOT (mark_clicked ())); - salt_mine_view->addAction (actionUnmarkAll); - QAction *a = new QAction (this); + QAction *a; + + salt_mine_view_new->addAction (actionUnmarkAllNew); + a = new QAction (this); a->setSeparator (true); - salt_mine_view->addAction (a); - salt_mine_view->addAction (actionShowMarkedOnly); - salt_mine_view->addAction (actionShowAll); - salt_mine_view->setContextMenuPolicy (Qt::ActionsContextMenu); + salt_mine_view_new->addAction (a); + salt_mine_view_new->addAction (actionShowMarkedOnlyNew); + salt_mine_view_new->addAction (actionShowAllNew); + a = new QAction (this); + a->setSeparator (true); + salt_mine_view_new->addAction (a); + salt_mine_view_new->addAction (actionRefresh); + salt_mine_view_new->setContextMenuPolicy (Qt::ActionsContextMenu); - connect (actionUnmarkAll, SIGNAL (triggered ()), this, SLOT (unmark_all ())); - connect (actionShowMarkedOnly, SIGNAL (triggered ()), this, SLOT (show_marked_only ())); - connect (actionShowAll, SIGNAL (triggered ()), this, SLOT (show_all ())); + salt_mine_view_update->addAction (actionUnmarkAllUpdate); + a = new QAction (this); + a->setSeparator (true); + salt_mine_view_update->addAction (a); + salt_mine_view_update->addAction (actionShowMarkedOnlyUpdate); + salt_mine_view_update->addAction (actionShowAllUpdate); + a = new QAction (this); + a->setSeparator (true); + salt_mine_view_update->addAction (a); + salt_mine_view_update->addAction (actionRefresh); + salt_mine_view_update->setContextMenuPolicy (Qt::ActionsContextMenu); + + connect (actionUnmarkAllNew, SIGNAL (triggered ()), this, SLOT (unmark_all_new ())); + connect (actionShowMarkedOnlyNew, SIGNAL (triggered ()), this, SLOT (show_marked_only_new ())); + connect (actionShowAllNew, SIGNAL (triggered ()), this, SLOT (show_all_new ())); + connect (actionUnmarkAllUpdate, SIGNAL (triggered ()), this, SLOT (unmark_all_update ())); + connect (actionShowMarkedOnlyUpdate, SIGNAL (triggered ()), this, SLOT (show_marked_only_update ())); + connect (actionShowAllUpdate, SIGNAL (triggered ()), this, SLOT (show_all_update ())); + connect (actionRefresh, SIGNAL (triggered ()), this, SLOT (refresh ())); } void SaltManagerDialog::mode_changed () { + QList sizes; + if (m_current_tab == 0) { + sizes = splitter->sizes (); + } else if (m_current_tab == 1) { + sizes = splitter_update->sizes (); + } else if (m_current_tab == 2) { + sizes = splitter_new->sizes (); + } + // keeps the splitters in sync - if (mode_tab->currentIndex () == 1) { - splitter_new->setSizes (splitter->sizes ()); - show_all (); - } else if (mode_tab->currentIndex () == 0) { - splitter->setSizes (splitter_new->sizes ()); + if (!sizes.empty ()) { + splitter_new->setSizes (sizes); + splitter_update->setSizes (sizes); + splitter->setSizes (sizes); + } + + if (mode_tab->currentIndex () >= 1) { + show_all_new (); + show_all_update (); + } + + m_current_tab = mode_tab->currentIndex (); +} + +void +SaltManagerDialog::show_all_new () +{ + search_new_edit->clear (); + + SaltModel *model = dynamic_cast (salt_mine_view_new->model ()); + if (model) { + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + salt_mine_view_new->setRowHidden (i, false); + } } } void -SaltManagerDialog::show_all () +SaltManagerDialog::show_all_update () { - search_new_edit->clear (); + search_update_edit->clear (); - SaltModel *model = dynamic_cast (salt_mine_view->model ()); - if (! model) { - return; - } - - for (int i = model->rowCount (QModelIndex ()); i > 0; ) { - --i; - salt_mine_view->setRowHidden (i, false); + SaltModel *model = dynamic_cast (salt_mine_view_update->model ()); + if (model) { + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + salt_mine_view_update->setRowHidden (i, false); + } } } void -SaltManagerDialog::show_marked_only () +SaltManagerDialog::show_marked_only_new () { search_new_edit->clear (); - SaltModel *model = dynamic_cast (salt_mine_view->model ()); + SaltModel *model = dynamic_cast (salt_mine_view_new->model ()); if (! model) { return; } + salt_mine_view_new->setCurrentIndex (QModelIndex ()); + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { --i; SaltGrain *g = model->grain_from_index (model->index (i, 0, QModelIndex ())); - salt_mine_view->setRowHidden (i, !(g && model->is_marked (g->name ()))); + salt_mine_view_new->setRowHidden (i, !(g && model->is_marked (g->name ()))); + mine_new_current_changed (); } } void -SaltManagerDialog::unmark_all () +SaltManagerDialog::show_marked_only_update () { - SaltModel *model = dynamic_cast (salt_mine_view->model ()); + search_update_edit->clear (); + + SaltModel *model = dynamic_cast (salt_mine_view_update->model ()); + if (! model) { + return; + } + + salt_mine_view_update->setCurrentIndex (QModelIndex ()); + + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + SaltGrain *g = model->grain_from_index (model->index (i, 0, QModelIndex ())); + salt_mine_view_update->setRowHidden (i, !(g && model->is_marked (g->name ()))); + mine_update_current_changed (); + } +} + +void +SaltManagerDialog::unmark_all_new () +{ + SaltModel *model = dynamic_cast (salt_mine_view_new->model ()); if (model) { model->clear_marked (); + show_all_new (); + update_apply_state (); + } +} + +void +SaltManagerDialog::unmark_all_update () +{ + SaltModel *model = dynamic_cast (salt_mine_view_update->model ()); + if (model) { + model->clear_marked (); + show_all_update (); update_apply_state (); } } @@ -230,7 +338,9 @@ SaltManagerDialog::search_text_changed (const QString &text) if (sender () == search_installed_edit) { view = salt_view; } else if (sender () == search_new_edit) { - view = salt_mine_view; + view = salt_mine_view_new; + } else if (sender () == search_update_edit) { + view = salt_mine_view_update; } else { return; } @@ -265,45 +375,78 @@ SaltManagerDialog::search_text_changed (const QString &text) void SaltManagerDialog::mark_clicked () { - SaltModel *model = dynamic_cast (salt_mine_view->model ()); + QListView *view; + if (sender () == salt_mine_view_new || sender () == mark_new_button) { + view = salt_mine_view_new; + } else { + view = salt_mine_view_update; + } + + SaltModel *model = dynamic_cast (view->model ()); if (! model) { return; } - SaltGrain *g = mine_current_grain (); - if (! g) { - return; - } - model->set_marked (g->name (), !model->is_marked (g->name ())); - update_apply_state (); + SaltGrain *g = model->grain_from_index (view->currentIndex ()); + if (g) { + model->set_marked (g->name (), ! model->is_marked (g->name ())); + update_apply_state (); + } } void SaltManagerDialog::update_apply_state () { - int marked = 0; + SaltModel *model; - SaltModel *model = dynamic_cast (salt_mine_view->model ()); - if (! model) { - return; - } + model = dynamic_cast (salt_mine_view_new->model ()); + if (model) { - for (int i = model->rowCount (QModelIndex ()); i > 0; ) { - --i; - QModelIndex index = model->index (i, 0, QModelIndex ()); - SaltGrain *g = model->grain_from_index (index); - if (g && model->is_marked (g->name ())) { - marked += 1; + int marked = 0; + + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + QModelIndex index = model->index (i, 0, QModelIndex ()); + SaltGrain *g = model->grain_from_index (index); + if (g && model->is_marked (g->name ())) { + marked += 1; + } } + + apply_new_button->setEnabled (marked > 0); + if (marked == 0) { + apply_label_new->setText (QString ()); + } else if (marked == 1) { + apply_label_new->setText (tr ("One package selected")); + } else if (marked > 1) { + apply_label_new->setText (tr ("%1 packages selected").arg (marked)); + } + } - apply_button->setEnabled (marked > 0); - if (marked == 0) { - apply_label->setText (QString ()); - } else if (marked == 1) { - apply_label->setText (tr ("One package selected")); - } else if (marked > 1) { - apply_label->setText (tr ("%1 packages selected").arg (marked)); + model = dynamic_cast (salt_mine_view_update->model ()); + if (model) { + + int marked = 0; + + for (int i = model->rowCount (QModelIndex ()); i > 0; ) { + --i; + QModelIndex index = model->index (i, 0, QModelIndex ()); + SaltGrain *g = model->grain_from_index (index); + if (g && model->is_marked (g->name ())) { + marked += 1; + } + } + + apply_update_button->setEnabled (marked > 0); + if (marked == 0) { + apply_label_update->setText (QString ()); + } else if (marked == 1) { + apply_label_update->setText (tr ("One package selected")); + } else if (marked > 1) { + apply_label_update->setText (tr ("%1 packages selected").arg (marked)); + } + } } @@ -312,12 +455,20 @@ SaltManagerDialog::apply () { BEGIN_PROTECTED + bool update = (sender () == apply_update_button); + lay::SaltDownloadManager manager; bool any = false; // fetch all marked grains and register for download - SaltModel *model = dynamic_cast (salt_mine_view->model ()); + SaltModel *model; + if (update) { + model = dynamic_cast (salt_mine_view_update->model ()); + } else { + model = dynamic_cast (salt_mine_view_new->model ()); + } + if (model) { for (int i = model->rowCount (QModelIndex ()); i > 0; ) { --i; @@ -331,13 +482,21 @@ BEGIN_PROTECTED } if (! any) { - throw tl::Exception (tl::to_string (tr ("No packages marked for installation or update"))); + if (update) { + throw tl::Exception (tl::to_string (tr ("No packages marked for update"))); + } else { + throw tl::Exception (tl::to_string (tr ("No packages marked for installation"))); + } } - manager.compute_dependencies (*mp_salt, *mp_salt_mine); + manager.compute_dependencies (*mp_salt, m_salt_mine); if (manager.show_confirmation_dialog (this, *mp_salt)) { - unmark_all (); + if (update) { + unmark_all_update (); + } else { + unmark_all_new (); + } manager.execute (*mp_salt); } @@ -426,9 +585,35 @@ SaltManagerDialog::salt_changed () void SaltManagerDialog::salt_mine_about_to_change () { - SaltModel *mine_model = dynamic_cast (salt_mine_view->model ()); - tl_assert (mine_model != 0); - mine_model->begin_update (); + SaltModel *model; + + model = dynamic_cast (salt_mine_view_new->model ()); + tl_assert (model != 0); + model->begin_update (); + + model = dynamic_cast (salt_mine_view_update->model ()); + tl_assert (model != 0); + model->begin_update (); +} + +void +SaltManagerDialog::refresh () +{ +BEGIN_PROTECTED + + if (! m_salt_mine_url.empty ()) { + + tl::log << tl::to_string (tr ("Downloading package repository from %1").arg (tl::to_qstring (m_salt_mine_url))); + + lay::Salt new_mine; + new_mine.load (m_salt_mine_url); + m_salt_mine = new_mine; + + salt_mine_changed (); + + } + +END_PROTECTED } void @@ -443,15 +628,11 @@ SaltManagerDialog::update_models () SaltModel *model = dynamic_cast (salt_view->model ()); tl_assert (model != 0); - // NOTE: the disabling of the event handler prevents us from - // letting the model connect to the salt's signal directly. - m_current_changed_enabled = false; - model->clear_messages (); // Establish a message saying that an update is available for (Salt::flat_iterator g = mp_salt->begin_flat (); g != mp_salt->end_flat (); ++g) { - SaltGrain *gm = mp_salt_mine->grain_by_name ((*g)->name ()); + SaltGrain *gm = m_salt_mine.grain_by_name ((*g)->name ()); if (gm && SaltGrain::compare_versions (gm->version (), (*g)->version ()) > 0) { model->set_message ((*g)->name (), SaltModel::Warning, tl::to_string (tr ("An update to version %1 is available").arg (tl::to_qstring (gm->version ())))); } @@ -459,8 +640,6 @@ SaltManagerDialog::update_models () model->update (); - m_current_changed_enabled = true; - if (mp_salt->is_empty ()) { list_stack->setCurrentIndex (1); @@ -478,40 +657,59 @@ SaltManagerDialog::update_models () } - SaltModel *mine_model = dynamic_cast (salt_mine_view->model ()); - tl_assert (mine_model != 0); + SaltModel *mine_model; - // NOTE: the disabling of the event handler prevents us from - // letting the model connect to the salt's signal directly. - m_current_changed_enabled = false; + mine_model = dynamic_cast (salt_mine_view_update->model ()); + tl_assert (mine_model != 0); mine_model->clear_order (); mine_model->clear_messages (); mine_model->enable_all (); + bool has_warning = false; + // Establish a message saying that an update is available for (Salt::flat_iterator g = mp_salt->begin_flat (); g != mp_salt->end_flat (); ++g) { - SaltGrain *gm = mp_salt_mine->grain_by_name ((*g)->name ()); + SaltGrain *gm = m_salt_mine.grain_by_name ((*g)->name ()); if (gm && SaltGrain::compare_versions (gm->version (), (*g)->version ()) > 0) { + has_warning = true; mine_model->set_message ((*g)->name (), SaltModel::Warning, tl::to_string (tr ("The installed version is outdated (%1)").arg (tl::to_qstring ((*g)->version ())))); mine_model->set_order ((*g)->name (), -1); } else if (gm) { - mine_model->set_message ((*g)->name (), SaltModel::None, tl::to_string (tr ("This package is already installed and up to date"))); + mine_model->set_message ((*g)->name (), SaltModel::None, tl::to_string (tr ("This package is up to date"))); mine_model->set_order ((*g)->name (), 1); mine_model->set_enabled ((*g)->name (), false); } } - mine_model->update (); + if (has_warning) { + mode_tab->setTabIcon (1, QIcon (":/warn_16.png")); + } else { + mode_tab->setTabIcon (1, QIcon ()); + } - m_current_changed_enabled = true; + mine_model->update (); // select the first grain if (mine_model->rowCount (QModelIndex ()) > 0) { - salt_mine_view->setCurrentIndex (mine_model->index (0, 0, QModelIndex ())); + salt_mine_view_update->setCurrentIndex (mine_model->index (0, 0, QModelIndex ())); } - mine_current_changed (); + mine_model = dynamic_cast (salt_mine_view_new->model ()); + tl_assert (mine_model != 0); + + mine_model->clear_order (); + mine_model->clear_messages (); + mine_model->enable_all (); + mine_model->update (); + + // select the first grain + if (mine_model->rowCount (QModelIndex ()) > 0) { + salt_mine_view_new->setCurrentIndex (mine_model->index (0, 0, QModelIndex ())); + } + + mine_new_current_changed (); + mine_update_current_changed (); current_changed (); update_apply_state (); } @@ -539,20 +737,56 @@ SaltManagerDialog::current_grain () } void -SaltManagerDialog::mine_current_changed () +SaltManagerDialog::mine_update_current_changed () { - BEGIN_PROTECTED - SaltGrain *g = mine_current_grain (); + SaltModel *model = dynamic_cast (salt_mine_view_update->model ()); + tl_assert (model != 0); + SaltGrain *g = model->grain_from_index (salt_mine_view_update->currentIndex ()); + + details_update_frame->setEnabled (g != 0); + + QString html; + SaltGrain *remote_grain = get_remote_grain_info (g, html); + + m_remote_update_grain.reset (remote_grain); + details_update_text->set_grain (remote_grain); + details_update_text->setHtml (html); + +END_PROTECTED +} + +void +SaltManagerDialog::mine_new_current_changed () +{ +BEGIN_PROTECTED + + SaltModel *model = dynamic_cast (salt_mine_view_new->model ()); + tl_assert (model != 0); + SaltGrain *g = model->grain_from_index (salt_mine_view_new->currentIndex ()); + details_new_frame->setEnabled (g != 0); + QString html; + SaltGrain *remote_grain = get_remote_grain_info (g, html); + + m_remote_new_grain.reset (remote_grain); + details_new_text->set_grain (remote_grain); + details_new_text->setHtml (html); + +END_PROTECTED +} + +lay::SaltGrain * +SaltManagerDialog::get_remote_grain_info (lay::SaltGrain *g, QString &html) +{ if (! g) { - details_new_text->set_grain (0); - return; + return 0; } - m_remote_grain.reset (0); + std::auto_ptr remote_grain; + remote_grain.reset (0); // Download actual grain definition file try { @@ -561,7 +795,7 @@ BEGIN_PROTECTED throw tl::Exception (tl::to_string (tr ("No download link available"))); } - QString text = tr ( + html = tr ( "" "" "" @@ -573,30 +807,26 @@ BEGIN_PROTECTED ) .arg (tl::to_qstring (SaltGrain::spec_url (g->url ()))); - details_new_text->setHtml (text); - QApplication::processEvents (QEventLoop::ExcludeUserInputEvents); tl::InputStream stream (SaltGrain::spec_url (g->url ())); - m_remote_grain.reset (new SaltGrain ()); - m_remote_grain->load (stream); - m_remote_grain->set_url (g->url ()); + remote_grain.reset (new SaltGrain ()); + remote_grain->load (stream); + remote_grain->set_url (g->url ()); - if (g->name () != m_remote_grain->name ()) { - throw tl::Exception (tl::to_string (tr ("Name mismatch between repository and actual package (repository: %1, package: %2)").arg (tl::to_qstring (g->name ())).arg (tl::to_qstring (m_remote_grain->name ())))); + if (g->name () != remote_grain->name ()) { + throw tl::Exception (tl::to_string (tr ("Name mismatch between repository and actual package (repository: %1, package: %2)").arg (tl::to_qstring (g->name ())).arg (tl::to_qstring (remote_grain->name ())))); } - if (SaltGrain::compare_versions (g->version (), m_remote_grain->version ()) != 0) { - throw tl::Exception (tl::to_string (tr ("Version mismatch between repository and actual package (repository: %1, package: %2)").arg (tl::to_qstring (g->version ())).arg (tl::to_qstring (m_remote_grain->version ())))); + if (SaltGrain::compare_versions (g->version (), remote_grain->version ()) != 0) { + throw tl::Exception (tl::to_string (tr ("Version mismatch between repository and actual package (repository: %1, package: %2)").arg (tl::to_qstring (g->version ())).arg (tl::to_qstring (remote_grain->version ())))); } - details_new_text->set_grain (m_remote_grain.get ()); - } catch (tl::Exception &ex) { - m_remote_grain.reset (0); + remote_grain.reset (0); - QString text = tr ( + html = tr ( "" "" "" @@ -610,18 +840,9 @@ BEGIN_PROTECTED .arg (tl::to_qstring (SaltGrain::spec_url (g->url ()))) .arg (tl::to_qstring (tl::escaped_to_html (ex.msg ()))); - details_new_text->setHtml (text); - } -END_PROTECTED -} - -lay::SaltGrain * -SaltManagerDialog::mine_current_grain () -{ - SaltModel *model = dynamic_cast (salt_mine_view->model ()); - return model ? model->grain_from_index (salt_mine_view->currentIndex ()) : 0; + return remote_grain.release (); } } diff --git a/src/lay/laySaltManagerDialog.h b/src/lay/laySaltManagerDialog.h index 4455ef9a3..f73d07674 100644 --- a/src/lay/laySaltManagerDialog.h +++ b/src/lay/laySaltManagerDialog.h @@ -24,6 +24,7 @@ #define HDR_laySaltManagerDialog #include "ui_SaltManagerDialog.h" +#include "laySalt.h" #include "tlDeferredExecution.h" #include @@ -32,8 +33,6 @@ namespace lay { -class Salt; -class SaltGrain; class SaltGrainPropertiesDialog; /** @@ -48,7 +47,7 @@ public: /** * @brief Constructor */ - SaltManagerDialog (QWidget *parent, lay::Salt *salt, lay::Salt *salt_mine); + SaltManagerDialog (QWidget *parent, lay::Salt *salt, const std::string &salt_mine_url); private slots: /** @@ -77,9 +76,14 @@ private slots: void current_changed (); /** - * @brief Called when the currently selected package from the salt mine has changed + * @brief Called when the currently selected package from the update page has changed */ - void mine_current_changed (); + void mine_update_current_changed (); + + /** + * @brief Called when the currently selected package from the new installation page has changed + */ + void mine_new_current_changed (); /** * @brief Called when the "edit" button is pressed @@ -117,31 +121,54 @@ private slots: void search_text_changed (const QString &text); /** - * @brief Called to show the marked items only + * @brief Called to show the marked items only (new packages tab) */ - void show_marked_only (); + void show_marked_only_new (); /** - * @brief Called to show all items again + * @brief Called to show all items again (new packages tab) */ - void show_all (); + void show_all_new (); /** - * @brief Called to unmark all items + * @brief Called to unmark all items (new packages tab) */ - void unmark_all (); + void unmark_all_new (); + + /** + * @brief Called to show the marked items only (update packages tab) + */ + void show_marked_only_update (); + + /** + * @brief Called to show all items again (update packages tab) + */ + void show_all_update (); + + /** + * @brief Called to unmark all items (update packages tab) + */ + void unmark_all_update (); + + /** + * @brief Reloads the salt mine + */ + void refresh (); private: - Salt *mp_salt, *mp_salt_mine; - std::auto_ptr m_remote_grain; - bool m_current_changed_enabled; + Salt *mp_salt; + Salt m_salt_mine; + std::string m_salt_mine_url; + std::auto_ptr m_remote_update_grain; + std::auto_ptr m_remote_new_grain; SaltGrainPropertiesDialog *mp_properties_dialog; tl::DeferredMethod dm_update_models; + int m_current_tab; SaltGrain *current_grain (); - SaltGrain *mine_current_grain (); void update_models (); void update_apply_state (); + SaltGrain *get_remote_grain_info (lay::SaltGrain *g, QString &html); }; } diff --git a/src/lay/laySaltModel.cc b/src/lay/laySaltModel.cc index 661b0934f..078582186 100644 --- a/src/lay/laySaltModel.cc +++ b/src/lay/laySaltModel.cc @@ -95,8 +95,10 @@ SaltItemDelegate::sizeHint (const QStyleOptionViewItem &option, const QModelInde // -------------------------------------------------------------------------------------- -SaltModel::SaltModel (QObject *parent, lay::Salt *salt) - : QAbstractItemModel (parent), mp_salt (salt), m_in_update (false) +SaltModel::SaltModel (QObject *parent, lay::Salt *salt, lay::Salt *salt_filtered, bool salt_exclude) + : QAbstractItemModel (parent), mp_salt (salt), + mp_salt_filtered (salt_filtered), m_salt_exclude (salt_exclude), + m_in_update (false) { create_ordered_list (); } @@ -389,6 +391,10 @@ SaltModel::create_ordered_list () if (m_display_order.empty ()) { for (Salt::flat_iterator i = mp_salt->begin_flat (); i != mp_salt->end_flat (); ++i) { + // filter the grains by looking them up in the reference salt + if (mp_salt_filtered && (mp_salt_filtered->grain_by_name ((*i)->name ()) != 0) == m_salt_exclude) { + continue; + } m_ordered_grains.push_back (*i); } @@ -406,6 +412,10 @@ SaltModel::create_ordered_list () for (int o = min_order; o <= max_order; ++o) { for (Salt::flat_iterator i = mp_salt->begin_flat (); i != mp_salt->end_flat (); ++i) { + // filter the grains by looking them up in the reference salt + if (mp_salt_filtered && (mp_salt_filtered->grain_by_name ((*i)->name ()) != 0) == m_salt_exclude) { + continue; + } std::map::const_iterator d = m_display_order.find ((*i)->name ()); int oi = 0; if (d != m_display_order.end ()) { diff --git a/src/lay/laySaltModel.h b/src/lay/laySaltModel.h index b95824665..ea6f02a2a 100644 --- a/src/lay/laySaltModel.h +++ b/src/lay/laySaltModel.h @@ -61,7 +61,7 @@ public: /** * @brief Constructor */ - SaltModel (QObject *parent, lay::Salt *salt); + SaltModel (QObject *parent, lay::Salt *salt, Salt *salt_filtered = 0, bool salt_exclude = false); /** * @brief Implementation of the QAbstractItemModel interface @@ -170,7 +170,8 @@ public: void clear_order (); public: - lay::Salt *mp_salt; + lay::Salt *mp_salt, *mp_salt_filtered; + bool m_salt_exclude; std::set m_marked; std::set m_disabled; std::map > m_messages; From 38f60ad3fdb2077a57b2d86261e3d61e0ab6f9db Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 22 Apr 2017 21:30:39 +0200 Subject: [PATCH 45/57] WIP: package manager lists file details now, API version added --- src/lay/SaltGrainPropertiesDialog.ui | 686 +++++++++++++---------- src/lay/images/empty_12.png | Bin 0 -> 146 bytes src/lay/images/file_12.png | Bin 0 -> 284 bytes src/lay/images/folder_12.png | Bin 0 -> 358 bytes src/lay/layResources.qrc | 3 + src/lay/laySaltGrain.cc | 8 + src/lay/laySaltGrain.h | 18 + src/lay/laySaltGrainDetailsTextWidget.cc | 46 +- src/lay/laySaltGrainPropertiesDialog.cc | 11 +- src/lay/laySaltManagerDialog.cc | 27 +- src/lay/laySaltManagerDialog.h | 2 +- 11 files changed, 484 insertions(+), 317 deletions(-) create mode 100644 src/lay/images/empty_12.png create mode 100644 src/lay/images/file_12.png create mode 100644 src/lay/images/folder_12.png diff --git a/src/lay/SaltGrainPropertiesDialog.ui b/src/lay/SaltGrainPropertiesDialog.ui index 1f8cc352f..f503a7832 100644 --- a/src/lay/SaltGrainPropertiesDialog.ui +++ b/src/lay/SaltGrainPropertiesDialog.ui @@ -41,100 +41,10 @@ 0 - - - - - 16777215 - 80 - - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 5 - - - - - - - - - 0 - 0 - - - - <a href="%1">Open link</a> - - - true - - - - - - - - 75 - true - - - - Author contact - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 75 - true - - - - License - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 5 - - - - - + @@ -150,71 +60,7 @@ - - - - Qt::Vertical - - - - 20 - 32 - - - - - - - - - 75 - true - - - - Title - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 75 - true - - - - Depends on - - - Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing - - - - - - - - 75 - true - - - - Version - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - + @@ -232,118 +78,58 @@ + + + + + + + + 75 + true + + + + Depends on + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - - - - Qt::NoFocus - - - ... - - - - :/warn.png:/warn.png - - - true - - - - - - - - + + 0 0 - - QFrame::NoFrame + + <a href="%1">Open link</a> - - QFrame::Raised + + true - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - - - - Qt::NoFocus - - - ... - - - - :/warn.png:/warn.png - - - true - - - - - + + + + Qt::Vertical + + + + 20 + 32 + + + + + @@ -468,8 +254,8 @@ - - + + 75 @@ -477,42 +263,46 @@ - Images - - - Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing - - - - - - - - true - - - - (use numeric versions like "1.5" or "2.1.3") - - - - - - - - 75 - true - - - - Author + Title Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + + + + + 75 + true + + + + API version + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 75 + true + + + + Author contact + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + @@ -680,7 +470,209 @@ - + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + + Qt::NoFocus + + + ... + + + + :/warn.png:/warn.png + + + true + + + + + + + + + + + true + + + + (license information like "GPLv3" or "MIT") + + + + + + + + 75 + true + + + + Version + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 75 + true + + + + Images + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + + Qt::NoFocus + + + ... + + + + :/warn.png:/warn.png + + + true + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + + true + + + + (use numeric versions like "1.5" or "2.1.3") + + + + + + + + 75 + true + + + + License + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + @@ -696,19 +688,7 @@ - - - - - true - - - - (license information like "GPLv3" or "MIT") - - - - + QFrame::NoFrame @@ -762,6 +742,110 @@ + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + + 16777215 + 80 + + + + + + + + + 75 + true + + + + Author + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::NoFocus + + + ... + + + + :/warn.png:/warn.png + + + true + + + + + + + + + + + true + + + + (API version required - i.e. "0.25") + + + diff --git a/src/lay/images/empty_12.png b/src/lay/images/empty_12.png new file mode 100644 index 0000000000000000000000000000000000000000..989ee8a3fac21b3ad60aef1bf1c11045f8c18132 GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&kmSQK*5Dp-y;YjHK@;M7UB8wRq zm~VhEW1f4mDNsLPZ!4! jjfu$#60D0GtQZ)Wd>9!0ru6s%r5HS2{an^LB{Ts5z4#)_ literal 0 HcmV?d00001 diff --git a/src/lay/images/file_12.png b/src/lay/images/file_12.png new file mode 100644 index 0000000000000000000000000000000000000000..5113c326282113b0a07ad9cc36e716d57df38f3a GIT binary patch literal 284 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&kmSQK*5Dp-y;YjHK@;M7UB8wRq zm~VhEW1f4mDNs5|7uS^&5}mG6&8IvJf^Qu)+p_as|uVim$S8LYK;KXLvf)WDbL@P zKlr%5jW!g1cfP&N($ zj*f|Lo811N{lASz;FZ_?hTNB@URrNRT2yjx$L`bJiz93HF8lK9*^OJ*?)otXS-zbo zckcDo#9%iYJ9j_7_vX)@EPnkchh>G=pPHSQdYY?_*c9GQy2?1i-}q}p@wUR<1-uy! zSDGt*CJT5cU16TUaQprCDZNKE=Q4M^W!UJd@8uwI;Pb_$n-3Q$$XT4c*B^fBz@uoE z`|dwP1-K*c_;I;6+%R%q@n3|Qimages/marked_24.png images/marked_64.png images/marked_16.png + images/folder_12.png + images/file_12.png + images/empty_12.png syntax/ruby.xml diff --git a/src/lay/laySaltGrain.cc b/src/lay/laySaltGrain.cc index e3fe54985..83ed47e4f 100644 --- a/src/lay/laySaltGrain.cc +++ b/src/lay/laySaltGrain.cc @@ -46,6 +46,7 @@ SaltGrain::operator== (const SaltGrain &other) const return m_name == other.m_name && m_path == other.m_path && m_version == other.m_version && + m_api_version == other.m_api_version && m_url == other.m_url && m_title == other.m_title && m_doc == other.m_doc && @@ -72,6 +73,12 @@ SaltGrain::set_version (const std::string &v) m_version = v; } +void +SaltGrain::set_api_version (const std::string &v) +{ + m_api_version = v; +} + void SaltGrain::set_path (const std::string &p) { @@ -301,6 +308,7 @@ struct ImageConverter static tl::XMLElementList s_xml_elements = tl::make_member (&SaltGrain::name, &SaltGrain::set_name, "name") + tl::make_member (&SaltGrain::version, &SaltGrain::set_version, "version") + + tl::make_member (&SaltGrain::api_version, &SaltGrain::set_api_version, "api-version") + tl::make_member (&SaltGrain::title, &SaltGrain::set_title, "title") + tl::make_member (&SaltGrain::doc, &SaltGrain::set_doc, "doc") + tl::make_member (&SaltGrain::doc_url, &SaltGrain::set_doc_url, "doc-url") + diff --git a/src/lay/laySaltGrain.h b/src/lay/laySaltGrain.h index df30e7f49..9f92763db 100644 --- a/src/lay/laySaltGrain.h +++ b/src/lay/laySaltGrain.h @@ -161,6 +161,23 @@ public: */ void set_version (const std::string &v); + /** + * @brief Gets the API version of the grain + * + * The API version is the KLayout version required to run the grain's macros. + * A version string is of the form "x.y..." where x, y and other version + * components are integer numbers. + */ + const std::string &api_version () const + { + return m_api_version; + } + + /** + * @brief Sets the API version of the grain + */ + void set_api_version (const std::string &v); + /** * @brief Gets the author of the grain */ @@ -403,6 +420,7 @@ public: private: std::string m_name; std::string m_version; + std::string m_api_version; std::string m_path; std::string m_url; std::string m_title; diff --git a/src/lay/laySaltGrainDetailsTextWidget.cc b/src/lay/laySaltGrainDetailsTextWidget.cc index 310a18a6e..660eed4d1 100644 --- a/src/lay/laySaltGrainDetailsTextWidget.cc +++ b/src/lay/laySaltGrainDetailsTextWidget.cc @@ -28,6 +28,8 @@ #include #include #include +#include +#include namespace lay { @@ -116,6 +118,37 @@ SaltGrainDetailsTextWidget::loadResource (int type, const QUrl &url) } } +static void produce_listing (QTextStream &stream, QDir dir, int level) +{ + for (int i = 0; i < level + 1; ++i) { + stream << "  "; + } + stream << "  "; + if (level > 0) { + stream << tl::escaped_to_html (tl::to_string (dir.dirName ())).c_str (); + } else { + stream << tl::escaped_to_html (tl::to_string (dir.absolutePath ())).c_str (); + } + stream << "
\n"; + + level += 1; + + QStringList entries = dir.entryList (QDir::AllEntries | QDir::NoDotAndDotDot, QDir::Name); + for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) { + + QFileInfo fi (dir.filePath (*e)); + if (fi.isDir ()) { + produce_listing (stream, QDir (fi.filePath ()), level); + } else { + for (int i = 0; i < level + 1; ++i) { + stream << "  "; + } + stream << "  " << tl::escaped_to_html (tl::to_string (*e)).c_str () << "
\n"; + } + + } +} + QString SaltGrainDetailsTextWidget::details_text () { @@ -196,6 +229,12 @@ SaltGrainDetailsTextWidget::details_text () } stream << "

"; + stream << "

"; + if (! g->api_version ().empty ()) { + stream << "" << QObject::tr ("API version") << ": " << tl::to_qstring (tl::escaped_to_html (g->api_version ())) << " "; + } + stream << "

"; + stream << "

"; if (! g->doc_url ().empty ()) { stream << "" << QObject::tr ("Documentation link") << ": doc_url ()) << "\">" << tl::to_qstring (tl::escaped_to_html (g->doc_url ())) << ""; @@ -215,7 +254,6 @@ SaltGrainDetailsTextWidget::details_text () stream << "
"; stream << "

" << QObject::tr ("Installation") << "

"; - stream << "

" << QObject::tr ("Installation path: ") << "" << tl::to_qstring (tl::escaped_to_html (g->path ())) << "

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

" << QObject::tr ("Download URL: ") << "" << tl::to_qstring (tl::escaped_to_html (g->url ())) << "

"; } @@ -235,6 +273,12 @@ SaltGrainDetailsTextWidget::details_text () stream << "

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

" << QObject::tr ("Installed files: ") << "

"; + produce_listing (stream, QDir (tl::to_qstring (g->path ())), 0); + stream << "

"; + } + stream << ""; stream << ""; diff --git a/src/lay/laySaltGrainPropertiesDialog.cc b/src/lay/laySaltGrainPropertiesDialog.cc index c3ce4244c..f1786a553 100644 --- a/src/lay/laySaltGrainPropertiesDialog.cc +++ b/src/lay/laySaltGrainPropertiesDialog.cc @@ -160,6 +160,7 @@ SaltGrainPropertiesDialog::update_controls () dependencies_alert->clear (); version->setText (tl::to_qstring (m_grain.version ())); + api_version->setText (tl::to_qstring (m_grain.api_version ())); title->setText (tl::to_qstring (m_grain.title ())); author->setText (tl::to_qstring (m_grain.author ())); author_contact->setText (tl::to_qstring (m_grain.author_contact ())); @@ -222,6 +223,7 @@ void SaltGrainPropertiesDialog::update_data () { m_grain.set_version (tl::to_string (version->text ())); + m_grain.set_api_version (tl::to_string (api_version->text ())); m_grain.set_title (tl::to_string (title->text ())); m_grain.set_author (tl::to_string (author->text ())); m_grain.set_author_contact (tl::to_string (author_contact->text ())); @@ -524,6 +526,12 @@ SaltGrainPropertiesDialog::accept () 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 ())); } + // API version + api_version_alert->clear (); + if (! m_grain.api_version ().empty () && ! SaltGrain::valid_version (m_grain.api_version ())) { + api_version_alert->error () << tr ("'%1' is not a valid API version string. An API version string needs to be numeric (like '0.25'').").arg (tl::to_qstring (m_grain.api_version ())); + } + // doc URL doc_url_alert->clear (); if (! m_grain.doc_url ().empty ()) { @@ -595,7 +603,8 @@ SaltGrainPropertiesDialog::accept () if (!license_alert->needs_attention () && !doc_url_alert->needs_attention () && !dependencies_alert->needs_attention () && - !version_alert->needs_attention ()) { + !version_alert->needs_attention () && + !api_version_alert->needs_attention ()) { QDialog::accept (); } else { if (QMessageBox::warning (this, tr ("Issues Encountered"), diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index 295b0fa71..e1ab7d007 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -217,6 +217,9 @@ SaltManagerDialog::SaltManagerDialog (QWidget *parent, lay::Salt *salt, const st void SaltManagerDialog::mode_changed () { + // commits edits: + setFocus (Qt::NoFocusReason); + QList sizes; if (m_current_tab == 0) { sizes = splitter->sizes (); @@ -747,12 +750,8 @@ BEGIN_PROTECTED details_update_frame->setEnabled (g != 0); - QString html; - SaltGrain *remote_grain = get_remote_grain_info (g, html); - + SaltGrain *remote_grain = get_remote_grain_info (g, details_update_text); m_remote_update_grain.reset (remote_grain); - details_update_text->set_grain (remote_grain); - details_update_text->setHtml (html); END_PROTECTED } @@ -768,18 +767,14 @@ BEGIN_PROTECTED details_new_frame->setEnabled (g != 0); - QString html; - SaltGrain *remote_grain = get_remote_grain_info (g, html); - + SaltGrain *remote_grain = get_remote_grain_info (g, details_new_text); m_remote_new_grain.reset (remote_grain); - details_new_text->set_grain (remote_grain); - details_new_text->setHtml (html); END_PROTECTED } lay::SaltGrain * -SaltManagerDialog::get_remote_grain_info (lay::SaltGrain *g, QString &html) +SaltManagerDialog::get_remote_grain_info (lay::SaltGrain *g, SaltGrainDetailsTextWidget *details) { if (! g) { return 0; @@ -795,7 +790,7 @@ SaltManagerDialog::get_remote_grain_info (lay::SaltGrain *g, QString &html) throw tl::Exception (tl::to_string (tr ("No download link available"))); } - html = tr ( + QString html = tr ( "" "" "" @@ -807,6 +802,8 @@ SaltManagerDialog::get_remote_grain_info (lay::SaltGrain *g, QString &html) ) .arg (tl::to_qstring (SaltGrain::spec_url (g->url ()))); + details->setHtml (html); + QApplication::processEvents (QEventLoop::ExcludeUserInputEvents); tl::InputStream stream (SaltGrain::spec_url (g->url ())); @@ -822,11 +819,13 @@ SaltManagerDialog::get_remote_grain_info (lay::SaltGrain *g, QString &html) throw tl::Exception (tl::to_string (tr ("Version mismatch between repository and actual package (repository: %1, package: %2)").arg (tl::to_qstring (g->version ())).arg (tl::to_qstring (remote_grain->version ())))); } + details->set_grain (remote_grain.get ()); + } catch (tl::Exception &ex) { remote_grain.reset (0); - html = tr ( + QString html = tr ( "" "" "" @@ -840,6 +839,8 @@ SaltManagerDialog::get_remote_grain_info (lay::SaltGrain *g, QString &html) .arg (tl::to_qstring (SaltGrain::spec_url (g->url ()))) .arg (tl::to_qstring (tl::escaped_to_html (ex.msg ()))); + details->setHtml (html); + } return remote_grain.release (); diff --git a/src/lay/laySaltManagerDialog.h b/src/lay/laySaltManagerDialog.h index f73d07674..88493198d 100644 --- a/src/lay/laySaltManagerDialog.h +++ b/src/lay/laySaltManagerDialog.h @@ -168,7 +168,7 @@ private: SaltGrain *current_grain (); void update_models (); void update_apply_state (); - SaltGrain *get_remote_grain_info (lay::SaltGrain *g, QString &html); + SaltGrain *get_remote_grain_info (lay::SaltGrain *g, SaltGrainDetailsTextWidget *details); }; } From 42756a76f69b858ab8685d5b8fea8d8fbea3fdaa Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 22 Apr 2017 21:45:49 +0200 Subject: [PATCH 46/57] WIP: API version of packages is checked now. --- src/lay/SaltManagerDialog.ui | 8 ++++---- src/lay/laySaltManagerDialog.cc | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/lay/SaltManagerDialog.ui b/src/lay/SaltManagerDialog.ui index b0cd99823..ccdc50640 100644 --- a/src/lay/SaltManagerDialog.ui +++ b/src/lay/SaltManagerDialog.ui @@ -17,11 +17,11 @@ - 1 + 0 - Browse Installed Packages + Installed Packages @@ -231,8 +231,8 @@ 0 0 - 343 - 207 + 537 + 284 diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index e1ab7d007..6fb4dc59c 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -25,6 +25,7 @@ #include "laySaltGrainPropertiesDialog.h" #include "laySaltDownloadManager.h" #include "laySalt.h" +#include "layVersion.h" #include "ui_SaltGrainTemplateSelectionDialog.h" #include "tlString.h" #include "tlExceptions.h" @@ -685,6 +686,14 @@ SaltManagerDialog::update_models () } } + // Establish a message indicating whether the API version does not match + for (Salt::flat_iterator g = m_salt_mine.begin_flat (); g != m_salt_mine.end_flat (); ++g) { + if (SaltGrain::compare_versions (lay::Version::version (), (*g)->api_version ()) < 0) { + mine_model->set_message ((*g)->name (), SaltModel::Warning, tl::to_string (tr ("This package requires a newer API (%1)").arg (tl::to_qstring ((*g)->api_version ())))); + mine_model->set_enabled ((*g)->name (), false); + } + } + if (has_warning) { mode_tab->setTabIcon (1, QIcon (":/warn_16.png")); } else { @@ -704,6 +713,15 @@ SaltManagerDialog::update_models () mine_model->clear_order (); mine_model->clear_messages (); mine_model->enable_all (); + + // Establish a message indicating whether the API version does not match + for (Salt::flat_iterator g = m_salt_mine.begin_flat (); g != m_salt_mine.end_flat (); ++g) { + if (SaltGrain::compare_versions (lay::Version::version (), (*g)->api_version ()) < 0) { + mine_model->set_message ((*g)->name (), SaltModel::Warning, tl::to_string (tr ("This package requires a newer API (%1)").arg (tl::to_qstring ((*g)->api_version ())))); + mine_model->set_enabled ((*g)->name (), false); + } + } + mine_model->update (); // select the first grain From 1f92b89e16ea4109a93fe49122099ef081fcb5e4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 22 Apr 2017 21:56:37 +0200 Subject: [PATCH 47/57] WIP: Don't allow to edit downloaded packages. --- src/lay/laySaltGrain.cc | 4 +++- src/lay/laySaltManagerDialog.cc | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lay/laySaltGrain.cc b/src/lay/laySaltGrain.cc index 83ed47e4f..d39664ad5 100644 --- a/src/lay/laySaltGrain.cc +++ b/src/lay/laySaltGrain.cc @@ -337,7 +337,9 @@ SaltGrain::xml_struct () bool SaltGrain::is_readonly () const { - return !QFileInfo (tl::to_qstring (path ())).isWritable (); + // A grain is readonly if the directory is not writable or there is a download URL + // (this means the grain has been installed from an URL). + return !QFileInfo (tl::to_qstring (path ())).isWritable () || !m_url.empty (); } void diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index 6fb4dc59c..d71be4edb 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -512,7 +512,10 @@ SaltManagerDialog::edit_properties () { SaltGrain *g = current_grain (); if (g) { - if (mp_properties_dialog->exec_dialog (g, mp_salt)) { + if (g->is_readonly ()) { + QMessageBox::critical (this, tr ("Package is not Editable"), + tr ("This package cannot be edited.\n\nEither you don't have write permissions on the directory or the package was installed from a repository.")); + } else if (mp_properties_dialog->exec_dialog (g, mp_salt)) { current_changed (); } } From 561ce8797e8a7c369bd1bf147d1f50cd5490f7b3 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 22 Apr 2017 22:03:40 +0200 Subject: [PATCH 48/57] WIP: some restyling of the salt manager dialog. --- src/lay/SaltManagerDialog.ui | 77 +++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/src/lay/SaltManagerDialog.ui b/src/lay/SaltManagerDialog.ui index ccdc50640..a0d9944c6 100644 --- a/src/lay/SaltManagerDialog.ui +++ b/src/lay/SaltManagerDialog.ui @@ -17,15 +17,21 @@ - 0 + 2 - Installed Packages + Current Packages + + + 0 + 1 + + Qt::Horizontal @@ -64,26 +70,6 @@ 0 - - - - Qt::NoFocus - - - Create New Package - - - ... - - - - :/add.png:/add.png - - - true - - - @@ -406,6 +392,53 @@ + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Create Package + + + false + + + + + + + Qt::Horizontal + + + + 540 + 20 + + + + + + + From 0b7beee12dc0970704c80c507ca9912ad24296ec Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 22 Apr 2017 22:47:08 +0200 Subject: [PATCH 49/57] WIP: indicating of packages in tech and macros Technologies and macros that come from packages are indicated as such in their description texts in the tech and macro editor. Technologies and macros that come from packages which are downloaded are readonly now. This will prevent editing of downloaded packages. --- src/lay/TechSetupDialog.ui | 6 +++++ src/lay/layMacroController.cc | 27 ++++++++++++++++++--- src/lay/layMacroController.h | 5 ++-- src/lay/layTechSetupDialog.cc | 39 ++++++++++++++++++------------ src/lay/layTechnologyController.cc | 17 ++++++++++++- src/laybasic/layTechnology.cc | 3 ++- src/laybasic/layTechnology.h | 19 +++++++++++++++ 7 files changed, 93 insertions(+), 23 deletions(-) diff --git a/src/lay/TechSetupDialog.ui b/src/lay/TechSetupDialog.ui index 490dc3654..56a85c4d9 100644 --- a/src/lay/TechSetupDialog.ui +++ b/src/lay/TechSetupDialog.ui @@ -115,6 +115,9 @@ :/add.png:/add.png + + true + @@ -126,6 +129,9 @@ :/clear.png:/clear.png + + true + diff --git a/src/lay/layMacroController.cc b/src/lay/layMacroController.cc index a069e6d1f..e9a086ed0 100644 --- a/src/lay/layMacroController.cc +++ b/src/lay/layMacroController.cc @@ -300,12 +300,21 @@ MacroController::sync_implicit_macros (bool ask_before_autorun) // Add additional places where the technologies define some macros std::map > tech_names_by_path; + std::map > grain_names_by_path; + std::set readonly_paths; for (lay::Technologies::const_iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { if (! t->base_path ().empty ()) { QDir base_dir (tl::to_qstring (t->base_path ())); if (base_dir.exists ()) { - tech_names_by_path [tl::to_string (base_dir.absolutePath ())].push_back (t->name ()); + std::string path = tl::to_string (base_dir.absolutePath ()); + tech_names_by_path [path].push_back (t->name ()); + if (t->is_readonly ()) { + readonly_paths.insert (path); + } + if (! t->grain_name ().empty ()) { + grain_names_by_path [path].push_back (t->grain_name ()); + } } } } @@ -325,7 +334,17 @@ MacroController::sync_implicit_macros (bool ask_before_autorun) description = tl::to_string (tr ("Technologies %1").arg (tl::to_qstring (tl::join (t->second, ",")))); } - external_paths.push_back (ExternalPathDescriptor (tl::to_string (macro_dir.path ()), description, macro_categories () [c].first, lay::MacroCollection::TechFolder)); + std::map >::const_iterator gn = grain_names_by_path.find (t->first); + if (gn != grain_names_by_path.end ()) { + description += " - "; + if (gn->second.size () == 1) { + description += tl::to_string (tr ("Package %1").arg (tl::to_qstring (gn->second.front ()))); + } else { + description += tl::to_string (tr ("Packages %1").arg (tl::to_qstring (tl::join (gn->second, ",")))); + } + } + + external_paths.push_back (ExternalPathDescriptor (tl::to_string (macro_dir.path ()), description, macro_categories () [c].first, lay::MacroCollection::TechFolder, readonly_paths.find (t->first) != readonly_paths.end ())); } @@ -350,7 +369,7 @@ MacroController::sync_implicit_macros (bool ask_before_autorun) if (macro_dir.exists ()) { std::string description = tl::to_string (tr ("Package %1").arg (tl::to_qstring (g->name ()))); - external_paths.push_back (ExternalPathDescriptor (tl::to_string (macro_dir.path ()), description, macro_categories () [c].first, lay::MacroCollection::SaltFolder)); + external_paths.push_back (ExternalPathDescriptor (tl::to_string (macro_dir.path ()), description, macro_categories () [c].first, lay::MacroCollection::SaltFolder, g->is_readonly ())); } @@ -420,7 +439,7 @@ MacroController::sync_implicit_macros (bool ask_before_autorun) // In that case, the add_folder method will return 0. // TODO: is it wise to make this writeable? - lay::MacroCollection *mc = lay::MacroCollection::root ().add_folder (p->description, p->path, p->cat, false); + lay::MacroCollection *mc = lay::MacroCollection::root ().add_folder (p->description, p->path, p->cat, p->readonly); if (mc) { mc->set_virtual_mode (p->type); new_folders.push_back (mc); diff --git a/src/lay/layMacroController.h b/src/lay/layMacroController.h index 0eaf18d8d..e245bf643 100644 --- a/src/lay/layMacroController.h +++ b/src/lay/layMacroController.h @@ -180,8 +180,8 @@ private: */ struct ExternalPathDescriptor { - ExternalPathDescriptor (const std::string &_path, const std::string &_description, const std::string &_cat, lay::MacroCollection::FolderType _type) - : path (_path), description (_description), cat (_cat), type (_type) + ExternalPathDescriptor (const std::string &_path, const std::string &_description, const std::string &_cat, lay::MacroCollection::FolderType _type, bool _readonly) + : path (_path), description (_description), cat (_cat), type (_type), readonly (_readonly) { // .. nothing yet .. } @@ -190,6 +190,7 @@ private: std::string description; std::string cat; lay::MacroCollection::FolderType type; + bool readonly; }; /** diff --git a/src/lay/layTechSetupDialog.cc b/src/lay/layTechSetupDialog.cc index 405274266..edddde5df 100644 --- a/src/lay/layTechSetupDialog.cc +++ b/src/lay/layTechSetupDialog.cc @@ -49,6 +49,28 @@ namespace lay { +// ---------------------------------------------------------------- + +static std::string +title_for_technology (const lay::Technology *t) +{ + std::string d; + if (t->name ().empty ()) { + d = t->description (); + } else { + d += t->name (); + if (! t->grain_name ().empty ()) { + d += " "; + d += tl::to_string (QObject::tr ("[Package %1]").arg (tl::to_qstring (t->grain_name ()))); + } + if (! t->description ().empty ()) { + d += " - "; + d += t->description (); + } + } + return d; +} + // ---------------------------------------------------------------- // TechBaseEditorPage implementation @@ -843,15 +865,8 @@ TechSetupDialog::update_tech_tree () QFont f (tech_tree->font ()); f.setItalic (t->second->is_readonly ()); - std::string d; - d += t->first; - if (! d.empty () && ! t->second->description ().empty ()) { - d += " - "; - } - d += t->second->description (); - QTreeWidgetItem *ti = new QTreeWidgetItem (tech_tree); - ti->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (d))); + ti->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (title_for_technology (t->second)))); ti->setData (0, Qt::UserRole, QVariant (tl::to_qstring (t->first))); ti->setData (0, Qt::FontRole, QVariant (f)); if (! t->second->tech_file_path ().empty ()) { @@ -1059,13 +1074,7 @@ TechSetupDialog::commit_tech_component () QTreeWidgetItem *item = tech_tree->topLevelItem (i - 1); lay::Technology *t = m_technologies.technology_by_name (tl::to_string (item->data (0, Qt::UserRole).toString ())); - std::string d = t->name (); - if (! d.empty () && ! t->description ().empty ()) { - d += " - "; - } - d += t->description (); - - item->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (d))); + item->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (title_for_technology (t)))); } diff --git a/src/lay/layTechnologyController.cc b/src/lay/layTechnologyController.cc index 51f7228b6..1d0e1491d 100644 --- a/src/lay/layTechnologyController.cc +++ b/src/lay/layTechnologyController.cc @@ -495,12 +495,18 @@ TechnologyController::rescan (lay::Technologies &technologies) } std::vector paths = m_paths; + std::set readonly_paths; + std::map grain_names; // add the salt grains as potential sources for tech definitions lay::SaltController *sc = lay::SaltController::instance (); if (sc) { for (lay::Salt::flat_iterator g = sc->salt ().begin_flat (); g != sc->salt ().end_flat (); ++g) { paths.push_back ((*g)->path ()); + grain_names.insert (std::make_pair ((*g)->path (), (*g)->name ())); + if ((*g)->is_readonly ()) { + readonly_paths.insert ((*g)->path ()); + } } } @@ -511,6 +517,14 @@ TechnologyController::rescan (lay::Technologies &technologies) continue; } + bool readonly = (readonly_paths.find (*p) != readonly_paths.end ()); + + std::string grain_name; + std::map::const_iterator gn = grain_names.find (*p); + if (gn != grain_names.end ()) { + grain_name = gn->second; + } + QStringList name_filters; name_filters << QString::fromUtf8 ("*.lyt"); @@ -534,7 +548,8 @@ TechnologyController::rescan (lay::Technologies &technologies) lay::Technology t; t.load (tl::to_string (*lf)); t.set_persisted (false); // don't save that one in the configuration - t.set_readonly (! QFileInfo (dir.filePath (*lf)).isWritable ()); + t.set_readonly (readonly || ! QFileInfo (dir.filePath (*lf)).isWritable ()); + t.set_grain_name (grain_name); technologies.add (new lay::Technology (t)); } catch (tl::Exception &ex) { diff --git a/src/laybasic/layTechnology.cc b/src/laybasic/layTechnology.cc index 09e596bdd..9e4c15bcb 100644 --- a/src/laybasic/layTechnology.cc +++ b/src/laybasic/layTechnology.cc @@ -252,7 +252,7 @@ Technology::~Technology () Technology::Technology (const Technology &d) : tl::Object (), - m_name (d.m_name), m_description (d.m_description), m_dbu (d.m_dbu), + m_name (d.m_name), m_description (d.m_description), m_grain_name (d.m_grain_name), m_dbu (d.m_dbu), m_explicit_base_path (d.m_explicit_base_path), m_default_base_path (d.m_default_base_path), m_load_layout_options (d.m_load_layout_options), m_save_layout_options (d.m_save_layout_options), @@ -270,6 +270,7 @@ Technology &Technology::operator= (const Technology &d) m_name = d.m_name; m_description = d.m_description; + m_grain_name = d.m_grain_name; m_dbu = d.m_dbu; m_default_base_path = d.m_default_base_path; m_explicit_base_path = d.m_explicit_base_path; diff --git a/src/laybasic/layTechnology.h b/src/laybasic/layTechnology.h index 50fc61c1e..ff28f3eaf 100644 --- a/src/laybasic/layTechnology.h +++ b/src/laybasic/layTechnology.h @@ -280,6 +280,24 @@ public: } } + /** + * @brief Sets the package source + * + * This attribute indicates that this technology was contributed by a package + */ + void set_grain_name (const std::string &g) + { + m_grain_name = g; + } + + /** + * @brief Gets the package source + */ + const std::string &grain_name () const + { + return m_grain_name; + } + /** * @brief Gets the base path * @@ -575,6 +593,7 @@ public: private: std::string m_name, m_description; + std::string m_grain_name; double m_dbu; std::string m_explicit_base_path, m_default_base_path; db::LoadLayoutOptions m_load_layout_options; From 1a9b5ead09e21e939919dd91e8a115f006639a1d Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 22 Apr 2017 23:32:59 +0200 Subject: [PATCH 50/57] WIP: documentation URL's of packages can be relative If the documentation URL is relative it's either taken relative to the package installation path or to the download URL. This means that documentation can be inside the package now. --- src/lay/laySaltGrain.cc | 33 ++++++++++++++++++++++++ src/lay/laySaltGrain.h | 8 ++++++ src/lay/laySaltGrainDetailsTextWidget.cc | 13 ++++++++-- src/lay/laySaltGrainDetailsTextWidget.h | 5 ++++ src/lay/laySaltGrainPropertiesDialog.cc | 3 ++- 5 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/lay/laySaltGrain.cc b/src/lay/laySaltGrain.cc index d39664ad5..d1a93d396 100644 --- a/src/lay/laySaltGrain.cc +++ b/src/lay/laySaltGrain.cc @@ -29,6 +29,7 @@ #include #include #include +#include namespace lay { @@ -109,6 +110,38 @@ SaltGrain::set_doc_url (const std::string &u) m_doc_url = u; } +std::string +SaltGrain::eff_doc_url () const +{ + if (m_doc_url.empty ()) { + return std::string (); + } + + QUrl url (tl::to_qstring (m_doc_url)); + if (! url.scheme ().isEmpty ()) { + return m_doc_url; + } + + if (! path ().empty ()) { + + // if the URL is a relative URL, make it absolute relative to the grain's installation directory + QFileInfo fi (url.toLocalFile ()); + if (! fi.isAbsolute ()) { + url = QUrl::fromLocalFile (QDir (tl::to_qstring (path ())).absoluteFilePath (fi.filePath ())); + } + url.setScheme (tl::to_qstring ("file")); + return tl::to_string (url.toString ()); + + } else { + + // base the documentation URL on the download URL + QUrl eff_url = QUrl (tl::to_qstring (m_url)); + eff_url.setPath (eff_url.path () + QString::fromUtf8 ("/") + url.path ()); + return tl::to_string (eff_url.toString ()); + + } +} + void SaltGrain::set_author (const std::string &a) { diff --git a/src/lay/laySaltGrain.h b/src/lay/laySaltGrain.h index 9f92763db..a8c7bff8c 100644 --- a/src/lay/laySaltGrain.h +++ b/src/lay/laySaltGrain.h @@ -145,6 +145,14 @@ public: */ void set_doc_url (const std::string &u); + /** + * @brief Gets the effective documentation URL + * + * The effective documentation URL is formed from the installation path + * and the documentation URL if the latter is a relative one. + */ + std::string eff_doc_url () const; + /** * @brief Gets the version of the grain * diff --git a/src/lay/laySaltGrainDetailsTextWidget.cc b/src/lay/laySaltGrainDetailsTextWidget.cc index 660eed4d1..59ea4f260 100644 --- a/src/lay/laySaltGrainDetailsTextWidget.cc +++ b/src/lay/laySaltGrainDetailsTextWidget.cc @@ -30,6 +30,7 @@ #include #include #include +#include namespace lay { @@ -37,7 +38,9 @@ namespace lay SaltGrainDetailsTextWidget::SaltGrainDetailsTextWidget (QWidget *w) : QTextBrowser (w), mp_grain (0) { - // .. nothing yet .. + setOpenLinks (false); + setOpenExternalLinks (false); + connect (this, SIGNAL (anchorClicked (const QUrl &)), this, SLOT (open_link (const QUrl &))); } void SaltGrainDetailsTextWidget::set_grain (SaltGrain *g) @@ -46,6 +49,12 @@ void SaltGrainDetailsTextWidget::set_grain (SaltGrain *g) setHtml (details_text ()); } +void +SaltGrainDetailsTextWidget::open_link (const QUrl &url) +{ + QDesktopServices::openUrl (url); +} + QVariant SaltGrainDetailsTextWidget::loadResource (int type, const QUrl &url) { @@ -237,7 +246,7 @@ SaltGrainDetailsTextWidget::details_text () stream << "

"; if (! g->doc_url ().empty ()) { - stream << "" << QObject::tr ("Documentation link") << ": doc_url ()) << "\">" << tl::to_qstring (tl::escaped_to_html (g->doc_url ())) << ""; + stream << "" << QObject::tr ("Documentation link") << ": eff_doc_url ()) << "\">" << tl::to_qstring (tl::escaped_to_html (g->eff_doc_url ())) << ""; } else { stream << ""; stream << QObject::tr ("This package does not have a documentation link. " diff --git a/src/lay/laySaltGrainDetailsTextWidget.h b/src/lay/laySaltGrainDetailsTextWidget.h index bb25a0e48..6ba47f144 100644 --- a/src/lay/laySaltGrainDetailsTextWidget.h +++ b/src/lay/laySaltGrainDetailsTextWidget.h @@ -36,6 +36,8 @@ class SaltGrain; class SaltGrainDetailsTextWidget : public QTextBrowser { +Q_OBJECT + public: /** * @brief Constructor @@ -50,6 +52,9 @@ public: protected: virtual QVariant loadResource (int type, const QUrl &url); +private slots: + void open_link (const QUrl &url); + private: lay::SaltGrain *mp_grain; diff --git a/src/lay/laySaltGrainPropertiesDialog.cc b/src/lay/laySaltGrainPropertiesDialog.cc index f1786a553..bfc24ab6f 100644 --- a/src/lay/laySaltGrainPropertiesDialog.cc +++ b/src/lay/laySaltGrainPropertiesDialog.cc @@ -327,7 +327,8 @@ void SaltGrainPropertiesDialog::url_changed (const QString &url) { // inserts the URL into the label - open_label->setText (m_open_label.arg (url)); + m_grain.set_doc_url (tl::to_string (url)); + open_label->setText (m_open_label.arg (tl::to_qstring (m_grain.eff_doc_url ()))); open_label->setEnabled (! url.isEmpty ()); } From 01e5c607fc7ca08f815b6110ca692fab8cb47419 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 23 Apr 2017 18:22:06 +0200 Subject: [PATCH 51/57] WIP: sort packages by their dependencies This fix ensures that packages satisfying dependencies for other packages come before these in the flat package order. This is in particular important for macro packages where those supplying libraries need to be executed before the ones that use these. --- src/lay/laySalt.cc | 61 +++++++++++++++++++++++++++++++++++++-- src/lay/laySalt.h | 7 +++++ src/unit_tests/laySalt.cc | 42 +++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 2 deletions(-) diff --git a/src/lay/laySalt.cc b/src/lay/laySalt.cc index 9def56e49..001fc4354 100644 --- a/src/lay/laySalt.cc +++ b/src/lay/laySalt.cc @@ -56,6 +56,12 @@ Salt &Salt::operator= (const Salt &other) return *this; } +SaltGrains & +Salt::root () +{ + return m_root; +} + Salt::flat_iterator Salt::begin_flat () { @@ -146,13 +152,32 @@ Salt::add_collection_to_flat (SaltGrains &gg) namespace { -struct NameCompare +struct NameAndTopoIndexCompare { + NameAndTopoIndexCompare (const std::map &topo_index) + : mp_topo_index (&topo_index) + { + // .. nothing yet .. + } + bool operator () (lay::SaltGrain *a, lay::SaltGrain *b) const { + std::map::const_iterator ti_a = mp_topo_index->find (a->name ()); + std::map::const_iterator ti_b = mp_topo_index->find (b->name ()); + + // Reverse sorting by topological index as highest priority + if (ti_a != mp_topo_index->end () && ti_b != mp_topo_index->end ()) { + if (ti_a->second != ti_b->second) { + return ti_a->second > ti_b->second; + } + } + // TODO: UTF-8 support? return a->name () < b->name (); } + +private: + const std::map *mp_topo_index; }; } @@ -169,9 +194,41 @@ Salt::validate () m_grains_by_name.insert (std::make_pair ((*i)->name (), *i)); } + // Compute a set of topological indexes. Packages which serve depedencies of other packages have a higher + // topological index. Later we sort the packages by descending topo index to ensure the packages which are + // input to others come first. + + std::map topological_index; + for (std::map::const_iterator g = m_grains_by_name.begin (); g != m_grains_by_name.end (); ++g) { + topological_index.insert (std::make_pair (g->first, 0)); + } + + // NOTE: we allow max. 10 levels of dependencies. That should be sufficient. Limiting the levels of dependencies prevents + // infinite recursion due to faulty recursive dependencies. + for (int n = 0; n < 10; ++n) { + + bool any_updated = false; + + for (std::map::const_iterator g = m_grains_by_name.begin (); g != m_grains_by_name.end (); ++g) { + int index = topological_index [g->first]; + for (std::vector::const_iterator d = g->second->dependencies ().begin (); d != g->second->dependencies ().end (); ++d) { + std::map::iterator ti = topological_index.find (d->name); + if (ti != topological_index.end () && ti->second < index + 1) { + ti->second = index + 1; + any_updated = true; + } + } + } + + if (! any_updated) { + break; + } + + } + // NOTE: we intentionally sort after the name list has been built - this way // the first entry will win in the name to grain map. - std::sort (mp_flat_grains.begin (), mp_flat_grains.end (), NameCompare ()); + std::sort (mp_flat_grains.begin (), mp_flat_grains.end (), NameAndTopoIndexCompare (topological_index)); } } diff --git a/src/lay/laySalt.h b/src/lay/laySalt.h index c37766bfc..aef3da0ac 100644 --- a/src/lay/laySalt.h +++ b/src/lay/laySalt.h @@ -185,6 +185,13 @@ public: */ bool create_grain (const SaltGrain &templ, SaltGrain &target); + /** + * @brief Gets the root collection + * + * This method is provided for test purposes mainly. + */ + SaltGrains &root (); + signals: /** * @brief A signal triggered before one of the collections changed diff --git a/src/unit_tests/laySalt.cc b/src/unit_tests/laySalt.cc index 8edff9a14..7c5e6eb81 100644 --- a/src/unit_tests/laySalt.cc +++ b/src/unit_tests/laySalt.cc @@ -357,3 +357,45 @@ TEST (4) EXPECT_EQ (salt.grain_by_name ("b")->name (), "b"); EXPECT_EQ (salt.grain_by_name ("c/c/v")->name (), "c/c/v"); } + +TEST (5) +{ + lay::SaltGrains grains; + + lay::SaltGrain g1; + g1.set_name ("g1"); + lay::SaltGrain::Dependency dep; + dep.name = "g2"; + g1.dependencies ().push_back (dep); + dep.name = "g3"; + g1.dependencies ().push_back (dep); + grains.add_grain (g1); + + lay::SaltGrains g34; + + lay::SaltGrain g3; + g3.set_name ("g3"); + g34.add_grain (g3); + + lay::SaltGrain g4; + g4.set_name ("g4"); + g34.add_grain (g4); + + grains.add_collection (g34); + + lay::SaltGrain g2; + g2.set_name ("g2"); + dep.name = "g3"; + g2.dependencies ().push_back (dep); + grains.add_grain (g2); + + lay::Salt salt; + salt.root ().add_collection (grains); + + std::vector names; + for (lay::Salt::flat_iterator i = salt.begin_flat (); i != salt.end_flat (); ++i) { + names.push_back ((*i)->name ()); + } + + EXPECT_EQ (tl::join (names, ","), "g3,g2,g1,g4"); +} From 1091ea6d5b6dbb0a1ff350ba2a1a83da2dbce3f0 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 23 Apr 2017 19:30:55 +0200 Subject: [PATCH 52/57] Technology specific libraries Libraries now carry a technology association and only libraries associated with the current technology can be selected. To enforce proper update, cancel() will be used upon change of technology. This avoids side effects when changing the technology without notifying the library selection widgets. The MainWindow::cancel method has been generalized to cover the functionality of cm_cancel without the exception handling. --- src/db/dbLibrary.h | 20 +++++ src/db/gsiDeclDbLibrary.cc | 18 ++++- src/edt/edtEditorOptionsPages.cc | 5 ++ src/edt/edtInstPropertiesPage.cc | 1 + src/lay/layLibraryController.cc | 107 +++++++++++++++---------- src/lay/layMainWindow.cc | 5 +- src/lay/laySaltTemplates.qrc | 4 + src/lay/layTechnologyController.cc | 5 ++ src/lay/salt_templates/macro/grain.xml | 2 +- src/laybasic/layWidgets.cc | 38 +++++++-- src/laybasic/layWidgets.h | 13 +++ 11 files changed, 161 insertions(+), 57 deletions(-) diff --git a/src/db/dbLibrary.h b/src/db/dbLibrary.h index 6819e7c36..3c2907123 100644 --- a/src/db/dbLibrary.h +++ b/src/db/dbLibrary.h @@ -101,6 +101,25 @@ public: m_name = name; } + /** + * @brief Gets the technology name this library is associated with + * + * If this attribute is non-empty, the library is selected only when the given technology is + * used for the layout. + */ + const std::string &get_technology () const + { + return m_technology; + } + + /** + * @brief Sets the technology name this library is associated with + */ + void set_technology (const std::string &t) + { + m_technology = t; + } + /** * @brief Getter for the description property */ @@ -153,6 +172,7 @@ public: private: std::string m_name; std::string m_description; + std::string m_technology; lib_id_type m_id; db::Layout m_layout; std::map m_referrers; diff --git a/src/db/gsiDeclDbLibrary.cc b/src/db/gsiDeclDbLibrary.cc index 64b357813..14f67a857 100644 --- a/src/db/gsiDeclDbLibrary.cc +++ b/src/db/gsiDeclDbLibrary.cc @@ -110,11 +110,25 @@ Class decl_Library ("Library", gsi::method ("description", &db::Library::get_description, "@brief Returns the libraries' description text\n" ) + - gsi::method ("description=", &db::Library::set_description, + gsi::method ("description=", &db::Library::set_description, "@brief Sets the libraries' description text\n" "@args description\n" ) + - gsi::method ("layout_const", (const db::Layout &(db::Library::*)() const) &db::Library::layout, + gsi::method ("technology", &db::Library::get_technology, + "@brief Returns name of the technology the library is associated with\n" + "If this attribute is a non-empty string, this library is only offered for " + "selection if the current layout uses this technology.\n" + "\n" + "This attribute has been introduced in version 0.25." + ) + + gsi::method ("technology=", &db::Library::set_technology, + "@brief sets the name of the technology the library is associated with\n" + "@args technology\n" + "\n" + "See \\technology for details. " + "This attribute has been introduced in version 0.25." + ) + + gsi::method ("layout_const", (const db::Layout &(db::Library::*)() const) &db::Library::layout, "@brief The layout object where the cells reside that this library defines (const version)\n" ) + gsi::method ("layout", (db::Layout &(db::Library::*)()) &db::Library::layout, diff --git a/src/edt/edtEditorOptionsPages.cc b/src/edt/edtEditorOptionsPages.cc index a0ac0308f..2e8fb8f9f 100644 --- a/src/edt/edtEditorOptionsPages.cc +++ b/src/edt/edtEditorOptionsPages.cc @@ -691,6 +691,11 @@ EditorOptionsInst::setup (lay::Plugin *root) m_cv_index = lay::LayoutView::current ()->active_cellview_index (); } mp_ui->lib_cbx->update_list (); + if (m_cv_index >= 0 && lay::LayoutView::current () && lay::LayoutView::current ()->cellview (m_cv_index).is_valid ()) { + mp_ui->lib_cbx->set_technology_filter (lay::LayoutView::current ()->cellview (m_cv_index)->tech_name (), true); + } else { + mp_ui->lib_cbx->set_technology_filter (std::string (), false); + } // cell name std::string s; diff --git a/src/edt/edtInstPropertiesPage.cc b/src/edt/edtInstPropertiesPage.cc index 7bc7b1465..380d45063 100644 --- a/src/edt/edtInstPropertiesPage.cc +++ b/src/edt/edtInstPropertiesPage.cc @@ -223,6 +223,7 @@ InstPropertiesPage::update () const db::Cell &def_cell = def_layout->cell (def_cell_index); std::pair dl = def_layout->defining_library (def_cell_index); + lib_cbx->set_technology_filter (cv->tech_name (), true); lib_cbx->set_current_library (dl.first); if (dl.first) { def_layout = &dl.first->layout (); diff --git a/src/lay/layLibraryController.cc b/src/lay/layLibraryController.cc index 88334f7be..31906445c 100644 --- a/src/lay/layLibraryController.cc +++ b/src/lay/layLibraryController.cc @@ -21,6 +21,7 @@ */ #include "layLibraryController.h" +#include "layTechnology.h" #include "layApplication.h" #include "laySaltController.h" #include "layConfig.h" @@ -125,76 +126,96 @@ LibraryController::sync_files () std::map > new_lib_files; - std::vector paths = lay::Application::instance ()->klayout_path (); + // build a list of paths vs. technology + std::vector > paths; + + std::vector klayout_path = lay::Application::instance ()->klayout_path (); + for (std::vector::const_iterator p = klayout_path.begin (); p != klayout_path.end (); ++p) { + paths.push_back (std::make_pair (*p, std::string ())); + } // add the salt grains as potential sources for library definitions lay::SaltController *sc = lay::SaltController::instance (); if (sc) { for (lay::Salt::flat_iterator g = sc->salt ().begin_flat (); g != sc->salt ().end_flat (); ++g) { - paths.push_back ((*g)->path ()); + paths.push_back (std::make_pair ((*g)->path (), std::string ())); + } + } + + // add the technologies as potential sources for library definitions + + for (lay::Technologies::iterator t = lay::Technologies::instance ()->begin (); t != lay::Technologies::instance ()->end (); ++t) { + if (! t->base_path ().empty ()) { + paths.push_back (std::make_pair (t->base_path (), t->name ())); } } // scan for libraries - for (std::vector ::const_iterator p = paths.begin (); p != paths.end (); ++p) { + for (std::vector >::const_iterator p = paths.begin (); p != paths.end (); ++p) { - QDir lp = QDir (tl::to_qstring (*p)).filePath (tl::to_qstring ("libraries")); - m_file_watcher->add_file (tl::to_string (lp.absolutePath ())); + QDir lp = QDir (tl::to_qstring (p->first)).filePath (tl::to_qstring ("libraries")); + if (lp.exists ()) { - QStringList name_filters; - name_filters << QString::fromUtf8 ("*"); + m_file_watcher->add_file (tl::to_string (lp.absolutePath ())); - QStringList libs = lp.entryList (name_filters, QDir::Files); - for (QStringList::const_iterator im = libs.begin (); im != libs.end (); ++im) { + QStringList name_filters; + name_filters << QString::fromUtf8 ("*"); - std::string filename = tl::to_string (*im); - std::string lib_path = tl::to_string (lp.absoluteFilePath (*im)); + QStringList libs = lp.entryList (name_filters, QDir::Files); + for (QStringList::const_iterator im = libs.begin (); im != libs.end (); ++im) { - try { + std::string filename = tl::to_string (*im); + std::string lib_path = tl::to_string (lp.absoluteFilePath (*im)); - QFileInfo fi (tl::to_qstring (lib_path)); + try { - bool needs_load = false; - std::map >::iterator ll = m_lib_files.find (lib_path); - if (ll == m_lib_files.end ()) { - needs_load = true; - } else { - if (fi.lastModified () > ll->second.second) { + QFileInfo fi (tl::to_qstring (lib_path)); + + bool needs_load = false; + std::map >::iterator ll = m_lib_files.find (lib_path); + if (ll == m_lib_files.end ()) { needs_load = true; } else { - new_lib_files.insert (*ll); - } - } - - if (needs_load) { - - std::auto_ptr lib (new db::Library ()); - lib->set_description (filename); - lib->set_name (tl::to_string (QFileInfo (*im).baseName ())); - - tl::log << "Reading library '" << filename << "'"; - tl::InputStream stream (lib_path); - db::Reader reader (stream); - reader.read (lib->layout ()); - - // Use the libname if there is one - for (db::Layout::meta_info_iterator m = lib->layout ().begin_meta (); m != lib->layout ().end_meta (); ++m) { - if (m->name == "libname" && ! m->value.empty ()) { - lib->set_name (m->value); - break; + if (fi.lastModified () > ll->second.second) { + needs_load = true; + } else { + new_lib_files.insert (*ll); } } - new_lib_files.insert (std::make_pair (lib_path, std::make_pair (lib->get_name (), fi.lastModified ()))); + if (needs_load) { - db::LibraryManager::instance ().register_lib (lib.release ()); + std::auto_ptr lib (new db::Library ()); + lib->set_description (filename); + lib->set_technology (p->second); + lib->set_name (tl::to_string (QFileInfo (*im).baseName ())); + tl::log << "Reading library '" << lib_path << "'"; + tl::InputStream stream (lib_path); + db::Reader reader (stream); + reader.read (lib->layout ()); + + // Use the libname if there is one + for (db::Layout::meta_info_iterator m = lib->layout ().begin_meta (); m != lib->layout ().end_meta (); ++m) { + if (m->name == "libname" && ! m->value.empty ()) { + lib->set_name (m->value); + break; + } + } + + tl::log << "Registering as '" << lib->get_name () << "' for tech '" << lib->get_technology () << "'"; + new_lib_files.insert (std::make_pair (lib_path, std::make_pair (lib->get_name (), fi.lastModified ()))); + + db::LibraryManager::instance ().register_lib (lib.release ()); + + } + + } catch (tl::Exception &ex) { + tl::error << ex.msg (); } - } catch (tl::Exception &ex) { - tl::error << ex.msg (); } } diff --git a/src/lay/layMainWindow.cc b/src/lay/layMainWindow.cc index f94f059a1..6da478457 100644 --- a/src/lay/layMainWindow.cc +++ b/src/lay/layMainWindow.cc @@ -2608,7 +2608,6 @@ MainWindow::cm_cancel () { BEGIN_PROTECTED cancel (); - select_mode (lay::LayoutView::default_mode ()); END_PROTECTED } @@ -2618,8 +2617,6 @@ MainWindow::cm_cancel () void MainWindow::cancel () { - BEGIN_PROTECTED - // if any transaction is pending (this may happen when an operation threw an exception) // close transactions. if (m_manager.transacting ()) { @@ -2630,7 +2627,7 @@ MainWindow::cancel () (*vp)->cancel (); } - END_PROTECTED + select_mode (lay::LayoutView::default_mode ()); } void diff --git a/src/lay/laySaltTemplates.qrc b/src/lay/laySaltTemplates.qrc index e4155fe58..5eb43cde5 100644 --- a/src/lay/laySaltTemplates.qrc +++ b/src/lay/laySaltTemplates.qrc @@ -28,6 +28,10 @@ salt_templates/macro/grain.xml + salt_templates/macro/macros/new_macro.lym + + + salt_templates/macro/doc/readme.html diff --git a/src/lay/layTechnologyController.cc b/src/lay/layTechnologyController.cc index 1d0e1491d..94d6b0fec 100644 --- a/src/lay/layTechnologyController.cc +++ b/src/lay/layTechnologyController.cc @@ -269,6 +269,11 @@ TechnologyController::menu_activated (const std::string &symbol) const { if (symbol == "technology_selector:apply_technology") { if (lay::LayoutView::current () && lay::LayoutView::current ()->active_cellview ().is_valid ()) { + // Cancels the current modes - changing the technology may make libraries unavailable + // for example. + if (mp_mw) { + mp_mw->cancel (); + } lay::LayoutView::current ()->active_cellview ()->apply_technology (m_current_technology); } return true; diff --git a/src/lay/salt_templates/macro/grain.xml b/src/lay/salt_templates/macro/grain.xml index ae9c5ffe2..8f69c4cd0 100644 --- a/src/lay/salt_templates/macro/grain.xml +++ b/src/lay/salt_templates/macro/grain.xml @@ -4,7 +4,7 @@ 0.0 Ruby Macro This template provides a Ruby macro - + doc/readme.html GPLv3 diff --git a/src/laybasic/layWidgets.cc b/src/laybasic/layWidgets.cc index 4b255e8a1..b1e7b2136 100644 --- a/src/laybasic/layWidgets.cc +++ b/src/laybasic/layWidgets.cc @@ -230,7 +230,8 @@ struct CellViewSelectionComboBoxPrivateData const lay::LayoutView *layout_view; }; -CellViewSelectionComboBox::CellViewSelectionComboBox (QWidget * /*parent*/) +CellViewSelectionComboBox::CellViewSelectionComboBox (QWidget *parent) + : QComboBox (parent) { mp_private = new CellViewSelectionComboBoxPrivateData (); mp_private->layout_view = 0; @@ -299,7 +300,8 @@ struct LayerSelectionComboBoxPrivateData int cv_index; }; -LayerSelectionComboBox::LayerSelectionComboBox (QWidget * /*parent*/) +LayerSelectionComboBox::LayerSelectionComboBox (QWidget *parent) + : QComboBox (parent) { mp_private = new LayerSelectionComboBoxPrivateData (); mp_private->no_layer_available = false; @@ -569,11 +571,22 @@ LayerSelectionComboBox::current_layer_props () const // ------------------------------------------------------------- // LibrarySelectionComboBox implementation -LibrarySelectionComboBox::LibrarySelectionComboBox (QWidget * /*parent*/) +LibrarySelectionComboBox::LibrarySelectionComboBox (QWidget *parent) + : QComboBox (parent), m_tech_set (false) { update_list (); } +void +LibrarySelectionComboBox::set_technology_filter (const std::string &tech, bool enabled) +{ + if (m_tech != tech || m_tech_set != enabled) { + m_tech = tech; + m_tech_set = enabled; + update_list (); + } +} + void LibrarySelectionComboBox::update_list () { @@ -585,12 +598,23 @@ LibrarySelectionComboBox::update_list () addItem (QObject::tr ("Local (no library)"), QVariant ()); for (db::LibraryManager::iterator l = db::LibraryManager::instance ().begin (); l != db::LibraryManager::instance ().end (); ++l) { + db::Library *lib = db::LibraryManager::instance ().lib (l->second); - if (! lib->get_description ().empty ()) { - addItem (tl::to_qstring (lib->get_name () + " - " + lib->get_description ()), QVariant ((unsigned int) lib->get_id ())); - } else { - addItem (tl::to_qstring (lib->get_name ()), QVariant ((unsigned int) lib->get_id ())); + if (! m_tech_set || lib->get_technology ().empty () || m_tech == lib->get_technology ()) { + + std::string item_text = lib->get_name (); + if (! lib->get_description ().empty ()) { + item_text += " - " + lib->get_description (); + } + if (m_tech_set && !lib->get_technology ().empty ()) { + item_text += " "; + item_text += tl::to_string (QObject::tr ("[Technology %1]").arg (tl::to_qstring (lib->get_technology ()))); + } + + addItem (tl::to_qstring (item_text), QVariant ((unsigned int) lib->get_id ())); + } + } set_current_library (lib); diff --git a/src/laybasic/layWidgets.h b/src/laybasic/layWidgets.h index 001a70a57..08019f82b 100644 --- a/src/laybasic/layWidgets.h +++ b/src/laybasic/layWidgets.h @@ -147,6 +147,19 @@ public: * @brief Update the list of libraries */ void update_list (); + + /** + * @brief Sets the technology filter + * + * If a technology filter is set, only the libraries associated with the given + * technology are shown. If enable is false, the technology name is ignored and + * all libraries are shown. + */ + void set_technology_filter (const std::string &tech, bool enable); + +private: + std::string m_tech; + bool m_tech_set; }; /** From b3e823ca28ce393ed54ec4f3b0e54b8213aea590 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 25 Apr 2017 23:41:36 +0200 Subject: [PATCH 53/57] Fixed the timers for measuring read sorting times (out of band fix) --- src/db/dbCIFReader.cc | 2 ++ src/db/dbDXFReader.cc | 2 ++ src/db/dbGDS2ReaderBase.cc | 2 ++ src/db/dbLayout.cc | 2 ++ src/db/dbOASISReader.cc | 2 +- src/laybasic/layLayoutView.cc | 4 ++-- 6 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/db/dbCIFReader.cc b/src/db/dbCIFReader.cc index d70eedf38..6e47030b3 100644 --- a/src/db/dbCIFReader.cc +++ b/src/db/dbCIFReader.cc @@ -917,6 +917,8 @@ CIFReader::read_cell (db::Layout &layout, db::Cell &cell, double sf, int level) void CIFReader::do_read (db::Layout &layout) { + tl::SelfTimer timer (tl::verbosity () >= 21, "File read"); + try { double sf = 0.01 / m_dbu; diff --git a/src/db/dbDXFReader.cc b/src/db/dbDXFReader.cc index 42ea9763e..86cb7a393 100644 --- a/src/db/dbDXFReader.cc +++ b/src/db/dbDXFReader.cc @@ -490,6 +490,8 @@ DXFReader::open_layer (db::Layout &layout, const std::string &n) void DXFReader::do_read (db::Layout &layout, db::cell_index_type top) { + tl::SelfTimer timer (tl::verbosity () >= 21, "File read"); + m_new_layers.clear (); // create the zero layer - this is not mapped to GDS but can be specified in the layer mapping as diff --git a/src/db/dbGDS2ReaderBase.cc b/src/db/dbGDS2ReaderBase.cc index e36afd08c..3447cafe2 100644 --- a/src/db/dbGDS2ReaderBase.cc +++ b/src/db/dbGDS2ReaderBase.cc @@ -233,6 +233,8 @@ eq_y (const GDS2XY &a, const GDS2XY &b) void GDS2ReaderBase::do_read (db::Layout &layout) { + tl::SelfTimer timer (tl::verbosity () >= 21, "File read"); + m_cellname = ""; m_libname = ""; diff --git a/src/db/dbLayout.cc b/src/db/dbLayout.cc index 14e6bccf0..3efa3a70e 100644 --- a/src/db/dbLayout.cc +++ b/src/db/dbLayout.cc @@ -1271,6 +1271,8 @@ Layout::update () const void Layout::do_update () { + tl::SelfTimer timer (tl::verbosity () >= 21, tl::to_string (QObject::tr ("Sorting"))); + // establish a progress report since this operation can take some time. // HINT: because of some gcc bug, automatic destruction of the tl::Progress // object does not work. We overcome this problem by creating the object with new diff --git a/src/db/dbOASISReader.cc b/src/db/dbOASISReader.cc index 1863db72b..0da801454 100644 --- a/src/db/dbOASISReader.cc +++ b/src/db/dbOASISReader.cc @@ -655,7 +655,7 @@ static const char magic_bytes[] = { "%SEMI-OASIS\015\012" }; void OASISReader::do_read (db::Layout &layout) { - tl::SelfTimer timer (tl::verbosity () >= 31, "File read"); + tl::SelfTimer timer (tl::verbosity () >= 21, "File read"); unsigned char r; char *mb; diff --git a/src/laybasic/layLayoutView.cc b/src/laybasic/layLayoutView.cc index c5aa79673..afeeaef13 100644 --- a/src/laybasic/layLayoutView.cc +++ b/src/laybasic/layLayoutView.cc @@ -3028,10 +3028,11 @@ LayoutView::load_layout (const std::string &filename, const db::LoadLayoutOption try { + tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (QObject::tr ("Loading"))); + // load the file { tl::log << tl::to_string (QObject::tr ("Loading file: ")) << filename << tl::to_string (QObject::tr (" with technology: ")) << technology; - tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (QObject::tr ("Loading"))); lmap = cv->load (options, technology); } @@ -3039,7 +3040,6 @@ LayoutView::load_layout (const std::string &filename, const db::LoadLayoutOption // implicitly at some other time. This may throw an exception // if the operation was cancelled. { - tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (QObject::tr ("Sorting"))); cv->layout ().update (); } From f79b14d0e42a0ca82e75d07042bf64b60694999b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 26 Apr 2017 23:19:36 +0200 Subject: [PATCH 54/57] Salt: added font, macro and mymacro templates. Fixed two small bugs in the properties dialog (accept wasn't possible if the doc link could not be read) and remove_from_collection (infinite loop). --- src/lay/laySalt.cc | 7 +-- src/lay/laySaltGrainPropertiesDialog.cc | 2 +- src/lay/laySaltTemplates.qrc | 8 +++ src/lay/salt_templates/font/doc/readme.html | 43 ++++++++++++++++ .../salt_templates/font/fonts/new_font.gds | Bin 0 -> 27078 bytes src/lay/salt_templates/font/grain.xml | 4 +- src/lay/salt_templates/macro/doc/readme.html | 46 ++++++++++++++++++ src/lay/salt_templates/macro/grain.xml | 4 +- .../salt_templates/macro/macros/new_macro.lym | 22 +++++++++ .../salt_templates/pymacro/doc/readme.html | 46 ++++++++++++++++++ src/lay/salt_templates/pymacro/grain.xml | 6 +-- .../pymacro/pymacros/new_macro.lym | 22 +++++++++ 12 files changed, 197 insertions(+), 13 deletions(-) create mode 100644 src/lay/salt_templates/font/doc/readme.html create mode 100644 src/lay/salt_templates/font/fonts/new_font.gds create mode 100644 src/lay/salt_templates/macro/doc/readme.html create mode 100644 src/lay/salt_templates/macro/macros/new_macro.lym create mode 100644 src/lay/salt_templates/pymacro/doc/readme.html create mode 100644 src/lay/salt_templates/pymacro/pymacros/new_macro.lym diff --git a/src/lay/laySalt.cc b/src/lay/laySalt.cc index 001fc4354..7ef818c5b 100644 --- a/src/lay/laySalt.cc +++ b/src/lay/laySalt.cc @@ -248,11 +248,8 @@ bool remove_from_collection (SaltGrains &collection, const std::string &name) for (SaltGrains::grain_iterator g = collection.begin_grains (); g != collection.end_grains (); ++g) { if (g->name () == name) { - SaltGrains::grain_iterator gnext = g; - ++gnext; - collection.remove_grain (g, true); - res = true; - g = gnext; + res = collection.remove_grain (g, true); + break; } } diff --git a/src/lay/laySaltGrainPropertiesDialog.cc b/src/lay/laySaltGrainPropertiesDialog.cc index bfc24ab6f..5743ea1c1 100644 --- a/src/lay/laySaltGrainPropertiesDialog.cc +++ b/src/lay/laySaltGrainPropertiesDialog.cc @@ -536,8 +536,8 @@ SaltGrainPropertiesDialog::accept () // doc URL doc_url_alert->clear (); if (! m_grain.doc_url ().empty ()) { - tl::InputStream stream (m_grain.doc_url ()); try { + tl::InputStream stream (m_grain.eff_doc_url ()); if (! stream.get (1)) { throw tl::Exception (tl::to_string (tr ("Empty document"))); } diff --git a/src/lay/laySaltTemplates.qrc b/src/lay/laySaltTemplates.qrc index 5eb43cde5..72cfaaede 100644 --- a/src/lay/laySaltTemplates.qrc +++ b/src/lay/laySaltTemplates.qrc @@ -7,6 +7,10 @@ salt_templates/font/grain.xml + salt_templates/font/fonts/new_font.gds + + + salt_templates/font/doc/readme.html @@ -39,6 +43,10 @@ salt_templates/pymacro/grain.xml + salt_templates/pymacro/pymacros/new_macro.lym + + + salt_templates/pymacro/doc/readme.html diff --git a/src/lay/salt_templates/font/doc/readme.html b/src/lay/salt_templates/font/doc/readme.html new file mode 100644 index 000000000..e38e9765a --- /dev/null +++ b/src/lay/salt_templates/font/doc/readme.html @@ -0,0 +1,43 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Change the name of the font layout file to a unique name
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
+ +

+Edit the font's layout to define the glyphs. Don't forget to update the metadata +in the "COMMENT" cell. The metadata helps the PCell to compute properties +of the font. +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/font/fonts/new_font.gds b/src/lay/salt_templates/font/fonts/new_font.gds new file mode 100644 index 0000000000000000000000000000000000000000..c790edfbb97a108579ab0771292ddfd2b40f0b3a GIT binary patch literal 27078 zcmeHQJ8T?97@j*{)|bRs#v%^~D>@MY0hw?wUwnuloCoCLylev|1&M^M#3+d(2*M>L zP^CnI0@2W>OhFM*pj1&xO6VvMN|6E~#PNUs&g{<2?w&bzW31Ud={(;3X8xUj{^!ij z?g>wn%S&RiR383CRKy;!Q#8e1{eMv{dvhla3-Q#@me*!KKX~fL?;duK-g)_(uP!W# z$t``_d0wMAEkva%g!e>QY!lvSSqL#THbj55Px*TxN{@t4r3O`=4BMB(_Pt*3zWP$T z-|73DD%E+ELT^80wEvoL{t7Wvqqa+`ROeCp?T5wse73`WI?ZP*7kt(`&7AOA&j+8a zgYNU`d0scfD77olwnB^hYB9!%$6i?##*oH8 z7*|+7j;Ul#_Ur3fY+6}i6-hZbS%ntCj8Th|LXt23S5hF$iuj_ze6hp6sgvZJe2;JX zBI#2&N|5dc5mWl8z7azDx4`G&7}rp?rrRDt?C;izU@4_)XkFlQ>C*o(f<@>tuOTm~ zeS1pZIz#FXZ1svA8_D1>6^m&A# zb%xZWPfv=lny*jHb26|phqXjoVTwq>_UK7c*q}Ve=%0+hZ2KI^;9I2qYyAnLrL3Q* zCETX)C2=d#lns-Kt94~l3jUlAS%k`oQs)st-j;#wL#jmtqezH3VffLlV6(3Clh!qo zwC+!01)6t{zOZ{LS;};t*Tcw;*;mQTU-m+bY&+Vt4ycmOCG6ox`?uJ{Me`3%QJVkj*}?O=+P9zd;m5 zL`5W}PlUdjodI7jIul;p!B6raXZvU!MmD&MMJp*@@GD2Uj%`xuRPwpS#%qtaH`1TC4 zO@|}w@gyyN3{uE)=AVI7O)JYVrW?1#SdW*#2`o383*-mRru`8*<1z?=9rIk#cS4Bu{N=@!4Vj+i2z_uk5t*F~92OgwIw^`0VDy-?p{4lR4~f z>#dyl+m@F9IW5nbe8iQGHcxX|o}Iktt@fs0m*qK~6Z@Rbi9Fjm$oob;@5u4l`Pn0j z$L1GAWgGqa!MI23o+JM69`t&D?e=$y_z2yXWn}y=uN(6nJs+Wc0*s7Zu@Tw_!^r5E zjnF%H4<|Hz4&qnCEI&L#rvk`i(kdeJ>HbT#(GE#eBBj+TtZ@-P4m&mi9 z+Q_^_p8v^479{dw-9{EAGWD5_(0fX_ZwG$15qeJxBaImwq4(4<(z<0MOA?v>)kf$& zMciiQbsM4gG%@nZmo`G#10&@nTYG7K!${?}jZh?JWVmW0^6`p*(vViGA2Z?_eVX@Y+$(i$=KetD5iQ? zdl1FcSWMiDV7QaSG%*?vFPYaP^b1H)=Z(9Y@$RtOkFKh5jaF{@?_`e?PoOq8*G z`y=fzdt(e^ZPCPxC!lF#N|V5Yg(0)%b{H3nKV+kfL7y$GrF0N_Ytcm3!|kAhRy$-0 zK|d`F(Uq~uT7)Kk0VC*(b-j+#GmN!G6TgJQ?J#C0)@UmJ&`(qDji%}gWMpGXmvB9W zt@A58BNSE7o<4o-%p%2dwRz_WJ*QgZBG>fRelyb(%k1s`Ds){Tm6|@5GJ} zZ&yiFg}0|f|BpPqWp>PO48Dopw|w->gm>_zg_Xfont 0.0 Font package - This template provides a font for the Basic.TEXT PCell - + This template provides a font for the Basic.TEXT PCell. + doc/readme.html GPLv3 diff --git a/src/lay/salt_templates/macro/doc/readme.html b/src/lay/salt_templates/macro/doc/readme.html new file mode 100644 index 000000000..03d1cd383 --- /dev/null +++ b/src/lay/salt_templates/macro/doc/readme.html @@ -0,0 +1,46 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
  • Specify dependencies if you plan to use items from other packages
  • +
+ +

+Of course, the most interesting thing is how to add, edit and develop macros within +your macro package. When the package was initialized, a "macros" folder with a single +sample macro has been created. You will find this folder in the macro editor +under the name you have given the package. The name of the sample macro is "new_macro". +You can add more macros, Ruby and other files there or modify the sample macro. +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/macro/grain.xml b/src/lay/salt_templates/macro/grain.xml index 8f69c4cd0..6d304c519 100644 --- a/src/lay/salt_templates/macro/grain.xml +++ b/src/lay/salt_templates/macro/grain.xml @@ -3,7 +3,7 @@ macro 0.0 Ruby Macro - This template provides a Ruby macro + This template provides a Ruby macro. doc/readme.html GPLv3 @@ -11,6 +11,6 @@ - + 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 diff --git a/src/lay/salt_templates/macro/macros/new_macro.lym b/src/lay/salt_templates/macro/macros/new_macro.lym new file mode 100644 index 000000000..0a7455cd6 --- /dev/null +++ b/src/lay/salt_templates/macro/macros/new_macro.lym @@ -0,0 +1,22 @@ + + + The New Macro + + + false + false + + false + + + ruby + + # This is the new macro created with the sample macro package + +RBA::MessageBox::information("Information", "This is the new macro created with the sample macro package", RBA::MessageBox::Ok) + +# In order pull in classes from other packages, just specify these classes +# in the dependencies of this package. Provided those packages contain macros +# which are marked as "autorun-early", the will be loaded before this package +# and their modules and classes will be available. + diff --git a/src/lay/salt_templates/pymacro/doc/readme.html b/src/lay/salt_templates/pymacro/doc/readme.html new file mode 100644 index 000000000..112d34f93 --- /dev/null +++ b/src/lay/salt_templates/pymacro/doc/readme.html @@ -0,0 +1,46 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
  • Specify dependencies if you plan to use items from other packages
  • +
+ +

+Of course, the most interesting thing is how to add, edit and develop macros within +your macro package. When the package was initialized, a "macros" folder with a single +sample macro has been created. You will find this folder in the macro editor +under the name you have given the package. The name of the sample macro is "new_macro". +You can add more macros, Python and other files there or modify the sample macro. +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/pymacro/grain.xml b/src/lay/salt_templates/pymacro/grain.xml index 2b281fcc9..e780101db 100644 --- a/src/lay/salt_templates/pymacro/grain.xml +++ b/src/lay/salt_templates/pymacro/grain.xml @@ -3,14 +3,14 @@ macro 0.0 Python Macro - This template provides a Python macro - + This template provides a Python macro. + doc/readme.html GPLv3 - + iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAF/wAABf8ByXatVgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAABI5SURBVHic5ZtZbFzXecd/59x19oUz3ERqoSlRiikrlmvHi5wmTSTESiC0iBW4CIwY7ksSyEXcB6MxikLNQ54cxGiAAAkCuEADFHUKAY0No7ZcWXZsN6ok29DuRQtlUlxEijOc5c7c5Zw+DIeiHBtIxaETIAc4uLgzF+f+v//5tnO+c+FPvIlP+4VPP/10TzabHYjH472WZaWVUjUp5VSj0Zg8cODAlV/96lfRp4ln1Qn43ve+17dp06Z9AwMDX3EcZ8RxnIRhGCiliMfj1Go1HMdBKUUQBM1Go3G+2Wy+Mjk5+dNvf/vbZ1Yb36oRsHfvXmPLli0/GR4e/ptisWjH43Fs26ZarZJOpzlx4gQPPPAAr732Ghs3bmRychLbtsnn83ieR71eVzMzM89fvHjx4f379y+sFk5ztQbO5/M/ymQy38lms1iWhW3baK2Znp5my5YtjIyMEI/HefDBB6nX67iuSxRFCCGIooggCGQul9szOzv7H8Cu1cK5agT4vn9/pVJhYWEB27bxfR/XdRkbG+Pq1asopajX6wCEYcj8/DzHjx9nx44d+L5PGIYsLCxQKpX+bLUwwioSkM1mRSaTYd26dUxOThIEAb7vs2PHDjzP48CBA6xbt45MJsNvfvMbvvzlL7N9+3ZKpRKGYTA3N8e5c+dwHEevFkZYRQLq9bo/NDTEoUOHiMfjTExM0N3dTT6fJ51Os379egzDoFarsW3bNiYmJujr6yOZTHLmzBnOnTvH0NAQtVqtuVoYYRUJSKfT1smTJ9mzZw+e53Ho0CHm5uaYmJggDEOUUkvPCiEwDINYLMbQ0BBbt25lcHCQt99+m3g8bq8WRlilKLBv375+wzBO7N69u2tmZoZDhw6RSCTYuHEjAwMDNJvNJRLawruuS09PD+fOnePtt98mk8mwbds2Lly4oAcGBg68/vrrj/385z+f7DTWjhPw/e9//77R0dHnz549m02n07iuy44dOzhx4gRTU1OMjY0xPT2N53l4nofruti2TbFY5J577uGOO+5ASsmpU6c4f/48qVQK3/cZGhqaP3LkyFefeuqp/+kk3o6bwODg4DOVSiVbLpfp7e1l586dPP/88xw5cgQpJV1dXdx2221ks1ls2yaKIur1OteuXePUqVMcPnyYTZs2sXXrVorFIq+++iobNmygXC7n1qxZ8y/ASCfxdpyAIAi6e3p66Ovr4/jx4zz33HPcfvvt7Nq1i2QySb1ep1arUavVmJubIx6P47ouGzdu5NZbbyWdTvPaa6/xzDPPIISgr6+PbDZLJpPh3Xff7eo03o4TcPLkyTdisdjuTZs2kc1mGR8f58qVK7zzzjvMzMwQBMFSYiSlRGtNEAQEQYAQglwuRz6fp1AosGbNGtauXYtt27z88st4ntdR9YdV8AFnz549dPDgwR1Hjx41N27cKNauXUsmk2F2dpZSqcTCwgK1Wm3JEQKYponruiQSCdLpNL29vQwMDHD58mXeeOMNjh8/rh999NHgS1/60qsjIyMdzQo7SsDevXuNH/zgB5OFQsGtVqupX/7yl7zyyivMzc1ppZRIp9MUi0UymQyJRALLsgDwfZ8gCJBSIqWkXC5z8OBBBgcH2bVrFw899BA9PT2VWq3WePLJJ/s6uWLsKAHPPffc3959990/LBQKCYBqtUq5XCYIgvqRI0fip0+fZmJigunpaarVKr7vA2DbNqlUip6eHvr7+xkdHWV0dFQ7jiMKhQKZTAaA2dnZytGjR/9x9+7dT3cKc0d9gFJq87Vr14J8Po+UkmQySSKR8H3fD3fu3MkXv/hFoigiDMOla7N5PdEzDAPbtrFtm0wmU08mk64Qwlgcm9nZ2TCKoq2dxNxRAlKp1Lqurq7sxMTE+MDAwIAQwhdCNBzHSTuO8zvPh2FIqVTCtm0sy8IwjOV/J6Ioqpim6WqtrfHx8fFMJjOQz+fXdRJzRwlwXbdXa002mx2oVquTWut4IpHIfESw6y83TdLp9CeOF0VRSms95/u+b5rmgFIKx3H6Oom5owQ4jlPQWiOEwPM8VymVrlQqDSFEVUrpSSmVZVmWlDKVTCZTbWIWQ2HD9/2SUqoaBIGOosiJoigHdBUKhYX2PoFpmrlOYu4oAZZlpZRSSClRSoVCCBzHcQzDcKSUGIaxFPsXFha8ZrNZDcMQy7KSpmnGlFK9Sim0bq2AhRAs3ishBFprpJSJTmKWHRxLGIYRE0Kw2ENoCbH8Ci3VTyaTbiqVKti2XbRtO3bDQItjLIGUMmr/ZlmW+/jjj9/w/EpaxwjYv3//LfF43G4DDcMw+jjhP+63T2rLiFDt+3Q6bXV1dd3RKdwdI6BYLN6bTCallPJ3QLfbR4X/6Ex/UtNa6/azsVhMFIvF7Z3C3TEfkEgk1pumubTGb1/bbbnQTT9gZn6BUrmC12iQTsQp5lIY8uPJkFLq5WR1dXV1LBR2jIBUKtULLQGllERRK1ttA4+U4vBb73NhYkYIaeDGXCLVygUEV/HqHgLN6FAfW4d+N9K1hY+iiFwuN9wp3B0jIBaLDcKNat2+Vuoe/37wuHASabp61wACpTVhECKDAMMwSKSyhGHIqUuzXLoyxwN3b0bKGy20HQpjsdhAp3B3zAckk8n+ttovdgmtGTv429MiU+zlc6NDPPT5LQwU0nh+SCOIaAQhnh9Q8ZqESlEsFqj6ivMfTuP7flto2R53MRkqdAp3xwhwHKfQzgEWZ15EUYTv+9SbAal4DMcyEUKQSdiYUmIaAtOQmFJgSolSEEaKVCrF2NQ8vu/TbDZp+UCxpAGmaWY6hbtjBFiWlVmuAVprs10LUEojpeSdizO8+NYlTl6axTIllmksdhPDkEgpEFJgGgbNxYWS7/v4vm8sNy3btmOdygU64gOeeOKJlOM48XYaDNBoNCzbtgmCAKU1UkDStWkGIem43ZrNMCQMJaECr6HRQmAIga81be3RWuN5nhmLteQVQpDJZKyenp47gNdXir0jBBQKhe3ZbNZqhbiQQ8eOc+aDsUw8FhNoTTMIuXjxUksz0NBKaQnDkGKhi3gigWFItFb4YUS14WMqtbRf4HmetCxrybxisZjI5XLb+WMhIJfL3e66LkEQcODwMbr7Blk/PGK4toVcjO2yrcIahGxni629wCBS+EFEud5AojE0KBXier/GDARBk3illMdMbsHJ3tl+Z0ciQUcISCQS/UIITp6/QiyZxbRsmtUGmut26wcRftRa6CgNGo3vBzR9nyhS7UUPjtlyS1pFxOI51m68C8OQwjQl16bOUpn6L2Ib/op4PP7HQ0A8Hu8VQnB2bJqNIyM0/AjLlISRZsFrUGuEtP2DECBoXcMwIAxDpLjRF2sApTBNDQh0cBUhE2S7t1L94FX8xjzpdHqwE9g7EgUMw+j2mj6GaWKbJs0g4Fq1yfhclQWv5QR/nyYAxzLxmx5pq04slgANWoHQPmifbHGY+tw7xGKx3k5g74gGuK7bPXOt0ipjRYoLU+XFGW/NNoAQ4NoGtmFgGAZCaMJAEoYGUgq00gih8ZsNglqJLesvkS7cDzoCYSGEhdCKWLKPazO/xbQ35DuBvVMmUKx7DaRhMj5Xo94MiNnXhzalJJNwWqHNqxD6HjqK8P0mge9jSBBao1SEikI+NzhJ38AIlpNHBzNoTBBWK3qYSVRQxUk5yccffzz24x//2FsJ9k4QICzLyigpKFUbWEkXpa7bu7GY+XnVBbzSDDljlqRRIooCGr6HH/gkYg6ua5NLmvRkahTW3EmuuB4dlkCYgAHCRggJURkhFJlstiO5wIoJ2L9//y2ZTCbhK4mOQqQQqGUmn4hZNBse4cIUfz5wkv7BYdLZIaRhoHWEiiKkYQEChInp5BBodFhpDSBsQCN0AxpjYAyApmO5wIoJKBaL96bTaamURkc+piFbyQ6trM0yJAtXZ7iz9wKfuW0HyfwwghDQhGHYigKLqz4d+WhVRkd1UAEoH6IFCObQYRVII4wQLQTNZpP169fvBP55JfhXTEAymVxnWRZhGOKYEhVprEWBTCFa6wCtKKSqJPKfAVW9Xo+KvNasRrMQlVsOT+tP7kaC0C9jOkU8z6Onp+cvnn322dg3vvGNm/YDKwqDWmvR29v7YK1WQ0rJto2DRI0FEq5JzDawLQMNmIbAdmKAhZCLBRLvDCz8N3hnIZwDHbbi3ScIL7RCmBlqC3OYqW0EQUAymYxnMpl/0lrfdIlvpXlAPJVK3eL7fouATWvJWIpgYTYydAQqIGg2MaRACgNhWC1hyy9B8wOQSTDSH+mpj+92H2hBuVwmMtYArZpiX1/fXwshbvok2YoIeOGFF4a6urriUkqazSZSSh7c+TluX58r9bu+zooaab1Aby6hpWFAMAPlQy3VVxrsAZTIoEgT6RQRKSLSH9u9ptQT4x9g5ncipcQ0TQzDoFgsrnnxxRe/drMyrMgHhGH4nXw+L+r1OlEU0Wg0SKfTbBosNh3Hodls6vaaXkUNaHwALK9sKyqlMcKwlSov73VPUK030GYPUkoy2a6Gld4Vs5wicH3v0bZtkcvlngCe/9QJyOVyO4QQ2LaNUoparYbrujiOIyzLWhIGAC8AVMvOl+RvkskPYxjGDcIjDKq1BpXL1zAy92PbNtneXj8Mw1hb+DYBi6dK7vzCF75gHj58OPz/yrAiE0ilUmvm5uYwTXNJJcvlMqZppm3bxnEcHMfBdd0WaMSNzq1+DupnWg7RO4OQJtLpRYYzWKYgmUySTqdJJBIopVKpVEpZlrV0xMayLMrlMr7vu9/97ne/dTMyrIiAer3+4tTUFG1QptlSKMMwElprsbiXJwChlEJFIRqDVs1kWdcKKUDmvwap+9D2ekI/IFIGQghc18UwDOk4TqP9rnafn5/Hdd2wt7f31zcjw8fXrX/PJqU8s3nz5u/09vaK9qGnIAiU4zhi+T4+gF8fx5FlDHcNURgShQ20Clr5v4pQSqCiCB0FqOaHTE5exMreixvPEovF2oeq6lJKt61tYRgyNjZGFEUv33PPPT+7KRlWQsAvfvGLs+fPnz914cKFJZUEwnYVeNFJYRgGZn4X41MVJsffpRlaRNYtKPdWtLsZ4qMQ+wyqOUFl8gXGLp5Cpj9PPNWP4zhIKVlMthzTNJds//Tp06xbty44ceLEv96sDCvOBF966aV/GxkZGa1UKjKVSgHoxfL4jQSYJtbAN2lULzExfRTCcSzbbK33tSaMQhBxZHKU+NrbcJzWIapldQaCIHBd1/WVUvbVq1dxXZfz58+/d/ny5Wf/YATUarWfvPXWW4/EYrGR7du337CH3/bu7RkMggDHGSHM3ILWGtM0dK06L2wngSPNpfjeVnG47vGVUpimida6EUWR/d577zE6OhocO3bstf379/t/MAJeeumlWjab/dldd9311Pz8vDQMQ7RD1A1hEHAcZylkLtYQhBByaabbprO8ffRcQRAEvP/++wwPD3Py5MmzsVjs71eCf0VOsN1Onz595L777vtLoLe7u1tYltUqDS1T3+V9eSbXziOW+42PPtu+GobB7OysHYYh3d3dwZtvvvnDRx55ZEXL4U5VhvThw4f/wTTNSCm1pAHLj8V83P1yIX+fZ6Mo4sKFC2LDhg3106dPH3n00Ud/ulLgHdEAgBMnTry/Z8+e3aZpDly+fBnP85a+A/gkLdCLFaC2CXx09uv1OtPT01y6dIkPP/yQarVKPp8niqKJY8eOPf7Zz3724kpxd4wAAK310VQq9fWurq75VCoVlstl8+LFi8bExAT1en0pqWkLqrVGKYVttz4KqdVqTE1NceHCBS5dukS1WsW2bbq6uurZbPZSvV6fuXLlSnD27Nk3Hn744R91AnPHD0vv2bPn71Kp1Df7+/tH+/v77f7+/trmzZtnMplMcvE7AkspRS6Xo1Ao6KmpKVGpVNrnf8jlciqdTpcbjcbs7OysUSqVeufn5+OlUolSqVRWSp01DONb+/bte68TeFfzw8mY53lfMQxjd39///39/f3D/f39Rl9fX214eHgukUgkGo1G3DRN1d3dXWk2m9X5+fnU/Px8d6lUEqVSSc3Pz49VKpV3tNZvmqb54mOPPXay0zg/tW+HH3jggbTjOF9NpVJf7evru3fNmjXr+/r6xLZt21icXW9+fv79crn8v/V6/Yjnef/55JNPXl1tXJ/6x9Pttnfv3rVhGH5979691rVr117et2/fW38oLH/S7f8AFTlWCbwHWQcAAAAASUVORK5CYII= diff --git a/src/lay/salt_templates/pymacro/pymacros/new_macro.lym b/src/lay/salt_templates/pymacro/pymacros/new_macro.lym new file mode 100644 index 000000000..4570bcb72 --- /dev/null +++ b/src/lay/salt_templates/pymacro/pymacros/new_macro.lym @@ -0,0 +1,22 @@ + + + The New Macro + + + false + false + + false + + + ruby + + # This is the new macro created with the sample macro package + +RBA::MessageBox::information("Information", "This is the new macro created with the sample macro package", RBA::MessageBox::Ok) + +# In order pull in classes from other packages, just specify these classes +# in the dependencies of this package. Provided those packages contain macros +# which are marked as "autorun-early", they will be loaded before this package +# and their modules and classes will become available. + From d426c5a1855e8c81ba0f735eff701c1f3ec4728e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 29 Apr 2017 01:38:25 +0200 Subject: [PATCH 55/57] Added many Salt templates. --- src/lay/laySaltTemplates.qrc | 53 ++++++++++ src/lay/salt_templates/drc/doc/readme.html | 45 ++++++++ src/lay/salt_templates/drc/drc/new_drc.lym | 20 ++++ src/lay/salt_templates/drc/grain.xml | 16 +++ src/lay/salt_templates/font/grain.xml | 2 +- src/lay/salt_templates/lib/doc/readme.html | 40 +++++++ src/lay/salt_templates/lib/grain.xml | 4 +- .../salt_templates/lib/libraries/new_lib.gds | Bin 0 -> 2422 bytes .../salt_templates/macro/macros/new_macro.lym | 1 + .../salt_templates/pcell_lib/doc/readme.html | 46 ++++++++ src/lay/salt_templates/pcell_lib/grain.xml | 6 +- .../salt_templates/pcell_lib/macros/pcell.lym | 99 ++++++++++++++++++ src/lay/salt_templates/pymacro/grain.xml | 2 +- .../pymacro/pymacros/new_macro.lym | 5 +- .../salt_templates/python_lib/doc/readme.html | 46 ++++++++ src/lay/salt_templates/python_lib/grain.xml | 16 +++ .../python_lib/pymacros/new_macro.lym | 27 +++++ .../salt_templates/ruby_lib/doc/readme.html | 46 ++++++++ src/lay/salt_templates/ruby_lib/grain.xml | 16 +++ .../ruby_lib/macros/new_macro.lym | 29 +++++ src/lay/salt_templates/tech/doc/readme.html | 50 +++++++++ src/lay/salt_templates/tech/grain.xml | 6 +- src/lay/salt_templates/tech/tech/tech.lyt | 9 ++ 23 files changed, 572 insertions(+), 12 deletions(-) create mode 100644 src/lay/salt_templates/drc/doc/readme.html create mode 100644 src/lay/salt_templates/drc/drc/new_drc.lym create mode 100644 src/lay/salt_templates/drc/grain.xml create mode 100644 src/lay/salt_templates/lib/doc/readme.html create mode 100644 src/lay/salt_templates/lib/libraries/new_lib.gds create mode 100644 src/lay/salt_templates/pcell_lib/doc/readme.html create mode 100644 src/lay/salt_templates/pcell_lib/macros/pcell.lym create mode 100644 src/lay/salt_templates/python_lib/doc/readme.html create mode 100644 src/lay/salt_templates/python_lib/grain.xml create mode 100644 src/lay/salt_templates/python_lib/pymacros/new_macro.lym create mode 100644 src/lay/salt_templates/ruby_lib/doc/readme.html create mode 100644 src/lay/salt_templates/ruby_lib/grain.xml create mode 100644 src/lay/salt_templates/ruby_lib/macros/new_macro.lym create mode 100644 src/lay/salt_templates/tech/doc/readme.html create mode 100644 src/lay/salt_templates/tech/tech/tech.lyt diff --git a/src/lay/laySaltTemplates.qrc b/src/lay/laySaltTemplates.qrc index 72cfaaede..7ff71d276 100644 --- a/src/lay/laySaltTemplates.qrc +++ b/src/lay/laySaltTemplates.qrc @@ -18,6 +18,10 @@ salt_templates/lib/grain.xml
+ salt_templates/lib/libraries/new_lib.gds + + + salt_templates/lib/doc/readme.html @@ -25,6 +29,10 @@ salt_templates/pcell_lib/grain.xml
+ salt_templates/pcell_lib/macros/pcell.lym + + + salt_templates/pcell_lib/doc/readme.html @@ -38,6 +46,17 @@ salt_templates/macro/doc/readme.html + + + salt_templates/ruby_lib/grain.xml + + + salt_templates/ruby_lib/macros/new_ruby_lib.lym + + + salt_templates/ruby_lib/doc/readme.html + + salt_templates/pymacro/grain.xml @@ -49,11 +68,45 @@ salt_templates/pymacro/doc/readme.html + + + salt_templates/python_lib/grain.xml + + + salt_templates/python_lib/macros/new_python_lib.lym + + + salt_templates/python_lib/doc/readme.html + + + + + salt_templates/drc/grain.xml + + + salt_templates/drc/drc/new_drc.lym + + + salt_templates/drc/doc/readme.html + + salt_templates/tech/grain.xml + salt_templates/tech/tech/tech.lyt + + + + + + + + + + + salt_templates/tech/doc/readme.html diff --git a/src/lay/salt_templates/drc/doc/readme.html b/src/lay/salt_templates/drc/doc/readme.html new file mode 100644 index 000000000..919634a60 --- /dev/null +++ b/src/lay/salt_templates/drc/doc/readme.html @@ -0,0 +1,45 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
  • Specify dependencies if you plan to use items from other packages
  • +
+ +

+Of course, the most interesting thing is how to add, edit and develop DRC scripts within +your macro package. When the package was initialized, a "drc" folder with a single +DRC script has been created. You will find this folder in the macro editor +in the DRC category under the name you have given the package. The name of the sample DRC script is "new_drc". +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/drc/drc/new_drc.lym b/src/lay/salt_templates/drc/drc/new_drc.lym new file mode 100644 index 000000000..174ff2ffc --- /dev/null +++ b/src/lay/salt_templates/drc/drc/new_drc.lym @@ -0,0 +1,20 @@ + + + The New DRC Script + + + false + false + + false + + + ruby + + # This is the new DRC script created with the sample DRC package + +report("Sample DRC") + +l1 = input(1, 0) +l1.width(1.0.um).output("Width (Layer 1) < 1.0 um") + diff --git a/src/lay/salt_templates/drc/grain.xml b/src/lay/salt_templates/drc/grain.xml new file mode 100644 index 000000000..d04b8791f --- /dev/null +++ b/src/lay/salt_templates/drc/grain.xml @@ -0,0 +1,16 @@ + + + drc + 0.0 + DRC template + This template provides a template for a DRC script + doc/readme.html + + GPLv3 + + + + + + + diff --git a/src/lay/salt_templates/font/grain.xml b/src/lay/salt_templates/font/grain.xml index a0986f035..a4cb1fba0 100644 --- a/src/lay/salt_templates/font/grain.xml +++ b/src/lay/salt_templates/font/grain.xml @@ -11,6 +11,6 @@ - + iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAF/wAABf8ByXatVgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAABApSURBVHic5VtbbBRXmv5O3bu6Lt3tbuxutzEYm0sMcRJ2pUA80rAEMjgrXiaWstpEI7EvSURWyT5EG7RaefOwTxkp0kgjTRQpq2yklUhEtEsUBYiAQDIrAg7IYOzYsgGD8d3d7b5Ud11O7YNdlbKBCOFystIcqVyu6tOnv/+rc/7/O/85BfyFF/Jz/+B7771XH4vFsrIsN/A8r1FKywzDTFar1Yljx47d/eSTT5yfE8+aE/DGG2+kN2/efDibzf5GFMUtoihGWZYFpRSyLKNcLkMURVBKYVlWrVqtjtRqtTMTExN/fOWVV66vNb41I6C7u5vdtm3bH1pbW/8hlUoJsixDEASUSiVomoa+vj4cOHAA586dQ1tbG+7evQtRFJFIJGAYBiqVCp2env78xo0bL/f09CysFU5urRpOJBK/13X91VgsBp7nIQgCXNfF1NQUtm3bhi1btkCWZbzwwguoVCqQJAmO44AQAsdxYFkWE4/HD87Ozn4KYP9a4VwzAkzT/FWxWMTCwgIEQYBpmpAkCbdu3cLMzAwopahUKgAA27aRy+XQ29uLzs5OmKYJ27axsLCAfD7/V2uFEVhDAmKxGNF1Hc3NzZiYmIBlWTBNE52dnTAMA8eOHUNzczN0Xcf58+fx7LPP4qmnnkI+nwfLspibm8Pg4CBEUXTXCiOwhgRUKhWzpaUFp0+fhizLGB8fx7p165BIJKBpGjZs2ACWZVEul9HR0YHx8XGk02koioLr169jcHAQLS0tKJfLtbXCCKwhAZqm8VevXsXBgwdhGAZOnz6Nubk5jI+Pw7ZtUEr9uoQQsCyLSCSClpYW7NixA01NTbh8+TJkWRbWCiOwRlHg8OHDGZZl+7q6uuqmp6dx+vRpRKNRtLW1IZvNolar+SR4xkuShPr6egwODuLy5cvQdR0dHR0YHR11s9nssW+++eb1999/fyJsrKET8Pbbbz+zffv2zwcGBmKapkGSJHR2dqKvrw+Tk5O4desWpqamYBgGDMOAJEkQBAGpVAq7du3Czp07wTAMrl27hpGREaiqCtM00dLSkrtw4cLz77777v+GiTf0IdDU1PRhsViMFQoFNDQ0YN++ffj8889x4cIFMAyDuro6PP7444jFYhAEAY7joFKpYH5+HteuXcPZs2exefNm7NixA6lUCl9//TU2btyIQqEQb2xs/A8AW8LEGzoBlmWtq6+vRzqdRm9vL44fP44nn3wS+/fvh6IoqFQqKJfLKJfLmJubgyzLkCQJbW1taG9vh6ZpOHfuHD788EMQQpBOpxGLxaDrOn744Ye6sPGGTsDVq1e/jUQiXZs3b0YsFsOdO3dw9+5dXLlyBdPT07AsyxdGDMPAdV1YlgXLsgAAiUQCiUQCyWQSjY2NWL9+PQRBwFdffQXDMELt/sAa+ICBgYHTp06d6rx48SLX1tZG1q9fD13XMTs7i3w+j4WFBZTLZd8RAgDHcZAkCdFoFJqmoaGhAdlsFmNjY/j222/R29vrHjp0yNq7d+/XW7ZsCVUVhkpAd3c3+84770wkk0mpVCqpH3/8Mc6cOYO5uTmXUko0TUMqlYKu64hGo+B5HgBgmiYsywLDMCCEYGFhAadOnUJTUxP279+PF198EfX19cVyuVw9cuRIOswZY6gEHD9+/B+ffvrpf08mk1EAKJVKKBQKsCyrcuHCBbm/vx/j4+OYmppCqVSCaZoAAEEQoKoq6uvrkclksH37dmzfvt0VBIF4hAHA7Oxs8eLFi//a1dX1XliYQ/UBlNKt8/PzViKRAMMwUBQF0WjUNE3T3rdvH/bs2QPHcWDbtn+u1X4UeizLQhAECIIAXdcriqJIhBB2qW3Mzs7ajuPsCBNzqASoqtpcV1cXGx8fv5PNZrOEEJMQUhVFURNF8Z76tm0jn89DEATwPA+WZYMfRx3HKXIcJ7muy9+5c+eOruvZRCLRHCbmUAmQJKnBdV3EYrFsqVSacF1Xjkaj+grDfvxxjoOmaQ9sz3Ec1XXdOdM0TY7jspRSiKKYDhNzqASIoph0XReEEBiGIVFKtWKxWCWElBiGMRiGoTzP8wzDqIqiqB4xS6GwappmnlJasizLdRxHdBwnDqAumUwueHkCjuPiYWIOlQCe51VKKRiGAaXUJoRAFEWRZVmRYRiwLOvH/oWFBaNWq5Vs2wbP8wrHcRFKaQOlFK67OAMmhGDpmhJC4LouGIaJhomZCbEtwrJshBCCpcMGFo0InoHFrq8oiqSqalIQhJQgCJFlDS214YNkGMe7x/O89Oabby6rv5oSGgE9PT2bZFkWPKC2bTv3M/5+9x5UAkRQ71rTNL6urm5nWLhDIyCVSu1WFIXxxAwCoL2y0viVT/pBxXVd16sbiURIKpV6KizcofmAaDS6geM4f47vnb1CCMHMzAyGh4dJqVQCz/OIRqPIZrNIpVI/SQTDMG6QrLq6utBCYWgEqKraACwayjAMHGdRrXrAR0ZGcPHiRQIAsVgMLMtieHgYAwMDvmP0nJ/nCL1rAPUcx2HTpk3YuXMn4vF4a1i4QyMgEok0Acu7tXc2DAO9vb3EdV3Isoyuri5XFEV89NFHJJPJoFqtYmZmZhEQxyGRSIBS6pPIcZyZz+eFaDQKx3EQiUSyYeEOjQBFUTKUUrAs65Hg+5eJiQnYtg2GYbB+/XqIogiGYfDYY4+hsbERg4ODPgHxeBydnZ2glPpHIpEofPrpp6loNIolMZQMC3doTlAUxaSnAZae/OIfQjA1NUUC49fv1x0dHW4kEkG5XPbbURTlnrZt22aq1SoURfHEkB4W7tAI4Hle9xzfkmjhPKMLhYJfz5O+QadXLpf962j0Xp1j2za7bds2nxxBECJhaYFQCHjrrbdUURRlTwYvFa5QKGBwcBClUsmve/v2bdLf34+5uTkAWLZCRAiBLMt+3YWFBYyNjUEQBNLR0eEPL13X+fr6+lC0QCgEJJPJp2KxGL/CAXKTk5O4cuUK8TI/AHD9+nVcunSJeEZXKpVlawSKovi9aGJiAuPj43AchwkoTEQiERKPx0PRAqE4wXg8/mRwcXPJGXItLS3Qdd09f/48IYRAVVXs37/fZVkWqqqiVqstG/8AwPM8LMsCpRSlUglLjo/lOG5ZhInH46FEglAIiEajGQ9cQAMI3rq/B1pVVWiaBoZhwDCLnc8wjGVtnThxwtcAruuivb0djuMs610AIMvy/x8CZFluCHZRx3Eoz/McgPt6+KAhQf/A8zzi8Thc1wWlFDMzM5BlGY7j8AzDUACMNy3WNK0pDOyhEMCy7DrgRxFUq9VMhmFEQghKpZJvraqq7krJGyQolUrhmWee8dNln332mTcEQAixCCGiN8QikUhDGNhDcYKSJK1bmqt7PcDykh3FYtGvp6oqgHtDoFe8ELhEIjzluNS2Hehh4DguEQb2UAiQZTkV1ACUUt/tewYSQnwPHyzBIRAUQaIoYu/evf7+IQBOoH2IoqiEoQXCIIAERVAwGwQshjnv/5X5v5VZ4aAIYlkWsVgsWJ0GyYvFYqFogVUT0NPTs0nX9agngpZA2sCi8d6Mjud5rMwMB58+IeS+KtAHyjCOVy9MLbBqAlKp1G5N05igCqSUOsC9EWBlKAsSACz6iAflBSilNBhparUaNmzYsG+1+FdNgKIozd4SlxfbvQxO0EDPAQZL0EFKkgSOWwxK98sUBbNCLMvCMAzU19f/zdGjR1flB1ZFgOu6pKGh4YVyuRycBcJd6vdeCPQcoPd/rVbDxMQEZmZmfCt5nsf09DRyuZzf/gpp7Xo+xrZtWJYFRVFkXdf/zXXdR17iW20PkFVV3WSa5jICPPDBWV5QA9y5cwdffvklGRsb8+sXi0WcOXMG/f399/yI9z1PZXrqURAEpNPpvyOEPPJOslUJoS+++KKltbVVZhgGtVrtnm4eHALBCJBKpbBnzx63VquhWq0Snuf95EdwNhgsrusSSikMwwAhBBzHgWVZpFKpxhMnTvztc8899/mj2LAqAmzbfjWRSJBKpQLHcVCtViEIAiilDPBjHoAQgmQyuSwcaprm7xNa+o6fBgvODr1CKSXFYhGu68KbGDEMA0EQSDwefwvAIxGwqiEQj8c7CSEQBMHf82eaJggh7Pz8vL8BIhaLQZKkh2rTM8xzqIGhxQGLOUOe5/3FVEII4vH4X//6179+pIe5KgJUVW2cm5sDx3F+lywUCjAMQ79y5QrxnFh7e7vrGfegEvTwHMdBFEV4u8wEQUClUtFVVaWe8d6KcqFQgGma0muvvfa7R7FhVQRUKpUTk5OT/hPxwtjg4KA8MTEBRVGwe/duNDU1Ecdx4DhOMNW9zHhvb8CSkoRlWSiXy7Asy1s2Z0RRrHq/5R25XA6SJNkNDQ3/8yg23H/d+iELwzDXt27d+mpDQwPxwFuWRTVNI62trWhvb4eqqp5BxLIsYts2qdVqpFqtEu++J4lN0/RJChLFsix4ngfDMBWGYSSvt9m2jVu3bsFxnK927dr1p0eyYTUEfPDBBwMjIyPXRkdH/S4JwPb2BHvzeu/pm6YJwzBQrVZ9Y71FkJXiJ+gLCCHgeR62bYscx/n3+vv70dzcbPX19f3no9qwaiV48uTJ/yoWi7RYLHqOy/UAegY86Ah68586PHIsy5IYhjEBYGZmBpIkYWRkZGhsbOzoL0ZAuVz+w/fffz88PDwMYDFeBx1a0JDg9crPHnTfu6aUguM4uK5bdRwHQ0NDaGxstEZHR8/19PSYvxgBJ0+eLH/33Xd/siyL5nI5sCxLHuapLq0HPlRdb2hwHAfLsjA0NITW1lZcvXp1IBKJ/PNq8IeSEDl69Oh7Q0NDfUNDQyBLu7oexrCHreeRwLIs5ufnVZ7nXV3XzZs3b77/0ksvrep9orBWhtyzZ8/+C8dxzuKs9UejfmoYBDPJD1PXcRyMjo6SjRs3Vvr7+y8cOnToj6sFvqowGCx9fX3DBw8e7OI4Ljs2NgbDMPz3AILz+KDRruvCcRzwPO/fC9apVCqYmprCzZs3cfv2bZRKJSQSCTiOM37p0qU3n3jiiRurxR0aAQDguu5FVVV/m0wmc4qi2IVCgbtx4wY7Pj7up8YkSfIN9cKkICy+FFIulzE5OYnR0VHcvHkTpVIJgiCgrq6uEovFblYqlem7d+9aAwMD37788su/DwNz6JulDx48+E+apv19Op3enslkhEwmU966deu0ruvK0nsEPKUU8XgcyWTSnZycJMVi0dv/g3g8TjVNK1Sr1dnZ2Vk2n8835HI5OZ/PI5/PFyilAyzL/u7w4cNDYeBdyxcnI4Zh/IZl2a5MJvOrTCbTmslk2EwmU960adNcNBqNVqtVmeM4um7dumKtVivlcjk1l8uty+fzJJ/P01wud6tYLF5xXffPHMedeP3116+GjfNne3f4wIEDmiiKz6uq+nw6nd7d2Ni4IZ1Ok46ODiw9XSOXyw0XCoXvKpXKBcMw/vvIkSMza43rZ3952ivd3d3rbdv+bXd3Nz8/P//V4cOHv/+lsPxFl/8DbC02U+AxXpwAAAAASUVORK5CYII= diff --git a/src/lay/salt_templates/lib/doc/readme.html b/src/lay/salt_templates/lib/doc/readme.html new file mode 100644 index 000000000..fbc16f403 --- /dev/null +++ b/src/lay/salt_templates/lib/doc/readme.html @@ -0,0 +1,40 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
+ +

+Of course, the most interesting thing is how to add, edit and develop libraries within +your library package. When the package was initialized, a "libraries" folder with a single +sample library has been created. +In the package details you will find the local path to your package and the library +layout file under "Installation". +You can use any versioning system to manage your files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/lib/grain.xml b/src/lay/salt_templates/lib/grain.xml index a071c2676..f39000459 100644 --- a/src/lay/salt_templates/lib/grain.xml +++ b/src/lay/salt_templates/lib/grain.xml @@ -4,13 +4,13 @@ 0.0 Static Library This template provides a static library - + doc/readme.html GPLv3 - + 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 diff --git a/src/lay/salt_templates/lib/libraries/new_lib.gds b/src/lay/salt_templates/lib/libraries/new_lib.gds new file mode 100644 index 0000000000000000000000000000000000000000..0b91bfa39b9fe1c8b75c9aea2ff263d49140e3b8 GIT binary patch literal 2422 zcmd^}-4h3o8+^ z@*VO5Vqs+y|9@^Lcjv{}SS16Oe{XMQv-f{DE3r7&k!s+|J&9yNM3!VVJC=%z_gV+1 zyW5R+y?rP$AC@-imuovOcOUD`hn3r#qkXBCey@+?xGoY+iNrHb0*PlG{-s+9@tX;^ zjGJo__b3vaczuF)F#qcH49`AsPeo2}l~K$GwIna_b3*mEertvOa-Uq^s})1D^)9XN_xo>lq3%7|TcS2k&DDpw`YxX9B@!;89^NEs^VFoi zEPqjHq;F|do`=3AT(h@S8gR`tM}RcoJm=JPQp66`C9j Mbc|lqSe24Nq3*NDE(t z(b5{^SsitdhoO(G`(sRRvqG7Mk@MEK;6o1fEH%Wr@yk_ZXPqGI|ojuAmK}9c& zNo7F6XrW(~jw$mmy2@FYQR3}|`R)m`5o>LOh=TBA-p(jc8Q vCZso7Mc2=Vx{mLC diff --git a/src/lay/salt_templates/pcell_lib/doc/readme.html b/src/lay/salt_templates/pcell_lib/doc/readme.html new file mode 100644 index 000000000..b004d843e --- /dev/null +++ b/src/lay/salt_templates/pcell_lib/doc/readme.html @@ -0,0 +1,46 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
  • Specify dependencies if you plan to use items from other packages
  • +
+ +

+Of course, the most interesting thing is how to add, edit and develop PCell macros within +your PCell package. When the package was initialized, a "macros" folder with a single +sample PCell has been created. You will find this folder in the macro editor +under the name you have given the package. The name of the sample macro is "pcell". +You can add more script code, Ruby and other files there or modify the sample script. +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/pcell_lib/grain.xml b/src/lay/salt_templates/pcell_lib/grain.xml index 20a656273..46fc05777 100644 --- a/src/lay/salt_templates/pcell_lib/grain.xml +++ b/src/lay/salt_templates/pcell_lib/grain.xml @@ -1,16 +1,16 @@ - macro + pcell_lib 0.0 PCell library This template provides a PCell library implemented in Ruby - + doc/readme.html GPLv3 - + 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 diff --git a/src/lay/salt_templates/pcell_lib/macros/pcell.lym b/src/lay/salt_templates/pcell_lib/macros/pcell.lym new file mode 100644 index 000000000..5955fc729 --- /dev/null +++ b/src/lay/salt_templates/pcell_lib/macros/pcell.lym @@ -0,0 +1,99 @@ + + + The New Macro + + + false + true + + false + + + ruby + + # PCell template + +# It is recommended to put PCell code into namespaces. +# TODO: change the module name +module PCellPackageModule + + include RBA + + # Remove any definition of our classes (this helps when + # reexecuting this code after a change has been applied) + # TODO: adjust the names + PCellPackageModule.constants.member?(:PCell) && remove_const(:PCell) + + # The PCell declaration + # Each PCell must provide a declaration. It is recommended to use the PCell name as the class name. + # TODO: change the class name + class PCell < PCellDeclarationHelper + + include RBA + + def initialize + + # Important: initialize the super class + super + + # declare the parameters + # i.e. param(:l, TypeLayer, "Layer", :default => LayerInfo::new(1, 0)) + # param(:s, TypeShape, "", :default => DPoint::new(0, 0)) + + end + + def display_text_impl + # Provide a descriptive text for the cell + "TODO: create description" + end + + def coerce_parameters_impl + # TODO: use x to access parameter x and set_x to modify it's value + end + + def produce_impl + # TODO: produce the cell content + # i.e. cell.shapes(l_layer).insert(Polygon.new(...)) + end + + # optional: + # def can_create_from_shape_impl + # TODO: determine if we have a shape that we can use to derive the + # PCell parameters from and return true in that case + # end + # + # optional: + # def parameters_from_shape_impl + # TODO: change parameters using set_x to reflect the parameter for the + # given shape + # end + # + # optional: + # def transformation_from_shape_impl + # TODO: return a RBA::Trans object for the initial transformation of + # the instance + # end + + end + + # TODO: add more PCell classes .. + + # Register a new library with the PCells + library = Library::new + + # TODO: change the description + library = "My PCell package" + + # register the PCell declarations + # TODO: change the names + library.layout.register_pcell("PCell", PCell::new) + + # TODO: register more PCell declarations ... + + # register our new library with the name "PCellPackage" + # TODO: change the library name + library.register("PCellPackage") + +end + + diff --git a/src/lay/salt_templates/pymacro/grain.xml b/src/lay/salt_templates/pymacro/grain.xml index e780101db..b3f00281f 100644 --- a/src/lay/salt_templates/pymacro/grain.xml +++ b/src/lay/salt_templates/pymacro/grain.xml @@ -1,6 +1,6 @@ - macro + pymacro 0.0 Python Macro This template provides a Python macro. diff --git a/src/lay/salt_templates/pymacro/pymacros/new_macro.lym b/src/lay/salt_templates/pymacro/pymacros/new_macro.lym index 4570bcb72..cf26eacf8 100644 --- a/src/lay/salt_templates/pymacro/pymacros/new_macro.lym +++ b/src/lay/salt_templates/pymacro/pymacros/new_macro.lym @@ -9,14 +9,15 @@ false - ruby + python # This is the new macro created with the sample macro package -RBA::MessageBox::information("Information", "This is the new macro created with the sample macro package", RBA::MessageBox::Ok) +pya.MessageBox.information("Information", "This is the new macro created with the sample macro package", pya.MessageBox.Ok) # In order pull in classes from other packages, just specify these classes # in the dependencies of this package. Provided those packages contain macros # which are marked as "autorun-early", they will be loaded before this package # and their modules and classes will become available. + diff --git a/src/lay/salt_templates/python_lib/doc/readme.html b/src/lay/salt_templates/python_lib/doc/readme.html new file mode 100644 index 000000000..e50eac64e --- /dev/null +++ b/src/lay/salt_templates/python_lib/doc/readme.html @@ -0,0 +1,46 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
  • Specify dependencies if you plan to use items from other packages
  • +
+ +

+Of course, the most interesting thing is how to add, edit and develop classes within +your python library package. When the package was initialized, a "pymacros" folder with a single +sample macro has been created. You will find this folder in the macro editor +under the name you have given the package. The name of the sample macro is "new_macro". +You can add more macros, Python and other files there or modify the sample macro. +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/python_lib/grain.xml b/src/lay/salt_templates/python_lib/grain.xml new file mode 100644 index 000000000..a4c917c3f --- /dev/null +++ b/src/lay/salt_templates/python_lib/grain.xml @@ -0,0 +1,16 @@ + + + python_lib + 0.0 + Python Library + This template provides a Python library. + doc/readme.html + + GPLv3 + + + + + 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 + + diff --git a/src/lay/salt_templates/python_lib/pymacros/new_macro.lym b/src/lay/salt_templates/python_lib/pymacros/new_macro.lym new file mode 100644 index 000000000..14ff7b3bb --- /dev/null +++ b/src/lay/salt_templates/python_lib/pymacros/new_macro.lym @@ -0,0 +1,27 @@ + + + The New Macro + + + false + true + + false + + + python + + # This is the new macro created with the sample Python library package + +class NewSampleLibraryClass(object): + + def __init__(self): + # TODO: add your code here + pass + +# In order pull in classes from other packages, just specify these classes +# in the dependencies of this package. Provided those packages contain macros +# which are marked as "autorun-early", they will be loaded before this package +# and their modules and classes will become available. + + diff --git a/src/lay/salt_templates/ruby_lib/doc/readme.html b/src/lay/salt_templates/ruby_lib/doc/readme.html new file mode 100644 index 000000000..60b4a1503 --- /dev/null +++ b/src/lay/salt_templates/ruby_lib/doc/readme.html @@ -0,0 +1,46 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
  • Specify dependencies if you plan to use items from other packages
  • +
+ +

+Of course, the most interesting thing is how to add, edit and develop classes within +your python library package. When the package was initialized, a "macros" folder with a single +sample macro has been created. You will find this folder in the macro editor +under the name you have given the package. The name of the sample macro is "new_macro". +You can add more macros, Ruby and other files there or modify the sample macro. +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/ruby_lib/grain.xml b/src/lay/salt_templates/ruby_lib/grain.xml new file mode 100644 index 000000000..b2276c062 --- /dev/null +++ b/src/lay/salt_templates/ruby_lib/grain.xml @@ -0,0 +1,16 @@ + + + ruby_lib + 0.0 + Ruby Library + This template provides a Ruby library. + doc/readme.html + + GPLv3 + + + + + iVBORw0KGgoAAAANSUhEUgAAAEEAAABACAYAAABFqxrgAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAF/wAABf8ByXatVgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAABMGSURBVHic7Vt7jF3Fef/NmfO87+fe3bt3d71rezH21jbYG+NgSIAYl5SWlBQpUSNokYITKiclKk2FguJCiZKiiJYkQrQRbZISqroBqiagFFODA3ZsJfj9wGt7117vy3v33nvu3td5zEz/2D031yGGxnuWRGo/aXQf595vfvObb2a++eYb4P8F5P2u8PHHHw+Gw+FsNBptp5RmFEVROOeTjuNMmqY5vnXrVvP9xvR+kCB97Wtf+2RPT88nIpHINbquZxVFIYwxGIYBIQTq9Tp0XYfrunAcJ2/b9pFSqfQf4+Pj//CFL3yhvtgAF5WEBx988KM9PT1PL1myJGcYBgzDQK1WQzKZxNGjR7F06VKoqoq3334ba9euxf79+zEwMADHcVCv11Gv183h4eGHt23b9o3FxEkXS/Hdd9+9Op1O7+zs7EyEw2FomgZd1zE+Po5wOIzBwUF0dXWhu7sby5YtA6UUlFJEIhEIIcA5B2NMF0L87urVq8+89tprhxcLq7RYilVVva5arWqzs7NwHKdZGGMYGhpCpVJBsVjE5OQkisUi6vU6Dh48iOHhYdi2Dcdx4LouZmdnSblcvmWxcAKAvFiKK5UKy2azWLZsGRzHQalUgm3byGQyUFUVJ0+exMGDB3Hrrbdi//79iEajWL16NRzHQaVSgSRJGBoawsTEBAzDWCyYABaRhFKpVOjr6xMnTpwgXq/ruo5UKoVIJIJQKIRVq1ZhfHwcnZ2dsG0bpVIJiUQCjDHs3bsXlmVh7dq12LdvX3mxcAKLSEImk8lOTU2R/v5+XH311Xj99dcxMjKCM2fOwLZtcM4hhGj+XpIkyLKMRCKBgYEBfOQjH8H58+cxMjKCZDIZWCycwOKRQOLx+Eav55966ilUq1XkcjkMDAxAluVLiPAISKVSAIB9+/Zh165dWLFiBXp6enD27NlPPvHEE+cfeOCBvwEg3r3qKwDrt8Jt27ZpGzdufH1oaGhDe3s7RkdHceutt6JUKmF0dBTDw8OYmJhozhGcc6iqikgkgq6uLmzZsgXt7e1wXRcvv/wy4vE4zp8/j1wuh2Aw+PMf/ehHN+zYscNX38F3S0ilUo9xzjcIIXDo0CF89rOfxdjYGF544QVUKhWEw2HkcjmsWbMGgUAAkiTBsiyUy2XU63U899xzkGUZGzduRH9/P/bv3498Po9QKIREIrGuv7//qwA+7ydm30kwTXNJKBRCPB6HaZp48MEHkUqlsH79euRyOVSrVViWhVKphGKxCMYYdF1HOp1GJpPB0qVLUa1W8fzzz+Po0aOIRqPo7u5GLBaDZVkYHx/v8huz7yTs3Lnz8ba2tjuz2SyJxWJIpVIYGxvD7t27MTU1hXK5DEopFEWBLMuQJAmMMdi2Ddd1oWka0uk0QqEQli9fjlwuh+7uboyPj2PPnj1idHT07/zG7DsJ3/ve924wTVN885vf5L29vbS3txe9vb1gjKFYLKJUKqFaraJer8NxHHDOIUkSFEWBYRgIh8OIRqPo7e2FLMs4deoUvv/976NWq7FHH31UUlX1usHBwd1+YvZ9Yjx48OB/ZTKZdbIsJ3bt2oUdO3ZgeHgYjuOAUop0Oo1EIoFQKARd10Epheu6sCwLhBBwziHLMk6cOIEjR45gcHAQd955JzZv3oxAIFDK5/NHBwYGbvATs+8kHDt27OLKlSvTAGDbNkzTRLlcxsWLF8t79uyJnDt3DpOTkyiVSqhUKqhUKrVQKBTwLCCTyaC3txcbNmxwQqGQkkwmkUqloOs6AOD48ePTO3bsaN++fTv3C7Ovw+Gxxx7LNBqNYLlcrkYikaCqqkin04jH48VkMhlbvnw5XNcFY6z1ddhxnFWeDlVVoSgKNE1TUqnUrKIoYe9ZpVKxGo1GUNO0JQDO+oXbVxI0TdvY1dUVKJVKxfkxHgRQlGU5FovF3mF1nHPU63XYtg1FUaCq6iXPhRBhxphJKY3att2YmpqqdHV1pSKRyCb8tpKQTCavUlUVsizHGWPFYrFoB4PBqKqqv3LYSZKEQCAARVEuq5MxFuacz5imCV3XU5RSJJPJVZf9wxWIryTEYrFuIQQIITAMI57P52csyxKc8zKAiiRJLqWUyrJsGIYR0TStWT/nnNfr9VnOedl1Xct1XQghDNd107FYTCWEhAHAdV0kEonlfuL2lYRwONzLOQel1NscyZRSSVXVsCRJYUmSQCkFIQSO4/BGo1E0TTOoaVqRUhohhEQ551GPSG9fwRhzATRXj2Aw6KvD5GtQxTCMDs55awMUQuZGAiEEzfcAFEAKUBojliXruh6nlF42ysU5VyRJapJgGEbaT9y+WoKqqkmv9+YtQgHmCIAQaBw5QNzTJ6BIBFL3UkzteQOjR4/maHcvVtz7aWix+K/UyxiTVVXlACTXdSHLctRP3L5agqqqYc8SGGNckiSZEAJWLGDs298gEy/8K8zjR1A5eQxaWzvk7j64kMAtC+M/3XtZvZxzQghxPWuSJEm/6667fIuP+kbC9u3bA4qiBDyglmXZlFLYExcw8sy3SG1yAq4AXAE4ILDOnYZy+ijivT0Wl2WwefJah40n81bleM+CwaC2fv36ZX5h940EWZZXR6NRZb6nwDm3+WwZ5//tu8Rt1MEBCELgCiC47jpMvfkmKg0bzsgZLZ5MANXqJfpaCWGMAQDzPofDYRIKhTb5hd03EhKJxKBhGMTrRSGEe+6lF0mjWgUngCAAB6BkMgisHcQsB2ouR8kFZiYmYITC76pfkiTu6aaUIhqN9vqF3beJMRqNdrb2njMxpk2dGoImExAAhAJaIonMNWvg7PsxQCXYgkCAgBCKYFvmvargnm4hBBKJxG8fCbquZ4A5L1CSJMiuo2YjAYzP1kEAxPqvQkTVUHj9DQgmoLdnURsdha2qImQ7RA4G36sKDqA5PAKBwBK/sPtGQigUynrvCSGAY7HOD29WopPj0FeuQeHNN1AaPwdStyAAyJSCUxkSY5AlAj0ShnXqOJyJMYhKGVoiAdq3Ai4XQDgCQghvnSMMw2j3C7ufltDheYuEEEzsfAXpwWsRTwZhnz6EyvBZCCLAZAVKJArVLEOWZUSXdJO2vj5ceO4ZCMsCEQIaJbAJEJUVFH5+AI7gsAfWBJI33tQkQdO0mF/YfZsYNU1Lej6CJEkozBTo+N6fwTp8BJWZOgTmYuWCMdRLJdRmZpDM5cBnJlE6eQSuY8+tIACYEGBEAu3uBU+kYE5M4u2XXwqVzgwBgBd4Cd53332X33n9GuILCdu3b5dUVY143iIAcAFUSyUUaxz1ah1EUwEjABoMIh4I4qobb8TFsVFYdQuCkLmCeRIIRWTz7Zj67j9CmzwHYgRACUG9UEDLMqlmMpmVfuD3hYRGo7EkGo0anM8FewghSF5/wywLGLDS7eCcIZjNItLVjfiSXkRWXY1AVxdSbW2gkRig6gClEBKB0d2Drnvuw4X9+1HhBNWSCb0tjbpERWrlKrQ4TKSzs3OjH/h9mRNyudz1kUiEekFTAEhvvL7R8+GbhKhUIVEJWiQKORAghBCwyTHQWAKdxRmYLkMwHEIwHoWuG5BK42icOoDaxBiIACwqQ+RnkMzmmB6Jyo7jgBACSilisViPH/h9ISEQCPR6m6YWh0bTAkFIoTDxlk1C5nwGSxCIShmxD2zEB9ZvZMXjx2j1xFFUTp0GmzUh2jJwIEEQAZe5cKpVrPnje+peTMGTYDDY6Qd+X0iIRCLtLZsbAICu66okSU0CAKA2cQGViQkQx0Y41wWpVESj4RAiUdCOHKiiwh4+AxcUjEpwOQBCkEy3I9TX1/QTPIfJMAxf4gq+kKCqagZAc3kEAFmWVUmS4E6OgeenUNi/F/t37oTk2ggKBzIhSCy9CrF1G6TYtRsAicDlDBLn4JUqSDQOYhah1GykNm0CY4x6+lviCtl3Bfa/FF8mRkpp2utxLzYi6nX69tPfwvEnn4Cc7Ub18FtwGIciUxACEAGUh97G6LPPYORvvwyYBeiJFEggBFYug1g1EAgkOzsQWrYMruvKzSH1C18h6Qd+X0jwIj0eAZxzHHjkYTq56zUYKoV14HWEbr4NlACyRCDrBiRZnttTAGAz05j59t8jJAm0JZJIp1JQNA0KlZC8fhMwZ12XkMA5h6ZpoW3btmkLxe8LCZ6j1Dr+C/mLRE2noBemUP/ZXoQzSVz36T9DauUACAAqy5B1HVQ3uKJpkJkLfnEcQjCEE3G0hyOIBIJQr7oawFx0SZIk5tUphEAkElGvueaaexaKf8Fzwle+8pV0KBSKAGgSIIQQ7WuuJWphFrOUYHI0j9rXvwG1LYEoc9C/5aNAPA1rZgZuYQaBcAixawYxfvAwqHkS0d//GBKGDs22QWQZYMyLKTiEEOqR3Wg0SH9//wNCiGcIIe5vjITly5f/ZSgU0hhjrTM37//Yx+nBR74MuzYLDgEJQL1chsZdTO/8b6R+pw/qqg9i/NAhESxMwTl9FBdtFc7YKPrWrEN4wyYQ24andz4CzbwVyLIsAEBbW1v/q6++Ogjg8vG595AFDQchhJRIJDY3lbVYQnjpMqSv3wSXADbmQ2qWA5tIcFyGkqvj+Hf+CfVz50hDNlC+WERtegYlQjF2+OCvqguSJDFJkpopfpIkIRgMSpTShxbSjoXOCWokEulVVRW2bbd+LwDg6q33I5jpgAwKh0hwQCASSYjlK3D6wGHUuYAjBIoOQy0UR8l1wDQduVs2v6OieZecu66LRqMBQghkWYYsy8jlcjdXKpX3jMpcThZEwrPPPptKJBJNL67RaACYswTvuw88/Ai0gI4eRUUYBNXiLC5Oz4AHArCIhBqhqNRqmHEFYqvX4uavfh3J+cnwlwOujDFSqVRACIGiKFAUxYsyBXbv3r31StuxoDkhGAz+eTQaJfNHZnBd17OI5rG53t6B5Xd8HKX/fB6dlOKca8EqTAEANEcANkdmwyBW3H0Pgt3d4JzDcRwAaHqg3pzg7VI9p8l7BgDt7e1/CuCR952ETCazbnp6Gul0upmTaNs2KKWSNz8AQPddn0Dj1DEEh4dhz9YhCYIUVZG6aRMyf3CHG+npUQE0GwqgmdIDAJZlNQ90vJPrVp/h/PnziMVi3U8//XRq69at+V+3HQs6wPjQhz5U0DTtk8lkEoqiNHtnvsckAM1eDK9ei/KBfShXq1i/5few/C/+CpmbbgYNBl1Jki7pjPnkbti2Ddu2IYSAqqpgjCm6rhNJkoiXEE4pxdmzZ5FMJqduueWWR6+kHQvOVPnhD3+Y7+/vT2azc268d9iq67r0y4cpjZHT0DJZSIGgt+SBMWYRQjTOuXffAYyxplV4lqGqKoQQCIVCddd1DU/v9PQ08vk8Jicn//q2227bfiVtWLDH+Morr/zz6OgoKKWQ5bkOFUIQznkzY9UrWs9SQDcu+R4A9zJXXNe9JN3XsyzPunRdB+e8QSltZr0NDQ3BMIz8zMzMt6+0DQsm4dy5c0/l8/nSyMgICCGwbVsoikI8k2aMvSOPubWHXddlrb8DfhG2b92UeStCo9FopryfPHkSfX19OHDgwE8/9alPXbjSNizYY3zxxRfPRKPRnW1tbX/U1dUFxhjTdV32VozWWb1VvOFg27bt9XTrMX6reBYxby06AMc0TYUxBsuyJk3T/M5C2uDLBqpSqXyxWCzmz5w5A86529qYllss7ygA4DhO0+dvNf9fLi1xCgghGseOHUN/fz/eeuutvZ/5zGf+fSH4fQmq/OAHPzjb1tb2L6lU6vPJZJJckpAxT0SrtH7mc+fu72oFra+qqmJmZkbp7u7G1NTU2PT09JcWit+3c4dKpfKlWq02kc/nZeCd47p1pWgt3lB5r+L91rIsTE1NaZ2dne6RI0ee+9znPnd8odh9S3Q4fPiw09fXx1asWLFlYmJCKpVKzeTt1rBYa5FlGfV6fVbX9XBrjLI1cGKaJsbGxjAyMoILFy7AdV2Ew2FimuahQ4cO/clLL71kvze6dxe/M1rJk08++ZMVK1Ysy+VyABCenZ016vU6CQQCiMViaGtra96HlGUZ+Xx+PBaLZQE0rwAVCgWUy3M3fiKRCCKRiDAMo1CpVC4WCgUpn8+np6amvnjvvfde8bJ4CWg/lLTKHXfc8UFK6cMdHR3rOzs7Ux0dHby3t3eiq6sLruvGSqVSoNFoEF3XsWTJEkxMTJQajUbMtm2oqop4PI5oNOqoqpovl8vlQqEQKRQKmXK5LJmmKcrl8hjn/M0HHnjgE35hXtTLobfffvsaAB9LJpMf7ujouLajoyOSzWZ5T0/PZDabFYSQaKFQqPX09CiqqprFYpEXi8WMaZpB0zRRKpWqpmmebDQaPxdC/KRWq/34oYcemvYb5/t6V3rLli1rDMP4w2w2e3NnZ+e17e3twQ0bNhxjjK0yTZOXSqWJYrF4slwuv1Gv11+p1Wp7/Uzkvpy87xfGPVm3bp2SyWRuufHGG9e2tbWdbjQar95///3F3xSe//PyP6QlwpbNMo63AAAAAElFTkSuQmCC + + diff --git a/src/lay/salt_templates/ruby_lib/macros/new_macro.lym b/src/lay/salt_templates/ruby_lib/macros/new_macro.lym new file mode 100644 index 000000000..2c7aae58b --- /dev/null +++ b/src/lay/salt_templates/ruby_lib/macros/new_macro.lym @@ -0,0 +1,29 @@ + + + The New Macro + + + false + true + + false + + + ruby + + # This is the new macro created with the sample Ruby library package + +class NewSampleLibraryClass + + def initialize + # TODO: add your code here + end + +end + +# In order pull in classes from other packages, just specify these classes +# in the dependencies of this package. Provided those packages contain macros +# which are marked as "autorun-early", they will be loaded before this package +# and their modules and classes will become available. + + diff --git a/src/lay/salt_templates/tech/doc/readme.html b/src/lay/salt_templates/tech/doc/readme.html new file mode 100644 index 000000000..30adefd4c --- /dev/null +++ b/src/lay/salt_templates/tech/doc/readme.html @@ -0,0 +1,50 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
  • Complete the technology definition
  • +
  • Specify dependencies if you plan to use items from other packages
  • +
+ +

+Of course, the most interesting thing is how to edit the technology definition. +When the package was initialized, a folder hierarchy consisting of a tech +folder containing a technology definition file and empty "libraries", "macros", +"pymacros" and "drc" folders was created. You can use these empty folders +to put your own files there. The technology definition can be edited in the +technology manager where you can find the technology as "new_tech" under the +name you had given the package. Initially, the technology definition is +without any particular definitions. +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/tech/grain.xml b/src/lay/salt_templates/tech/grain.xml index 2def15e72..06aa9fad4 100644 --- a/src/lay/salt_templates/tech/grain.xml +++ b/src/lay/salt_templates/tech/grain.xml @@ -1,16 +1,16 @@ - macro + tech 0.0 Technology This template provides a technology - + doc/readme.html GPLv3 - + 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 diff --git a/src/lay/salt_templates/tech/tech/tech.lyt b/src/lay/salt_templates/tech/tech/tech.lyt new file mode 100644 index 000000000..ac78bda13 --- /dev/null +++ b/src/lay/salt_templates/tech/tech/tech.lyt @@ -0,0 +1,9 @@ + + + new_tech + + 0.001 + + + true + From eca08cbd995f37b04825abbb11cf36fad0a9d4be Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 29 Apr 2017 01:38:25 +0200 Subject: [PATCH 56/57] Added many Salt templates. --- src/lay/laySaltTemplates.qrc | 53 ++++++++++ src/lay/salt_templates/drc/doc/readme.html | 45 ++++++++ src/lay/salt_templates/drc/drc/new_drc.lym | 20 ++++ src/lay/salt_templates/drc/grain.xml | 16 +++ src/lay/salt_templates/font/grain.xml | 2 +- src/lay/salt_templates/lib/doc/readme.html | 40 +++++++ src/lay/salt_templates/lib/grain.xml | 4 +- .../salt_templates/lib/libraries/new_lib.gds | Bin 0 -> 2422 bytes .../salt_templates/macro/macros/new_macro.lym | 1 + .../salt_templates/pcell_lib/doc/readme.html | 46 ++++++++ src/lay/salt_templates/pcell_lib/grain.xml | 6 +- .../salt_templates/pcell_lib/macros/pcell.lym | 99 ++++++++++++++++++ src/lay/salt_templates/pymacro/grain.xml | 2 +- .../pymacro/pymacros/new_macro.lym | 5 +- .../salt_templates/python_lib/doc/readme.html | 46 ++++++++ src/lay/salt_templates/python_lib/grain.xml | 16 +++ .../python_lib/pymacros/new_macro.lym | 27 +++++ .../salt_templates/ruby_lib/doc/readme.html | 46 ++++++++ src/lay/salt_templates/ruby_lib/grain.xml | 16 +++ .../ruby_lib/macros/new_macro.lym | 29 +++++ src/lay/salt_templates/tech/doc/readme.html | 50 +++++++++ src/lay/salt_templates/tech/grain.xml | 6 +- src/lay/salt_templates/tech/tech/tech.lyt | 9 ++ 23 files changed, 572 insertions(+), 12 deletions(-) create mode 100644 src/lay/salt_templates/drc/doc/readme.html create mode 100644 src/lay/salt_templates/drc/drc/new_drc.lym create mode 100644 src/lay/salt_templates/drc/grain.xml create mode 100644 src/lay/salt_templates/lib/doc/readme.html create mode 100644 src/lay/salt_templates/lib/libraries/new_lib.gds create mode 100644 src/lay/salt_templates/pcell_lib/doc/readme.html create mode 100644 src/lay/salt_templates/pcell_lib/macros/pcell.lym create mode 100644 src/lay/salt_templates/python_lib/doc/readme.html create mode 100644 src/lay/salt_templates/python_lib/grain.xml create mode 100644 src/lay/salt_templates/python_lib/pymacros/new_macro.lym create mode 100644 src/lay/salt_templates/ruby_lib/doc/readme.html create mode 100644 src/lay/salt_templates/ruby_lib/grain.xml create mode 100644 src/lay/salt_templates/ruby_lib/macros/new_macro.lym create mode 100644 src/lay/salt_templates/tech/doc/readme.html create mode 100644 src/lay/salt_templates/tech/tech/tech.lyt diff --git a/src/lay/laySaltTemplates.qrc b/src/lay/laySaltTemplates.qrc index 72cfaaede..7ff71d276 100644 --- a/src/lay/laySaltTemplates.qrc +++ b/src/lay/laySaltTemplates.qrc @@ -18,6 +18,10 @@ salt_templates/lib/grain.xml + salt_templates/lib/libraries/new_lib.gds + + + salt_templates/lib/doc/readme.html @@ -25,6 +29,10 @@ salt_templates/pcell_lib/grain.xml + salt_templates/pcell_lib/macros/pcell.lym + + + salt_templates/pcell_lib/doc/readme.html @@ -38,6 +46,17 @@ salt_templates/macro/doc/readme.html + + + salt_templates/ruby_lib/grain.xml + + + salt_templates/ruby_lib/macros/new_ruby_lib.lym + + + salt_templates/ruby_lib/doc/readme.html + + salt_templates/pymacro/grain.xml @@ -49,11 +68,45 @@ salt_templates/pymacro/doc/readme.html + + + salt_templates/python_lib/grain.xml + + + salt_templates/python_lib/macros/new_python_lib.lym + + + salt_templates/python_lib/doc/readme.html + + + + + salt_templates/drc/grain.xml + + + salt_templates/drc/drc/new_drc.lym + + + salt_templates/drc/doc/readme.html + + salt_templates/tech/grain.xml + salt_templates/tech/tech/tech.lyt + + + + + + + + + + + salt_templates/tech/doc/readme.html diff --git a/src/lay/salt_templates/drc/doc/readme.html b/src/lay/salt_templates/drc/doc/readme.html new file mode 100644 index 000000000..919634a60 --- /dev/null +++ b/src/lay/salt_templates/drc/doc/readme.html @@ -0,0 +1,45 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
  • Specify dependencies if you plan to use items from other packages
  • +
+ +

+Of course, the most interesting thing is how to add, edit and develop DRC scripts within +your macro package. When the package was initialized, a "drc" folder with a single +DRC script has been created. You will find this folder in the macro editor +in the DRC category under the name you have given the package. The name of the sample DRC script is "new_drc". +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/drc/drc/new_drc.lym b/src/lay/salt_templates/drc/drc/new_drc.lym new file mode 100644 index 000000000..174ff2ffc --- /dev/null +++ b/src/lay/salt_templates/drc/drc/new_drc.lym @@ -0,0 +1,20 @@ + + + The New DRC Script + + + false + false + + false + + + ruby + + # This is the new DRC script created with the sample DRC package + +report("Sample DRC") + +l1 = input(1, 0) +l1.width(1.0.um).output("Width (Layer 1) < 1.0 um") + diff --git a/src/lay/salt_templates/drc/grain.xml b/src/lay/salt_templates/drc/grain.xml new file mode 100644 index 000000000..d04b8791f --- /dev/null +++ b/src/lay/salt_templates/drc/grain.xml @@ -0,0 +1,16 @@ + + + drc + 0.0 + DRC template + This template provides a template for a DRC script + doc/readme.html + + GPLv3 + + + + + + + diff --git a/src/lay/salt_templates/font/grain.xml b/src/lay/salt_templates/font/grain.xml index a0986f035..a4cb1fba0 100644 --- a/src/lay/salt_templates/font/grain.xml +++ b/src/lay/salt_templates/font/grain.xml @@ -11,6 +11,6 @@ - + iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAF/wAABf8ByXatVgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAABApSURBVHic5VtbbBRXmv5O3bu6Lt3tbuxutzEYm0sMcRJ2pUA80rAEMjgrXiaWstpEI7EvSURWyT5EG7RaefOwTxkp0kgjTRQpq2yklUhEtEsUBYiAQDIrAg7IYOzYsgGD8d3d7b5Ud11O7YNdlbKBCOFystIcqVyu6tOnv/+rc/7/O/85BfyFF/Jz/+B7771XH4vFsrIsN/A8r1FKywzDTFar1Yljx47d/eSTT5yfE8+aE/DGG2+kN2/efDibzf5GFMUtoihGWZYFpRSyLKNcLkMURVBKYVlWrVqtjtRqtTMTExN/fOWVV66vNb41I6C7u5vdtm3bH1pbW/8hlUoJsixDEASUSiVomoa+vj4cOHAA586dQ1tbG+7evQtRFJFIJGAYBiqVCp2env78xo0bL/f09CysFU5urRpOJBK/13X91VgsBp7nIQgCXNfF1NQUtm3bhi1btkCWZbzwwguoVCqQJAmO44AQAsdxYFkWE4/HD87Ozn4KYP9a4VwzAkzT/FWxWMTCwgIEQYBpmpAkCbdu3cLMzAwopahUKgAA27aRy+XQ29uLzs5OmKYJ27axsLCAfD7/V2uFEVhDAmKxGNF1Hc3NzZiYmIBlWTBNE52dnTAMA8eOHUNzczN0Xcf58+fx7LPP4qmnnkI+nwfLspibm8Pg4CBEUXTXCiOwhgRUKhWzpaUFp0+fhizLGB8fx7p165BIJKBpGjZs2ACWZVEul9HR0YHx8XGk02koioLr169jcHAQLS0tKJfLtbXCCKwhAZqm8VevXsXBgwdhGAZOnz6Nubk5jI+Pw7ZtUEr9uoQQsCyLSCSClpYW7NixA01NTbh8+TJkWRbWCiOwRlHg8OHDGZZl+7q6uuqmp6dx+vRpRKNRtLW1IZvNolar+SR4xkuShPr6egwODuLy5cvQdR0dHR0YHR11s9nssW+++eb1999/fyJsrKET8Pbbbz+zffv2zwcGBmKapkGSJHR2dqKvrw+Tk5O4desWpqamYBgGDMOAJEkQBAGpVAq7du3Czp07wTAMrl27hpGREaiqCtM00dLSkrtw4cLz77777v+GiTf0IdDU1PRhsViMFQoFNDQ0YN++ffj8889x4cIFMAyDuro6PP7444jFYhAEAY7joFKpYH5+HteuXcPZs2exefNm7NixA6lUCl9//TU2btyIQqEQb2xs/A8AW8LEGzoBlmWtq6+vRzqdRm9vL44fP44nn3wS+/fvh6IoqFQqKJfLKJfLmJubgyzLkCQJbW1taG9vh6ZpOHfuHD788EMQQpBOpxGLxaDrOn744Ye6sPGGTsDVq1e/jUQiXZs3b0YsFsOdO3dw9+5dXLlyBdPT07AsyxdGDMPAdV1YlgXLsgAAiUQCiUQCyWQSjY2NWL9+PQRBwFdffQXDMELt/sAa+ICBgYHTp06d6rx48SLX1tZG1q9fD13XMTs7i3w+j4WFBZTLZd8RAgDHcZAkCdFoFJqmoaGhAdlsFmNjY/j222/R29vrHjp0yNq7d+/XW7ZsCVUVhkpAd3c3+84770wkk0mpVCqpH3/8Mc6cOYO5uTmXUko0TUMqlYKu64hGo+B5HgBgmiYsywLDMCCEYGFhAadOnUJTUxP279+PF198EfX19cVyuVw9cuRIOswZY6gEHD9+/B+ffvrpf08mk1EAKJVKKBQKsCyrcuHCBbm/vx/j4+OYmppCqVSCaZoAAEEQoKoq6uvrkclksH37dmzfvt0VBIF4hAHA7Oxs8eLFi//a1dX1XliYQ/UBlNKt8/PzViKRAMMwUBQF0WjUNE3T3rdvH/bs2QPHcWDbtn+u1X4UeizLQhAECIIAXdcriqJIhBB2qW3Mzs7ajuPsCBNzqASoqtpcV1cXGx8fv5PNZrOEEJMQUhVFURNF8Z76tm0jn89DEATwPA+WZYMfRx3HKXIcJ7muy9+5c+eOruvZRCLRHCbmUAmQJKnBdV3EYrFsqVSacF1Xjkaj+grDfvxxjoOmaQ9sz3Ec1XXdOdM0TY7jspRSiKKYDhNzqASIoph0XReEEBiGIVFKtWKxWCWElBiGMRiGoTzP8wzDqIqiqB4xS6GwappmnlJasizLdRxHdBwnDqAumUwueHkCjuPiYWIOlQCe51VKKRiGAaXUJoRAFEWRZVmRYRiwLOvH/oWFBaNWq5Vs2wbP8wrHcRFKaQOlFK67OAMmhGDpmhJC4LouGIaJhomZCbEtwrJshBCCpcMGFo0InoHFrq8oiqSqalIQhJQgCJFlDS214YNkGMe7x/O89Oabby6rv5oSGgE9PT2bZFkWPKC2bTv3M/5+9x5UAkRQ71rTNL6urm5nWLhDIyCVSu1WFIXxxAwCoL2y0viVT/pBxXVd16sbiURIKpV6KizcofmAaDS6geM4f47vnb1CCMHMzAyGh4dJqVQCz/OIRqPIZrNIpVI/SQTDMG6QrLq6utBCYWgEqKraACwayjAMHGdRrXrAR0ZGcPHiRQIAsVgMLMtieHgYAwMDvmP0nJ/nCL1rAPUcx2HTpk3YuXMn4vF4a1i4QyMgEok0Acu7tXc2DAO9vb3EdV3Isoyuri5XFEV89NFHJJPJoFqtYmZmZhEQxyGRSIBS6pPIcZyZz+eFaDQKx3EQiUSyYeEOjQBFUTKUUrAs65Hg+5eJiQnYtg2GYbB+/XqIogiGYfDYY4+hsbERg4ODPgHxeBydnZ2glPpHIpEofPrpp6loNIolMZQMC3doTlAUxaSnAZae/OIfQjA1NUUC49fv1x0dHW4kEkG5XPbbURTlnrZt22aq1SoURfHEkB4W7tAI4Hle9xzfkmjhPKMLhYJfz5O+QadXLpf962j0Xp1j2za7bds2nxxBECJhaYFQCHjrrbdUURRlTwYvFa5QKGBwcBClUsmve/v2bdLf34+5uTkAWLZCRAiBLMt+3YWFBYyNjUEQBNLR0eEPL13X+fr6+lC0QCgEJJPJp2KxGL/CAXKTk5O4cuUK8TI/AHD9+nVcunSJeEZXKpVlawSKovi9aGJiAuPj43AchwkoTEQiERKPx0PRAqE4wXg8/mRwcXPJGXItLS3Qdd09f/48IYRAVVXs37/fZVkWqqqiVqstG/8AwPM8LMsCpRSlUglLjo/lOG5ZhInH46FEglAIiEajGQ9cQAMI3rq/B1pVVWiaBoZhwDCLnc8wjGVtnThxwtcAruuivb0djuMs610AIMvy/x8CZFluCHZRx3Eoz/McgPt6+KAhQf/A8zzi8Thc1wWlFDMzM5BlGY7j8AzDUACMNy3WNK0pDOyhEMCy7DrgRxFUq9VMhmFEQghKpZJvraqq7krJGyQolUrhmWee8dNln332mTcEQAixCCGiN8QikUhDGNhDcYKSJK1bmqt7PcDykh3FYtGvp6oqgHtDoFe8ELhEIjzluNS2Hehh4DguEQb2UAiQZTkV1ACUUt/tewYSQnwPHyzBIRAUQaIoYu/evf7+IQBOoH2IoqiEoQXCIIAERVAwGwQshjnv/5X5v5VZ4aAIYlkWsVgsWJ0GyYvFYqFogVUT0NPTs0nX9agngpZA2sCi8d6Mjud5rMwMB58+IeS+KtAHyjCOVy9MLbBqAlKp1G5N05igCqSUOsC9EWBlKAsSACz6iAflBSilNBhparUaNmzYsG+1+FdNgKIozd4SlxfbvQxO0EDPAQZL0EFKkgSOWwxK98sUBbNCLMvCMAzU19f/zdGjR1flB1ZFgOu6pKGh4YVyuRycBcJd6vdeCPQcoPd/rVbDxMQEZmZmfCt5nsf09DRyuZzf/gpp7Xo+xrZtWJYFRVFkXdf/zXXdR17iW20PkFVV3WSa5jICPPDBWV5QA9y5cwdffvklGRsb8+sXi0WcOXMG/f399/yI9z1PZXrqURAEpNPpvyOEPPJOslUJoS+++KKltbVVZhgGtVrtnm4eHALBCJBKpbBnzx63VquhWq0Snuf95EdwNhgsrusSSikMwwAhBBzHgWVZpFKpxhMnTvztc8899/mj2LAqAmzbfjWRSJBKpQLHcVCtViEIAiilDPBjHoAQgmQyuSwcaprm7xNa+o6fBgvODr1CKSXFYhGu68KbGDEMA0EQSDwefwvAIxGwqiEQj8c7CSEQBMHf82eaJggh7Pz8vL8BIhaLQZKkh2rTM8xzqIGhxQGLOUOe5/3FVEII4vH4X//6179+pIe5KgJUVW2cm5sDx3F+lywUCjAMQ79y5QrxnFh7e7vrGfegEvTwHMdBFEV4u8wEQUClUtFVVaWe8d6KcqFQgGma0muvvfa7R7FhVQRUKpUTk5OT/hPxwtjg4KA8MTEBRVGwe/duNDU1Ecdx4DhOMNW9zHhvb8CSkoRlWSiXy7Asy1s2Z0RRrHq/5R25XA6SJNkNDQ3/8yg23H/d+iELwzDXt27d+mpDQwPxwFuWRTVNI62trWhvb4eqqp5BxLIsYts2qdVqpFqtEu++J4lN0/RJChLFsix4ngfDMBWGYSSvt9m2jVu3bsFxnK927dr1p0eyYTUEfPDBBwMjIyPXRkdH/S4JwPb2BHvzeu/pm6YJwzBQrVZ9Y71FkJXiJ+gLCCHgeR62bYscx/n3+vv70dzcbPX19f3no9qwaiV48uTJ/yoWi7RYLHqOy/UAegY86Ah68586PHIsy5IYhjEBYGZmBpIkYWRkZGhsbOzoL0ZAuVz+w/fffz88PDwMYDFeBx1a0JDg9crPHnTfu6aUguM4uK5bdRwHQ0NDaGxstEZHR8/19PSYvxgBJ0+eLH/33Xd/siyL5nI5sCxLHuapLq0HPlRdb2hwHAfLsjA0NITW1lZcvXp1IBKJ/PNq8IeSEDl69Oh7Q0NDfUNDQyBLu7oexrCHreeRwLIs5ufnVZ7nXV3XzZs3b77/0ksvrep9orBWhtyzZ8/+C8dxzuKs9UejfmoYBDPJD1PXcRyMjo6SjRs3Vvr7+y8cOnToj6sFvqowGCx9fX3DBw8e7OI4Ljs2NgbDMPz3AILz+KDRruvCcRzwPO/fC9apVCqYmprCzZs3cfv2bZRKJSQSCTiOM37p0qU3n3jiiRurxR0aAQDguu5FVVV/m0wmc4qi2IVCgbtx4wY7Pj7up8YkSfIN9cKkICy+FFIulzE5OYnR0VHcvHkTpVIJgiCgrq6uEovFblYqlem7d+9aAwMD37788su/DwNz6JulDx48+E+apv19Op3enslkhEwmU966deu0ruvK0nsEPKUU8XgcyWTSnZycJMVi0dv/g3g8TjVNK1Sr1dnZ2Vk2n8835HI5OZ/PI5/PFyilAyzL/u7w4cNDYeBdyxcnI4Zh/IZl2a5MJvOrTCbTmslk2EwmU960adNcNBqNVqtVmeM4um7dumKtVivlcjk1l8uty+fzJJ/P01wud6tYLF5xXffPHMedeP3116+GjfNne3f4wIEDmiiKz6uq+nw6nd7d2Ni4IZ1Ok46ODiw9XSOXyw0XCoXvKpXKBcMw/vvIkSMza43rZ3952ivd3d3rbdv+bXd3Nz8/P//V4cOHv/+lsPxFl/8DbC02U+AxXpwAAAAASUVORK5CYII=
diff --git a/src/lay/salt_templates/lib/doc/readme.html b/src/lay/salt_templates/lib/doc/readme.html new file mode 100644 index 000000000..fbc16f403 --- /dev/null +++ b/src/lay/salt_templates/lib/doc/readme.html @@ -0,0 +1,40 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
+ +

+Of course, the most interesting thing is how to add, edit and develop libraries within +your library package. When the package was initialized, a "libraries" folder with a single +sample library has been created. +In the package details you will find the local path to your package and the library +layout file under "Installation". +You can use any versioning system to manage your files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/lib/grain.xml b/src/lay/salt_templates/lib/grain.xml index a071c2676..f39000459 100644 --- a/src/lay/salt_templates/lib/grain.xml +++ b/src/lay/salt_templates/lib/grain.xml @@ -4,13 +4,13 @@ 0.0 Static Library This template provides a static library - + doc/readme.html GPLv3 - + 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 diff --git a/src/lay/salt_templates/lib/libraries/new_lib.gds b/src/lay/salt_templates/lib/libraries/new_lib.gds new file mode 100644 index 0000000000000000000000000000000000000000..0b91bfa39b9fe1c8b75c9aea2ff263d49140e3b8 GIT binary patch literal 2422 zcmd^}-4h3o8+^ z@*VO5Vqs+y|9@^Lcjv{}SS16Oe{XMQv-f{DE3r7&k!s+|J&9yNM3!VVJC=%z_gV+1 zyW5R+y?rP$AC@-imuovOcOUD`hn3r#qkXBCey@+?xGoY+iNrHb0*PlG{-s+9@tX;^ zjGJo__b3vaczuF)F#qcH49`AsPeo2}l~K$GwIna_b3*mEertvOa-Uq^s})1D^)9XN_xo>lq3%7|TcS2k&DDpw`YxX9B@!;89^NEs^VFoi zEPqjHq;F|do`=3AT(h@S8gR`tM}RcoJm=JPQp66`C9j Mbc|lqSe24Nq3*NDE(t z(b5{^SsitdhoO(G`(sRRvqG7Mk@MEK;6o1fEH%Wr@yk_ZXPqGI|ojuAmK}9c& zNo7F6XrW(~jw$mmy2@FYQR3}|`R)m`5o>LOh=TBA-p(jc8Q vCZso7Mc2=Vx{mLC diff --git a/src/lay/salt_templates/pcell_lib/doc/readme.html b/src/lay/salt_templates/pcell_lib/doc/readme.html new file mode 100644 index 000000000..b004d843e --- /dev/null +++ b/src/lay/salt_templates/pcell_lib/doc/readme.html @@ -0,0 +1,46 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
  • Specify dependencies if you plan to use items from other packages
  • +
+ +

+Of course, the most interesting thing is how to add, edit and develop PCell macros within +your PCell package. When the package was initialized, a "macros" folder with a single +sample PCell has been created. You will find this folder in the macro editor +under the name you have given the package. The name of the sample macro is "pcell". +You can add more script code, Ruby and other files there or modify the sample script. +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/pcell_lib/grain.xml b/src/lay/salt_templates/pcell_lib/grain.xml index 20a656273..46fc05777 100644 --- a/src/lay/salt_templates/pcell_lib/grain.xml +++ b/src/lay/salt_templates/pcell_lib/grain.xml @@ -1,16 +1,16 @@ - macro + pcell_lib 0.0 PCell library This template provides a PCell library implemented in Ruby - + doc/readme.html GPLv3 - + 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 diff --git a/src/lay/salt_templates/pcell_lib/macros/pcell.lym b/src/lay/salt_templates/pcell_lib/macros/pcell.lym new file mode 100644 index 000000000..5955fc729 --- /dev/null +++ b/src/lay/salt_templates/pcell_lib/macros/pcell.lym @@ -0,0 +1,99 @@ + + + The New Macro + + + false + true + + false + + + ruby + + # PCell template + +# It is recommended to put PCell code into namespaces. +# TODO: change the module name +module PCellPackageModule + + include RBA + + # Remove any definition of our classes (this helps when + # reexecuting this code after a change has been applied) + # TODO: adjust the names + PCellPackageModule.constants.member?(:PCell) && remove_const(:PCell) + + # The PCell declaration + # Each PCell must provide a declaration. It is recommended to use the PCell name as the class name. + # TODO: change the class name + class PCell < PCellDeclarationHelper + + include RBA + + def initialize + + # Important: initialize the super class + super + + # declare the parameters + # i.e. param(:l, TypeLayer, "Layer", :default => LayerInfo::new(1, 0)) + # param(:s, TypeShape, "", :default => DPoint::new(0, 0)) + + end + + def display_text_impl + # Provide a descriptive text for the cell + "TODO: create description" + end + + def coerce_parameters_impl + # TODO: use x to access parameter x and set_x to modify it's value + end + + def produce_impl + # TODO: produce the cell content + # i.e. cell.shapes(l_layer).insert(Polygon.new(...)) + end + + # optional: + # def can_create_from_shape_impl + # TODO: determine if we have a shape that we can use to derive the + # PCell parameters from and return true in that case + # end + # + # optional: + # def parameters_from_shape_impl + # TODO: change parameters using set_x to reflect the parameter for the + # given shape + # end + # + # optional: + # def transformation_from_shape_impl + # TODO: return a RBA::Trans object for the initial transformation of + # the instance + # end + + end + + # TODO: add more PCell classes .. + + # Register a new library with the PCells + library = Library::new + + # TODO: change the description + library = "My PCell package" + + # register the PCell declarations + # TODO: change the names + library.layout.register_pcell("PCell", PCell::new) + + # TODO: register more PCell declarations ... + + # register our new library with the name "PCellPackage" + # TODO: change the library name + library.register("PCellPackage") + +end + + diff --git a/src/lay/salt_templates/pymacro/grain.xml b/src/lay/salt_templates/pymacro/grain.xml index e780101db..b3f00281f 100644 --- a/src/lay/salt_templates/pymacro/grain.xml +++ b/src/lay/salt_templates/pymacro/grain.xml @@ -1,6 +1,6 @@ - macro + pymacro 0.0 Python Macro This template provides a Python macro. diff --git a/src/lay/salt_templates/pymacro/pymacros/new_macro.lym b/src/lay/salt_templates/pymacro/pymacros/new_macro.lym index 4570bcb72..cf26eacf8 100644 --- a/src/lay/salt_templates/pymacro/pymacros/new_macro.lym +++ b/src/lay/salt_templates/pymacro/pymacros/new_macro.lym @@ -9,14 +9,15 @@ false - ruby + python # This is the new macro created with the sample macro package -RBA::MessageBox::information("Information", "This is the new macro created with the sample macro package", RBA::MessageBox::Ok) +pya.MessageBox.information("Information", "This is the new macro created with the sample macro package", pya.MessageBox.Ok) # In order pull in classes from other packages, just specify these classes # in the dependencies of this package. Provided those packages contain macros # which are marked as "autorun-early", they will be loaded before this package # and their modules and classes will become available. + diff --git a/src/lay/salt_templates/python_lib/doc/readme.html b/src/lay/salt_templates/python_lib/doc/readme.html new file mode 100644 index 000000000..e50eac64e --- /dev/null +++ b/src/lay/salt_templates/python_lib/doc/readme.html @@ -0,0 +1,46 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
  • Specify dependencies if you plan to use items from other packages
  • +
+ +

+Of course, the most interesting thing is how to add, edit and develop classes within +your python library package. When the package was initialized, a "pymacros" folder with a single +sample macro has been created. You will find this folder in the macro editor +under the name you have given the package. The name of the sample macro is "new_macro". +You can add more macros, Python and other files there or modify the sample macro. +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/python_lib/grain.xml b/src/lay/salt_templates/python_lib/grain.xml new file mode 100644 index 000000000..a4c917c3f --- /dev/null +++ b/src/lay/salt_templates/python_lib/grain.xml @@ -0,0 +1,16 @@ + + + python_lib + 0.0 + Python Library + This template provides a Python library. + doc/readme.html + + GPLv3 + + + + + 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 + + diff --git a/src/lay/salt_templates/python_lib/pymacros/new_macro.lym b/src/lay/salt_templates/python_lib/pymacros/new_macro.lym new file mode 100644 index 000000000..14ff7b3bb --- /dev/null +++ b/src/lay/salt_templates/python_lib/pymacros/new_macro.lym @@ -0,0 +1,27 @@ + + + The New Macro + + + false + true + + false + + + python + + # This is the new macro created with the sample Python library package + +class NewSampleLibraryClass(object): + + def __init__(self): + # TODO: add your code here + pass + +# In order pull in classes from other packages, just specify these classes +# in the dependencies of this package. Provided those packages contain macros +# which are marked as "autorun-early", they will be loaded before this package +# and their modules and classes will become available. + + diff --git a/src/lay/salt_templates/ruby_lib/doc/readme.html b/src/lay/salt_templates/ruby_lib/doc/readme.html new file mode 100644 index 000000000..60b4a1503 --- /dev/null +++ b/src/lay/salt_templates/ruby_lib/doc/readme.html @@ -0,0 +1,46 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
  • Specify dependencies if you plan to use items from other packages
  • +
+ +

+Of course, the most interesting thing is how to add, edit and develop classes within +your python library package. When the package was initialized, a "macros" folder with a single +sample macro has been created. You will find this folder in the macro editor +under the name you have given the package. The name of the sample macro is "new_macro". +You can add more macros, Ruby and other files there or modify the sample macro. +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/ruby_lib/grain.xml b/src/lay/salt_templates/ruby_lib/grain.xml new file mode 100644 index 000000000..b2276c062 --- /dev/null +++ b/src/lay/salt_templates/ruby_lib/grain.xml @@ -0,0 +1,16 @@ + + + ruby_lib + 0.0 + Ruby Library + This template provides a Ruby library. + doc/readme.html + + GPLv3 + + + + + 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 + + diff --git a/src/lay/salt_templates/ruby_lib/macros/new_macro.lym b/src/lay/salt_templates/ruby_lib/macros/new_macro.lym new file mode 100644 index 000000000..2c7aae58b --- /dev/null +++ b/src/lay/salt_templates/ruby_lib/macros/new_macro.lym @@ -0,0 +1,29 @@ + + + The New Macro + + + false + true + + false + + + ruby + + # This is the new macro created with the sample Ruby library package + +class NewSampleLibraryClass + + def initialize + # TODO: add your code here + end + +end + +# In order pull in classes from other packages, just specify these classes +# in the dependencies of this package. Provided those packages contain macros +# which are marked as "autorun-early", they will be loaded before this package +# and their modules and classes will become available. + + diff --git a/src/lay/salt_templates/tech/doc/readme.html b/src/lay/salt_templates/tech/doc/readme.html new file mode 100644 index 000000000..30adefd4c --- /dev/null +++ b/src/lay/salt_templates/tech/doc/readme.html @@ -0,0 +1,50 @@ + + + +

Your new Package

+ +

+Your new package is there! You can edit the package properties in the package manager. +Use the edit button in the upper right corner of the package details panel to +open the package editor. +

+ +

+Here is what you should do: +

+ +
    +
  • Enter your author details
  • +
  • Choose a license model
  • +
  • Provide an icon and optionally a screenshot image
  • +
  • Complete the technology definition
  • +
  • Specify dependencies if you plan to use items from other packages
  • +
+ +

+Of course, the most interesting thing is how to edit the technology definition. +When the package was initialized, a folder hierarchy consisting of a tech +folder containing a technology definition file and empty "libraries", "macros", +"pymacros" and "drc" folders was created. You can use these empty folders +to put your own files there. The technology definition can be edited in the +technology manager where you can find the technology as "new_tech" under the +name you had given the package. Initially, the technology definition is +without any particular definitions. +

+ +

+In the package details you will find the local path to your package +data under "Installation". You can use any versioning system to manage your +files there. +

+ +

+Once you have finished your package, don't forget to specify the package version +so users of you package will be informed of updates. Finally you can publish the +package files to a place of your choice and submit the download URL to the +Salt Mine server. +

+ + + + diff --git a/src/lay/salt_templates/tech/grain.xml b/src/lay/salt_templates/tech/grain.xml index 2def15e72..06aa9fad4 100644 --- a/src/lay/salt_templates/tech/grain.xml +++ b/src/lay/salt_templates/tech/grain.xml @@ -1,16 +1,16 @@ - macro + tech 0.0 Technology This template provides a technology - + doc/readme.html GPLv3 - + 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 diff --git a/src/lay/salt_templates/tech/tech/tech.lyt b/src/lay/salt_templates/tech/tech/tech.lyt new file mode 100644 index 000000000..ac78bda13 --- /dev/null +++ b/src/lay/salt_templates/tech/tech/tech.lyt @@ -0,0 +1,9 @@ + + + new_tech + + 0.001 + + + true + From 88e753e86faa6ec4e1822d96bb2a5b0c805c513f Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 29 Apr 2017 01:59:19 +0200 Subject: [PATCH 57/57] Fixed some typos and bugs in the Salt templates --- src/lay/laySaltTemplates.qrc | 6 +++--- src/lay/salt_templates/drc/drc/new_drc.lym | 6 ++++-- src/lay/salt_templates/macro/macros/new_macro.lym | 2 +- src/lay/salt_templates/pcell_lib/macros/pcell.lym | 2 +- src/lay/salt_templates/pymacro/pymacros/new_macro.lym | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/lay/laySaltTemplates.qrc b/src/lay/laySaltTemplates.qrc index 7ff71d276..1f3918944 100644 --- a/src/lay/laySaltTemplates.qrc +++ b/src/lay/laySaltTemplates.qrc @@ -51,7 +51,7 @@ salt_templates/ruby_lib/grain.xml - salt_templates/ruby_lib/macros/new_ruby_lib.lym + salt_templates/ruby_lib/macros/new_macro.lym salt_templates/ruby_lib/doc/readme.html @@ -72,8 +72,8 @@ salt_templates/python_lib/grain.xml - - salt_templates/python_lib/macros/new_python_lib.lym + + salt_templates/python_lib/pymacros/new_macro.lym salt_templates/python_lib/doc/readme.html diff --git a/src/lay/salt_templates/drc/drc/new_drc.lym b/src/lay/salt_templates/drc/drc/new_drc.lym index 174ff2ffc..29e11c907 100644 --- a/src/lay/salt_templates/drc/drc/new_drc.lym +++ b/src/lay/salt_templates/drc/drc/new_drc.lym @@ -1,6 +1,7 @@ The New DRC Script + drc false @@ -9,12 +10,13 @@ false - ruby - + dsl + drc-dsl-xml # This is the new DRC script created with the sample DRC package report("Sample DRC") l1 = input(1, 0) l1.width(1.0.um).output("Width (Layer 1) < 1.0 um") + diff --git a/src/lay/salt_templates/macro/macros/new_macro.lym b/src/lay/salt_templates/macro/macros/new_macro.lym index c39e3a5ec..2aec7a1c6 100644 --- a/src/lay/salt_templates/macro/macros/new_macro.lym +++ b/src/lay/salt_templates/macro/macros/new_macro.lym @@ -13,7 +13,7 @@ # This is the new macro created with the sample macro package -RBA::MessageBox::information("Information", "This is the new macro created with the sample macro package", RBA::MessageBox::Ok) +RBA::MessageBox::info("Information", "This is the new macro created with the sample macro package", RBA::MessageBox::Ok) # In order pull in classes from other packages, just specify these classes # in the dependencies of this package. Provided those packages contain macros diff --git a/src/lay/salt_templates/pcell_lib/macros/pcell.lym b/src/lay/salt_templates/pcell_lib/macros/pcell.lym index 5955fc729..8965f06c3 100644 --- a/src/lay/salt_templates/pcell_lib/macros/pcell.lym +++ b/src/lay/salt_templates/pcell_lib/macros/pcell.lym @@ -82,7 +82,7 @@ module PCellPackageModule library = Library::new # TODO: change the description - library = "My PCell package" + library.description = "My PCell package" # register the PCell declarations # TODO: change the names diff --git a/src/lay/salt_templates/pymacro/pymacros/new_macro.lym b/src/lay/salt_templates/pymacro/pymacros/new_macro.lym index cf26eacf8..6afdbbd78 100644 --- a/src/lay/salt_templates/pymacro/pymacros/new_macro.lym +++ b/src/lay/salt_templates/pymacro/pymacros/new_macro.lym @@ -13,7 +13,7 @@ # This is the new macro created with the sample macro package -pya.MessageBox.information("Information", "This is the new macro created with the sample macro package", pya.MessageBox.Ok) +pya.MessageBox.info("Information", "This is the new macro created with the sample macro package", pya.MessageBox.Ok) # In order pull in classes from other packages, just specify these classes # in the dependencies of this package. Provided those packages contain macros