WIP: introducing MacroController

The MacroController is the central facility for managing
macros and their views. The plugin framework has been
extended to support such a design.

In addition, some small bugs have been fixed related to
macro interpreters (specifically the DRC).
This commit is contained in:
Matthias Koefferlein 2017-04-05 00:20:32 +02:00
parent 90693f9f67
commit abb5424ece
21 changed files with 1032 additions and 542 deletions

View File

@ -90,8 +90,17 @@ module DRC
# Constructor
def initialize
# Make the DSL use ruby syntax highlighting
self.syntax_scheme = "ruby"
self.suffix = "drc"
self.debugger_scheme = RBA::MacroInterpreter::RubyDebugger
self.storage_scheme = RBA::MacroInterpreter::PlainTextWithHashAnnotationsFormat
self.description = "DRC (Text)"
# Registers the new interpreter
register("drc-dsl")
end
# Implements the execute method
@ -99,27 +108,6 @@ module DRC
DRC::execute_drc(macro)
end
# Make the DSL use ruby syntax highlighting
def syntax_scheme
"ruby"
end
def suffix
"drc"
end
def debugger_scheme
return RBA::MacroInterpreter::RubyDebugger
end
def storage_scheme
return RBA::MacroInterpreter::PlainTextWithHashAnnotationsFormat
end
def description
return "DRC (Text)"
end
end
# Register the new interpreters

View File

@ -9,138 +9,140 @@ DEFINES += MAKE_LAY_LIBRARY
TEMPLATE = lib
HEADERS = \
layApplication.h \
layClipDialog.h \
layCrashMessage.h \
layFillDialog.h \
layGenericSyntaxHighlighter.h \
layGSIHelpProvider.h \
layHelpDialog.h \
layHelpProvider.h \
layHelpSource.h \
layLayoutStatisticsForm.h \
layLogViewerDialog.h \
layMacro.h \
layMacroEditorDialog.h \
layMacroEditorPage.h \
layMacroEditorSetupDialog.h \
layMacroEditorTree.h \
layMacroInterpreter.h \
layMacroPropertiesDialog.h \
layMacroVariableView.h \
layMainConfigPages.h \
layMainWindow.h \
layNavigator.h \
layProgress.h \
layProgressWidget.h \
layResourceHelpProvider.h \
layRuntimeErrorForm.h \
laySearchReplaceConfigPage.h \
laySearchReplaceDialog.h \
laySearchReplacePropertiesWidgets.h \
laySelectCellViewForm.h \
laySession.h \
laySettingsForm.h \
layTechnologySelector.h \
layTechSetupDialog.h \
layTextProgress.h \
layVersion.h \
layApplication.h \
layClipDialog.h \
layCrashMessage.h \
layFillDialog.h \
layGenericSyntaxHighlighter.h \
layGSIHelpProvider.h \
layHelpDialog.h \
layHelpProvider.h \
layHelpSource.h \
layLayoutStatisticsForm.h \
layLogViewerDialog.h \
layMacro.h \
layMacroEditorDialog.h \
layMacroEditorPage.h \
layMacroEditorSetupDialog.h \
layMacroEditorTree.h \
layMacroInterpreter.h \
layMacroPropertiesDialog.h \
layMacroVariableView.h \
layMainConfigPages.h \
layMainWindow.h \
layNavigator.h \
layProgress.h \
layProgressWidget.h \
layResourceHelpProvider.h \
layRuntimeErrorForm.h \
laySearchReplaceConfigPage.h \
laySearchReplaceDialog.h \
laySearchReplacePropertiesWidgets.h \
laySelectCellViewForm.h \
laySession.h \
laySettingsForm.h \
layTechnologySelector.h \
layTechSetupDialog.h \
layTextProgress.h \
layVersion.h \
layCommon.h \
layConfig.h
layConfig.h \
layMacroController.h
FORMS = \
ClipDialog.ui \
CrashMessage.ui \
Console.ui \
DeleteModeDialog.ui \
FillDialog.ui \
HelpAboutDialog.ui \
KeyBindingsConfigPage.ui \
LayoutStatistics.ui \
LogViewerDialog.ui \
MacroEditorDialog.ui \
MacroEditorSetupDialog.ui \
MacroPropertiesDialog.ui \
MacroTemplateSelectionDialog.ui \
MainConfigPage.ui \
MainConfigPage2.ui \
MainConfigPage3.ui \
MainConfigPage4.ui \
MainConfigPage5.ui \
MainConfigPage6.ui \
ReplacePropertiesBox.ui \
ReplacePropertiesInstance.ui \
ReplacePropertiesPath.ui \
ReplacePropertiesShape.ui \
ReplacePropertiesText.ui \
RuntimeErrorForm.ui \
SearchPropertiesBox.ui \
SearchPropertiesInstance.ui \
SearchPropertiesPath.ui \
SearchPropertiesShape.ui \
SearchPropertiesText.ui \
SearchReplaceConfigPage.ui \
SearchReplaceDialog.ui \
SelectCellViewForm.ui \
SettingsForm.ui \
TechBaseEditorPage.ui \
TechComponentSetupDialog.ui \
TechLayerMappingEditorPage.ui \
TechMacrosPage.ui \
TechSetupDialog.ui \
XORToolDialog.ui \
ClipDialog.ui \
CrashMessage.ui \
Console.ui \
DeleteModeDialog.ui \
FillDialog.ui \
HelpAboutDialog.ui \
KeyBindingsConfigPage.ui \
LayoutStatistics.ui \
LogViewerDialog.ui \
MacroEditorDialog.ui \
MacroEditorSetupDialog.ui \
MacroPropertiesDialog.ui \
MacroTemplateSelectionDialog.ui \
MainConfigPage.ui \
MainConfigPage2.ui \
MainConfigPage3.ui \
MainConfigPage4.ui \
MainConfigPage5.ui \
MainConfigPage6.ui \
ReplacePropertiesBox.ui \
ReplacePropertiesInstance.ui \
ReplacePropertiesPath.ui \
ReplacePropertiesShape.ui \
ReplacePropertiesText.ui \
RuntimeErrorForm.ui \
SearchPropertiesBox.ui \
SearchPropertiesInstance.ui \
SearchPropertiesPath.ui \
SearchPropertiesShape.ui \
SearchPropertiesText.ui \
SearchReplaceConfigPage.ui \
SearchReplaceDialog.ui \
SelectCellViewForm.ui \
SettingsForm.ui \
TechBaseEditorPage.ui \
TechComponentSetupDialog.ui \
TechLayerMappingEditorPage.ui \
TechMacrosPage.ui \
TechSetupDialog.ui \
XORToolDialog.ui \
TechLoadOptionsEditorPage.ui \
TechSaveOptionsEditorPage.ui \
MainConfigPage7.ui
SOURCES = \
gsiDeclLayApplication.cc \
gsiDeclLayHelpDialog.cc \
gsiDeclLayMacro.cc \
gsiDeclLayMainWindow.cc \
layApplication.cc \
layClipDialog.cc \
layCrashMessage.cc \
layFillDialog.cc \
layGenericSyntaxHighlighter.cc \
layGSIHelpProvider.cc \
layHelpDialog.cc \
layHelpProvider.cc \
layHelpSource.cc \
layLayoutStatisticsForm.cc \
layLogViewerDialog.cc \
layMacro.cc \
layMacroEditorDialog.cc \
layMacroEditorPage.cc \
layMacroEditorSetupDialog.cc \
layMacroEditorTree.cc \
layMacroInterpreter.cc \
layMacroPropertiesDialog.cc \
layMacroVariableView.cc \
layMainConfigPages.cc \
layMainWindow.cc \
layNavigator.cc \
layProgress.cc \
layProgressWidget.cc \
layResourceHelpProvider.cc \
layRuntimeErrorForm.cc \
laySearchReplaceConfigPage.cc \
laySearchReplaceDialog.cc \
laySearchReplacePlugin.cc \
laySearchReplacePropertiesWidgets.cc \
laySelectCellViewForm.cc \
laySession.cc \
laySettingsForm.cc \
layTechnologySelector.cc \
layTechSetupDialog.cc \
layTextProgress.cc \
layVersion.cc \
gsiDeclLayApplication.cc \
gsiDeclLayHelpDialog.cc \
gsiDeclLayMacro.cc \
gsiDeclLayMainWindow.cc \
layApplication.cc \
layClipDialog.cc \
layCrashMessage.cc \
layFillDialog.cc \
layGenericSyntaxHighlighter.cc \
layGSIHelpProvider.cc \
layHelpDialog.cc \
layHelpProvider.cc \
layHelpSource.cc \
layLayoutStatisticsForm.cc \
layLogViewerDialog.cc \
layMacro.cc \
layMacroEditorDialog.cc \
layMacroEditorPage.cc \
layMacroEditorSetupDialog.cc \
layMacroEditorTree.cc \
layMacroInterpreter.cc \
layMacroPropertiesDialog.cc \
layMacroVariableView.cc \
layMainConfigPages.cc \
layMainWindow.cc \
layNavigator.cc \
layProgress.cc \
layProgressWidget.cc \
layResourceHelpProvider.cc \
layRuntimeErrorForm.cc \
laySearchReplaceConfigPage.cc \
laySearchReplaceDialog.cc \
laySearchReplacePlugin.cc \
laySearchReplacePropertiesWidgets.cc \
laySelectCellViewForm.cc \
laySession.cc \
laySettingsForm.cc \
layTechnologySelector.cc \
layTechSetupDialog.cc \
layTextProgress.cc \
layVersion.cc \
layMacroController.cc
RESOURCES = layBuildInMacros.qrc \
layHelpResources.qrc \
layLayoutStatistics.qrc \
layMacroTemplates.qrc \
layResources.qrc \
layHelpResources.qrc \
layLayoutStatistics.qrc \
layMacroTemplates.qrc \
layResources.qrc \
INCLUDEPATH += ../tl ../gsi ../db ../rdb ../laybasic ../ant ../img ../edt
DEPENDPATH += ../tl ../gsi ../db ../rdb ../laybasic ../ant ../img ../edt

View File

@ -33,6 +33,7 @@
#include "layProgress.h"
#include "layTextProgress.h"
#include "layBackgroundAwareTreeStyle.h"
#include "layMacroController.h"
#include "gtf.h"
#include "gsiDecl.h"
#include "gsiInterpreter.h"
@ -500,6 +501,8 @@ Application::Application (int &argc, char **argv, bool non_ui_mode)
mp_qapp = this;
mp_qapp_gui = (non_ui_mode ? 0 : this);
mp_dm_scheduler.reset (new tl::DeferredMethodScheduler ());
// install a special style proxy to overcome the issue of black-on-black tree expanders
if (mp_qapp_gui) {
mp_qapp_gui->setStyle (new lay::BackgroundAwareTreeStyle (0));
@ -1099,11 +1102,10 @@ Application::Application (int &argc, char **argv, bool non_ui_mode)
mp_plugin_root = new lay::PluginRoot ();
}
// initialize the plugins (this should be the last action in the constructor since the
// main window should be functional now.
// initialize the plugins for the first time
for (tl::Registrar<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
lay::PluginDeclaration *pd = const_cast<lay::PluginDeclaration *> (&*cls);
pd->initialize (mp_mw);
pd->initialize (mp_plugin_root);
}
// establish the configuration
@ -1199,6 +1201,12 @@ Application::finish ()
void
Application::shutdown ()
{
// uninitialize the plugins
for (tl::Registrar<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
lay::PluginDeclaration *pd = const_cast<lay::PluginDeclaration *> (&*cls);
pd->uninitialize (mp_plugin_root);
}
if (mp_mw) {
delete mp_mw;
mp_mw = 0;
@ -1357,9 +1365,10 @@ Application::run ()
macro->set_file_path (*m);
if (macro->show_in_menu ()) {
// menu-based macros are just registered so they are shown in the menu
if (mp_mw) {
tl::log << "Register macro '" << *m << "'";
mp_mw->add_temp_macro (macro.release ());
lay::MacroController *mc = lay::MacroController::instance ();
if (mc) {
tl::log << "Registering macro '" << *m << "'";
mc->add_temp_macro (macro.release ());
}
} else {
// other macros given with -rm are run
@ -1479,15 +1488,25 @@ Application::run ()
player.replay (m_gtf_replay_rate, m_gtf_replay_stop);
}
// update the menus with the macro menu bindings as late as possible (now we
// can be sure that the menus are created propertly)
mp_mw->update_menu_with_macros ();
// Give the plugins a change to do some last-minute initialisation and checks
for (tl::Registrar<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
lay::PluginDeclaration *pd = const_cast<lay::PluginDeclaration *> (&*cls);
pd->initialized (mp_mw);
}
if (! m_no_gui && m_gtf_replay.empty () && ! mp_recorder) {
// Show initial tip window if required
mp_mw->about_to_exec ();
}
} else if (mp_plugin_root) {
// Give the plugins a change to do some last-minute initialisation and checks
for (tl::Registrar<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
lay::PluginDeclaration *pd = const_cast<lay::PluginDeclaration *> (&*cls);
pd->initialized (mp_plugin_root);
}
}
if (! m_run_macro.empty ()) {
@ -1586,7 +1605,7 @@ Application::process_events (QEventLoop::ProcessEventsFlags flags, bool silent)
if (mp_mw) {
if (silent) {
tl::DeferredMethodScheduler::instance ()->enable (false);
mp_dm_scheduler->enable (false);
}
#if QT_VERSION < 0x050000
@ -1598,7 +1617,7 @@ Application::process_events (QEventLoop::ProcessEventsFlags flags, bool silent)
mp_mw->enter_busy_mode (false);
if (silent) {
tl::DeferredMethodScheduler::instance ()->enable (true);
mp_dm_scheduler->enable (true);
}
}

View File

@ -33,6 +33,7 @@
#include <set>
#include <string>
#include <memory>
namespace gsi
{
@ -44,6 +45,11 @@ namespace gtf
class Recorder;
}
namespace tl
{
class DeferredMethodScheduler;
}
namespace lay
{
@ -339,6 +345,7 @@ private:
bool m_enable_undo;
QCoreApplication *mp_qapp;
QApplication *mp_qapp_gui;
std::auto_ptr<tl::DeferredMethodScheduler> mp_dm_scheduler;
// HINT: the ruby interpreter must be destroyed before MainWindow
// in order to maintain a valid MainWindow reference for ruby scripts and Ruby's GC all the time.
gsi::Interpreter *mp_ruby_interpreter;

View File

@ -0,0 +1,395 @@
/*
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 "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),
dm_do_update_menu_with_macros (this, &MacroController::do_update_menu_with_macros)
{
connect (&m_temp_macros, SIGNAL (menu_needs_update ()), this, SLOT (update_menu_with_macros ()));
connect (&m_temp_macros, SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ()));
}
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);
}
connect (&lay::MacroCollection::root (), SIGNAL (menu_needs_update ()), this, SLOT (update_menu_with_macros ()));
connect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ()));
// update the menus with the macro menu bindings as late as possible (now we
// can be sure that the menus are created propertly)
do_update_menu_with_macros ();
}
void
MacroController::uninitialize (lay::PluginRoot * /*root*/)
{
disconnect (&lay::MacroCollection::root (), SIGNAL (menu_needs_update ()), this, SLOT (update_menu_with_macros ()));
disconnect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ()));
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 ();
// refresh macro editor to show new macro plus to install the menus
refresh ();
}
} 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::refresh ()
{
if (mp_macro_editor) {
mp_macro_editor->refresh ();
}
}
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 = lay::Application::instance ()->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::update_menu_with_macros ()
{
// empty action to macro table now we know it's invalid
m_action_to_macro.clear ();
dm_do_update_menu_with_macros ();
}
void
MacroController::do_update_menu_with_macros ()
{
if (!mp_mw) {
return;
}
// TODO: implement this by asking the technology manager for the active technology
const lay::Technology *tech = 0;
if (mp_mw->current_view () && mp_mw->current_view ()->active_cellview_index () >= 0 && mp_mw->current_view ()->active_cellview_index () <= int (mp_mw->current_view ()->cellviews ())) {
std::string active_tech = mp_mw->current_view ()->active_cellview ()->tech_name ();
tech = lay::Technologies::instance ()->technology_by_name (active_tech);
}
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));
}
}
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");
}

View File

@ -0,0 +1,157 @@
/*
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_layMacroController
#define HDR_layMacroController
#include "layCommon.h"
#include "layPlugin.h"
#include "layMacro.h"
#include "tlObject.h"
#include "tlDeferredExecution.h"
#include <string>
#include <map>
#include <set>
#include <QObject>
namespace lay
{
class MacroEditorDialog;
class MainWindow;
class Technology;
/**
* @brief A controller for the macro environment
*
* This object is a singleton that acts as a controller
* for the macro environment. The controller is responsible
* to managing the macro folders, autorunning of macros
* and other things.
*
* It interacts with the MacroEditorDialog which basically
* is the view for the macros.
*
* By making the controller a PluginDeclaration it will receive
* initialization and configuration calls.
*/
class MacroController
: public lay::PluginDeclaration, public tl::Object
{
Q_OBJECT
public:
/**
* @brief Default constructor
*/
MacroController ();
/**
* @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
*/
virtual bool configure (const std::string &key, const std::string &value);
/**
* @brief Reimplementation of the PluginDeclaration interface
*/
virtual void config_finalize();
/**
* @brief Reimplementation of the PluginDeclaration interface
*/
virtual bool can_exit (lay::PluginRoot *root) const;
/**
* @brief Gets a value indicating whether the plugin will accept a dropped file with the given URL or path
*/
virtual bool accepts_drop (const std::string &path_or_url) const;
/**
* @brief Gets called when a file or URL is dropped on the plugin
*/
virtual void drop_url (const std::string &path_or_url);
/**
* @brief Shows the macro editor
*
* Depending on the category, a different tip dialog will be shown.
* If "force_add" is true, a new macro will be created, otherwise only
* if none exists yet.
*/
void show_editor (const std::string &cat = std::string (), bool force_add = false);
/**
* @brief Reloads all macros from the paths registered
*/
void refresh ();
/**
* @brief Adds a temporary macro
*
* Temporary macros are such present on the command line or
* dragged into the main window without installing.
* They need to be present so they participate in the
* menu building. Hence they are stored temporarily.
* The MainWindow object will become owner of the macro object.
*/
void add_temp_macro (lay::Macro *m);
/**
* @brief Gets the singleton instance for this object
*/
static MacroController *instance ();
public slots:
/**
* @brief Update the menu with macros bound to a menu
*/
void update_menu_with_macros ();
private:
lay::MacroEditorDialog *mp_macro_editor;
lay::MainWindow *mp_mw;
tl::DeferredMethod<MacroController> dm_do_update_menu_with_macros;
std::vector<lay::Action> m_macro_actions;
std::map<QAction *, lay::Macro *> m_action_to_macro;
lay::MacroCollection m_temp_macros;
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 ();
};
}
#endif

View File

@ -37,6 +37,7 @@
#include "layQtTools.h"
#include "layConfig.h"
#include "layWidgets.h"
#include "layProgress.h"
#include "tlString.h"
#include "tlClassRegistry.h"
#include "tlExceptions.h"
@ -253,6 +254,10 @@ MacroEditorDialog::MacroEditorDialog (QWidget * /*parent*/, lay::MacroCollection
m_add_edit_trace_enabled (true),
dm_refresh_file_watcher (this, &MacroEditorDialog::do_refresh_file_watcher)
{
// Makes this dialog receive events while progress bars are on - this way we can set breakpoints
// during execution of a macro even if anything lengthy is running.
lay::mark_widget_alive (this, true);
Ui::MacroEditorDialog::setupUi (this);
connect (mp_root, SIGNAL (macro_changed (Macro *)), this, SLOT (macro_changed (Macro *)));

View File

@ -148,6 +148,12 @@ public:
void select_category (const std::string &cat);
public slots:
/**
* @brief Reloads all macros from the paths registered
*/
void refresh ();
private slots:
void help_button_clicked ();
void add_button_clicked ();
void close_button_clicked ();
@ -166,7 +172,6 @@ public slots:
void properties_button_clicked ();
void setup_button_clicked ();
void breakpoint_button_clicked ();
void refresh ();
void add_location ();
void remove_location ();
void clear_breakpoints_button_clicked ();

View File

@ -474,6 +474,9 @@ KeyBindingsConfigPage::apply (const std::vector<std::pair<std::string, std::stri
}
mp_ui->binding_le->setText (QString ());
mp_ui->binding_le->setEnabled (false);
m_enable_event = true;
}

View File

@ -50,6 +50,7 @@
#include "tlAssert.h"
#include "tlDeferredExecution.h"
#include "tlStream.h"
#include "tlExceptions.h"
#include "dbMemStatistics.h"
#include "dbManager.h"
#include "dbStream.h"
@ -81,7 +82,6 @@
#include "layMainConfigPages.h"
#include "layAbstractMenu.h"
#include "layQtTools.h"
#include "tlExceptions.h"
#include "laySaveLayoutOptionsDialog.h"
#include "layLoadLayoutOptionsDialog.h"
#include "layLogViewerDialog.h"
@ -93,6 +93,7 @@
#include "laySelectCellViewForm.h"
#include "layLayoutPropertiesForm.h"
#include "layLayoutStatisticsForm.h"
#include "layMacroController.h"
#include "ui_HelpAboutDialog.h"
#include "gsi.h"
#include "gtf.h"
@ -410,7 +411,6 @@ MainWindow::MainWindow (QApplication *app, const char *name)
m_open_mode (0),
m_disable_tab_selected (false),
m_exited (false),
dm_do_update_menu_with_macros (this, &MainWindow::do_update_menu_with_macros),
dm_do_update_menu (this, &MainWindow::do_update_menu),
m_grid_micron (0.001),
m_default_grids_updated (true),
@ -419,7 +419,6 @@ MainWindow::MainWindow (QApplication *app, const char *name)
m_synchronized_views (false),
m_synchronous (false),
m_busy (false),
m_work_in_progress (false),
mp_app (app)
{
setObjectName (QString::fromUtf8 (name));
@ -437,16 +436,6 @@ MainWindow::MainWindow (QApplication *app, const char *name)
lay::register_help_handler (this, SLOT (show_help (const QString &)));
connect (&lay::MacroCollection::root (), SIGNAL (menu_needs_update ()), this, SLOT (update_menu_with_macros ()));
connect (&lay::MacroCollection::root (), SIGNAL (macro_collection_changed (MacroCollection *)), this, SLOT (update_menu_with_macros ()));
if (lay::Application::instance ()) {
mp_macro_editor = new lay::MacroEditorDialog (this, &lay::MacroCollection::root ());
mp_macro_editor->setModal (false);
} else {
mp_macro_editor = 0;
}
mp_assistant = new lay::HelpDialog (this);
mp_pr = new lay::ProgressReporter ();
@ -679,13 +668,6 @@ MainWindow::~MainWindow ()
{
lay::register_help_handler (0, 0);
// uninitialize the plugins (this should be the first action in the constructor since the
// main window should be functional still.
for (tl::Registrar<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
lay::PluginDeclaration *pd = const_cast<lay::PluginDeclaration *> (&*cls);
pd->uninitialize (this);
}
// since the configuration actions unregister themselves, we need to do this before the main
// window is gone:
m_ca_collection.clear ();
@ -712,9 +694,6 @@ MainWindow::~MainWindow ()
delete mp_log_viewer_dialog;
mp_log_viewer_dialog = 0;
delete mp_macro_editor;
mp_macro_editor = 0;
delete mp_assistant;
mp_assistant = 0;
}
@ -1183,12 +1162,6 @@ MainWindow::close_all ()
void
MainWindow::about_to_exec ()
{
// Give the plugins a change to do some last-minute initialisation and checks
for (tl::Registrar<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
lay::PluginDeclaration *pd = const_cast<lay::PluginDeclaration *> (&*cls);
pd->initialized (this);
}
bool f;
// TODO: later, each view may get it's own editable flag
@ -1654,18 +1627,10 @@ void
MainWindow::apply_key_bindings ()
{
for (std::vector<std::pair<std::string, std::string> >::const_iterator kb = m_key_bindings.begin (); kb != m_key_bindings.end (); ++kb) {
if (menu ()->is_valid (kb->first)) {
lay::Action a = menu ()->action (kb->first);
a.set_shortcut (kb->second);
if (m_action_to_macro.find (a.qaction ()) != m_action_to_macro.end ()) {
m_action_to_macro [a.qaction ()]->set_shortcut (kb->second);
}
}
}
}
@ -1793,8 +1758,11 @@ MainWindow::can_close ()
}
if (mp_macro_editor && ! mp_macro_editor->can_exit ()) {
return false;
for (tl::Registrar<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
lay::PluginDeclaration *pd = const_cast<lay::PluginDeclaration *> (&*cls);
if (! pd->can_exit (this)) {
return false;
}
}
std::string df_list;
@ -1901,6 +1869,8 @@ MainWindow::cm_view_log ()
void
MainWindow::cm_print ()
{
// TODO: move to lay::LayoutView
BEGIN_PROTECTED
// Late-initialize the printer to save time on startup
@ -2295,116 +2265,6 @@ MainWindow::cm_redo ()
END_PROTECTED
}
void
MainWindow::add_macro_items_to_menu (lay::MacroCollection &collection, int &n, std::set<std::string> &groups, const lay::Technology *tech)
{
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 = lay::Application::instance ()->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*/);
}
}
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);
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);
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 ()));
} 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);
addAction (a.qaction ());
MacroSignalAdaptor *adaptor = new MacroSignalAdaptor (a.qaction (), c->second);
QObject::connect (a.qaction (), SIGNAL (triggered ()), adaptor, SLOT (run ()));
}
}
}
void
MainWindow::update_menu_with_macros ()
{
// empty action to macro table now we know it's invalid
m_action_to_macro.clear ();
dm_do_update_menu_with_macros ();
}
void
MainWindow::do_update_menu_with_macros ()
{
const lay::Technology *tech = 0;
if (current_view () && current_view ()->active_cellview_index () >= 0 && current_view ()->active_cellview_index () <= int (current_view ()->cellviews ())) {
std::string active_tech = current_view ()->active_cellview ()->tech_name ();
tech = lay::Technologies::instance ()->technology_by_name (active_tech);
}
// delete all existing items
for (std::vector<lay::Action>::iterator a = m_macro_actions.begin (); a != m_macro_actions.end (); ++a) {
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);
add_macro_items_to_menu (lay::MacroCollection::root (), n, groups, tech);
}
void
MainWindow::bookmark_menu_show ()
{
@ -3475,6 +3335,8 @@ MainWindow::cm_adjust_origin ()
void
MainWindow::cm_new_cell ()
{
// TODO: move this function to lay::LayoutView
BEGIN_PROTECTED
lay::LayoutView *curr = current_view ();
@ -4634,68 +4496,15 @@ MainWindow::show_progress_bar (bool show)
} else {
if (m_work_in_progress != show) {
m_work_in_progress = show;
if (show) {
// to avoid recursions of any kind, disallow any user interaction except
// cancelling the operation
mp_app->installEventFilter (this);
QApplication::setOverrideCursor (QCursor (Qt::WaitCursor));
} else {
mp_app->removeEventFilter (this);
QApplication::restoreOverrideCursor ();
}
// HINT: enabling the scheduler is accumulative - make sure that this method is only called when necessary
tl::DeferredMethodScheduler::instance ()->enable (! show);
mp_main_stack_widget->setCurrentIndex (show ? 1 : 0);
if (show) {
clear_current_pos ();
}
mp_main_stack_widget->setCurrentIndex (show ? 1 : 0);
if (show) {
clear_current_pos ();
}
return true;
}
}
bool
MainWindow::eventFilter (QObject *obj, QEvent *event)
{
// do not handle events that are not targeted towards widgets
if (! dynamic_cast <QWidget *> (obj)) {
return false;
}
// do not handle events if a modal widget is active (i.e. a message box)
if (QApplication::activeModalWidget () && QApplication::activeModalWidget () != this) {
return false;
}
if (dynamic_cast <QInputEvent *> (event)) {
QObject *o = obj;
while (o) {
// If the watched object is a child of the progress widget or the macro editor, pass the event on to this.
// Including the macro editor keeps it alive while progress events are processed.
if (o == mp_progress_widget || o == mp_macro_editor) {
return false;
}
o = o->parent ();
}
// eat the event
return true;
} else {
return false;
}
}
void
MainWindow::cm_technologies ()
{
@ -4716,41 +4525,42 @@ MainWindow::cm_technologies ()
}
// because the macro-tech association might have changed, do this:
update_menu_with_macros ();
// TODO: let the macro controller monitor the technologies.
lay::MacroController *mc = lay::MacroController::instance ();
if (mc) {
mc->update_menu_with_macros ();
}
}
}
void
MainWindow::add_temp_macro (lay::Macro *m)
{
m_temp_macros.add_unspecific (m);
update_menu_with_macros ();
}
void
MainWindow::show_macro_editor (const std::string &cat, bool add)
{
if (mp_macro_editor) {
mp_macro_editor->show (cat, add);
lay::MacroController *mc = lay::MacroController::instance ();
if (mc) {
mc->show_editor (cat, add);
}
}
void
MainWindow::cm_edit_drc_scripts ()
{
// TODO: implement this as generic menu provided by the Interpreter
show_macro_editor ("drc", false);
}
void
MainWindow::cm_new_drc_script ()
{
// TODO: implement this as generic menu provided by the Interpreter
show_macro_editor ("drc", true);
}
void
MainWindow::cm_macro_editor ()
{
// TODO: implement this as generic menu provided by the plugin declaration
show_macro_editor ();
}
@ -5394,8 +5204,10 @@ MainWindow::dropEvent(QDropEvent *event)
QList<QUrl> urls = event->mimeData ()->urls ();
for (QList<QUrl>::const_iterator url = urls.begin (); url != urls.end (); ++url) {
QUrl eff_url (*url);
QString path;
if (url->scheme () == QString::fromUtf8 ("file")) {
if (eff_url.scheme () == QString::fromUtf8 ("file")) {
path = url->toLocalFile ();
#if defined(__APPLE__)
@ -5452,101 +5264,38 @@ MainWindow::dropEvent(QDropEvent *event)
CFRelease( relCFURL );
CFRelease( relCFStringRef );
}
eff_url = QUrl::fromLocalFile (path);
#endif
} else if (url->scheme () == QString::fromUtf8 ("http") || url->scheme () == QString::fromUtf8 ("https")) {
path = url->toString ();
} else if (eff_url.scheme () == QString::fromUtf8 ("http") || eff_url.scheme () == QString::fromUtf8 ("https")) {
path = eff_url.toString ();
} else {
// other schemes are not supported currently.
continue;
}
#if defined(__APPLE__)
QFileInfo file_info (path); // Use the one resolved above
QString suffix = file_info.suffix ().toLower ();
#else
QFileInfo file_info (url->path ()); // Original
QString suffix = file_info.suffix ().toLower ();
#endif
if (suffix == QString::fromUtf8 ("rb") ||
suffix == QString::fromUtf8 ("rbm") ||
suffix == QString::fromUtf8 ("lym") ||
suffix == QString::fromUtf8 ("lydrc") ||
suffix == QString::fromUtf8 ("drc")) {
// Let the plugins decide if they accept the drop
// load and run macro
std::auto_ptr<lay::Macro> macro (new lay::Macro ());
macro->load_from (tl::to_string (path));
macro->set_file_path (tl::to_string (path));
if (macro->is_autorun () || macro->show_in_menu ()) {
// install ruby module permanently
if (QMessageBox::question (this,
QObject::tr ("Install Macro"),
tl::to_qstring (tl::sprintf (tl::to_string (QObject::tr ("Install macro '%s' permanently?\n\nPress 'Yes' to install the macro in the application settings folder permanently.")).c_str (), tl::to_string (file_info.fileName ()).c_str ())),
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_info.fileName ());
if (! target.exists () || QMessageBox::question (this,
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 '%s'")).c_str (), tl::to_string (target.filePath ()).c_str ());
}
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 ();
// refresh macro editor to show new macro plus to install the menus
if (mp_macro_editor) {
mp_macro_editor->refresh ();
}
}
} 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 ();
for (tl::Registrar<lay::PluginDeclaration>::iterator cls = tl::Registrar<lay::PluginDeclaration>::begin (); cls != tl::Registrar<lay::PluginDeclaration>::end (); ++cls) {
lay::PluginDeclaration *pd = const_cast<lay::PluginDeclaration *> (&*cls);
if (pd->accepts_drop (tl::to_string (eff_url.toString ()))) {
pd->drop_url (tl::to_string (eff_url.toString ()));
return;
}
}
} else if (suffix == QString::fromUtf8 ("lyp")) {
if (current_view () && current_view ()->accepts_drop (tl::to_string (eff_url.toString ()))) {
current_view ()->drop_url (tl::to_string (eff_url.toString ()));
return;
}
// Now try the built-in ones
QFileInfo file_info (eff_url.path ());
QString suffix = file_info.suffix ().toLower ();
if (suffix == QString::fromUtf8 ("lyp")) {
load_layer_properties (tl::to_string (path), false /*current view only*/, false /*don't add a default*/);

View File

@ -520,17 +520,6 @@ public:
*/
void show_macro_editor (const std::string &cat = std::string (), bool add = false);
/**
* @brief Adds a temporary macro
*
* Temporary macros are such present on the command line or
* dragged into the main window without installing.
* They need to be present so they participate in the
* menu building. Hence they are stored temporarily.
* The MainWindow object will become owner of the macro object.
*/
void add_temp_macro (lay::Macro *m);
/**
* @brief Reimplementation of the plugin interface: handle a generic menu request
*/
@ -828,11 +817,6 @@ public slots:
* Intended as a connection target for QLabel linkVisisted signals.
*/
void show_help (const QString &url);
/**
* @brief Update the menu with macros bound to a menu
*/
void update_menu_with_macros ();
/**
* @brief visibility of one of the dock widgets changed
@ -852,7 +836,6 @@ protected slots:
protected:
void update_content ();
void do_update_menu ();
void do_update_menu_with_macros ();
private:
TextProgressDelegate m_text_progress;
@ -895,7 +878,6 @@ private:
QLabel *mp_tech_status_label;
bool m_disable_tab_selected;
bool m_exited;
tl::DeferredMethod<MainWindow> dm_do_update_menu_with_macros;
tl::DeferredMethod<MainWindow> dm_do_update_menu;
QTimer m_message_timer;
QTimer m_file_changed_timer;
@ -914,16 +896,11 @@ private:
bool m_synchronized_views;
bool m_synchronous;
bool m_busy;
bool m_work_in_progress;
QApplication *mp_app;
lay::MacroEditorDialog *mp_macro_editor;
lay::HelpDialog *mp_assistant;
std::string m_current_session;
std::string m_message;
std::auto_ptr<QPrinter> mp_printer;
std::vector<lay::Action> m_macro_actions;
std::map<QAction *, lay::Macro *> m_action_to_macro;
lay::MacroCollection m_temp_macros;
std::vector<QString> m_changed_files;
std::map<std::string, lay::Action> m_actions_for_slot;
@ -937,7 +914,6 @@ private:
void init_menu ();
bool eventFilter (QObject *watched, QEvent *event);
void closeEvent (QCloseEvent *event);
void resizeEvent (QResizeEvent *event);
@ -963,8 +939,6 @@ private:
virtual void plugin_removed (lay::PluginDeclaration *cls);
void libraries_changed ();
void add_macro_items_to_menu (lay::MacroCollection &collection, int &n, std::set<std::string> &groups, const lay::Technology *tech);
void apply_key_bindings ();
};

View File

@ -23,20 +23,40 @@
#include <QMutex>
#include <QApplication>
#include <QInputEvent>
#include <stdlib.h>
#include <math.h>
#include "layProgress.h"
#include "layMainWindow.h"
#include "layApplication.h"
#include "layProgressWidget.h"
#include "tlProgress.h"
#include "tlDeferredExecution.h"
namespace lay
{
// --------------------------------------------------------------------
static const char *s_alive_prop_name = "klayout_progressAlive";
void mark_widget_alive (QWidget *w, bool alive)
{
if (alive) {
w->setProperty (s_alive_prop_name, QVariant (true));
} else {
w->setProperty (s_alive_prop_name, QVariant ());
}
}
static bool is_marked_alive (QObject *obj)
{
return obj->property (s_alive_prop_name).isValid ();
}
// --------------------------------------------------------------------
ProgressReporter::ProgressReporter ()
: m_start_time (), mp_pb (0), m_pw_visible (false)
{
@ -55,11 +75,11 @@ ProgressReporter::set_progress_bar (lay::ProgressBar *pb)
return;
}
if (mp_pb) {
mp_pb->show_progress_bar (m_pw_visible);
set_visible (m_pw_visible);
}
mp_pb = pb;
if (mp_pb) {
mp_pb->show_progress_bar (m_pw_visible);
set_visible (m_pw_visible);
}
}
@ -75,10 +95,7 @@ ProgressReporter::register_object (tl::Progress *progress)
// make dialog visible after some time has passed
if (! m_pw_visible && (tl::Clock::current () - m_start_time).seconds () > 1.0) {
if (mp_pb) {
mp_pb->show_progress_bar (true);
}
m_pw_visible = true;
set_visible (true);
}
update_and_yield ();
@ -95,10 +112,9 @@ ProgressReporter::unregister_object (tl::Progress *progress)
// close or refresh window
if (mp_objects.empty ()) {
if (mp_pb) {
mp_pb->show_progress_bar (false);
if (m_pw_visible) {
set_visible (false);
}
m_pw_visible = false;
m_start_time = tl::Clock ();
}
@ -116,10 +132,7 @@ ProgressReporter::trigger (tl::Progress *progress)
if (! mp_objects.empty () && mp_objects.front () == progress) {
// make dialog visible after some time has passed
if (! m_pw_visible && (tl::Clock::current () - m_start_time).seconds () > 1.0) {
if (mp_pb) {
mp_pb->show_progress_bar (true);
}
m_pw_visible = true;
set_visible (true);
}
update_and_yield ();
}
@ -130,10 +143,7 @@ ProgressReporter::yield (tl::Progress * /*progress*/)
{
// make dialog visible after some time has passed
if (! m_pw_visible && (tl::Clock::current () - m_start_time).seconds () > 1.0) {
if (mp_pb) {
mp_pb->show_progress_bar (true);
}
m_pw_visible = true;
set_visible (true);
update_and_yield ();
} else if (m_pw_visible) {
// process events if necessary
@ -166,8 +176,67 @@ ProgressReporter::update_and_yield ()
void
ProgressReporter::process_events ()
{
if (m_pw_visible && lay::MainWindow::instance () && lay::Application::instance ()) {
lay::Application::instance ()->process_events ();
if (m_pw_visible && lay::MainWindow::instance () && QApplication::instance ()) {
QApplication::instance ()->processEvents (QEventLoop::AllEvents);
}
}
void
ProgressReporter::set_visible (bool vis)
{
if (mp_pb) {
mp_pb->show_progress_bar (vis);
}
if (vis != m_pw_visible) {
// prevent deferred method execution inside progress events - this might interfere with the
// actual operation
tl::DeferredMethodScheduler::enable (!vis);
}
m_pw_visible = vis;
if (QApplication::instance()) {
if (vis) {
// to avoid recursions of any kind, disallow any user interaction except
// cancelling the operation
QApplication::instance ()->installEventFilter (this);
} else {
QApplication::instance ()->removeEventFilter (this);
}
}
}
bool
ProgressReporter::eventFilter (QObject *obj, QEvent *event)
{
// do not handle events that are not targeted towards widgets
if (! dynamic_cast <QWidget *> (obj)) {
return false;
}
// do not handle events if a modal widget is active (i.e. a message box)
if (QApplication::activeModalWidget () && ! dynamic_cast<lay::MainWindow *> (QApplication::activeModalWidget ())) {
return false;
}
if (dynamic_cast <QInputEvent *> (event)) {
QObject *o = obj;
while (o) {
// If the watched object is a child of the progress widget or the macro editor, pass the event on to this.
// Including the macro editor keeps it alive while progress events are processed.
if (dynamic_cast<lay::ProgressWidget *> (o) || is_marked_alive (o)) {
return false;
}
o = o->parent ();
}
// eat the event
return true;
} else {
return false;
}
}

View File

@ -54,7 +54,7 @@ public:
};
class LAY_PUBLIC ProgressReporter
: public tl::ProgressAdaptor
: public QObject, public tl::ProgressAdaptor
{
public:
ProgressReporter ();
@ -64,6 +64,7 @@ public:
virtual void unregister_object (tl::Progress *progress);
virtual void trigger (tl::Progress *progress);
virtual void yield (tl::Progress *progress);
virtual bool eventFilter (QObject *dest, QEvent *event);
void signal_break ();
void set_progress_bar (lay::ProgressBar *pb);
@ -76,8 +77,15 @@ private:
void process_events ();
void update_and_yield ();
void set_visible (bool vis);
};
/**
* @brief Marks a widget as alive
* "alive" widgets receive input events also while a progress reporter is shown
*/
LAY_PUBLIC void mark_widget_alive (QWidget *w, bool alive);
}
#endif

View File

@ -25,6 +25,7 @@
#include "layPlugin.h"
#include "laybasicConfig.h"
#include "layMainWindow.h"
#include "layMacroController.h"
#include "layTechnology.h"
#include "laybasicConfig.h"
#include "tlDeferredExecution.h"
@ -112,8 +113,12 @@ private:
lay::MainWindow *mw = lay::MainWindow::instance ();
if (mw) {
mw->tech_message (tech_string_from_name (active_tech));
}
lay::MacroController *mc = lay::MacroController::instance ();
if (mc) {
// TODO: let the macro controller monitor the active technology
// need to do this since macros may be bound to the new technology
mw->update_menu_with_macros ();
mc->update_menu_with_macros ();
}
}

View File

@ -664,6 +664,26 @@ void LayoutView::viewport_changed ()
viewport_changed_event ();
}
bool LayoutView::accepts_drop (const std::string &path_or_url) const
{
for (std::vector<lay::Plugin *>::const_iterator p = mp_plugins.begin (); p != mp_plugins.end (); ++p) {
if ((*p)->accepts_drop (path_or_url)) {
return true;
}
}
return false;
}
void LayoutView::drop_url (const std::string &path_or_url)
{
for (std::vector<lay::Plugin *>::const_iterator p = mp_plugins.begin (); p != mp_plugins.end (); ++p) {
if ((*p)->accepts_drop (path_or_url)) {
(*p)->drop_url (path_or_url);
break;
}
}
}
lay::Plugin *LayoutView::create_plugin (lay::PluginRoot *root, const lay::PluginDeclaration *cls)
{
lay::Plugin *p = cls->create_plugin (manager (), root, this);
@ -2584,7 +2604,7 @@ LayoutView::get_screenshot ()
tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (QObject::tr ("Save screenshot")));
// Execute all deferred methods - ensure there are no pending tasks
tl::DeferredMethodScheduler::instance ()->execute ();
tl::DeferredMethodScheduler::execute ();
return mp_canvas->screenshot ();
}
@ -2614,7 +2634,7 @@ LayoutView::save_screenshot (const std::string &fn)
writer.setText (QString::fromUtf8 ("Rect"), tl::to_qstring (desc));
// Execute all deferred methods - ensure there are no pending tasks
tl::DeferredMethodScheduler::instance ()->execute ();
tl::DeferredMethodScheduler::execute ();
if (! writer.write (mp_canvas->screenshot ())) {
throw tl::Exception (tl::to_string (QObject::tr ("Unable to write screenshot to file: %s (%s)")), fn, tl::to_string (writer.errorString ()));
@ -2629,7 +2649,7 @@ LayoutView::get_image (unsigned int width, unsigned int height)
tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (QObject::tr ("Save image")));
// Execute all deferred methods - ensure there are no pending tasks
tl::DeferredMethodScheduler::instance ()->execute ();
tl::DeferredMethodScheduler::execute ();
return mp_canvas->image (width, height);
}
@ -2641,7 +2661,7 @@ LayoutView::get_image_with_options (unsigned int width, unsigned int height, int
tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (QObject::tr ("Save image")));
// Execute all deferred methods - ensure there are no pending tasks
tl::DeferredMethodScheduler::instance ()->execute ();
tl::DeferredMethodScheduler::execute ();
return mp_canvas->image_with_options (width, height, linewidth, oversampling, resolution, background, foreground, active, target_box, monochrome);
}
@ -2667,7 +2687,7 @@ LayoutView::save_image (const std::string &fn, unsigned int width, unsigned int
writer.setText (QString::fromUtf8 ("Rect"), tl::to_qstring (vp.box ().to_string ()));
// Execute all deferred methods - ensure there are no pending tasks
tl::DeferredMethodScheduler::instance ()->execute ();
tl::DeferredMethodScheduler::execute ();
if (! writer.write (mp_canvas->image (width, height))) {
throw tl::Exception (tl::to_string (QObject::tr ("Unable to write screenshot to file: %s (%s)")), fn, tl::to_string (writer.errorString ()));
@ -2699,7 +2719,7 @@ LayoutView::save_image_with_options (const std::string &fn,
writer.setText (QString::fromUtf8 ("Rect"), tl::to_qstring (vp.box ().to_string ()));
// Execute all deferred methods - ensure there are no pending tasks
tl::DeferredMethodScheduler::instance ()->execute ();
tl::DeferredMethodScheduler::execute ();
if (! writer.write (mp_canvas->image_with_options (width, height, linewidth, oversampling, resolution, background, foreground, active, target_box, monochrome))) {
throw tl::Exception (tl::to_string (QObject::tr ("Unable to write screenshot to file: %s (%s)")), fn, tl::to_string (writer.errorString ()));

View File

@ -1878,7 +1878,17 @@ public:
return m_drawing_workers;
}
/**
/**
* @brief Gets a value indicating whether the view will accept a dropped file with the given URL or path
*/
virtual bool accepts_drop (const std::string &path_or_url) const;
/**
* @brief Gets called when a file or URL is dropped on the view
*/
virtual void drop_url (const std::string &path_or_url);
/**
* @brief Localize a plugin by name
*
* This method will return 0, if no such plugin is registered

View File

@ -264,6 +264,17 @@ public:
// .. the default implementation does nothing ..
}
/**
* @brief Gets a value indicating whether the plugin can exit
*
* If the plugin wants to prevent the application from closing, it may return false
* in this method.
*/
virtual bool can_exit (lay::PluginRoot * /*root*/) const
{
return true;
}
/**
* @brief Fetch the menu objects for this plugin
*
@ -369,6 +380,22 @@ public:
return m_editable_enabled;
}
/**
* @brief Gets a value indicating whether the plugin will accept a dropped file with the given URL or path
*/
virtual bool accepts_drop (const std::string & /*path_or_url*/) const
{
return false;
}
/**
* @brief Gets called when a file or URL is dropped on the plugin
*/
virtual void drop_url (const std::string & /*path_or_url*/)
{
// .. nothing yet ..
}
/**
* @brief Notifies that plugin root that a new plugin was registered
*
@ -661,6 +688,22 @@ public:
return 0;
}
/**
* @brief Gets a value indicating whether the plugin will accept a dropped file with the given URL or path
*/
virtual bool accepts_drop (const std::string & /*path_or_url*/) const
{
return false;
}
/**
* @brief Gets called when a file or URL is dropped on the plugin
*/
virtual void drop_url (const std::string & /*path_or_url*/)
{
// .. nothing yet ..
}
/**
* @brief Associate a plugin with the plugin declaration for that service (getter)
*/

View File

@ -34,8 +34,8 @@ namespace tl
static DeferredMethodScheduler *s_inst = 0;
DeferredMethodScheduler::DeferredMethodScheduler (QObject *parent)
: QObject (parent),
DeferredMethodScheduler::DeferredMethodScheduler ()
: QObject (0),
m_disabled (0), m_scheduled (false)
{
connect (&m_timer, SIGNAL (timeout ()), this, SLOT (timer ()));
@ -47,6 +47,9 @@ DeferredMethodScheduler::DeferredMethodScheduler (QObject *parent)
connect (&m_fallback_timer, SIGNAL (timeout ()), this, SLOT (timer ()));
m_fallback_timer.setInterval (500);
m_fallback_timer.setSingleShot (false);
tl_assert (! s_inst);
s_inst = this;
}
DeferredMethodScheduler::~DeferredMethodScheduler ()
@ -57,12 +60,8 @@ DeferredMethodScheduler::~DeferredMethodScheduler ()
DeferredMethodScheduler *
DeferredMethodScheduler::instance ()
{
static QMutex lock;
lock.lock ();
if (s_inst == 0) {
s_inst = new DeferredMethodScheduler (qApp);
}
lock.unlock ();
// NOTE: this is not locked intentionally. The instance is shut down once in the application shortly before it
// will terminate and it is created initially before any threads start.
return s_inst;
}
@ -98,7 +97,7 @@ DeferredMethodScheduler::unqueue (DeferredMethodBase *method)
}
void
DeferredMethodScheduler::enable (bool en)
DeferredMethodScheduler::do_enable (bool en)
{
m_lock.lock ();
if (en) {
@ -129,7 +128,7 @@ DeferredMethodScheduler::timer ()
m_timer.start ();
} else {
try {
execute ();
do_execute ();
} catch (tl::Exception &ex) {
tl::error << tl::to_string (QObject::tr ("Exception caught: ")) << ex.msg ();
} catch (std::exception &ex) {
@ -141,7 +140,7 @@ DeferredMethodScheduler::timer ()
}
void
DeferredMethodScheduler::execute ()
DeferredMethodScheduler::do_execute ()
{
std::list<DeferredMethodBase *> methods;

View File

@ -71,6 +71,16 @@ class TL_PUBLIC DeferredMethodScheduler
{
Q_OBJECT
public:
/**
* @brief Constructor
*/
DeferredMethodScheduler ();
/**
* @brief Destructor
*/
~DeferredMethodScheduler ();
/**
* @brief The singleton instance of the scheduler
*/
@ -93,17 +103,27 @@ public:
* when the application is supposed only to show a progress bar and
* therefore will process events but is not supposed to execute anything else.
*
* Enabling is cumulative: multiple enable(true) calls must be matched to
* the same number of enable(false) calls.
* Enabling is cumulative: multiple enable(true) calls must be matched to
* the same number of enable(false) calls.
*/
void enable (bool en);
static void enable (bool en)
{
if (instance ()) {
instance ()->do_enable (en);
}
}
/**
* @brief Execute all queued methods
*
*
* This method can be called to force execution of all queued methods.
*/
void execute ();
static void execute ()
{
if (instance ()) {
instance ()->do_execute ();
}
}
private slots:
void timer ();
@ -115,10 +135,9 @@ private:
QTimer m_timer, m_fallback_timer;
QMutex m_lock;
DeferredMethodScheduler (QObject *parent);
~DeferredMethodScheduler ();
virtual bool event (QEvent *event);
void do_enable (bool en);
void do_execute ();
};
/**
@ -169,7 +188,9 @@ public:
*/
~DeferredMethod ()
{
DeferredMethodScheduler::instance ()->unqueue (this);
if (DeferredMethodScheduler::instance ()) {
DeferredMethodScheduler::instance ()->unqueue (this);
}
}
/**
@ -177,9 +198,13 @@ public:
*/
void operator() ()
{
// management of m_compressed and m_scheduled is done in the DeferredMethodScheduler -
// there it is safely locked. Doing it there saves a private mutex for this object.
DeferredMethodScheduler::instance ()->schedule (this);
if (DeferredMethodScheduler::instance ()) {
// management of m_compressed and m_scheduled is done in the DeferredMethodScheduler -
// there it is safely locked. Doing it there saves a private mutex for this object.
DeferredMethodScheduler::instance ()->schedule (this);
} else {
execute ();
}
}
/**
@ -187,7 +212,9 @@ public:
*/
void cancel ()
{
DeferredMethodScheduler::instance ()->unqueue (this);
if (DeferredMethodScheduler::instance ()) {
DeferredMethodScheduler::instance ()->unqueue (this);
}
}
/**
@ -196,7 +223,7 @@ public:
void execute ()
{
// cancel execution which might be pending
DeferredMethodScheduler::instance ()->unqueue (this);
cancel ();
(mp_t->*m_method) ();
}

View File

@ -41,6 +41,7 @@
#include "tlString.h"
#include <QFileInfo>
#include <QUrl>
namespace tl
{
@ -325,7 +326,8 @@ InputStream::InputStream (const std::string &abstract_path)
mp_delegate = new InputPipe (ex.get ());
#endif
} else if (ex.test ("file:")) {
mp_delegate = new InputZLibFile (ex.get ());
QUrl url (tl::to_qstring (abstract_path));
mp_delegate = new InputZLibFile (tl::to_string (url.toLocalFile ()));
} else {
mp_delegate = new InputZLibFile (abstract_path);
}
@ -345,7 +347,8 @@ std::string InputStream::absolute_path (const std::string &abstract_path)
return abstract_path;
#endif
} else if (ex.test ("file:")) {
return tl::to_string (QFileInfo (tl::to_qstring (ex.get ())).absoluteFilePath ());
QUrl url (tl::to_qstring (abstract_path));
return tl::to_string (QFileInfo (url.toLocalFile ()).absoluteFilePath ());
} else {
return tl::to_string (QFileInfo (tl::to_qstring (abstract_path)).absoluteFilePath ());
}

View File

@ -44,6 +44,8 @@ public:
TEST(1)
{
tl::DeferredMethodScheduler scheduler ();
g_na = g_nb = 0;
QCoreApplication::instance ()->processEvents ();
@ -70,8 +72,8 @@ TEST(1)
EXPECT_EQ (g_na, 0);
EXPECT_EQ (g_nb, 0);
tl::DeferredMethodScheduler::instance ()->enable (false);
tl::DeferredMethodScheduler::instance ()->enable (false);
tl::DeferredMethodScheduler::enable (false);
tl::DeferredMethodScheduler::enable (false);
QCoreApplication::instance ()->processEvents ();
@ -80,7 +82,7 @@ TEST(1)
EXPECT_EQ (g_na, 0);
EXPECT_EQ (g_nb, 0);
tl::DeferredMethodScheduler::instance ()->enable (true);
tl::DeferredMethodScheduler::enable (true);
x->db ();
x->db ();
@ -92,7 +94,7 @@ TEST(1)
EXPECT_EQ (g_na, 0);
EXPECT_EQ (g_nb, 0);
tl::DeferredMethodScheduler::instance ()->enable (true);
tl::DeferredMethodScheduler::enable (true);
QCoreApplication::instance ()->processEvents ();