From a7038282cedee16147c8134ca092096815647125 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 20 Apr 2017 00:09:53 +0200 Subject: [PATCH] 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;