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.
This commit is contained in:
Matthias Koefferlein 2017-04-20 00:09:53 +02:00
parent 9c2c3301cf
commit a7038282ce
12 changed files with 479 additions and 63 deletions

View File

@ -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 \

View File

@ -1055,48 +1055,6 @@ Application::run ()
}
// scan for libraries
for (std::vector <std::string>::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<db::Library> 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 ();

View File

@ -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 <QDir>
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<std::string, std::string> > & /*options*/) const
{
// .. nothing yet ..
}
void
LibraryController::get_menu_entries (std::vector<lay::MenuEntry> & /*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<std::string, std::pair<std::string, QDateTime> > new_lib_files;
std::vector<std::string> 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 <std::string>::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<std::string, std::pair<std::string, QDateTime> >::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<db::Library> 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<std::string> new_names;
for (std::map<std::string, std::pair<std::string, QDateTime> >::const_iterator lf = new_lib_files.begin (); lf != new_lib_files.end (); ++lf) {
new_names.insert (lf->second.first);
}
for (std::map<std::string, std::pair<std::string, QDateTime> >::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<bool, db::lib_id_type> 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<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
LibraryController *sc = dynamic_cast <LibraryController *> (cls.operator-> ());
if (sc) {
return sc;
}
}
return 0;
}
// The singleton instance of the library controller
static tl::RegisteredClass<lay::PluginDeclaration> salt_controller_decl (new lay::LibraryController (), 150, "LibraryController");
}

View File

@ -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 <vector>
#include <string>
#include <QObject>
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<std::string, std::string> > &options) const;
/**
* @brief Reimplementation of the PluginDeclaration interface
*/
void get_menu_entries (std::vector<lay::MenuEntry> &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<LibraryController> dm_sync_files;
std::map<std::string, std::pair<std::string, QDateTime> > m_lib_files;
void sync_files ();
};
}
#endif

View File

@ -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<lay::MacroCollection *>::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);

View File

@ -220,12 +220,14 @@ private:
std::vector<ExternalPathDescriptor> m_external_paths;
tl::FileSystemWatcher *m_file_watcher;
tl::DeferredMethod<MacroController> dm_do_update_menu_with_macros;
tl::DeferredMethod<MacroController> dm_do_sync_with_external_sources;
tl::DeferredMethod<MacroController> dm_sync_file_watcher;
tl::DeferredMethod<MacroController> 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<std::string> &groups, const lay::Technology *tech, std::vector<std::pair<std::string, std::string> > *key_bindings);
void do_update_menu_with_macros ();
void do_sync_with_external_sources ();
void sync_file_watcher ();
void sync_files ();
};

View File

@ -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

View File

@ -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
*/

View File

@ -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 <SaltModel *> (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 <SaltModel *> (salt_mine_view->model ());
tl_assert (mine_model != 0);
mine_model->begin_update ();
}
void
SaltManagerDialog::salt_mine_changed ()
{

View File

@ -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
*/

View File

@ -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<SaltGrain *> (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

View File

@ -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<std::string, std::pair<Severity, std::string> > m_messages;
std::map<std::string, int> m_display_order;
std::vector<SaltGrain *> m_ordered_grains;
bool m_in_update;
bool is_marked (const std::string &name) const;
bool is_enabled (const std::string &name) const;