mirror of https://github.com/KLayout/klayout.git
1872 lines
48 KiB
C++
1872 lines
48 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 "lymMacro.h"
|
|
#include "lymMacroInterpreter.h"
|
|
#include "tlExceptions.h"
|
|
#include "gsiDecl.h"
|
|
#include "gsiInterpreter.h"
|
|
|
|
#include "tlString.h"
|
|
#include "tlStableVector.h"
|
|
#include "tlClassRegistry.h"
|
|
#include "tlLog.h"
|
|
#include "tlXMLParser.h"
|
|
|
|
#include "rba.h"
|
|
#include "pya.h"
|
|
|
|
#include <QFile>
|
|
#include <QDir>
|
|
#include <QUrl>
|
|
#include <QResource>
|
|
|
|
#include <fstream>
|
|
#include <memory>
|
|
|
|
namespace lym
|
|
{
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
Macro::Macro ()
|
|
: m_modified (true), m_readonly (false), m_autorun (false), m_autorun_default (false), m_autorun_early (false), m_show_in_menu (false), m_is_file (false), mp_parent (0), m_interpreter (None), m_format (Macro::NoFormat)
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
void Macro::on_menu_needs_update ()
|
|
{
|
|
// forward the signal to the root collection - the main window will attach to this
|
|
MacroCollection::root ().on_menu_needs_update ();
|
|
}
|
|
|
|
void Macro::on_changed ()
|
|
{
|
|
emit changed ();
|
|
if (mp_parent) {
|
|
mp_parent->on_macro_changed (this);
|
|
}
|
|
}
|
|
|
|
void Macro::assign (const lym::Macro &other)
|
|
{
|
|
m_description = other.m_description;
|
|
m_version = other.m_version;
|
|
m_prolog = other.m_prolog;
|
|
m_category = other.m_category;
|
|
m_epilog = other.m_epilog;
|
|
m_text = other.m_text;
|
|
m_doc = other.m_doc;
|
|
m_version = other.m_version;
|
|
m_modified = other.m_modified;
|
|
m_readonly = other.m_readonly;
|
|
m_autorun = other.m_autorun;
|
|
m_autorun_default = other.m_autorun_default;
|
|
m_autorun_early = other.m_autorun_early;
|
|
m_show_in_menu = other.m_show_in_menu;
|
|
m_shortcut = other.m_shortcut;
|
|
m_format = other.m_format;
|
|
m_group_name = other.m_group_name;
|
|
m_menu_path = other.m_menu_path;
|
|
m_format = other.m_format;
|
|
m_interpreter = other.m_interpreter;
|
|
m_dsl_interpreter = other.m_dsl_interpreter;
|
|
m_is_file = other.m_is_file;
|
|
m_file_path = other.m_file_path;
|
|
on_changed ();
|
|
}
|
|
|
|
bool Macro::operator== (const Macro &other) const
|
|
{
|
|
return
|
|
m_description == other.m_description &&
|
|
m_version == other.m_version &&
|
|
m_epilog == other.m_epilog &&
|
|
m_prolog == other.m_prolog &&
|
|
m_category == other.m_category &&
|
|
m_text == other.m_text &&
|
|
m_autorun == other.m_autorun &&
|
|
m_autorun_early == other.m_autorun_early &&
|
|
m_show_in_menu == other.m_show_in_menu &&
|
|
m_shortcut == other.m_shortcut &&
|
|
m_interpreter == other.m_interpreter &&
|
|
m_dsl_interpreter == other.m_dsl_interpreter &&
|
|
m_format == other.m_format;
|
|
}
|
|
|
|
void Macro::save ()
|
|
{
|
|
save_to (path ());
|
|
}
|
|
|
|
bool Macro::del ()
|
|
{
|
|
if (m_is_file) {
|
|
if (tl::verbosity () >= 20) {
|
|
tl::log << "Deleting macro " << path ();
|
|
}
|
|
QFile f (tl::to_qstring (path ()));
|
|
return f.remove ();
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
struct Interpreter2s
|
|
{
|
|
std::string to_string (Macro::Interpreter i) const
|
|
{
|
|
switch (i) {
|
|
case Macro::Ruby:
|
|
return "ruby";
|
|
case Macro::Python:
|
|
return "python";
|
|
case Macro::Text:
|
|
return "text";
|
|
case Macro::DSLInterpreter:
|
|
return "dsl";
|
|
default:
|
|
return "none";
|
|
}
|
|
}
|
|
|
|
void from_string (const std::string &s, Macro::Interpreter &i) const
|
|
{
|
|
if (s == "ruby") {
|
|
i = Macro::Ruby;
|
|
} else if (s == "python") {
|
|
i = Macro::Python;
|
|
} else if (s == "dsl") {
|
|
i = Macro::DSLInterpreter;
|
|
} else if (s == "text") {
|
|
i = Macro::Text;
|
|
} else {
|
|
i = Macro::None;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief Declaration of the XML structure of a macro
|
|
*/
|
|
static tl::XMLStruct<lym::Macro> xml_struct ("klayout-macro",
|
|
tl::make_member (&Macro::description, &Macro::set_description, "description") +
|
|
tl::make_member (&Macro::version, &Macro::set_version, "version") +
|
|
tl::make_member (&Macro::category, &Macro::set_category, "category") +
|
|
tl::make_member (&Macro::prolog, &Macro::set_prolog, "prolog") +
|
|
tl::make_member (&Macro::epilog, &Macro::set_epilog, "epilog") +
|
|
tl::make_member (&Macro::doc, &Macro::set_doc, "doc") +
|
|
tl::make_member (&Macro::is_autorun, &Macro::set_autorun, "autorun") +
|
|
tl::make_member (&Macro::is_autorun_early, &Macro::set_autorun_early, "autorun-early") +
|
|
tl::make_member (&Macro::shortcut, &Macro::set_shortcut, "shortcut") +
|
|
tl::make_member (&Macro::show_in_menu, &Macro::set_show_in_menu, "show-in-menu") +
|
|
tl::make_member (&Macro::group_name, &Macro::set_group_name, "group-name") +
|
|
tl::make_member (&Macro::menu_path, &Macro::set_menu_path, "menu-path") +
|
|
tl::make_member (&Macro::interpreter, &Macro::set_interpreter, "interpreter", Interpreter2s ()) +
|
|
tl::make_member (&Macro::dsl_interpreter, &Macro::set_dsl_interpreter, "dsl-interpreter-name") +
|
|
tl::make_member (&Macro::text, &Macro::set_text, "text") +
|
|
tl::make_member<Macro> ("format") // for backward compatibility
|
|
);
|
|
|
|
void Macro::save_to (const std::string &path)
|
|
{
|
|
if (tl::verbosity () >= 20) {
|
|
tl::log << "Saving macro to " << path;
|
|
}
|
|
|
|
tl::OutputStream os (path, tl::OutputStream::OM_Plain);
|
|
|
|
if (m_format == MacroFormat) {
|
|
xml_struct.write (os, *this);
|
|
} else if (m_format == PlainTextWithHashAnnotationsFormat) {
|
|
sync_text_with_properties ();
|
|
os << text ();
|
|
} else if (m_format == PlainTextFormat) {
|
|
os << text ();
|
|
}
|
|
|
|
if (m_modified || ! m_is_file) {
|
|
m_modified = false;
|
|
m_is_file = true;
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
void Macro::load_from (const std::string &path)
|
|
{
|
|
m_format = NoFormat;
|
|
|
|
if (tl::verbosity () >= 20) {
|
|
tl::log << "Loading macro from " << path;
|
|
}
|
|
|
|
if (format_from_suffix (path, m_interpreter, m_dsl_interpreter, m_autorun_default, m_format)) {
|
|
|
|
m_autorun = m_autorun_default;
|
|
|
|
if (m_format == MacroFormat) {
|
|
|
|
// default interpreter for .lym files is Ruby - but should be mentioned in the file anyway
|
|
m_interpreter = Ruby;
|
|
|
|
tl::XMLFileSource source (path);
|
|
xml_struct.parse (source, *this);
|
|
|
|
} else if (m_format == PlainTextFormat || m_format == PlainTextWithHashAnnotationsFormat) {
|
|
|
|
tl::InputStream stream (path);
|
|
m_text = stream.read_all ();
|
|
sync_properties_with_text ();
|
|
|
|
}
|
|
|
|
} else {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Unable to determine format for file from suffix ")) + path);
|
|
}
|
|
|
|
m_modified = true;
|
|
on_changed ();
|
|
}
|
|
|
|
void Macro::load_from_string (const std::string &text, const std::string &url)
|
|
{
|
|
m_format = NoFormat;
|
|
|
|
if (tl::verbosity () >= 20) {
|
|
tl::log << "Loading macro from " << url;
|
|
}
|
|
|
|
if (format_from_suffix (tl::to_string (QUrl (tl::to_qstring (url)).path ()), m_interpreter, m_dsl_interpreter, m_autorun_default, m_format)) {
|
|
|
|
m_autorun = m_autorun_default;
|
|
|
|
if (m_format == MacroFormat) {
|
|
|
|
tl::XMLStringSource source (text);
|
|
xml_struct.parse (source, *this);
|
|
|
|
} else if (m_format == PlainTextWithHashAnnotationsFormat) {
|
|
|
|
m_text = text;
|
|
sync_properties_with_text ();
|
|
|
|
} else if (m_format == PlainTextFormat) {
|
|
|
|
m_text = text;
|
|
|
|
}
|
|
|
|
} else {
|
|
throw tl::Exception (tl::to_string (QObject::tr ("Unable to determine format for file from suffix ")) + url);
|
|
}
|
|
|
|
m_modified = true;
|
|
on_changed ();
|
|
}
|
|
|
|
void Macro::load ()
|
|
{
|
|
load_from (path ());
|
|
m_modified = false;
|
|
m_is_file = true;
|
|
on_changed ();
|
|
}
|
|
|
|
bool
|
|
Macro::format_from_suffix (const std::string &fn, Macro::Interpreter &interpreter, std::string &dsl_name, bool &autorun_pref, Macro::Format &format)
|
|
{
|
|
std::string suffix = tl::to_string (QFileInfo (tl::to_qstring (fn)).suffix ());
|
|
|
|
interpreter = None;
|
|
dsl_name = std::string ();
|
|
format = NoFormat;
|
|
autorun_pref = false;
|
|
|
|
// know suffixes
|
|
if (suffix == "rb" || suffix == "rbm") {
|
|
|
|
autorun_pref = (suffix == "rbm");
|
|
interpreter = Ruby;
|
|
format = PlainTextWithHashAnnotationsFormat;
|
|
return true;
|
|
|
|
} else if (suffix == "py" || suffix == "pym") {
|
|
|
|
autorun_pref = (suffix == "pym");
|
|
interpreter = Python;
|
|
format = PlainTextWithHashAnnotationsFormat;
|
|
return true;
|
|
|
|
} else if (suffix == "txt") {
|
|
|
|
format = PlainTextFormat;
|
|
return true;
|
|
|
|
} else if (suffix == "lym") {
|
|
|
|
format = MacroFormat;
|
|
return true;
|
|
|
|
} else if (!suffix.empty ()) {
|
|
|
|
// locate the suffix in the DSL interpreter declarations
|
|
for (tl::Registrar<lym::MacroInterpreter>::iterator cls = tl::Registrar<lym::MacroInterpreter>::begin (); cls != tl::Registrar<lym::MacroInterpreter>::end (); ++cls) {
|
|
|
|
if (cls->suffix () == suffix) {
|
|
|
|
interpreter = DSLInterpreter; // by default - may be overridden by content of file
|
|
dsl_name = cls.current_name ();
|
|
format = cls->storage_scheme ();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::string
|
|
Macro::suffix_for_format (Macro::Interpreter interpreter, const std::string &dsl_name, Macro::Format format)
|
|
{
|
|
std::string s;
|
|
if (interpreter == DSLInterpreter) {
|
|
s = MacroInterpreter::suffix (dsl_name);
|
|
} else if (format == MacroFormat) {
|
|
s = "lym";
|
|
} else if (interpreter == Ruby) {
|
|
s = "rb";
|
|
} else if (interpreter == Python) {
|
|
s = "py";
|
|
} else {
|
|
s = "txt";
|
|
}
|
|
if (! s.empty ()) {
|
|
return "." + s;
|
|
} else {
|
|
return ".lym";
|
|
}
|
|
}
|
|
|
|
std::string Macro::interpreter_name () const
|
|
{
|
|
if (interpreter () == Ruby) {
|
|
return "Ruby";
|
|
} else if (interpreter () == Python) {
|
|
return "Python";
|
|
} else if (interpreter () == DSLInterpreter) {
|
|
return MacroInterpreter::description (dsl_interpreter ());
|
|
} else {
|
|
return std::string ();
|
|
}
|
|
}
|
|
|
|
std::string Macro::summary () const
|
|
{
|
|
return std::string ("<html><body><b>") + interpreter_name () + "</b> " + path () + "</body></html>";
|
|
}
|
|
|
|
std::string Macro::path () const
|
|
{
|
|
if (! m_file_path.empty ()) {
|
|
return m_file_path;
|
|
}
|
|
|
|
std::string suffix = suffix_for_format (m_interpreter, m_dsl_interpreter, m_format);
|
|
if (mp_parent) {
|
|
return tl::to_string (QFileInfo (QDir (tl::to_qstring (mp_parent->path ())), tl::to_qstring (m_name + suffix)).filePath ());
|
|
} else {
|
|
return m_name + suffix;
|
|
}
|
|
}
|
|
|
|
void Macro::set_file_path (const std::string &fp)
|
|
{
|
|
m_file_path = fp;
|
|
}
|
|
|
|
void Macro::set_is_file ()
|
|
{
|
|
if (! m_is_file) {
|
|
m_is_file = true;
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
void Macro::reset_modified ()
|
|
{
|
|
if (m_modified) {
|
|
m_modified = false;
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
bool Macro::rename (const std::string &n)
|
|
{
|
|
if (m_is_file) {
|
|
std::string suffix = suffix_for_format (m_interpreter, m_dsl_interpreter, m_format);
|
|
if (tl::verbosity () >= 20) {
|
|
tl::log << "Renaming macro " << path () << " to " << n;
|
|
}
|
|
QFile f (tl::to_qstring (path ()));
|
|
if (! f.rename (QFileInfo (QDir (tl::to_qstring (mp_parent->path ())), tl::to_qstring (n + suffix)).filePath ())) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (parent ()) {
|
|
parent ()->rename_macro (this, n);
|
|
}
|
|
|
|
m_name = n;
|
|
on_changed ();
|
|
return true;
|
|
}
|
|
|
|
std::string Macro::dir () const
|
|
{
|
|
if (mp_parent) {
|
|
return mp_parent->path ();
|
|
} else {
|
|
return std::string ();
|
|
}
|
|
}
|
|
|
|
std::string Macro::display_string () const
|
|
{
|
|
std::string r = name ();
|
|
if (! m_description.empty ()) {
|
|
r += " - " + m_description;
|
|
}
|
|
if (! m_shortcut.empty ()) {
|
|
r += " (" + m_shortcut + ")";
|
|
}
|
|
return r;
|
|
}
|
|
|
|
void Macro::set_doc (const std::string &d)
|
|
{
|
|
if (m_doc != d) {
|
|
m_modified = true;
|
|
m_doc = d;
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
void Macro::set_description (const std::string &d)
|
|
{
|
|
if (m_description != d) {
|
|
m_modified = true;
|
|
m_description = d;
|
|
if (m_show_in_menu) {
|
|
on_menu_needs_update ();
|
|
}
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
void Macro::set_epilog (const std::string &s)
|
|
{
|
|
if (m_epilog != s) {
|
|
m_modified = true;
|
|
m_epilog = s;
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
void Macro::set_prolog (const std::string &s)
|
|
{
|
|
if (m_prolog != s) {
|
|
m_modified = true;
|
|
m_prolog = s;
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
void Macro::set_version (const std::string &s)
|
|
{
|
|
if (m_version != s) {
|
|
m_modified = true;
|
|
m_version = s;
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
const std::string &Macro::text () const
|
|
{
|
|
return m_text;
|
|
}
|
|
|
|
struct PropertyField
|
|
{
|
|
const char *name;
|
|
const std::string &(lym::Macro::*string_getter) () const;
|
|
void (lym::Macro::*string_setter) (const std::string &);
|
|
bool (lym::Macro::*bool_getter) () const;
|
|
void (lym::Macro::*bool_setter) (bool);
|
|
};
|
|
|
|
static PropertyField property_fields[] = {
|
|
{ "description", &lym::Macro::description, &lym::Macro::set_description, 0, 0 },
|
|
{ "prolog", &lym::Macro::prolog, &lym::Macro::set_prolog, 0, 0 },
|
|
{ "epilog", &lym::Macro::epilog, &lym::Macro::set_epilog, 0, 0 },
|
|
{ "version", &lym::Macro::version, &lym::Macro::set_version, 0, 0 },
|
|
{ "autorun", 0, 0, &lym::Macro::is_autorun, &lym::Macro::set_autorun },
|
|
{ "autorun-early", 0, 0, &lym::Macro::is_autorun_early, &lym::Macro::set_autorun_early},
|
|
{ "show-in-menu", 0, 0, &lym::Macro::show_in_menu, &lym::Macro::set_show_in_menu },
|
|
{ "group-name", &lym::Macro::group_name, &lym::Macro::set_group_name, 0, 0 },
|
|
{ "menu-path", &lym::Macro::menu_path, &lym::Macro::set_menu_path, 0, 0 },
|
|
{ "shortcut", &lym::Macro::shortcut, &lym::Macro::set_shortcut, 0, 0 }
|
|
};
|
|
|
|
static std::string escape_pta_string (const char *cp)
|
|
{
|
|
std::string res;
|
|
while (*cp) {
|
|
if (*cp == '\n') {
|
|
res += "\\n";
|
|
} else if ((unsigned char)*cp < 0x20) {
|
|
res += " ";
|
|
} else if (*cp == '\\') {
|
|
res += "\\\\";
|
|
} else {
|
|
res += *cp;
|
|
}
|
|
++cp;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static std::string unescape_pta_string (const char *cp)
|
|
{
|
|
std::string res;
|
|
while (*cp) {
|
|
if (*cp == '\\' && cp[1]) {
|
|
++cp;
|
|
if (*cp == 'n') {
|
|
res += "\n";
|
|
} else {
|
|
res += *cp;
|
|
}
|
|
} else {
|
|
res += *cp;
|
|
}
|
|
++cp;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void Macro::sync_text_with_properties ()
|
|
{
|
|
if (m_format != PlainTextWithHashAnnotationsFormat) {
|
|
return;
|
|
}
|
|
|
|
std::vector<std::string> lines = tl::split (m_text, "\n");
|
|
|
|
std::vector<std::string> new_lines;
|
|
for (size_t i = 0; i < sizeof (property_fields) / sizeof (property_fields[0]); ++i) {
|
|
const PropertyField *pf = property_fields + i;
|
|
if (pf->string_getter) {
|
|
std::string v = (this->*(pf->string_getter)) ();
|
|
if (! v.empty ()) {
|
|
new_lines.push_back (std::string ("# $") + pf->name + ": " + escape_pta_string (v.c_str ()));
|
|
}
|
|
} else if (pf->bool_getter) {
|
|
bool v = (this->*(pf->bool_getter)) ();
|
|
if (v) {
|
|
new_lines.push_back (std::string ("# $") + pf->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool stop_fishing = false;
|
|
|
|
for (std::vector<std::string>::const_iterator l = lines.begin (); l != lines.end (); ++l) {
|
|
|
|
tl::Extractor ex (l->c_str ());
|
|
|
|
bool taken = false;
|
|
if (stop_fishing) {
|
|
// done - no more lines are removed
|
|
} else if (ex.test ("#") && ex.test ("$")) {
|
|
for (size_t i = 0; i < sizeof (property_fields) / sizeof (property_fields[0]) && !taken; ++i) {
|
|
taken = ex.test (property_fields [i].name);
|
|
}
|
|
} else if (! ex.at_end ()) {
|
|
stop_fishing = true;
|
|
}
|
|
|
|
if (! taken) {
|
|
new_lines.push_back (*l);
|
|
}
|
|
|
|
}
|
|
|
|
std::string new_text = tl::join (new_lines, "\n");
|
|
if (new_text != m_text) {
|
|
m_text = new_text;
|
|
m_modified = true;
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
void Macro::sync_properties_with_text ()
|
|
{
|
|
if (m_format != PlainTextWithHashAnnotationsFormat) {
|
|
return;
|
|
}
|
|
|
|
// reset the properties first
|
|
for (size_t i = 0; i < sizeof (property_fields) / sizeof (property_fields[0]); ++i) {
|
|
const PropertyField *pf = property_fields + i;
|
|
if (pf->string_setter) {
|
|
(this->*(pf->string_setter)) (std::string ());
|
|
} else if (pf->bool_setter) {
|
|
(this->*(pf->bool_setter)) (false);
|
|
}
|
|
}
|
|
|
|
m_autorun = m_autorun_default;
|
|
|
|
std::vector<std::string> lines = tl::split (m_text, "\n");
|
|
|
|
for (std::vector<std::string>::const_iterator l = lines.begin (); l != lines.end (); ++l) {
|
|
|
|
tl::Extractor ex (l->c_str ());
|
|
|
|
if (ex.test ("#") && ex.test ("$")) {
|
|
|
|
for (size_t i = 0; i < sizeof (property_fields) / sizeof (property_fields[0]); ++i) {
|
|
|
|
const PropertyField *pf = property_fields + i;
|
|
if (ex.test (pf->name)) {
|
|
|
|
if (pf->string_setter) {
|
|
ex.test (":");
|
|
(this->*(pf->string_setter)) (unescape_pta_string (ex.skip ()));
|
|
} else if (pf->bool_setter) {
|
|
(this->*(pf->bool_setter)) (true);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (! ex.at_end ()) {
|
|
// stop fishing
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void Macro::set_text (const std::string &t)
|
|
{
|
|
if (text () != t) {
|
|
m_text = t;
|
|
m_modified = true;
|
|
sync_properties_with_text ();
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
void Macro::set_format (Format f)
|
|
{
|
|
if (f != m_format) {
|
|
m_modified = true;
|
|
m_format = f;
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
void Macro::set_dsl_interpreter (const std::string &n)
|
|
{
|
|
if (n != m_dsl_interpreter) {
|
|
m_modified = true;
|
|
m_dsl_interpreter = n;
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
void Macro::set_interpreter (Interpreter i)
|
|
{
|
|
if (i != m_interpreter) {
|
|
m_modified = true;
|
|
m_interpreter = i;
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
void Macro::set_autorun_early (bool f)
|
|
{
|
|
if (f != m_autorun_early) {
|
|
m_modified = true;
|
|
m_autorun_early = f;
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
void Macro::set_autorun (bool f)
|
|
{
|
|
if (f != m_autorun) {
|
|
m_modified = true;
|
|
m_autorun = f;
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
void Macro::set_show_in_menu (bool f)
|
|
{
|
|
if (f != m_show_in_menu) {
|
|
m_modified = true;
|
|
m_show_in_menu = f;
|
|
on_menu_needs_update ();
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
void Macro::set_menu_path (const std::string &mp)
|
|
{
|
|
if (m_menu_path != mp) {
|
|
m_modified = true;
|
|
m_menu_path = mp;
|
|
on_menu_needs_update ();
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
void Macro::set_group_name (const std::string &g)
|
|
{
|
|
if (m_group_name != g) {
|
|
m_modified = true;
|
|
m_group_name = g;
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
void Macro::set_shortcut (const std::string &s)
|
|
{
|
|
if (s != m_shortcut) {
|
|
m_modified = true;
|
|
m_shortcut = s;
|
|
on_menu_needs_update ();
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
void Macro::set_readonly (bool f)
|
|
{
|
|
if (m_readonly != f) {
|
|
m_readonly = f;
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
class ExternalMethod
|
|
: public gsi::MethodBase
|
|
{
|
|
public:
|
|
ExternalMethod (const std::string &name, const std::string &doc, bool c, bool s)
|
|
: gsi::MethodBase (name, doc, c, s)
|
|
{
|
|
// no return type
|
|
gsi::ArgType a;
|
|
a.set_type (gsi::BasicType (-1));
|
|
set_return (a);
|
|
}
|
|
|
|
virtual MethodBase *clone () const
|
|
{
|
|
return new ExternalMethod (*this);
|
|
}
|
|
|
|
// this class is not intended to go functional. It's just a hook for the documentation
|
|
virtual void call(void*, gsi::SerialArgs&, gsi::SerialArgs&) const
|
|
{
|
|
tl_assert (false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief A descriptor for an external class (scripted)
|
|
*
|
|
* This declaration is not intended to go functional. It's just a hook for the documentation.
|
|
*/
|
|
class ExternalClass
|
|
: public gsi::ClassBase
|
|
{
|
|
public:
|
|
ExternalClass (const std::string &name, const std::string &category, const gsi::ClassBase *base, const std::string &doc, const gsi::Methods &mm)
|
|
: gsi::ClassBase (doc, mm), m_category (category)
|
|
{
|
|
set_name (name);
|
|
set_base (base);
|
|
}
|
|
|
|
const std::string &category () const
|
|
{
|
|
return m_category;
|
|
}
|
|
|
|
virtual bool consolidate () const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
std::string m_category;
|
|
};
|
|
|
|
void Macro::install_doc () const
|
|
{
|
|
std::vector<std::string> lines = tl::split (tl::trim (doc ()), "\n");
|
|
if (! lines.empty () && tl::trim (lines [0]).find ("@class") == 0) {
|
|
|
|
// this macro provides documentation for the GSI namespace
|
|
gsi::ClassBase *cls = 0;
|
|
|
|
for (size_t i = 0; i < lines.size (); ++i) {
|
|
|
|
bool st = false;
|
|
tl::Extractor ex (lines [i].c_str ());
|
|
if (ex.test ("@class")) {
|
|
|
|
std::string cls_name, super_cls_name;
|
|
ex.read_word_or_quoted (cls_name);
|
|
if (ex.test ("<")) {
|
|
ex.read_word_or_quoted (super_cls_name);
|
|
}
|
|
|
|
std::string doc;
|
|
while (++i < lines.size ()) {
|
|
std::string l = tl::trim (lines [i]);
|
|
if (l.find ("@method") == 0 || l.find ("@static_method") == 0) {
|
|
break;
|
|
}
|
|
if (! doc.empty ()) {
|
|
doc += "\n";
|
|
}
|
|
doc += lines [i];
|
|
}
|
|
--i;
|
|
|
|
if (cls) {
|
|
tl::error << tl::to_string (QObject::tr ("Reading class doc from ")) << path () << ": " << tl::to_string (QObject::tr ("Duplicate @class"));
|
|
return;
|
|
}
|
|
|
|
for (gsi::ClassBase::class_iterator c = gsi::ClassBase::begin_classes (); c != gsi::ClassBase::end_classes (); ++c) {
|
|
if (c->name () == cls_name) {
|
|
const ExternalClass *ec = dynamic_cast<const ExternalClass *> (&*c);
|
|
if (!ec || ec->category () == category ()) {
|
|
cls = const_cast <gsi::ClassBase *> (&*c);
|
|
}
|
|
}
|
|
}
|
|
|
|
const gsi::ClassBase *super_cls = 0;
|
|
if (! super_cls_name.empty ()) {
|
|
for (gsi::ClassBase::class_iterator c = gsi::ClassBase::begin_classes (); c != gsi::ClassBase::end_classes (); ++c) {
|
|
if (c->name () == super_cls_name) {
|
|
super_cls = &*c;
|
|
break;
|
|
}
|
|
}
|
|
if (! super_cls) {
|
|
tl::error << tl::to_string (QObject::tr ("Reading class doc from ")) << path () << ": " << tl::to_string (QObject::tr ("Cannot find super class: ")) << super_cls_name;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (! cls) {
|
|
// create a new class declaration
|
|
static tl::stable_vector<ExternalClass> ext_classes;
|
|
ExternalClass *ext_cls = new ExternalClass (cls_name, category (), super_cls, doc, gsi::Methods ());
|
|
ext_classes.push_back (ext_cls);
|
|
cls = ext_cls;
|
|
}
|
|
|
|
} else if (ex.test ("@method") || (st = ex.test ("@static_method")) == true) {
|
|
|
|
if (cls == 0) {
|
|
tl::error << tl::to_string (QObject::tr ("Reading class doc from ")) << path () << ": " << tl::to_string (QObject::tr ("@method without preceeding @class"));
|
|
}
|
|
|
|
std::string n;
|
|
ex.read_word_or_quoted (n);
|
|
|
|
std::string doc;
|
|
while (++i < lines.size ()) {
|
|
std::string l = tl::trim (lines [i]);
|
|
if (l.find ("@method") == 0 || l.find ("@static_method") == 0) {
|
|
break;
|
|
}
|
|
if (! doc.empty ()) {
|
|
doc += "\n";
|
|
}
|
|
doc += lines [i];
|
|
}
|
|
--i;
|
|
|
|
ExternalMethod *meth = new ExternalMethod (n, doc, false, st);
|
|
cls->add_method (meth);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
static gsi::Interpreter *script_interpreter (lym::Macro::Interpreter lang)
|
|
{
|
|
gsi::Interpreter *ip = 0;
|
|
|
|
// This
|
|
if (lang == lym::Macro::Ruby) {
|
|
ip = rba::RubyInterpreter::instance ();
|
|
} else if (lang == lym::Macro::Python) {
|
|
ip = pya::PythonInterpreter::instance ();
|
|
}
|
|
|
|
return (ip && ip->available() ? ip : 0);
|
|
}
|
|
|
|
bool Macro::can_run () const
|
|
{
|
|
gsi::Interpreter *ip = script_interpreter (interpreter ());
|
|
if (ip) {
|
|
return true;
|
|
} else if (interpreter () == lym::Macro::DSLInterpreter) {
|
|
return lym::MacroInterpreter::can_run (this);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int Macro::run () const
|
|
{
|
|
if (tl::verbosity () >= 20) {
|
|
tl::log << "Running macro " << path ();
|
|
}
|
|
|
|
try {
|
|
gsi::Interpreter *ip = script_interpreter (interpreter ());
|
|
if (ip) {
|
|
if (! prolog ().empty ()) {
|
|
ip->eval_string (prolog ().c_str ());
|
|
}
|
|
ip->eval_string (text ().c_str (), path ().c_str (), 1);
|
|
if (! epilog ().empty ()) {
|
|
ip->eval_string (epilog ().c_str ());
|
|
}
|
|
} else if (interpreter () == lym::Macro::DSLInterpreter) {
|
|
lym::MacroInterpreter::execute_macro (this);
|
|
}
|
|
} catch (tl::ExitException &ex) {
|
|
return ex.status ();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
static MacroCollection ms_root;
|
|
|
|
MacroCollection::MacroCollection ()
|
|
: mp_parent (0), m_virtual_mode (ProjectFolder), m_readonly (false)
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
MacroCollection::~MacroCollection ()
|
|
{
|
|
for (iterator m = begin (); m != end (); ++m) {
|
|
delete m->second;
|
|
}
|
|
m_macros.clear ();
|
|
|
|
for (child_iterator mm = begin_children (); mm != end_children (); ++mm) {
|
|
delete mm->second;
|
|
}
|
|
m_folders.clear ();
|
|
}
|
|
|
|
void MacroCollection::begin_changes ()
|
|
{
|
|
// Note: it is very important that each on_changed occurs after exactly one begin_changes.
|
|
// (See #459 for example)
|
|
if (mp_parent) {
|
|
mp_parent->begin_changes ();
|
|
} else {
|
|
emit about_to_change ();
|
|
}
|
|
}
|
|
|
|
void MacroCollection::on_menu_needs_update ()
|
|
{
|
|
emit menu_needs_update ();
|
|
}
|
|
|
|
void MacroCollection::on_changed ()
|
|
{
|
|
// Note: it is very important that each on_changed occurs after exactly one begin_changes.
|
|
// (See #459 for example)
|
|
emit changed ();
|
|
on_macro_collection_changed (this);
|
|
}
|
|
|
|
void MacroCollection::on_macro_collection_changed (MacroCollection *mc)
|
|
{
|
|
if (mp_parent) {
|
|
mp_parent->on_macro_collection_changed (mc);
|
|
} else {
|
|
emit macro_collection_changed (mc);
|
|
}
|
|
}
|
|
|
|
void MacroCollection::on_child_deleted (MacroCollection *mc)
|
|
{
|
|
emit child_deleted (mc);
|
|
on_macro_collection_deleted (mc);
|
|
}
|
|
|
|
void MacroCollection::on_macro_collection_deleted (MacroCollection *mc)
|
|
{
|
|
if (mp_parent) {
|
|
mp_parent->on_macro_collection_deleted (mc);
|
|
} else {
|
|
emit macro_collection_deleted (mc);
|
|
}
|
|
}
|
|
|
|
void MacroCollection::on_macro_deleted_here (Macro *macro)
|
|
{
|
|
emit macro_deleted_here (macro);
|
|
on_macro_deleted (macro);
|
|
}
|
|
|
|
void MacroCollection::on_macro_deleted (Macro *macro)
|
|
{
|
|
if (mp_parent) {
|
|
mp_parent->on_macro_deleted (macro);
|
|
} else {
|
|
emit macro_deleted (macro);
|
|
}
|
|
}
|
|
|
|
void MacroCollection::on_macro_changed (Macro *macro)
|
|
{
|
|
if (mp_parent) {
|
|
mp_parent->on_macro_changed (macro);
|
|
} else {
|
|
emit macro_changed (macro);
|
|
}
|
|
}
|
|
|
|
void MacroCollection::collect_used_nodes (std::set <Macro *> ¯os, std::set <MacroCollection *> ¯o_collections)
|
|
{
|
|
for (MacroCollection::child_iterator c = begin_children (); c != end_children (); ++c) {
|
|
macro_collections.insert (c->second);
|
|
c->second->collect_used_nodes (macros, macro_collections);
|
|
}
|
|
for (MacroCollection::iterator c = begin (); c != end (); ++c) {
|
|
macros.insert (c->second);
|
|
}
|
|
}
|
|
|
|
Macro *MacroCollection::macro_by_name (const std::string &name, Macro::Format format)
|
|
{
|
|
std::multimap <std::string, Macro *>::iterator i = m_macros.find (name);
|
|
while (i != m_macros.end () && i->first == name) {
|
|
if (format == Macro::NoFormat || i->second->format () == format) {
|
|
return i->second;
|
|
}
|
|
++i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const Macro *MacroCollection::macro_by_name (const std::string &name, Macro::Format format) const
|
|
{
|
|
std::multimap <std::string, Macro *>::const_iterator i = m_macros.find (name);
|
|
while (i != m_macros.end () && i->first == name) {
|
|
if (format == Macro::NoFormat || i->second->format () == format) {
|
|
return i->second;
|
|
}
|
|
++i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
MacroCollection *MacroCollection::folder_by_name (const std::string &name)
|
|
{
|
|
std::map <std::string, MacroCollection *>::iterator i = m_folders.find (name);
|
|
if (i != m_folders.end ()) {
|
|
return i->second;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
const MacroCollection *MacroCollection::folder_by_name (const std::string &name) const
|
|
{
|
|
std::map <std::string, MacroCollection *>::const_iterator i = m_folders.find (name);
|
|
if (i != m_folders.end ()) {
|
|
return i->second;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
std::string MacroCollection::path () const
|
|
{
|
|
if (m_virtual_mode) {
|
|
return m_path;
|
|
} else if (mp_parent) {
|
|
return tl::to_string (QFileInfo (QDir (tl::to_qstring (mp_parent->path ())), tl::to_qstring (m_path)).filePath ());
|
|
} else {
|
|
return m_path;
|
|
}
|
|
}
|
|
|
|
std::string MacroCollection::display_string () const
|
|
{
|
|
if (m_virtual_mode) {
|
|
return "[" + m_description + "]";
|
|
} else {
|
|
std::string r = name ();
|
|
if (! m_description.empty ()) {
|
|
r += " - " + m_description;
|
|
}
|
|
return r;
|
|
}
|
|
}
|
|
|
|
void
|
|
MacroCollection::make_readonly (bool f)
|
|
{
|
|
if (m_readonly != f) {
|
|
begin_changes ();
|
|
m_readonly = f;
|
|
on_changed ();
|
|
}
|
|
}
|
|
|
|
MacroCollection *
|
|
MacroCollection::add_folder (const std::string &description, const std::string &path, const std::string &cat, bool readonly, bool force_create)
|
|
{
|
|
if (! path.empty () && path[0] == ':') {
|
|
readonly = true;
|
|
} else {
|
|
|
|
QFileInfo file_info (tl::to_qstring (path));
|
|
|
|
if (! file_info.exists ()) {
|
|
|
|
// Try to create the folder since it does not exist yet or skip that one
|
|
if (! force_create) {
|
|
|
|
if (tl::verbosity () >= 20) {
|
|
tl::log << "Folder does not exist - skipping: " << path;
|
|
}
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
if (tl::verbosity () >= 20) {
|
|
tl::log << "Folder does not exist yet - trying to create it: " << path;
|
|
}
|
|
if (! QDir::root ().mkpath (file_info.absoluteFilePath ())) {
|
|
if (tl::verbosity () >= 10) {
|
|
tl::error << "Unable to create folder path: " << path;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
file_info.refresh ();
|
|
|
|
}
|
|
|
|
if (! file_info.isDir ()) {
|
|
if (tl::verbosity () >= 10) {
|
|
tl::error << "Folder is not a directory: " << path;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
QString cp = file_info.canonicalFilePath ();
|
|
if (cp.isEmpty ()) {
|
|
return 0;
|
|
}
|
|
|
|
for (child_iterator f = m_folders.begin (); f != m_folders.end (); ++f) {
|
|
// skip, if that folder is in the collection already
|
|
if (QFileInfo (tl::to_qstring (f->first)).canonicalFilePath () == cp) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (! readonly && ! file_info.isWritable ()) {
|
|
readonly = true;
|
|
if (tl::verbosity () >= 20) {
|
|
tl::log << "Folder is read-only: " << path;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
begin_changes ();
|
|
|
|
MacroCollection *mc = m_folders.insert (std::make_pair (path, new MacroCollection ())).first->second;
|
|
mc->set_parent (this);
|
|
mc->set_name (path);
|
|
mc->set_description (description);
|
|
mc->set_category (cat);
|
|
mc->set_readonly (readonly);
|
|
mc->scan (path);
|
|
|
|
on_changed ();
|
|
|
|
return mc;
|
|
}
|
|
|
|
void MacroCollection::rescan ()
|
|
{
|
|
for (std::map <std::string, MacroCollection *>::const_iterator m = m_folders.begin (); m != m_folders.end (); ++m) {
|
|
m->second->scan (m->first);
|
|
}
|
|
}
|
|
|
|
void MacroCollection::scan (const std::string &path)
|
|
{
|
|
if (tl::verbosity () >= 20) {
|
|
tl::info << "Scanning macro path " << path << " (readonly=" << m_readonly << ")";
|
|
}
|
|
|
|
if (! path.empty () && path[0] == ':') {
|
|
|
|
// look for an index file
|
|
QResource res (tl::to_qstring (path + "/index.txt"));
|
|
QByteArray data;
|
|
if (res.isCompressed ()) {
|
|
data = qUncompress ((const unsigned char *)res.data (), (int)res.size ());
|
|
} else {
|
|
data = QByteArray ((const char *)res.data (), (int)res.size ());
|
|
}
|
|
|
|
// Read index file
|
|
std::vector<std::string> lines = tl::split (std::string (data.constData (), data.size ()), "\n");
|
|
std::string description_prefix;
|
|
for (std::vector<std::string>::const_iterator l = lines.begin (); l != lines.end (); ++l) {
|
|
|
|
std::string ll = tl::trim (*l);
|
|
if (! ll.empty () && ll [0] != '#') {
|
|
|
|
std::string url = path + "/" + ll;
|
|
QResource res (tl::to_qstring (url));
|
|
if (res.size () > 0) {
|
|
|
|
QByteArray data;
|
|
if (res.isCompressed ()) {
|
|
data = qUncompress ((const unsigned char *)res.data (), (int)res.size ());
|
|
} else {
|
|
data = QByteArray ((const char *)res.data (), (int)res.size ());
|
|
}
|
|
|
|
try {
|
|
|
|
Macro::Format format = Macro::NoFormat;
|
|
Macro::Interpreter interpreter = Macro::None;
|
|
std::string dsl_name;
|
|
bool autorun = false;
|
|
|
|
if (Macro::format_from_suffix (ll, interpreter, dsl_name, autorun, format)) {
|
|
|
|
std::string n = tl::to_string (QFileInfo (tl::to_qstring (ll)).baseName ());
|
|
|
|
iterator mm = m_macros.find (n);
|
|
bool found = false;
|
|
while (mm != m_macros.end () && mm->first == n && ! found) {
|
|
if ((interpreter == Macro::None || mm->second->interpreter () == interpreter) &&
|
|
(dsl_name.empty () || mm->second->dsl_interpreter () == dsl_name) &&
|
|
mm->second->format () == format) {
|
|
found = true;
|
|
}
|
|
++mm;
|
|
}
|
|
if (! found) {
|
|
Macro *m = m_macros.insert (std::make_pair (n, new Macro ()))->second;
|
|
m->set_parent (this);
|
|
m->set_interpreter (interpreter);
|
|
m->set_autorun_default (autorun);
|
|
m->set_autorun (autorun);
|
|
m->set_dsl_interpreter (dsl_name);
|
|
m->set_format (format);
|
|
m->set_name (n);
|
|
m->load_from_string (std::string (data.constData (), data.size ()), url);
|
|
m->set_readonly (m_readonly);
|
|
m->reset_modified ();
|
|
m->set_is_file ();
|
|
}
|
|
|
|
}
|
|
|
|
} catch (tl::Exception &ex) {
|
|
tl::error << "Reading " << url << ": " << ex.msg ();
|
|
}
|
|
|
|
} else {
|
|
tl::error << "Resource " << url << " not found";
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
QDir dir (tl::to_qstring (path));
|
|
QStringList filters;
|
|
filters << QString::fromUtf8 ("*.lym");
|
|
filters << QString::fromUtf8 ("*.txt");
|
|
// TODO: should be either *.rb or *.python, depending on the category.
|
|
// Right now we rely on the folders not containing foreign files.
|
|
filters << QString::fromUtf8 ("*.rb");
|
|
filters << QString::fromUtf8 ("*.py");
|
|
|
|
// add the suffixes in the DSL interpreter declarations
|
|
for (tl::Registrar<lym::MacroInterpreter>::iterator cls = tl::Registrar<lym::MacroInterpreter>::begin (); cls != tl::Registrar<lym::MacroInterpreter>::end (); ++cls) {
|
|
if (! cls->suffix ().empty ()) {
|
|
filters << tl::to_qstring ("*." + cls->suffix ());
|
|
}
|
|
}
|
|
|
|
QStringList files = dir.entryList (filters, QDir::Files);
|
|
for (QStringList::ConstIterator f = files.begin (); f != files.end (); ++f) {
|
|
|
|
std::auto_ptr<lym::Macro> new_macro;
|
|
|
|
try {
|
|
|
|
std::string n = tl::to_string (QFileInfo (*f).baseName ());
|
|
|
|
Macro::Format format = Macro::NoFormat;
|
|
Macro::Interpreter interpreter = Macro::None;
|
|
std::string dsl_name;
|
|
bool autorun = false;
|
|
|
|
if (Macro::format_from_suffix (tl::to_string (*f), interpreter, dsl_name, autorun, format)) {
|
|
|
|
iterator mm = m_macros.find (n);
|
|
bool found = false;
|
|
while (mm != m_macros.end () && mm->first == n && ! found) {
|
|
if ((interpreter == Macro::None || mm->second->interpreter () == interpreter) &&
|
|
(dsl_name.empty () || mm->second->dsl_interpreter () == dsl_name) &&
|
|
mm->second->format () == format) {
|
|
found = true;
|
|
}
|
|
++mm;
|
|
}
|
|
if (! found) {
|
|
Macro *m = new Macro ();
|
|
new_macro.reset (m);
|
|
m->set_format (format);
|
|
m->set_autorun_default (autorun);
|
|
m->set_autorun (autorun);
|
|
m->set_interpreter (interpreter);
|
|
m->set_dsl_interpreter (dsl_name);
|
|
m->set_parent (this);
|
|
m->set_name (n);
|
|
m->load ();
|
|
m->set_readonly (m_readonly);
|
|
}
|
|
|
|
}
|
|
|
|
if (new_macro.get ()) {
|
|
m_macros.insert (std::make_pair (n, new_macro.release ()));
|
|
}
|
|
|
|
} catch (tl::Exception &ex) {
|
|
tl::error << "Reading " << tl::to_string (*f) << " in " << path << ": " << ex.msg ();
|
|
}
|
|
|
|
}
|
|
|
|
QStringList folders = dir.entryList (QDir::Dirs | QDir::NoDotAndDotDot);
|
|
for (QStringList::ConstIterator f = folders.begin (); f != folders.end (); ++f) {
|
|
|
|
try {
|
|
|
|
std::string n = tl::to_string (*f);
|
|
MacroCollection *&mc = m_folders.insert (std::make_pair (n, (MacroCollection *) 0)).first->second;
|
|
if (! mc) {
|
|
mc = new MacroCollection ();
|
|
mc->set_parent (this);
|
|
mc->set_name (n);
|
|
mc->set_virtual_mode (NotVirtual);
|
|
bool ro = (m_readonly || ! QFileInfo (dir.filePath (*f)).isWritable ());
|
|
mc->set_readonly (ro);
|
|
mc->scan (tl::to_string (dir.filePath (*f)));
|
|
}
|
|
|
|
} catch (tl::Exception &ex) {
|
|
tl::error << ex.msg ();
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void MacroCollection::erase (lym::Macro *mp)
|
|
{
|
|
for (iterator m = m_macros.begin (); m != m_macros.end (); ++m) {
|
|
if (m->second == mp) {
|
|
begin_changes ();
|
|
on_macro_deleted_here (mp);
|
|
m_macros.erase (m);
|
|
delete mp;
|
|
on_changed ();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MacroCollection::erase (lym::MacroCollection *mp)
|
|
{
|
|
for (child_iterator f = m_folders.begin (); f != m_folders.end (); ++f) {
|
|
if (f->second == mp) {
|
|
begin_changes ();
|
|
on_child_deleted (mp);
|
|
m_folders.erase (f);
|
|
delete mp;
|
|
on_changed ();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MacroCollection::erase (iterator i)
|
|
{
|
|
begin_changes ();
|
|
on_macro_deleted_here (i->second);
|
|
delete i->second;
|
|
m_macros.erase (i);
|
|
on_changed ();
|
|
}
|
|
|
|
void MacroCollection::erase (child_iterator i)
|
|
{
|
|
begin_changes ();
|
|
on_child_deleted (i->second);
|
|
delete i->second;
|
|
m_folders.erase (i);
|
|
on_changed ();
|
|
}
|
|
|
|
void MacroCollection::save ()
|
|
{
|
|
for (child_iterator f = m_folders.begin (); f != m_folders.end (); ++f) {
|
|
f->second->save ();
|
|
}
|
|
|
|
for (iterator m = m_macros.begin (); m != m_macros.end (); ++m) {
|
|
if (m->second->is_modified () && ! m->second->is_readonly () && ! m->second->path ().empty ()) {
|
|
try {
|
|
m->second->save ();
|
|
} catch (tl::Exception &ex) {
|
|
tl::error << ex.msg ();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MacroCollection::rename (const std::string &n)
|
|
{
|
|
if (tl::verbosity () >= 20) {
|
|
tl::info << "Renaming macro folder " << path () << " to " << n;
|
|
}
|
|
QFile f (tl::to_qstring (path ()));
|
|
begin_changes ();
|
|
if (! f.rename (QFileInfo (QDir (tl::to_qstring (mp_parent->path ())), tl::to_qstring (n)).filePath ())) {
|
|
on_changed ();
|
|
return false;
|
|
} else {
|
|
m_path = n;
|
|
on_changed ();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
lym::MacroCollection *MacroCollection::create_folder (const char *prefix, bool mkdir)
|
|
{
|
|
std::string name;
|
|
int n = 0;
|
|
do {
|
|
name = (prefix ? prefix : "new_folder");
|
|
if (n > 0) {
|
|
name += "_" + tl::to_string (n);
|
|
}
|
|
if (m_folders.find (name) == m_folders.end ()) {
|
|
break;
|
|
}
|
|
++n;
|
|
} while (true);
|
|
|
|
if (mkdir && ! QDir (tl::to_qstring (path ())).mkdir (tl::to_qstring (name))) {
|
|
return 0;
|
|
}
|
|
|
|
begin_changes ();
|
|
|
|
lym::MacroCollection *m = m_folders.insert (std::make_pair (name, new lym::MacroCollection ())).first->second;
|
|
m->set_virtual_mode (NotVirtual);
|
|
m->set_name (name);
|
|
m->set_parent (this);
|
|
|
|
on_changed ();
|
|
|
|
return m;
|
|
}
|
|
|
|
lym::Macro *MacroCollection::create (const char *prefix, Macro::Format format)
|
|
{
|
|
std::string name;
|
|
int n = 0;
|
|
do {
|
|
name = (prefix ? prefix : "new_macro");
|
|
if (n > 0) {
|
|
name += "_" + tl::to_string (n);
|
|
}
|
|
if (! macro_by_name (name, format)) {
|
|
break;
|
|
}
|
|
++n;
|
|
} while (true);
|
|
|
|
begin_changes ();
|
|
|
|
lym::Macro *m = m_macros.insert (std::make_pair (name, new lym::Macro ()))->second;
|
|
m->set_name (name);
|
|
m->set_parent (this);
|
|
|
|
on_changed ();
|
|
|
|
return m;
|
|
}
|
|
|
|
void MacroCollection::add_unspecific (lym::Macro *m)
|
|
{
|
|
begin_changes ();
|
|
m_macros.insert (std::make_pair (m->name (), m));
|
|
m->set_parent (this);
|
|
on_changed ();
|
|
}
|
|
|
|
bool MacroCollection::add (lym::Macro *m)
|
|
{
|
|
QDir d (tl::to_qstring (path ()));
|
|
QDir dd = QFileInfo (tl::to_qstring (m->path ())).dir ();
|
|
|
|
if (d == dd) {
|
|
|
|
begin_changes ();
|
|
m_macros.insert (std::make_pair (m->name (), m));
|
|
m->set_parent (this);
|
|
on_changed ();
|
|
return true;
|
|
|
|
} else {
|
|
|
|
for (child_iterator c = begin_children (); c != end_children (); ++c) {
|
|
if (c->second->add (m)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// try to detect new child folders. If that is the case, create that folder and add
|
|
// the macro there.
|
|
QDir dm (tl::to_qstring (m->dir ()));
|
|
while (true) {
|
|
|
|
std::string folder_name = tl::to_string (dm.dirName ());
|
|
if (! dm.cdUp ()) {
|
|
break;
|
|
}
|
|
|
|
if (dm == d) {
|
|
begin_changes ();
|
|
lym::MacroCollection *mc = m_folders.insert (std::make_pair (folder_name, new MacroCollection ())).first->second;
|
|
mc->set_virtual_mode (NotVirtual);
|
|
mc->set_parent (this);
|
|
on_changed ();
|
|
return mc->add (m);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool MacroCollection::del ()
|
|
{
|
|
if (tl::verbosity () >= 20) {
|
|
tl::info << "Deleting macro folder " << path ();
|
|
}
|
|
return QDir ().rmdir (tl::to_qstring (path ()));
|
|
}
|
|
|
|
void MacroCollection::rename_macro (Macro *macro, const std::string &new_name)
|
|
{
|
|
iterator m = m_macros.find (macro->name ());
|
|
while (m != m_macros.end () && m->first == macro->name ()) {
|
|
if (m->second == macro) {
|
|
m_macros.erase (m);
|
|
m_macros.insert (std::make_pair (new_name, macro));
|
|
return;
|
|
}
|
|
++m;
|
|
}
|
|
}
|
|
|
|
lym::Macro *MacroCollection::find_macro (const std::string &path)
|
|
{
|
|
for (iterator m = begin (); m != end (); ++m) {
|
|
if (m->second->path () == path) {
|
|
return m->second;
|
|
}
|
|
}
|
|
|
|
for (child_iterator mc = begin_children (); mc != end_children (); ++mc) {
|
|
lym::Macro *macro = mc->second->find_macro (path);
|
|
if (macro) {
|
|
return macro;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
MacroCollection &MacroCollection::root ()
|
|
{
|
|
return ms_root;
|
|
}
|
|
|
|
static bool sync_macros (lym::MacroCollection *current, lym::MacroCollection *actual)
|
|
{
|
|
bool ret = false;
|
|
|
|
if (actual) {
|
|
current->make_readonly (actual->is_readonly ());
|
|
}
|
|
|
|
std::vector<lym::MacroCollection *> folders_to_delete;
|
|
|
|
for (lym::MacroCollection::child_iterator m = current->begin_children (); m != current->end_children (); ++m) {
|
|
lym::MacroCollection *cm = actual ? actual->folder_by_name (m->first) : 0;
|
|
if (! cm) {
|
|
folders_to_delete.push_back (m->second);
|
|
}
|
|
}
|
|
|
|
if (actual) {
|
|
for (lym::MacroCollection::child_iterator m = actual->begin_children (); m != actual->end_children (); ++m) {
|
|
lym::MacroCollection *cm = current->folder_by_name (m->first);
|
|
if (! cm) {
|
|
cm = current->create_folder (m->first.c_str (), false);
|
|
ret = true;
|
|
}
|
|
if (sync_macros(cm, m->second)) {
|
|
ret = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// delete folders which do no longer exist
|
|
for (std::vector<lym::MacroCollection *>::iterator m = folders_to_delete.begin (); m != folders_to_delete.end (); ++m) {
|
|
ret = true;
|
|
sync_macros (*m, 0);
|
|
current->erase (*m);
|
|
}
|
|
|
|
std::vector<lym::Macro *> macros_to_delete;
|
|
|
|
for (lym::MacroCollection::iterator m = current->begin (); m != current->end (); ++m) {
|
|
lym::Macro *cm = actual ? actual->macro_by_name (m->first, m->second->format ()) : 0;
|
|
if (! cm) {
|
|
macros_to_delete.push_back (m->second);
|
|
}
|
|
}
|
|
|
|
if (actual) {
|
|
for (lym::MacroCollection::iterator m = actual->begin (); m != actual->end (); ++m) {
|
|
lym::Macro *cm = current->macro_by_name (m->first, m->second->format ());
|
|
if (cm) {
|
|
if (*cm != *m->second) {
|
|
cm->assign (*m->second);
|
|
}
|
|
cm->set_readonly (m->second->is_readonly ());
|
|
} else {
|
|
cm = current->create (m->first.c_str (), m->second->format ());
|
|
cm->assign (*m->second);
|
|
cm->set_readonly (m->second->is_readonly ());
|
|
ret = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// erase macros from collection which are no longer used
|
|
for (std::vector<lym::Macro *>::const_iterator m = macros_to_delete.begin (); m != macros_to_delete.end (); ++m) {
|
|
current->erase (*m);
|
|
ret = true;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void MacroCollection::reload ()
|
|
{
|
|
// create a new collection and synchronize
|
|
|
|
lym::MacroCollection new_collection;
|
|
for (lym::MacroCollection::child_iterator c = begin_children (); c != end_children (); ++c) {
|
|
new_collection.add_folder (c->second->description (), c->second->path (), c->second->category (), c->second->is_readonly (), false /* don't force to create */);
|
|
}
|
|
|
|
// and synchronize current with the actual one
|
|
sync_macros (this, &new_collection);
|
|
}
|
|
|
|
static bool has_autorun_for (const lym::MacroCollection &collection, bool early)
|
|
{
|
|
for (lym::MacroCollection::const_child_iterator c = collection.begin_children (); c != collection.end_children (); ++c) {
|
|
if (has_autorun_for (*c->second, early)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (lym::MacroCollection::const_iterator c = collection.begin (); c != collection.end (); ++c) {
|
|
if ((early && c->second->is_autorun_early ()) || (!early && c->second->is_autorun () && !c->second->is_autorun_early ())) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool MacroCollection::has_autorun () const
|
|
{
|
|
return has_autorun_for (*this, false);
|
|
}
|
|
|
|
static void autorun_for (lym::MacroCollection &collection, bool early)
|
|
{
|
|
for (lym::MacroCollection::child_iterator c = collection.begin_children (); c != collection.end_children (); ++c) {
|
|
autorun_for (*c->second, early);
|
|
}
|
|
|
|
for (lym::MacroCollection::iterator c = collection.begin (); c != collection.end (); ++c) {
|
|
if (((early && c->second->is_autorun_early ()) || (!early && c->second->is_autorun () && !c->second->is_autorun_early ())) && c->second->can_run ()) {
|
|
BEGIN_PROTECTED_SILENT
|
|
c->second->install_doc ();
|
|
c->second->run ();
|
|
END_PROTECTED_SILENT
|
|
}
|
|
}
|
|
}
|
|
|
|
void MacroCollection::autorun ()
|
|
{
|
|
autorun_for (*this, false);
|
|
}
|
|
|
|
void MacroCollection::autorun_early ()
|
|
{
|
|
autorun_for (*this, true);
|
|
}
|
|
|
|
void MacroCollection::dump (int l)
|
|
{
|
|
for (int i = 0; i < l; ++i) { printf (" "); }
|
|
printf ("----\n");
|
|
for (int i = 0; i < l; ++i) { printf (" "); }
|
|
printf ("Collection: %s\n", name ().c_str ());
|
|
for (int i = 0; i < l; ++i) { printf (" "); }
|
|
printf ("Collection-path: %s\n", path ().c_str ());
|
|
for (int i = 0; i < l; ++i) { printf (" "); }
|
|
printf ("Collection-description: %s\n", description ().c_str ());
|
|
for (int i = 0; i < l; ++i) { printf (" "); }
|
|
printf("Collection-readonly: %d\n", is_readonly ());
|
|
printf ("\n");
|
|
|
|
for (iterator m = begin (); m != end (); ++m) {
|
|
for (int i = 0; i < l; ++i) { printf (" "); }
|
|
printf("Name: %s%s\n", m->second->name ().c_str (), m->second->is_modified() ? "*" : "");
|
|
for (int i = 0; i < l; ++i) { printf (" "); }
|
|
printf(" Path: %s\n", m->second->path ().c_str ());
|
|
for (int i = 0; i < l; ++i) { printf (" "); }
|
|
printf(" Readonly: %d\n", m->second->is_readonly ());
|
|
for (int i = 0; i < l; ++i) { printf (" "); }
|
|
printf(" Autorun: %d\n", m->second->is_autorun ());
|
|
for (int i = 0; i < l; ++i) { printf (" "); }
|
|
printf(" Autorun-early: %d\n", m->second->is_autorun_early ());
|
|
for (int i = 0; i < l; ++i) { printf (" "); }
|
|
printf(" Description: %s\n", m->second->description ().c_str ());
|
|
}
|
|
|
|
for (child_iterator m = begin_children (); m != end_children (); ++m) {
|
|
m->second->dump (l + 1);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
MacroSignalAdaptor::MacroSignalAdaptor (QObject *parent, Macro *macro)
|
|
: QObject (parent), mp_macro (macro)
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
void MacroSignalAdaptor::run ()
|
|
{
|
|
BEGIN_PROTECTED
|
|
if (mp_macro) {
|
|
mp_macro->run ();
|
|
}
|
|
END_PROTECTED
|
|
}
|
|
|
|
}
|
|
|