diff --git a/aegisub/src/auto4_base.cpp b/aegisub/src/auto4_base.cpp index 74619f5ef..7cb6b0eb0 100644 --- a/aegisub/src/auto4_base.cpp +++ b/aegisub/src/auto4_base.cpp @@ -325,6 +325,8 @@ namespace Automation4 { { if (find(scripts.begin(), scripts.end(), script) == scripts.end()) scripts.push_back(script); + + ScriptsChanged(); } void ScriptManager::Remove(Script *script) @@ -334,11 +336,14 @@ namespace Automation4 { delete *i; scripts.erase(i); } + + ScriptsChanged(); } void ScriptManager::RemoveAll() { delete_clear(scripts); + ScriptsChanged(); } const std::vector& ScriptManager::GetMacros() @@ -361,7 +366,7 @@ namespace Automation4 { void AutoloadScriptManager::Reload() { - RemoveAll(); + delete_clear(scripts); int error_count = 0; @@ -386,7 +391,7 @@ namespace Automation4 { wxString fullpath = script_path.GetFullPath(); if (ScriptFactory::CanHandleScriptFormat(fullpath)) { Script *s = ScriptFactory::CreateFromFile(fullpath, true); - Add(s); + scripts.push_back(s); if (!s->GetLoadedState()) error_count++; } more = dir.GetNext(&fn); @@ -395,6 +400,8 @@ namespace Automation4 { if (error_count > 0) { wxLogWarning("One or more scripts placed in the Automation autoload directory failed to load\nPlease review the errors above, correct them and use the Reload Autoload dir button in Automation Manager to attempt loading the scripts again."); } + + ScriptsChanged(); } LocalScriptManager::LocalScriptManager(agi::Context *c) @@ -406,7 +413,7 @@ namespace Automation4 { void LocalScriptManager::Reload() { - RemoveAll(); + delete_clear(scripts); wxString local_scripts = context->ass->GetScriptInfo("Automation Scripts"); if (local_scripts.empty()) return; @@ -434,12 +441,15 @@ namespace Automation4 { wxFileName sfname(trimmed); sfname.MakeAbsolute(basepath); if (sfname.FileExists()) { - Add(Automation4::ScriptFactory::CreateFromFile(sfname.GetFullPath(), true)); - } else { + scripts.push_back(Automation4::ScriptFactory::CreateFromFile(sfname.GetFullPath(), true)); + } + else { wxLogWarning("Automation Script referenced could not be found.\nFilename specified: %c%s\nSearched relative to: %s\nResolved filename: %s", first_char, trimmed, basepath, sfname.GetFullPath()); } } + + ScriptsChanged(); } void LocalScriptManager::OnSubtitlesSave() diff --git a/aegisub/src/auto4_base.h b/aegisub/src/auto4_base.h index b85a07115..a6cb23b85 100644 --- a/aegisub/src/auto4_base.h +++ b/aegisub/src/auto4_base.h @@ -206,9 +206,12 @@ namespace Automation4 { /// A manager of loaded automation scripts class ScriptManager { + protected: std::vector scripts; std::vector macros; + agi::signal::Signal<> ScriptsChanged; + public: /// Deletes all scripts managed virtual ~ScriptManager(); @@ -227,6 +230,8 @@ namespace Automation4 { const std::vector& GetMacros(); // No need to have getters for the other kinds of features, I think. // They automatically register themselves in the relevant places. + + DEFINE_SIGNAL_ADDERS(ScriptsChanged, AddScriptChangeListener) }; /// Manager for scripts specified by a subtitle file diff --git a/aegisub/src/libresrc/default_menu.json b/aegisub/src/libresrc/default_menu.json index 0df733441..d1c7d4296 100644 --- a/aegisub/src/libresrc/default_menu.json +++ b/aegisub/src/libresrc/default_menu.json @@ -26,15 +26,15 @@ { "command" : "edit/line/delete" } ], "main" : [ - { "submenu" : "main/file", "text" : "&File" }, - { "submenu" : "main/edit", "text" : "&Edit" }, - { "submenu" : "main/subtitle", "text" : "&Subtitle" }, - { "submenu" : "main/timing", "text" : "&Timing" }, - { "submenu" : "main/video", "text" : "&Video" }, - { "submenu" : "main/audio", "text" : "&Audio" }, - { "submenu" : "main/automation", "text" : "A&utomation" }, - { "submenu" : "main/view", "text" : "&View" }, - { "submenu" : "main/help", "text" : "&Help", "special" : "help" } + { "submenu" : "main/file", "text" : "&File" }, + { "submenu" : "main/edit", "text" : "&Edit" }, + { "submenu" : "main/subtitle", "text" : "&Subtitle" }, + { "submenu" : "main/timing", "text" : "&Timing" }, + { "submenu" : "main/video", "text" : "&Video" }, + { "submenu" : "main/audio", "text" : "&Audio" }, + { "special" : "automation", "text" : "A&utomation" }, + { "submenu" : "main/view", "text" : "&View" }, + { "submenu" : "main/help", "text" : "&Help", "special" : "help" } ], "main/file" : [ { "command" : "subtitle/new" }, @@ -168,10 +168,6 @@ { "command" : "audio/open/blank" }, { "command" : "audio/open/noise" } ], - "main/automation" : [ - { "command" : "am/manager" }, - {} - ], "main/view" : [ { "command" : "app/language" }, { "command" : "app/options", "special" : "options" }, diff --git a/aegisub/src/menu.cpp b/aegisub/src/menu.cpp index 23a39ffe1..a3e829d18 100644 --- a/aegisub/src/menu.cpp +++ b/aegisub/src/menu.cpp @@ -22,8 +22,10 @@ #include "include/aegisub/menu.h" +#include "include/aegisub/context.h" #include "include/aegisub/hotkey.h" +#include "auto4_base.h" #include "command/command.h" #include "compat.h" #include "libresrc/libresrc.h" @@ -113,6 +115,14 @@ public: } }; +struct menu_item_cmp { + wxMenuItem *item; + menu_item_cmp(wxMenuItem *item) : item(item) { } + bool operator()(std::pair o) const { + return o.second == item; + } +}; + /// @class CommandManager /// @brief Event dispatcher to update menus on open and handle click events /// @@ -171,6 +181,14 @@ public: return item->GetId(); } + /// Unregister a dynamic menu item + void Remove(wxMenuItem *item) { + std::deque >::iterator it = + find_if(dynamic_items.begin(), dynamic_items.end(), menu_item_cmp(item)); + if (it != dynamic_items.end()) + dynamic_items.erase(it); + } + /// Create a MRU menu and register the needed handlers /// @param name MRU type /// @param parent Menu to append the new MRU menu to @@ -280,12 +298,6 @@ void process_menu_item(wxMenu *parent, agi::Context *c, json::Object const& ele, return; } - if (special == "automation") { - /// @todo Actually implement this - parent->Append(-1, _("No Automation macros loaded"))->Enable(false); - return; - } - if (!read_entry(ele, "command", &command)) return; @@ -325,6 +337,51 @@ wxMenu *build_menu(std::string const& name, agi::Context *c, CommandManager *cm, return menu; } +struct comp_str_menu { + agi::Context *c; + comp_str_menu(agi::Context *c) : c(c) { } + bool operator()(const cmd::Command *lft, const cmd::Command *rgt) const { + return lft->StrMenu(c) < rgt->StrMenu(c); + } +}; + +class AutomationMenu : public wxMenu { + agi::Context *c; + CommandManager *cm; + agi::signal::Connection global_slot; + agi::signal::Connection local_slot; + + void Regenerate() { + wxMenuItemList &items = GetMenuItems(); + for (size_t i = items.size() - 1; i >= 2; --i) { + cm->Remove(items[i]); + Delete(items[i]); + } + + std::vector macros = wxGetApp().global_scripts->GetMacros(); + std::vector local_macros = c->local_scripts->GetMacros(); + copy(local_macros.begin(), local_macros.end(), back_inserter(macros)); + sort(macros.begin(), macros.end(), comp_str_menu(c)); + + if (macros.empty()) + Append(-1, _("No Automation macros loaded"))->Enable(false); + else { + for (size_t i = 0; i < macros.size(); ++i) + cm->AddCommand(macros[i], this, ""); + } + } +public: + AutomationMenu(agi::Context *c, CommandManager *cm) + : c(c) + , cm(cm) + , global_slot(wxGetApp().global_scripts->AddScriptChangeListener(&AutomationMenu::Regenerate, this)) + , local_slot(c->local_scripts->AddScriptChangeListener(&AutomationMenu::Regenerate, this)) + { + cm->AddCommand(cmd::get("am/manager"), this, ""); + AppendSeparator(); + Regenerate(); + } +}; } namespace menu { @@ -336,7 +393,14 @@ namespace menu { std::string submenu, disp; read_entry(*it, "submenu", &submenu); read_entry(*it, "text", &disp); - menu->Append(build_menu(submenu, c, &menu->cm), lagi_wxString(disp)); + if (!submenu.empty()) { + menu->Append(build_menu(submenu, c, &menu->cm), lagi_wxString(disp)); + } + else { + read_entry(*it, "special", &submenu); + if (submenu == "automation") + menu->Append(new AutomationMenu(c, &menu->cm), lagi_wxString(disp)); + } } window->SetMenuBar(menu);