mirror of https://github.com/KLayout/klayout.git
711 lines
25 KiB
C++
711 lines
25 KiB
C++
|
|
/*
|
|
|
|
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 "layMacroController.h"
|
|
#include "layTechnologyController.h"
|
|
#include "laySaltController.h"
|
|
#include "layMacroEditorDialog.h"
|
|
#include "layMacroInterpreter.h"
|
|
#include "layMainWindow.h"
|
|
#include "layMainConfigPages.h"
|
|
#include "layConfig.h"
|
|
#include "layApplication.h"
|
|
|
|
#include <QDir>
|
|
#include <QUrl>
|
|
#include <QMessageBox>
|
|
|
|
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)
|
|
{
|
|
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
|
|
MacroController::load ()
|
|
{
|
|
// 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<std::string, std::string> ("macros", tl::to_string (QObject::tr ("Ruby"))));
|
|
m_macro_categories.push_back (std::pair<std::string, std::string> ("pymacros", tl::to_string (QObject::tr ("Python"))));
|
|
m_macro_categories.push_back (std::pair<std::string, std::string> ("drc", tl::to_string (QObject::tr ("DRC"))));
|
|
|
|
// Scan for macros and set interpreter path
|
|
for (std::vector <InternalPathDescriptor>::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 (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);
|
|
}
|
|
}
|
|
|
|
for (tl::Registrar<gsi::Interpreter>::iterator i = gsi::interpreters.begin (); i != gsi::interpreters.end (); ++i) {
|
|
i->add_package_location (p->path);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroController::initialized (lay::PluginRoot *root)
|
|
{
|
|
mp_mw = dynamic_cast <lay::MainWindow *> (root);
|
|
if (mp_mw) {
|
|
mp_macro_editor = new lay::MacroEditorDialog (mp_mw, &lay::MacroCollection::root ());
|
|
mp_macro_editor->setModal (false);
|
|
}
|
|
|
|
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 (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 (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)
|
|
macro_collection_changed ();
|
|
}
|
|
|
|
void
|
|
MacroController::uninitialize (lay::PluginRoot * /*root*/)
|
|
{
|
|
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 (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 (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;
|
|
mp_macro_editor = 0;
|
|
mp_mw = 0;
|
|
}
|
|
|
|
bool
|
|
MacroController::configure (const std::string &key, const std::string &value)
|
|
{
|
|
if (key == cfg_key_bindings && mp_mw) {
|
|
|
|
// Update the shortcuts of the menu item if they have been edited in the configuration editor
|
|
std::vector<std::pair<std::string, std::string> > key_bindings = unpack_key_binding (value);
|
|
for (std::vector<std::pair<std::string, std::string> >::const_iterator kb = key_bindings.begin (); kb != key_bindings.end (); ++kb) {
|
|
if (mp_mw->menu ()->is_valid (kb->first)) {
|
|
lay::Action a = mp_mw->menu ()->action (kb->first);
|
|
if (m_action_to_macro.find (a.qaction ()) != m_action_to_macro.end ()) {
|
|
m_action_to_macro [a.qaction ()]->set_shortcut (kb->second);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
MacroController::config_finalize()
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
bool
|
|
MacroController::can_exit (lay::PluginRoot * /*root*/) const
|
|
{
|
|
if (mp_macro_editor) {
|
|
return mp_macro_editor->can_exit ();
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool
|
|
MacroController::accepts_drop (const std::string &path_or_url) const
|
|
{
|
|
QUrl url (tl::to_qstring (path_or_url));
|
|
QFileInfo file_info (url.path ());
|
|
QString suffix = file_info.suffix ().toLower ();
|
|
|
|
if (suffix == QString::fromUtf8 ("rb") ||
|
|
suffix == QString::fromUtf8 ("py") ||
|
|
suffix == QString::fromUtf8 ("lym")) {
|
|
return true;
|
|
}
|
|
|
|
// check the suffixes in the DSL interpreter declarations
|
|
for (tl::Registrar<lay::MacroInterpreter>::iterator cls = tl::Registrar<lay::MacroInterpreter>::begin (); cls != tl::Registrar<lay::MacroInterpreter>::end (); ++cls) {
|
|
if (suffix == tl::to_qstring (cls->suffix ())) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
MacroController::drop_url (const std::string &path_or_url)
|
|
{
|
|
// Normalize the URL to become either a normal path or a URL
|
|
std::string path = path_or_url;
|
|
|
|
QUrl url (tl::to_qstring (path_or_url));
|
|
QString file_name = QFileInfo (url.path ()).fileName ();
|
|
|
|
if (url.scheme () == QString::fromUtf8 ("file")) {
|
|
path = tl::to_string (url.toLocalFile ());
|
|
}
|
|
|
|
// load and run macro
|
|
std::auto_ptr<lay::Macro> macro (new lay::Macro ());
|
|
macro->load_from (path);
|
|
macro->set_file_path (path);
|
|
|
|
if (macro->is_autorun () || macro->show_in_menu ()) {
|
|
|
|
// install macro permanently
|
|
if (QMessageBox::question (mp_mw,
|
|
QObject::tr ("Install Macro"),
|
|
QObject::tr ("Install macro '%1' permanently?\n\nPress 'Yes' to install the macro in the application settings folder permanently.").arg (file_name),
|
|
QMessageBox::Yes | QMessageBox::No,
|
|
QMessageBox::No) == QMessageBox::Yes) {
|
|
|
|
// Use the application data folder
|
|
QDir folder (tl::to_qstring (lay::Application::instance ()->appdata_path ()));
|
|
|
|
std::string cat = "macros";
|
|
if (! macro->category ().empty ()) {
|
|
cat = macro->category ();
|
|
}
|
|
|
|
if (! folder.cd (tl::to_qstring (cat))) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Folder '%s' does not exists in installation path '%s' - cannot install")).c_str (), cat, lay::Application::instance ()->appdata_path ());
|
|
}
|
|
|
|
QFileInfo target (folder, file_name);
|
|
|
|
if (! target.exists () || QMessageBox::question (mp_mw,
|
|
QObject::tr ("Overwrite Macro"),
|
|
QObject::tr ("Overwrite existing macro?"),
|
|
QMessageBox::Yes | QMessageBox::No,
|
|
QMessageBox::No) == QMessageBox::Yes) {
|
|
|
|
QFile target_file (target.filePath ());
|
|
if (target.exists () && ! target_file.remove ()) {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Unable to remove file '%1'").arg (target.filePath ())));
|
|
}
|
|
|
|
macro->set_file_path (tl::to_string (target.filePath ()));
|
|
|
|
// run the macro now - if it fails, it is not installed, but the file path is already set to
|
|
// the target path.
|
|
if (macro->is_autorun ()) {
|
|
macro->run ();
|
|
}
|
|
|
|
macro->save ();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (macro->is_autorun ()) {
|
|
// If it is not installed, run it now ..
|
|
macro->run ();
|
|
} else if (macro->show_in_menu ()) {
|
|
// .. or add as temporary macro so it is shown in the menu.
|
|
add_temp_macro (macro.release ());
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
macro->run ();
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroController::show_editor (const std::string &cat, bool force_add)
|
|
{
|
|
if (mp_macro_editor) {
|
|
mp_macro_editor->show (cat, force_add);
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroController::enable_implicit_macros (bool enable)
|
|
{
|
|
m_no_implicit_macros = !enable;
|
|
}
|
|
|
|
void
|
|
MacroController::sync_implicit_macros (bool ask_before_autorun)
|
|
{
|
|
if (m_no_implicit_macros) {
|
|
return;
|
|
}
|
|
|
|
std::vector<ExternalPathDescriptor> external_paths;
|
|
std::vector<std::string> package_locations;
|
|
|
|
// Add additional places where the technologies define some macros
|
|
|
|
std::map<std::string, std::vector<std::string> > tech_names_by_path;
|
|
std::map<std::string, std::vector<std::string> > grain_names_by_path;
|
|
std::set<std::string> 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 ()) {
|
|
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 ());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (std::map<std::string, std::vector<std::string> >::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->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, ","))));
|
|
}
|
|
|
|
std::map<std::string, std::vector<std::string> >::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 ()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 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;
|
|
|
|
package_locations.push_back (g->path ());
|
|
|
|
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 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, g->is_readonly ()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// delete macro collections which are no longer required or update description
|
|
|
|
std::vector<lay::MacroCollection *> folders_to_delete;
|
|
|
|
// determine the paths that will be in use
|
|
std::map<std::string, const ExternalPathDescriptor *> new_folders_by_path;
|
|
for (std::vector<ExternalPathDescriptor>::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<std::string, const ExternalPathDescriptor *> prev_folders_by_path;
|
|
for (std::vector<ExternalPathDescriptor>::const_iterator p = m_external_paths.begin (); p != m_external_paths.end (); ++p) {
|
|
prev_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) {
|
|
if (m->second->virtual_mode () == lay::MacroCollection::TechFolder ||
|
|
m->second->virtual_mode () == lay::MacroCollection::SaltFolder) {
|
|
std::map<std::string, const ExternalPathDescriptor *>::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 {
|
|
m->second->set_description (u->second->description);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (std::vector<lay::MacroCollection *>::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);
|
|
}
|
|
|
|
// refresh the package locations by first removing the package locations and then rebuilding
|
|
// TODO: maybe that is a performance bottleneck, but right now, remove_package_location doesn't do a lot.
|
|
|
|
for (std::vector<std::string>::const_iterator p = m_package_locations.begin (); p != m_package_locations.end (); ++p) {
|
|
for (tl::Registrar<gsi::Interpreter>::iterator i = gsi::interpreters.begin (); i != gsi::interpreters.end (); ++i) {
|
|
i->remove_package_location (*p);
|
|
}
|
|
}
|
|
|
|
m_package_locations = package_locations;
|
|
|
|
for (std::vector<std::string>::const_iterator p = m_package_locations.begin (); p != m_package_locations.end (); ++p) {
|
|
for (tl::Registrar<gsi::Interpreter>::iterator i = gsi::interpreters.begin (); i != gsi::interpreters.end (); ++i) {
|
|
i->add_package_location (*p);
|
|
}
|
|
}
|
|
|
|
// store new paths
|
|
m_external_paths = external_paths;
|
|
|
|
// add new folders
|
|
|
|
std::vector<lay::MacroCollection *> new_folders;
|
|
|
|
for (std::vector<ExternalPathDescriptor>::const_iterator p = m_external_paths.begin (); p != m_external_paths.end (); ++p) {
|
|
|
|
if (prev_folders_by_path.find (p->path) != prev_folders_by_path.end ()) {
|
|
continue;
|
|
}
|
|
|
|
if (tl::verbosity () >= 20) {
|
|
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.
|
|
|
|
// TODO: is it wise to make this writeable?
|
|
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);
|
|
}
|
|
|
|
}
|
|
|
|
{
|
|
|
|
// This prevents the message dialog below to issue deferred methods
|
|
tl::NoDeferredMethods silent;
|
|
|
|
bool has_autorun = false;
|
|
for (std::vector<lay::MacroCollection *>::const_iterator m = new_folders.begin (); m != new_folders.end () && ! has_autorun; ++m) {
|
|
has_autorun = (*m)->has_autorun ();
|
|
}
|
|
|
|
if (has_autorun) {
|
|
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 ();
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroController::add_path (const std::string &path, const std::string &description, const std::string &category, bool readonly)
|
|
{
|
|
m_internal_paths.push_back (InternalPathDescriptor (path, description, category, readonly));
|
|
}
|
|
|
|
void
|
|
MacroController::add_temp_macro (lay::Macro *m)
|
|
{
|
|
m_temp_macros.add_unspecific (m);
|
|
}
|
|
|
|
void
|
|
MacroController::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)
|
|
{
|
|
for (lay::MacroCollection::child_iterator c = collection.begin_children (); c != collection.end_children (); ++c) {
|
|
|
|
// check whether the macro collection is associated with the selected technology (if there is one)
|
|
bool consider = false;
|
|
if (! tech || c->second->virtual_mode () != lay::MacroCollection::TechFolder) {
|
|
consider = true;
|
|
} else {
|
|
const std::vector<std::pair<std::string, std::string> > &mc = macro_categories ();
|
|
for (std::vector<std::pair<std::string, std::string> >::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))));
|
|
}
|
|
}
|
|
|
|
if (consider) {
|
|
add_macro_items_to_menu (*c->second, n, groups, 0 /*don't check 2nd level and below*/, key_bindings);
|
|
}
|
|
|
|
}
|
|
|
|
for (lay::MacroCollection::iterator c = collection.begin (); c != collection.end (); ++c) {
|
|
|
|
std::string sc = tl::trim (c->second->shortcut ());
|
|
|
|
if (c->second->show_in_menu ()) {
|
|
|
|
std::string mp = tl::trim (c->second->menu_path ());
|
|
if (mp.empty ()) {
|
|
mp = "macros_menu.end";
|
|
}
|
|
|
|
std::string gn = tl::trim (c->second->group_name ());
|
|
if (! gn.empty () && groups.find (gn) == groups.end ()) {
|
|
groups.insert (gn);
|
|
lay::Action as;
|
|
as.set_separator (true);
|
|
m_macro_actions.push_back (as);
|
|
mp_mw->menu ()->insert_item (mp, "macro_in_menu_" + tl::to_string (n++), as);
|
|
}
|
|
|
|
lay::Action a;
|
|
if (c->second->description ().empty ()) {
|
|
a.set_title (c->second->path ());
|
|
} else {
|
|
a.set_title (c->second->description ());
|
|
}
|
|
a.set_shortcut (sc);
|
|
m_macro_actions.push_back (a);
|
|
mp_mw->menu ()->insert_item (mp, "macro_in_menu_" + tl::to_string (n++), a);
|
|
|
|
m_action_to_macro.insert (std::make_pair (a.qaction (), c->second));
|
|
|
|
MacroSignalAdaptor *adaptor = new MacroSignalAdaptor (a.qaction (), c->second);
|
|
QObject::connect (a.qaction (), SIGNAL (triggered ()), adaptor, SLOT (run ()));
|
|
|
|
// store the key bindings in the array
|
|
if (!sc.empty () && key_bindings) {
|
|
key_bindings->push_back (std::make_pair (mp, sc));
|
|
}
|
|
|
|
} else if (! sc.empty ()) {
|
|
|
|
// Create actions for shortcut-only actions too and add them to the main window
|
|
// to register their shortcut.
|
|
|
|
lay::Action a;
|
|
if (c->second->description ().empty ()) {
|
|
a.set_title (c->second->path ());
|
|
} else {
|
|
a.set_title (c->second->description ());
|
|
}
|
|
a.set_shortcut (sc);
|
|
m_macro_actions.push_back (a);
|
|
|
|
mp_mw->addAction (a.qaction ());
|
|
MacroSignalAdaptor *adaptor = new MacroSignalAdaptor (a.qaction (), c->second);
|
|
QObject::connect (a.qaction (), SIGNAL (triggered ()), adaptor, SLOT (run ()));
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroController::sync_with_external_sources ()
|
|
{
|
|
dm_do_sync_with_external_sources ();
|
|
}
|
|
|
|
void
|
|
MacroController::do_sync_with_external_sources ()
|
|
{
|
|
try {
|
|
sync_implicit_macros (true);
|
|
} catch (tl::Exception &ex) {
|
|
tl::error << ex.msg ();
|
|
}
|
|
}
|
|
|
|
void
|
|
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
|
|
MacroController::do_update_menu_with_macros ()
|
|
{
|
|
if (!mp_mw) {
|
|
return;
|
|
}
|
|
|
|
const lay::Technology *tech = 0;
|
|
if (lay::TechnologyController::instance ()) {
|
|
tech = lay::TechnologyController::instance ()->active_technology ();
|
|
}
|
|
|
|
std::vector<std::pair<std::string, std::string> > key_bindings = unpack_key_binding (mp_mw->config_get (cfg_key_bindings));
|
|
std::sort (key_bindings.begin (), key_bindings.end ());
|
|
|
|
std::vector<std::pair<std::string, std::string> > new_key_bindings;
|
|
for (std::vector<std::pair<std::string, std::string> >::const_iterator kb = key_bindings.begin (); kb != key_bindings.end (); ++kb) {
|
|
if (mp_mw->menu ()->is_valid (kb->first)) {
|
|
lay::Action a = mp_mw->menu ()->action (kb->first);
|
|
if (m_action_to_macro.find (a.qaction ()) == m_action_to_macro.end ()) {
|
|
new_key_bindings.push_back (*kb);
|
|
}
|
|
}
|
|
}
|
|
|
|
// delete all existing items
|
|
for (std::vector<lay::Action>::iterator a = m_macro_actions.begin (); a != m_macro_actions.end (); ++a) {
|
|
mp_mw->menu ()->delete_items (*a);
|
|
}
|
|
m_macro_actions.clear ();
|
|
m_action_to_macro.clear ();
|
|
|
|
int n = 1;
|
|
std::set<std::string> groups;
|
|
add_macro_items_to_menu (m_temp_macros, n, groups, tech, 0);
|
|
add_macro_items_to_menu (lay::MacroCollection::root (), n, groups, tech, &new_key_bindings);
|
|
|
|
// update the key bindings if required
|
|
std::sort (new_key_bindings.begin (), new_key_bindings.end ());
|
|
if (new_key_bindings != key_bindings) {
|
|
mp_mw->config_set (cfg_key_bindings, pack_key_binding (new_key_bindings));
|
|
}
|
|
}
|
|
|
|
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 ()
|
|
{
|
|
for (tl::Registrar<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
|
|
MacroController *mc = dynamic_cast <MacroController *> (cls.operator-> ());
|
|
if (mc) {
|
|
return mc;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// The singleton instance of the macro controller
|
|
static tl::RegisteredClass<lay::PluginDeclaration> macro_controller_decl (new lay::MacroController (), 120, "MacroController");
|
|
|
|
}
|
|
|