mirror of https://github.com/KLayout/klayout.git
Added methods to manipulate key bindings and menu visibility through scripts (MainWindow#set_key_bindings, MainWindow#get_key_bindings, MainWindow#set_menu_items_hidden ...)
This commit is contained in:
parent
6a23769387
commit
c7113b8c72
|
|
@ -23,6 +23,7 @@
|
||||||
#include "gsiDecl.h"
|
#include "gsiDecl.h"
|
||||||
#include "gsiSignals.h"
|
#include "gsiSignals.h"
|
||||||
#include "layMainWindow.h"
|
#include "layMainWindow.h"
|
||||||
|
#include "layConfig.h"
|
||||||
|
|
||||||
#if defined(HAVE_QTBINDINGS)
|
#if defined(HAVE_QTBINDINGS)
|
||||||
# include "gsiQtGuiExternals.h"
|
# include "gsiQtGuiExternals.h"
|
||||||
|
|
@ -230,12 +231,99 @@ get_config_names (lay::MainWindow *mw)
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void
|
||||||
config_end (lay::MainWindow *mw)
|
config_end (lay::MainWindow *mw)
|
||||||
{
|
{
|
||||||
mw->dispatcher ()->config_end ();
|
mw->dispatcher ()->config_end ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
set_key_bindings (lay::MainWindow *mw, const std::map<std::string, std::string> &bindings)
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> b = mw->menu ()->get_shortcuts (false);
|
||||||
|
std::map<std::string, std::string> b_def = mw->menu ()->get_shortcuts (true);
|
||||||
|
|
||||||
|
for (std::map<std::string, std::string>::const_iterator i = bindings.begin (); i != bindings.end (); ++i) {
|
||||||
|
b[i->first] = i->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cfg_key_bindings needs a special notation: lay::Action::no_shortcut () to force "none" instead of default
|
||||||
|
// and and empty string to restore default.
|
||||||
|
for (std::map<std::string, std::string>::iterator i = b.begin (); i != b.end (); ++i) {
|
||||||
|
std::map<std::string, std::string>::const_iterator j = b_def.find (i->first);
|
||||||
|
if (j != b_def.end ()) {
|
||||||
|
if (i->second != j->second) {
|
||||||
|
if (i->second.empty ()) {
|
||||||
|
i->second = lay::Action::no_shortcut ();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i->second.clear ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<std::string, std::string> > bv (b.begin (), b.end ());
|
||||||
|
mw->dispatcher ()->config_set (lay::cfg_key_bindings, lay::pack_key_binding (bv));
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::map<std::string, std::string>
|
||||||
|
get_key_bindings (lay::MainWindow *mw)
|
||||||
|
{
|
||||||
|
return mw->menu ()->get_shortcuts (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::map<std::string, std::string>
|
||||||
|
get_default_key_bindings (lay::MainWindow *mw)
|
||||||
|
{
|
||||||
|
return mw->menu ()->get_shortcuts (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static std::map<std::string, bool>
|
||||||
|
get_menu_items_hidden (lay::MainWindow *mw)
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> kb = get_key_bindings (mw);
|
||||||
|
std::map<std::string, bool> h;
|
||||||
|
|
||||||
|
if (mw->dispatcher ()->menu ()) {
|
||||||
|
for (std::map<std::string, std::string>::const_iterator i = kb.begin (); i != kb.end (); ++i) {
|
||||||
|
lay::Action *a = mw->dispatcher ()->menu ()->action (i->first);
|
||||||
|
if (a) {
|
||||||
|
h.insert (std::make_pair (i->first, a->is_hidden ()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::map<std::string, bool>
|
||||||
|
get_default_menu_items_hidden (lay::MainWindow *mw)
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> kb = get_key_bindings (mw);
|
||||||
|
|
||||||
|
// currently, all menu items are visible by default
|
||||||
|
std::map<std::string, bool> h;
|
||||||
|
for (std::map<std::string, std::string>::const_iterator i = kb.begin (); i != kb.end (); ++i) {
|
||||||
|
h.insert (std::make_pair (i->first, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
set_menu_items_hidden (lay::MainWindow *mw, const std::map<std::string, bool> &hidden)
|
||||||
|
{
|
||||||
|
std::map<std::string, bool> h = get_menu_items_hidden (mw);
|
||||||
|
for (std::map<std::string, bool>::const_iterator i = hidden.begin (); i != hidden.end (); ++i) {
|
||||||
|
h[i->first] = i->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<std::string, bool> > hv;
|
||||||
|
hv.insert (hv.end (), h.begin (), h.end ());
|
||||||
|
mw->dispatcher ()->config_set (lay::cfg_menu_items_hidden, lay::pack_menu_items_hidden (hv));
|
||||||
|
}
|
||||||
|
|
||||||
Class<lay::MainWindow> decl_MainWindow (QT_EXTERNAL_BASE (QMainWindow) "lay", "MainWindow",
|
Class<lay::MainWindow> decl_MainWindow (QT_EXTERNAL_BASE (QMainWindow) "lay", "MainWindow",
|
||||||
|
|
||||||
// Dispatcher interface and convenience functions
|
// Dispatcher interface and convenience functions
|
||||||
|
|
@ -245,31 +333,121 @@ Class<lay::MainWindow> decl_MainWindow (QT_EXTERNAL_BASE (QMainWindow) "lay", "M
|
||||||
) +
|
) +
|
||||||
method_ext ("clear_config", &clear_config,
|
method_ext ("clear_config", &clear_config,
|
||||||
"@brief Clears the configuration parameters\n"
|
"@brief Clears the configuration parameters\n"
|
||||||
"Since version 0.27 this is a convenience method which is equivalent to 'dispatcher().clear_config()'. See \\Dispatcher#clear_config for details."
|
"This method is provided for using MainWindow without an Application object. "
|
||||||
|
"It's a convience method which is equivalent to 'dispatcher().clear_config()'. See \\Dispatcher#clear_config for details.\n"
|
||||||
|
"\n"
|
||||||
|
"This method has been introduced in version 0.27.\n"
|
||||||
) +
|
) +
|
||||||
method_ext ("write_config", &write_config, gsi::arg ("file_name"),
|
method_ext ("write_config", &write_config, gsi::arg ("file_name"),
|
||||||
"@brief Writes configuration to a file\n"
|
"@brief Writes configuration to a file\n"
|
||||||
"Since version 0.27 this is a convenience method which is equivalent to 'dispatcher().write_config(...)'. See \\Dispatcher#write_config for details."
|
"This method is provided for using MainWindow without an Application object. "
|
||||||
|
"It's a convience method which is equivalent to 'dispatcher().write_config(...)'. See \\Dispatcher#write_config for details.\n"
|
||||||
|
"\n"
|
||||||
|
"This method has been introduced in version 0.27.\n"
|
||||||
) +
|
) +
|
||||||
method_ext ("read_config", &read_config, gsi::arg ("file_name"),
|
method_ext ("read_config", &read_config, gsi::arg ("file_name"),
|
||||||
"@brief Reads the configuration from a file\n"
|
"@brief Reads the configuration from a file\n"
|
||||||
"Since version 0.27 this is a convenience method which is equivalent to 'dispatcher().read_config(...)'. See \\Dispatcher#read_config for details."
|
"This method is provided for using MainWindow without an Application object. "
|
||||||
|
"It's a convience method which is equivalent to 'dispatcher().read_config(...)'. See \\Dispatcher#read_config for details.\n"
|
||||||
|
"\n"
|
||||||
|
"This method has been introduced in version 0.27.\n"
|
||||||
) +
|
) +
|
||||||
method_ext ("get_config", &get_config, gsi::arg ("name"),
|
method_ext ("get_config", &get_config, gsi::arg ("name"),
|
||||||
"@brief Gets the value of a local configuration parameter\n"
|
"@brief Gets the value of a local configuration parameter\n"
|
||||||
"Since version 0.27 this is a convenience method which is equivalent to 'dispatcher().get_config(...)'. See \\Dispatcher#get_config for details."
|
"This method is provided for using MainWindow without an Application object. "
|
||||||
|
"It's a convience method which is equivalent to 'dispatcher().get_config(...)'. See \\Dispatcher#get_config for details.\n"
|
||||||
|
"\n"
|
||||||
|
"This method has been introduced in version 0.27.\n"
|
||||||
) +
|
) +
|
||||||
method_ext ("set_config", &set_config, gsi::arg ("name"), gsi::arg ("value"),
|
method_ext ("set_config", &set_config, gsi::arg ("name"), gsi::arg ("value"),
|
||||||
"@brief Set a local configuration parameter with the given name to the given value\n"
|
"@brief Set a local configuration parameter with the given name to the given value\n"
|
||||||
"Since version 0.27 this is a convenience method which is equivalent to 'dispatcher().set_config(...)'. See \\Dispatcher#set_config for details."
|
"This method is provided for using MainWindow without an Application object. "
|
||||||
|
"It's a convience method which is equivalent to 'dispatcher().set_config(...)'. See \\Dispatcher#set_config for details.\n"
|
||||||
|
"\n"
|
||||||
|
"This method has been introduced in version 0.27.\n"
|
||||||
) +
|
) +
|
||||||
method_ext ("get_config_names", &get_config_names,
|
method_ext ("get_config_names", &get_config_names,
|
||||||
"@brief Gets the configuration parameter names\n"
|
"@brief Gets the configuration parameter names\n"
|
||||||
"Since version 0.27 this is a convenience method which is equivalent to 'dispatcher().get_config_names(...)'. See \\Dispatcher#get_config_names for details."
|
"This method is provided for using MainWindow without an Application object. "
|
||||||
|
"It's a convience method which is equivalent to 'dispatcher().get_config_names(...)'. See \\Dispatcher#get_config_names for details.\n"
|
||||||
|
"\n"
|
||||||
|
"This method has been introduced in version 0.27.\n"
|
||||||
) +
|
) +
|
||||||
method_ext ("commit_config", &config_end,
|
method_ext ("commit_config", &config_end,
|
||||||
"@brief Commits the configuration settings\n"
|
"@brief Commits the configuration settings\n"
|
||||||
"Since version 0.27 this is a convenience method which is equivalent to 'dispatcher().config_end(...)'. See \\Dispatcher#config_end for details."
|
"This method is provided for using MainWindow without an Application object. "
|
||||||
|
"It's a convience method which is equivalent to 'dispatcher().config_end(...)'. See \\Dispatcher#config_end for details.\n"
|
||||||
|
"\n"
|
||||||
|
"This method has been introduced in version 0.27.\n"
|
||||||
|
) +
|
||||||
|
|
||||||
|
// key binding configuration
|
||||||
|
gsi::method_ext ("get_key_bindings", &get_key_bindings,
|
||||||
|
"@brief Gets the current key bindings\n"
|
||||||
|
"This method returns a hash with the key binding vs. menu item path.\n"
|
||||||
|
"\n"
|
||||||
|
"This method has been introduced in version 0.27.\n"
|
||||||
|
) +
|
||||||
|
gsi::method_ext ("get_default_key_bindings", &get_default_key_bindings,
|
||||||
|
"@brief Gets the default key bindings\n"
|
||||||
|
"This method returns a hash with the default key binding vs. menu item path.\n"
|
||||||
|
"You can use this hash with \\set_key_bindings to reset all key bindings to the default ones.\n"
|
||||||
|
"\n"
|
||||||
|
"This method has been introduced in version 0.27.\n"
|
||||||
|
) +
|
||||||
|
gsi::method_ext ("set_key_bindings", &set_key_bindings, gsi::arg ("bindings"),
|
||||||
|
"@brief Sets key bindings.\n"
|
||||||
|
"Sets the given key bindings. "
|
||||||
|
"Pass a hash listing the key bindings per menu item paths. Key strings follow the usual notation, e.g. 'Ctrl+A', 'Shift+X' or just 'F2'.\n"
|
||||||
|
"Use an empty value to remove a key binding from a menu entry.\n"
|
||||||
|
"\n"
|
||||||
|
"\\get_key_bindings will give you the current key bindings, \\get_default_key_bindings will give you the default ones.\n"
|
||||||
|
"\n"
|
||||||
|
"Examples:\n"
|
||||||
|
"\n"
|
||||||
|
"@code\n"
|
||||||
|
"# reset all key bindings to default:\n"
|
||||||
|
"mw = RBA::MainWindow.instance()\n"
|
||||||
|
"mw.set_key_bindings(mw.get_default_key_bindings())\n"
|
||||||
|
"\n"
|
||||||
|
"# disable key binding for 'copy':\n"
|
||||||
|
"RBA::MainWindow.instance.set_key_bindings({ \"edit_menu.copy\" => \"\" })\n"
|
||||||
|
"\n"
|
||||||
|
"# configure 'copy' to use Shift+K and 'cut' to use Ctrl+K:\n"
|
||||||
|
"RBA::MainWindow.instance.set_key_bindings({ \"edit_menu.copy\" => \"Shift+K\", \"edit_menu.cut\" => \"Ctrl+K\" })\n"
|
||||||
|
"@/code\n"
|
||||||
|
"\n"
|
||||||
|
"This method has been introduced in version 0.27.\n"
|
||||||
|
) +
|
||||||
|
gsi::method_ext ("get_menu_items_hidden", &get_menu_items_hidden,
|
||||||
|
"@brief Gets the flags indicating whether menu items are hidden\n"
|
||||||
|
"This method returns a hash with the hidden flag vs. menu item path.\n"
|
||||||
|
"You can use this hash with \\set_menu_items_hidden.\n"
|
||||||
|
"\n"
|
||||||
|
"This method has been introduced in version 0.27.\n"
|
||||||
|
) +
|
||||||
|
gsi::method_ext ("get_default_menu_items_hidden", &get_default_menu_items_hidden,
|
||||||
|
"@brief Gets the flags indicating whether menu items are hidden by default\n"
|
||||||
|
"You can use this hash with \\set_menu_items_hidden to restore the visibility of all menu items.\n"
|
||||||
|
"\n"
|
||||||
|
"This method has been introduced in version 0.27.\n"
|
||||||
|
) +
|
||||||
|
gsi::method_ext ("set_menu_items_hidden", &set_menu_items_hidden,
|
||||||
|
"@brief sets the flags indicating whether menu items are hidden\n"
|
||||||
|
"This method allows hiding certain menu items. It takes a hash with hidden flags vs. menu item paths. "
|
||||||
|
"\n"
|
||||||
|
"Examples:\n"
|
||||||
|
"\n"
|
||||||
|
"@code\n"
|
||||||
|
"# show all menu items:\n"
|
||||||
|
"mw = RBA::MainWindow.instance()\n"
|
||||||
|
"mw.set_menu_items_hidden(mw.get_default_menu_items_hidden())\n"
|
||||||
|
"\n"
|
||||||
|
"# hide the 'copy' entry from the 'Edit' menu:\n"
|
||||||
|
"RBA::MainWindow.instance().set_menu_items_hidden({ \"edit_menu.copy\" => true })\n"
|
||||||
|
"@/code\n"
|
||||||
|
"\n"
|
||||||
|
"This method has been introduced in version 0.27.\n"
|
||||||
) +
|
) +
|
||||||
|
|
||||||
// QMainWindow interface
|
// QMainWindow interface
|
||||||
|
|
|
||||||
|
|
@ -356,27 +356,6 @@ CustomizeMenuConfigPage::~CustomizeMenuConfigPage ()
|
||||||
mp_ui = 0;
|
mp_ui = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void get_shortcuts (const lay::AbstractMenu &menu, const std::string &root, std::map<std::string, std::string> &bindings, bool with_defaults)
|
|
||||||
{
|
|
||||||
std::vector<std::string> items = menu.items (root);
|
|
||||||
for (std::vector<std::string>::const_iterator i = items.begin (); i != items.end (); ++i) {
|
|
||||||
if (i->size () > 0) {
|
|
||||||
if (menu.is_valid (*i) && menu.action (*i)->is_visible ()) {
|
|
||||||
if (menu.is_menu (*i)) {
|
|
||||||
// a menu must be listed (so it can be hidden), but does not have a shortcut
|
|
||||||
// but we don't include special menus
|
|
||||||
if (i->at (0) != '@') {
|
|
||||||
bindings.insert (std::make_pair (*i, std::string ()));
|
|
||||||
}
|
|
||||||
get_shortcuts (menu, *i, bindings, with_defaults);
|
|
||||||
} else if (! menu.is_separator (*i)) {
|
|
||||||
bindings.insert (std::make_pair (*i, with_defaults ? menu.action (*i)->get_default_shortcut () : menu.action (*i)->get_effective_shortcut ()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
CustomizeMenuConfigPage::reset_clicked ()
|
CustomizeMenuConfigPage::reset_clicked ()
|
||||||
{
|
{
|
||||||
|
|
@ -405,12 +384,10 @@ CustomizeMenuConfigPage::apply (const std::vector<std::pair<std::string, std::st
|
||||||
m_paths_for_action.clear ();
|
m_paths_for_action.clear ();
|
||||||
|
|
||||||
// get the current bindings
|
// get the current bindings
|
||||||
m_current_bindings.clear ();
|
m_current_bindings = mp_dispatcher->menu ()->get_shortcuts (false);
|
||||||
get_shortcuts (*mp_dispatcher->menu (), std::string (), m_current_bindings, false);
|
|
||||||
|
|
||||||
// get the default bindings
|
// get the default bindings
|
||||||
std::map<std::string, std::string> default_bindings;
|
std::map<std::string, std::string> default_bindings = mp_dispatcher->menu ()->get_shortcuts (true);
|
||||||
get_shortcuts (*mp_dispatcher->menu (), std::string (), default_bindings, true);
|
|
||||||
|
|
||||||
m_enable_event = false;
|
m_enable_event = false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1677,4 +1677,26 @@ AbstractMenu::collect_configure_actions (std::vector<lay::ConfigureAction *> &ca
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
AbstractMenu::get_shortcuts (const std::string &root, std::map<std::string, std::string> &bindings, bool with_defaults)
|
||||||
|
{
|
||||||
|
std::vector<std::string> items = this->items (root);
|
||||||
|
for (std::vector<std::string>::const_iterator i = items.begin (); i != items.end (); ++i) {
|
||||||
|
if (i->size () > 0) {
|
||||||
|
if (is_valid (*i) && action (*i)->is_visible ()) {
|
||||||
|
if (is_menu (*i)) {
|
||||||
|
// a menu must be listed (so it can be hidden), but does not have a shortcut
|
||||||
|
// but we don't include special menus
|
||||||
|
if (i->at (0) != '@') {
|
||||||
|
bindings.insert (std::make_pair (*i, std::string ()));
|
||||||
|
}
|
||||||
|
get_shortcuts (*i, bindings, with_defaults);
|
||||||
|
} else if (! is_separator (*i)) {
|
||||||
|
bindings.insert (std::make_pair (*i, with_defaults ? action (*i)->get_default_shortcut () : action (*i)->get_effective_shortcut ()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -773,6 +773,18 @@ public:
|
||||||
*/
|
*/
|
||||||
QActionGroup *make_exclusive_group (const std::string &name);
|
QActionGroup *make_exclusive_group (const std::string &name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the keyboard shortcuts
|
||||||
|
* @param with_defaults Returns the default shortcuts if true. Otherwise returns the effective shortcut.
|
||||||
|
* @return a hash with menu paths for keys and key binding for values
|
||||||
|
*/
|
||||||
|
std::map<std::string, std::string> get_shortcuts (bool with_defaults)
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> b;
|
||||||
|
get_shortcuts (std::string (), b, with_defaults);
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Gets the root node of the menu
|
* @brief Gets the root node of the menu
|
||||||
*/
|
*/
|
||||||
|
|
@ -798,6 +810,7 @@ private:
|
||||||
void collect_group (std::vector<std::string> &grp, const std::string &name, const AbstractMenuItem &item) const;
|
void collect_group (std::vector<std::string> &grp, const std::string &name, const AbstractMenuItem &item) const;
|
||||||
void collect_configure_actions (std::vector<ConfigureAction *> &ca, AbstractMenuItem &item);
|
void collect_configure_actions (std::vector<ConfigureAction *> &ca, AbstractMenuItem &item);
|
||||||
void emit_changed ();
|
void emit_changed ();
|
||||||
|
void get_shortcuts (const std::string &root, std::map<std::string, std::string> &bindings, bool with_defaults);
|
||||||
|
|
||||||
Dispatcher *mp_dispatcher;
|
Dispatcher *mp_dispatcher;
|
||||||
AbstractMenuItem m_root;
|
AbstractMenuItem m_root;
|
||||||
|
|
|
||||||
|
|
@ -383,6 +383,42 @@ RESULT
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_6
|
||||||
|
|
||||||
|
app = RBA::Application.instance
|
||||||
|
mw = app.main_window
|
||||||
|
|
||||||
|
assert_equal(mw.get_key_bindings["file_menu.exit"], "Ctrl+Q")
|
||||||
|
|
||||||
|
# key bindings
|
||||||
|
|
||||||
|
mw.set_key_bindings({"file_menu.exit" => "F2"})
|
||||||
|
assert_equal(mw.get_key_bindings["file_menu.exit"], "F2")
|
||||||
|
|
||||||
|
mw.set_key_bindings({"file_menu.exit" => ""})
|
||||||
|
assert_equal(mw.get_key_bindings["file_menu.exit"], "")
|
||||||
|
|
||||||
|
mw.set_key_bindings(mw.get_default_key_bindings)
|
||||||
|
assert_equal(mw.get_key_bindings["file_menu.exit"], "Ctrl+Q")
|
||||||
|
|
||||||
|
mw.set_key_bindings({"file_menu.exit" => ""})
|
||||||
|
assert_equal(mw.get_key_bindings["file_menu.exit"], "")
|
||||||
|
|
||||||
|
# menu items hidden
|
||||||
|
|
||||||
|
assert_equal(mw.get_menu_items_hidden["file_menu.exit"], false)
|
||||||
|
|
||||||
|
mw.set_menu_items_hidden({"file_menu.exit" => true})
|
||||||
|
assert_equal(mw.get_menu_items_hidden["file_menu.exit"], true)
|
||||||
|
|
||||||
|
mw.set_menu_items_hidden(mw.get_default_menu_items_hidden)
|
||||||
|
assert_equal(mw.get_menu_items_hidden["file_menu.exit"], false)
|
||||||
|
|
||||||
|
mw.set_menu_items_hidden({"file_menu.exit" => true})
|
||||||
|
assert_equal(mw.get_menu_items_hidden["file_menu.exit"], true)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
load("test_epilogue.rb")
|
load("test_epilogue.rb")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue