From 5b422440a1f32fd91d960319b7e6cc0461266ee4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 15 Apr 2017 01:03:24 +0200 Subject: [PATCH] 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 ();