diff --git a/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj b/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj index 376c31dfa..fc9754759 100644 --- a/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj +++ b/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj @@ -1915,10 +1915,6 @@ RelativePath="..\..\src\command\keyframe.cpp" > - - diff --git a/aegisub/src/command/command.cpp b/aegisub/src/command/command.cpp index 875d265a5..721fdc457 100644 --- a/aegisub/src/command/command.cpp +++ b/aegisub/src/command/command.cpp @@ -19,6 +19,7 @@ /// @ingroup command #include "command.h" + #include "icon.h" #include @@ -85,7 +86,6 @@ namespace cmd { void init_grid(); void init_help(); void init_keyframe(); - void init_menu(); void init_recent(); void init_subtitle(); void init_time(); @@ -102,7 +102,6 @@ namespace cmd { init_grid(); init_help(); init_keyframe(); - init_menu(); init_recent(); init_subtitle(); init_time(); diff --git a/aegisub/src/command/command.h b/aegisub/src/command/command.h index 4b3e1b8d3..52eba5a03 100644 --- a/aegisub/src/command/command.h +++ b/aegisub/src/command/command.h @@ -37,8 +37,8 @@ DEFINE_SIMPLE_EXCEPTION_NOINNER(CommandIconNone, CommandError, "command/icon") DEFINE_SIMPLE_EXCEPTION_NOINNER(CommandIconInvalid, CommandError, "command/icon/invalid") #define CMD_NAME(a) const char* name() { return a; } -#define STR_MENU(a) wxString StrMenu() const { return a; } -#define STR_DISP(a) wxString StrDisplay() const { return a; } +#define STR_MENU(a) wxString StrMenu(const agi::Context *) const { return a; } +#define STR_DISP(a) wxString StrDisplay(const agi::Context *) const { return a; } #define STR_HELP(a) wxString StrHelp() const { return a; } #define CMD_TYPE(a) int Type() const { using namespace cmd; return a; } @@ -85,10 +85,15 @@ namespace cmd { /// Holds an individual Command class Command { public: - virtual const char* name()=0; ///< Command name. - virtual wxString StrMenu() const=0; ///< String for menu purposes including accelerators. - virtual wxString StrDisplay() const=0; ///< Plain string for display purposes. - virtual wxString StrHelp() const=0; ///< Short help string descripting the command purpose. + /// Command name + virtual const char* name()=0; + /// String for menu purposes including accelerators, but not hotkeys + virtual wxString StrMenu(const agi::Context *) const=0; + /// Plain string for display purposes; should normally be the same as StrMenu + /// but without accelerators + virtual wxString StrDisplay(const agi::Context *) const=0; + /// Short help string descripting the command purpose. + virtual wxString StrHelp() const=0; /// Get this command's type flags /// @return Bitmask of CommandFlags diff --git a/aegisub/src/command/edit.cpp b/aegisub/src/command/edit.cpp index 3db1252a9..ad2053211 100644 --- a/aegisub/src/command/edit.cpp +++ b/aegisub/src/command/edit.cpp @@ -347,11 +347,16 @@ struct edit_line_swap : public Command { /// Redoes last action. struct edit_redo : public Command { CMD_NAME("edit/redo") - STR_MENU("&Redo") - STR_DISP("Redo") STR_HELP("Redoes last action.") CMD_TYPE(COMMAND_VALIDATE | COMMAND_DYNAMIC_NAME) + wxString StrMenu(const agi::Context *c) const { + return wxString::Format(_("&Redo %s"), c->ass->GetRedoDescription()); + } + wxString StrDisplay(const agi::Context *c) const { + return wxString::Format(_("Redo %s"), c->ass->GetRedoDescription()); + } + bool Validate(const agi::Context *c) { return !c->ass->IsRedoStackEmpty(); } @@ -380,11 +385,16 @@ struct edit_search_replace : public Command { /// Undoes last action. struct edit_undo : public Command { CMD_NAME("edit/undo") - STR_MENU("&Undo") - STR_DISP("Undo") STR_HELP("Undoes last action.") CMD_TYPE(COMMAND_VALIDATE | COMMAND_DYNAMIC_NAME) + wxString StrMenu(const agi::Context *c) const { + return wxString::Format(_("&Undo %s"), c->ass->GetUndoDescription()); + } + wxString StrDisplay(const agi::Context *c) const { + return wxString::Format(_("Undo %s"), c->ass->GetUndoDescription()); + } + bool Validate(const agi::Context *c) { return !c->ass->IsUndoStackEmpty(); } diff --git a/aegisub/src/command/menu_.cpp b/aegisub/src/command/menu_.cpp deleted file mode 100644 index eb4410b81..000000000 --- a/aegisub/src/command/menu_.cpp +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) 2010, Amar Takhar -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// * Neither the name of the Aegisub Group nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -// -// Aegisub Project http://www.aegisub.org/ -// -// $Id$ - -/// @file menu.cpp -/// @brief menu/ commands, related to activating/deactivating/populating menu items -/// @ingroup command -/// - -#include "../config.h" - -#ifndef AGI_PRE -#endif - -#include "command.h" - -#include "../include/aegisub/context.h" - -namespace { - using cmd::Command; -/// @defgroup cmd-menu Main menu dropdown and submenu related commands. -/// @{ - -COMMAND_GROUP(main_audio, "main/audio", "Audio", "Audio", "Audio manipulation."); -COMMAND_GROUP(main_automation, "main/automation", "Automation", "Automation", "Automation manipulation and scripts."); -COMMAND_GROUP(main_edit, "main/edit", "&Edit", "Edit", "Editing operations."); -COMMAND_GROUP(main_edit_sort_lines, "main/edit/sort lines", "Sort lines", "Sort lines", "Sort lines by column."); -COMMAND_GROUP(main_file, "main/file", "&File", "File", "Operations on subtitles."); -COMMAND_GROUP(main_help, "main/help", "Help", "Help", "Help options."); -COMMAND_GROUP(main_subtitle, "main/subtitle", "&Subtitle", "Subtitle", "Subtitle manipulation."); -COMMAND_GROUP(main_subtitle_insert_lines, "main/subtitle/insert lines", "&Insert Lines", "Insert Lines", "Insert lines into currently active subtitle file."); -COMMAND_GROUP(main_subtitle_sort_lines, "main/subtitle/sort lines", "Sort Lines", "Sort Lines", "Sort lines by column."); -COMMAND_GROUP(main_subtitle_join_lines, "main/subtitle/join lines", "Join Lines", "Join Lines", "Merge 2 or more lines together."); -COMMAND_GROUP(main_timing, "main/timing", "&Timing", "Timing", "Time manipulation."); -COMMAND_GROUP(main_timing_make_times_continuous, "main/timing/make times continuous", "Make Times Continuous", "Make Times Continuous", "Make time continuous."); -COMMAND_GROUP(main_video, "main/video", "&Video", "Video", "Video operations."); -COMMAND_GROUP(main_video_override_ar, "main/video/override ar", "Override AR", "Override AR", "Override Aspect Ratio"); -COMMAND_GROUP(main_video_set_zoom, "main/video/set zoom", "Set Zoom", "Set Zoom", "Set zoom level."); -COMMAND_GROUP(main_view, "main/view", "View", "View", "View options."); -} - -/// @} - -namespace cmd { - void init_menu() { - reg(new main_audio); - reg(new main_automation); - reg(new main_edit); - reg(new main_edit_sort_lines); - reg(new main_file); - reg(new main_help); - reg(new main_subtitle); - reg(new main_subtitle_insert_lines); - reg(new main_subtitle_join_lines); - reg(new main_subtitle_sort_lines); - reg(new main_timing); - reg(new main_timing_make_times_continuous); - reg(new main_video); - reg(new main_video_override_ar); - reg(new main_video_set_zoom); - reg(new main_view); - } -} diff --git a/aegisub/src/command/recent.cpp b/aegisub/src/command/recent.cpp index e0888bc4f..db26d631e 100644 --- a/aegisub/src/command/recent.cpp +++ b/aegisub/src/command/recent.cpp @@ -57,9 +57,9 @@ namespace { /// @{ COMMAND_GROUP(recent_audio, "recent/audio", "Recent", "Recent", "Open recent audio."); -COMMAND_GROUP(recent_keyframe, "recent/keyframe", "Recent", "Recent", "Open recent keyframes."); +COMMAND_GROUP(recent_keyframes, "recent/keyframe", "Recent", "Recent", "Open recent keyframes."); COMMAND_GROUP(recent_subtitle, "recent/subtitle", "Recent", "Recent", "Open recent subtitles."); -COMMAND_GROUP(recent_timecode, "recent/timecode", "Recent", "Recent", "Open recent timecodes."); +COMMAND_GROUP(recent_timecodes, "recent/timecodes", "Recent", "Recent", "Open recent timecodes."); COMMAND_GROUP(recent_video, "recent/video", "Recent", "Recent", "Open recent video."); struct recent_audio_entry : public Command { @@ -73,8 +73,8 @@ struct recent_audio_entry : public Command { } }; -struct recent_keyframe_entry : public Command { - CMD_NAME("recent/keyframe/") +struct recent_keyframes_entry : public Command { + CMD_NAME("recent/keyframes/") STR_MENU("Recent") STR_DISP("Recent") STR_HELP("Open recent keyframes.") @@ -95,8 +95,8 @@ struct recent_subtitle_entry : public Command { } }; -struct recent_timecode_entry : public Command { - CMD_NAME("recent/timecode/") +struct recent_timecodes_entry : public Command { + CMD_NAME("recent/timecodes/") STR_MENU("Recent") STR_DISP("Recent") STR_HELP("Open recent timecodes.") @@ -141,17 +141,17 @@ public: namespace cmd { void init_recent() { reg(new recent_audio); - reg(new recent_keyframe); + reg(new recent_keyframes); reg(new recent_subtitle); - reg(new recent_timecode); + reg(new recent_timecodes); reg(new recent_video); /// @todo 16 is an implementation detail that maybe needs to be exposed for (int i = 0; i < 16; ++i) { reg(new mru_wrapper(i)); - reg(new mru_wrapper(i)); + reg(new mru_wrapper(i)); reg(new mru_wrapper(i)); - reg(new mru_wrapper(i)); + reg(new mru_wrapper(i)); reg(new mru_wrapper(i)); } } diff --git a/aegisub/src/frame_main.cpp b/aegisub/src/frame_main.cpp index 5b04d66de..388329918 100644 --- a/aegisub/src/frame_main.cpp +++ b/aegisub/src/frame_main.cpp @@ -144,10 +144,6 @@ FrameMain::FrameMain (wxArrayString args) context->previousFocus = 0; AegisubApp::Get()->frame = this; - StartupLog("Binding commands"); - // XXX: This is a hack for now, it will need to be dealt with when other frames are involved. - Bind(wxEVT_COMMAND_MENU_SELECTED, &FrameMain::cmd_call, this); - #ifdef __WXMAC__ // Bind(FrameMain::OnAbout, &FrameMain::cmd_call, this, cmd::id("app/about")); #endif @@ -163,7 +159,7 @@ FrameMain::FrameMain (wxArrayString args) InitToolbar(); StartupLog("Initialize menu bar"); - InitMenu(); + menu::GetMenuBar("main", this, context.get()); StartupLog("Create status bar"); CreateStatusBar(2); @@ -248,29 +244,26 @@ void FrameMain::cmd_call(wxCommandEvent& event) { LOG_D("event/select") << "Id: " << id; if (id < cmd::count()) cmd::call(context.get(), id); - else if (id >= ID_MENU_AUTOMATION_MACRO) - OnAutomationMacro(event); } +void FrameMain::OnMenuOpen(wxMenuEvent &event) { + if (wxMenu *menu = event.GetMenu()) { + wxMenuItemList& items = menu->GetMenuItems(); + for (wxMenuItemList::iterator it = items.begin(); it != items.end(); ++it) { + if (wxMenu *submenu = (*it)->GetSubMenu()) { + submenu->GetEventHandler()->QueueEvent(event.Clone()); + } + } + } +} + +/// @brief Initialize toolbar void FrameMain::InitToolbar () { wxSystemOptions::SetOption("msw.remap", 0); toolbar::AttachToolbar(this, "main", context.get(), "Default"); GetToolBar()->Realize(); } -void FrameMain::InitMenu() { - -#ifdef __WXMAC__ - // Make sure special menu items are placed correctly on Mac -// wxApp::s_macAboutMenuItemId = Menu_Help_About; -// wxApp::s_macExitMenuItemId = Menu_File_Exit; -// wxApp::s_macPreferencesMenuItemId = Menu_Tools_Options; -// wxApp::s_macHelpMenuTitleName = _("&Help"); -#endif - - SetMenuBar(menu::menu->GetMainMenu()); -} - void FrameMain::InitContents() { StartupLog("Create background panel"); Panel = new wxPanel(this,-1,wxDefaultPosition,wxDefaultSize,wxTAB_TRAVERSAL | wxCLIP_CHILDREN); @@ -607,7 +600,6 @@ BEGIN_EVENT_TABLE(FrameMain, wxFrame) EVT_SASH_DRAGGED(ID_SASH_MAIN_AUDIO, FrameMain::OnAudioBoxResize) - EVT_MENU_OPEN(FrameMain::OnMenuOpen) EVT_KEY_DOWN(FrameMain::OnKeyDown) #ifdef __WXMAC__ @@ -616,208 +608,6 @@ BEGIN_EVENT_TABLE(FrameMain, wxFrame) #endif END_EVENT_TABLE() -void FrameMain::RebuildRecentList(const char *root_command, const char *mru_name) { - wxMenu *menu = menu::menu->GetMenu(root_command); - - int count = (int)menu->GetMenuItemCount(); - for (int i=count;--i>=0;) { - menu->Destroy(menu->FindItemByPosition(i)); - } - - const agi::MRUManager::MRUListMap *map_list = config::mru->Get(mru_name); - if (map_list->empty()) { - menu->Append(-1, _("Empty"))->Enable(false); - return; - } - - int i = 0; - for (agi::MRUManager::MRUListMap::const_iterator it = map_list->begin(); it != map_list->end(); ++it) { - std::stringstream ss; - ss << root_command; - ss << "/"; - ss << i; - - wxFileName shortname(lagi_wxString(*it)); - - menu->Append(cmd::id(ss.str()), - wxString::Format("%s%d %s", i <= 9 ? "&" : "", i + 1, shortname.GetFullName())); - ++i; - } -} - -static void validate(wxMenuBar *menu, const agi::Context *c, const char *command) { - menu->Enable(cmd::id(command), cmd::get(command)->Validate(c)); -} - -static void check(wxMenuBar *menu, const agi::Context *c, const char *command) { - menu->Check(cmd::id(command), cmd::get(command)->IsActive(c)); -} - -void FrameMain::OnMenuOpen (wxMenuEvent &event) { - wxMenuBar *MenuBar = menu::menu->GetMainMenu(); - - MenuBar->Freeze(); - wxMenu *curMenu = event.GetMenu(); - - // File menu - if (curMenu == menu::menu->GetMenu("main/file")) { - RebuildRecentList("recent/subtitle", "Subtitle"); - validate(MenuBar, context.get(), "subtitle/open/video"); - } - - // View menu - else if (curMenu == menu::menu->GetMenu("main/view")) { - if (!showVideo && !showAudio) MenuBar->Check(cmd::id("app/display/subs"),true); - else if (showVideo && !showAudio) MenuBar->Check(cmd::id("app/display/video_subs"),true); - else if (showAudio && showVideo) MenuBar->Check(cmd::id("app/display/full"),true); - else MenuBar->Check(cmd::id("app/display/audio_subs"),true); - - check(MenuBar, context.get(), "grid/tags/show"); - check(MenuBar, context.get(), "grid/tags/simplify"); - check(MenuBar, context.get(), "grid/tags/hide"); - } - // Video menu - else if (curMenu == menu::menu->GetMenu("main/video")) { - validate(MenuBar, context.get(), "timecode/save"); - validate(MenuBar, context.get(), "timecode/close"); - validate(MenuBar, context.get(), "keyframe/close"); - validate(MenuBar, context.get(), "keyframe/save"); - - check(MenuBar, context.get(), "video/aspect/default"); - check(MenuBar, context.get(), "video/aspect/full"); - check(MenuBar, context.get(), "video/aspect/wide"); - check(MenuBar, context.get(), "video/aspect/cinematic"); - check(MenuBar, context.get(), "video/aspect/custom"); - - check(MenuBar, context.get(), "video/show_overscan"); - - RebuildRecentList("recent/video", "Video"); - RebuildRecentList("recent/timecode", "Timecodes"); - RebuildRecentList("recent/keyframe", "Keyframes"); - } - - // Audio menu - else if (curMenu == menu::menu->GetMenu("main/audio")) { - validate(MenuBar, context.get(), "audio/open/video"); - validate(MenuBar, context.get(), "audio/close"); - RebuildRecentList("recent/audio", "Audio"); - } - - // Subtitles menu - else if (curMenu == menu::menu->GetMenu("main/subtitle")) { - validate(MenuBar, context.get(), "main/subtitle/insert lines"); - validate(MenuBar, context.get(), "edit/line/duplicate"); - validate(MenuBar, context.get(), "edit/line/duplicate/shift"); - validate(MenuBar, context.get(), "edit/line/swap"); - validate(MenuBar, context.get(), "edit/line/join/concatenate"); - validate(MenuBar, context.get(), "edit/line/join/keep_first"); - validate(MenuBar, context.get(), "edit/line/join/as_karaoke"); - validate(MenuBar, context.get(), "main/subtitle/join lines"); - validate(MenuBar, context.get(), "edit/line/recombine"); - } - - // Timing menu - else if (curMenu == menu::menu->GetMenu("main/timing")) { - validate(MenuBar, context.get(), "time/snap/start_video"); - validate(MenuBar, context.get(), "time/snap/end_video"); - validate(MenuBar, context.get(), "time/snap/scene"); - validate(MenuBar, context.get(), "time/frame/current"); - - validate(MenuBar, context.get(), "time/continuous/start"); - validate(MenuBar, context.get(), "time/continuous/end"); - } - - // Edit menu - else if (curMenu == menu::menu->GetMenu("main/edit")) { - wxMenu *editMenu = menu::menu->GetMenu("main/edit"); - - // Undo state - wxString undo_text = wxString::Format("%s %s\t%s", - cmd::get("edit/undo")->StrMenu(), - context->ass->GetUndoDescription(), - hotkey::get_hotkey_str_first("Default", "edit/undo")); - wxMenuItem *item = editMenu->FindItem(cmd::id("edit/undo")); - item->SetItemLabel(undo_text); - item->Enable(!context->ass->IsUndoStackEmpty()); - - // Redo state - wxString redo_text = wxString::Format("%s %s\t%s", - cmd::get("edit/redo")->StrMenu(), - context->ass->GetRedoDescription(), - hotkey::get_hotkey_str_first("Default", "edit/redo")); - item = editMenu->FindItem(cmd::id("edit/redo")); - item->SetItemLabel(redo_text); - item->Enable(!context->ass->IsRedoStackEmpty()); - - validate(MenuBar, context.get(), "edit/line/cut"); - validate(MenuBar, context.get(), "edit/line/copy"); - validate(MenuBar, context.get(), "edit/line/paste"); - validate(MenuBar, context.get(), "edit/line/paste/over"); - } - - // Automation menu -#ifdef WITH_AUTOMATION - else if (curMenu == menu::menu->GetMenu("main/automation")) { - wxMenu *automationMenu = menu::menu->GetMenu("main/automation"); - - // Remove old macro items - for (unsigned int i = 0; i < activeMacroItems.size(); i++) { - wxMenu *p = 0; - wxMenuItem *it = MenuBar->FindItem(ID_MENU_AUTOMATION_MACRO + i, &p); - if (it) - p->Delete(it); - } - activeMacroItems.clear(); - - // Add new ones - int added = 0; - added += AddMacroMenuItems(automationMenu, wxGetApp().global_scripts->GetMacros()); - added += AddMacroMenuItems(automationMenu, context->local_scripts->GetMacros()); - - // If none were added, show a ghosted notice - if (added == 0) { - automationMenu->Append(ID_MENU_AUTOMATION_MACRO, _("No Automation macros loaded"))->Enable(false); - activeMacroItems.push_back(0); - } - } -#endif - - MenuBar->Thaw(); -} - -int FrameMain::AddMacroMenuItems(wxMenu *menu, const std::vector ¯os) { -#ifdef WITH_AUTOMATION - if (macros.empty()) { - return 0; - } - - int id = activeMacroItems.size();; - for (std::vector::const_iterator i = macros.begin(); i != macros.end(); ++i) { - wxMenuItem * m = menu->Append(ID_MENU_AUTOMATION_MACRO + id, (*i)->GetName(), (*i)->GetDescription()); - m->Enable((*i)->Validate(context->ass, SubsGrid->GetAbsoluteSelection(), SubsGrid->GetFirstSelRow())); - activeMacroItems.push_back(*i); - id++; - } - - return macros.size(); -#else - return 0; -#endif -} - -void FrameMain::OnAutomationMacro (wxCommandEvent &event) { -#ifdef WITH_AUTOMATION - SubsGrid->BeginBatch(); - // First get selection data - std::vector selected_lines = SubsGrid->GetAbsoluteSelection(); - int first_sel = SubsGrid->GetFirstSelRow(); - // Run the macro... - activeMacroItems[event.GetId()-ID_MENU_AUTOMATION_MACRO]->Process(context->ass, selected_lines, first_sel, this); - SubsGrid->SetSelectionFromAbsolute(selected_lines); - SubsGrid->EndBatch(); -#endif -} - void FrameMain::OnCloseWindow (wxCloseEvent &event) { // Stop audio and video context->videoController->Stop(); diff --git a/aegisub/src/frame_main.h b/aegisub/src/frame_main.h index 4b82e194f..133099f49 100644 --- a/aegisub/src/frame_main.h +++ b/aegisub/src/frame_main.h @@ -99,7 +99,6 @@ class FrameMain: public wxFrame { void InitToolbar(); void InitContents(); - void InitMenu(); bool LoadList(wxArrayString list); void UpdateTitle(); @@ -112,15 +111,11 @@ class FrameMain: public wxFrame { void OnAutoSave(wxTimerEvent &event); void OnStatusClear(wxTimerEvent &event); void OnCloseWindow (wxCloseEvent &event); - /// @brief General handler for all Automation-generated menu items - void OnAutomationMacro(wxCommandEvent &event); /// Close the currently open subs, asking the user if they want to save if there are unsaved changes /// @param enableCancel Should the user be able to cancel the close? int TryToCloseSubs(bool enableCancel=true); - void RebuildRecentList(const char *root_command, const char *mru_name); - // AudioControllerAudioEventListener implementation void OnAudioOpen(AudioProvider *provider); void OnAudioClose(); diff --git a/aegisub/src/include/aegisub/menu.h b/aegisub/src/include/aegisub/menu.h index d60b77ba3..4cb362b9b 100644 --- a/aegisub/src/include/aegisub/menu.h +++ b/aegisub/src/include/aegisub/menu.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010, Amar Takhar +// Copyright (c) 2011, Thomas Goyne // // Permission to use, copy, modify, and distribute this software for any // purpose with or without fee is hereby granted, provided that the above @@ -19,51 +19,44 @@ /// @ingroup menu toolbar #ifndef AGI_PRE -#include - -#include +#include #endif #include -namespace json { class Array; } +namespace agi { struct Context; } + +class wxMenu; +class wxMenuBar; namespace menu { -DEFINE_BASE_EXCEPTION_NOINNER(MenuError, agi::Exception) -DEFINE_SIMPLE_EXCEPTION_NOINNER(MenuJsonValueArray, MenuError, "menu/value/array") -DEFINE_SIMPLE_EXCEPTION_NOINNER(MenuJsonValueSingle, MenuError, "menu/value") -DEFINE_SIMPLE_EXCEPTION_NOINNER(MenuJsonValueNull, MenuError, "menu/value") -DEFINE_SIMPLE_EXCEPTION_NOINNER(MenuInvalidName, MenuError, "menu/invalid") + DEFINE_BASE_EXCEPTION_NOINNER(Error, agi::Exception) + DEFINE_SIMPLE_EXCEPTION_NOINNER(UnknownMenu, Error, "menu/unknown") + DEFINE_SIMPLE_EXCEPTION_NOINNER(InvalidMenu, Error, "menu/invalid") -class Menu; -extern Menu *menu; + /// @brief Get the menu with the specified name as a wxMenuBar + /// @param name Name of the menu + /// + /// Throws: + /// UnknownMenu if no menu with the given name was found + /// BadMenu if there is a menu with the given name, but it is invalid + void GetMenuBar(std::string const& name, wxFrame *window, agi::Context *c); -class Menu { -public: - Menu(); - ~Menu(); - wxMenuBar* GetMainMenu() { return main_menu; } - wxMenu* GetMenu(std::string name); + /// @brief Get the menu with the specified name as a wxMenu + /// @param name Name of the menu + /// + /// Throws: + /// UnknownMenu if no menu with the given name was found + /// BadMenu if there is a menu with the given name, but it is invalid + wxMenu *GetMenu(std::string const& name, agi::Context *c); -private: - - typedef std::map MTMap; - typedef std::pair MTPair; - - enum MenuTypes { - Option = 1, - Check = 2, - Radio = 3, - Submenu = 4, - Recent = 5, - Spacer = 100 - }; - - wxMenuBar *main_menu; - MTMap map; - - wxMenu* BuildMenu(std::string name, const json::Array& array, int submenu=0); - -}; - -} // namespace menu + /// @brief Open a popup menu at the mouse + /// @param menu Menu to open + /// @param parent_window Parent window for the menu; cannot be NULL + /// + /// This function should be used rather than wxWindow::PopupMenu due to + /// that PopupMenu does not trigger menu open events and triggers update + /// ui events on the opening window rather than the menu for some bizarre + /// reason + void OpenPopupMenu(wxMenu *menu, wxWindow *parent_window); +} diff --git a/aegisub/src/libresrc/default_menu.json b/aegisub/src/libresrc/default_menu.json index b23c624f6..5bcbb65a2 100644 --- a/aegisub/src/libresrc/default_menu.json +++ b/aegisub/src/libresrc/default_menu.json @@ -1,580 +1,175 @@ { - "main" : [ - { - "type" : 4, - "command" : "file", - "contents" : [ - { - "type" : 1, - "command" : "subtitle/new" - }, - { - "type" : 1, - "command" : "subtitle/open" - }, - { - "type" : 1, - "command" : "subtitle/open/charset" - }, - { - "type" : 1, - "command" : "subtitle/open/video" - }, - { - "type" : 1, - "command" : "subtitle/save" - }, - { - "type" : 1, - "command" : "subtitle/save/as" - }, - { - "type" : 1, - "command" : "tool/export" - }, - { - "type" : 5, - "command" : "recent/subtitle" - }, - { - "type" : 100 - }, - { - "type" : 1, - "command" : "subtitle/properties" - }, - { - "type" : 1, - "command" : "subtitle/attachment" - }, - { - "type" : 1, - "command" : "tool/font_collector" - }, - { - "type" : 100 - }, - { - "type" : 1, - "command" : "app/new_window" - }, - { - "type" : 1, - "command" : "app/exit" - } - ] - }, - { - "type" : 4, - "command" : "edit", - "contents" : [ - { - "type" : 1, - "command" : "edit/undo" - }, - { - "type" : 1, - "command" : "edit/redo" - }, - { - "type" : 100 - }, - { - "type" : 1, - "command" : "edit/line/cut" - }, - { - "type" : 1, - "command" : "edit/line/copy" - }, - { - "type" : 1, - "command" : "edit/line/paste" - }, - { - "type" : 1, - "command" : "edit/line/paste/over" - }, - { - "type" : 100 - }, - { - "type" : 1, - "command" : "subtitle/find" - }, - { - "type" : 1, - "command" : "subtitle/find/next" - }, - { - "type" : 1, - "command" : "edit/search_replace" - } - ] - }, - { - "type" : 4, - "command" : "subtitle", - "contents" : [ - { - "type" : 1, - "command" : "tool/style/manager" - }, - { - "type" : 1, - "command" : "tool/style/assistant" - }, - { - "type" : 1, - "command" : "tool/translation_assistant" - }, - { - "type" : 1, - "command" : "tool/resampleres" - }, - { - "type" : 1, - "command" : "subtitle/spellcheck" - }, - { - "type" : 100 - }, - { - "type" : 1, - "command" : "tool/assdraw" - }, - { - "type" : 4, - "command" : "insert lines", - "contents" : [ - { - "type" : 1, - "command" : "subtitle/insert/before" - }, - { - "type" : 1, - "command" : "subtitle/insert/after" - }, - { - "type" : 1, - "command" : "subtitle/insert/before/videotime" - }, - { - "type" : 1, - "command" : "subtitle/insert/after/videotime" - } - ] - }, - { - "type" : 1, - "command" : "edit/line/duplicate" - }, - { - "type" : 1, - "command" : "edit/line/duplicate/shift" - }, - { - "type" : 1, - "command" : "edit/line/delete" - }, - { - "type" : 100 - }, - { - "type" : 4, - "command" : "join lines", - "contents" : [ - { - "type" : 1, - "command" : "edit/line/join/concatenate" - }, - { - "type" : 1, - "command" : "edit/line/join/keep_first" - }, - { - "type" : 1, - "command" : "edit/line/join/as_karaoke" - } - ] - }, - { - "type" : 1, - "command" : "edit/line/recombine" - }, - { - "type" : 1, - "command" : "edit/line/split/by_karaoke" - }, - { - "type" : 100 - }, - { - "type" : 4, - "command" : "sort lines", - "contents" : [ - { - "type" : 1, - "command" : "time/sort/start" - }, - { - "type" : 1, - "command" : "time/sort/end" - }, - { - "type" : 1, - "command" : "time/sort/style" - } - ] - }, - { - "type" : 1, - "command" : "edit/line/swap" - }, - { - "type" : 1, - "command" : "tool/line/select" - } - ] - }, - { - "type" : 4, - "command" : "timing", - "contents" : [ - { - "type" : 1, - "command" : "time/shift" - }, - { - "type" : 1, - "command" : "tool/time/postprocess" - }, - { - "type" : 1, - "command" : "tool/time/kanji" - }, - { - "type" : 100 - }, - { - "type" : 1, - "command" : "time/snap/start_video" - }, - { - "type" : 1, - "command" : "time/snap/end_video" - }, - { - "type" : 1, - "command" : "time/snap/scene" - }, - { - "type" : 1, - "command" : "time/frame/current" - }, - { - "type" : 100 - }, - { - "type" : 4, - "command" : "make times continuous", - "contents" : [ - { - "type" : 1, - "command" : "time/continuous/start" - }, - { - "type" : 1, - "command" : "time/continuous/end" - } - ] - } - ] - }, - - { - "type" : 4, - "command" : "video", - "contents" : [ - { - "type" : 1, - "command" : "video/open" - }, - { - "type" : 1, - "command" : "video/close" - }, - { - "type" : 5, - "command" : "recent/video" - }, - { - "type" : 1, - "command" : "video/open/dummy" - }, - { - "type" : 1, - "command" : "video/details" - }, - { - "type" : 100 - }, - { - "type" : 1, - "command" : "timecode/open" - }, - { - "type" : 1, - "command" : "timecode/save" - }, - { - "type" : 1, - "command" : "timecode/close" - }, - { - "type" : 5, - "command" : "recent/timecode" - }, - { - "type" : 100 - }, - { - "type" : 1, - "command" : "keyframe/open" - }, - { - "type" : 1, - "command" : "keyframe/save" - }, - { - "type" : 1, - "command" : "keyframe/close" - }, - { - "type" : 5, - "command" : "recent/keyframe" - }, - { - "type" : 100 - }, - { - "type" : 1, - "command" : "video/detach" - }, - { - "type" : 4, - "command" : "set zoom", - "contents" : [ - { - "type" : 1, - "command" : "video/zoom/50" - }, - { - "type" : 1, - "command" : "video/zoom/100" - }, - { - "type" : 1, - "command" : "video/zoom/200" - } - ] - }, - { - "type" : 4, - "command" : "override ar", - "contents" : [ - { - "type" : 2, - "command" : "video/aspect/default" - }, - { - "type" : 2, - "command" : "video/aspect/full" - }, - { - "type" : 2, - "command" : "video/aspect/wide" - }, - { - "type" : 2, - "command" : "video/aspect/cinematic" - }, - { - "type" : 2, - "command" : "video/aspect/custom" - } - ] - }, - { - "type" : 2, - "command" : "video/show_overscan" - }, - { - "type" : 100 - }, - { - "type" : 1, - "command" : "video/jump" - }, - { - "type" : 1, - "command" : "video/jump/start" - }, - { - "type" : 1, - "command" : "video/jump/end" - } - ] - }, - { - "type" : 4, - "command" : "audio", - "contents" : [ - { - "type" : 1, - "command" : "audio/open" - }, - { - "type" : 1, - "command" : "audio/open/video" - }, - { - "type" : 1, - "command" : "audio/close" - }, - { - "type" : 5, - "command" : "recent/audio" - }, - { - "type" : 100 - }, - { - "type" : 1, - "command" : "audio/view/spectrum" - }, - { - "type" : 1, - "command" : "audio/view/waveform" - }, - { - "type" : 1, - "command" : "audio/open/blank" - }, - { - "type" : 1, - "command" : "audio/open/noise" - } - ] - }, - { - "type" : 4, - "command" : "automation", - "contents" : [ - { - "type" : 1, - "command" : "am/manager" - }, - { - "type" : 100 - } - ] - }, - { - "type" : 4, - "command" : "view", - "contents" : [ - { - "type" : 1, - "command" : "app/language" - }, - { - "type" : 1, - "command" : "app/options" - }, - { - "type" : 100 - }, - { - "type" : 3, - "command" : "app/display/subs" - }, - { - "type" : 3, - "command" : "app/display/video_subs" - }, - { - "type" : 3, - "command" : "app/display/audio_subs" - }, - { - "type" : 3, - "command" : "app/display/full" - }, - { - "type" : 100 - }, - { - "type" : 3, - "command" : "grid/tags/show" - }, - { - "type" : 3, - "command" : "grid/tags/simplify" - }, - { - "type" : 3, - "command" : "grid/tags/hide" - } - ] - }, - { - "type" : 4, - "command" : "help", - "contents" : [ - { - "type" : 1, - "command" : "help/contents" - }, - { - "type" : 100 - }, - { - "type" : 1, - "command" : "help/files" - }, - { - "type" : 1, - "command" : "help/website" - }, - { - "type" : 1, - "command" : "help/forums" - }, - { - "type" : 1, - "command" : "help/bugs" - }, - { - "type" : 100 - }, - { - "type" : 1, - "command" : "help/irc" - }, - { - "type" : 1, - "command" : "app/updates" - }, - { - "type" : 1, - "command" : "app/about" - }, - { - "type" : 1, - "command" : "app/log" - } - ] - } - ] + "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" } + ], + "main/file" : [ + { "command" : "subtitle/new" }, + { "command" : "subtitle/open" }, + { "command" : "subtitle/open/charset" }, + { "command" : "subtitle/open/video" }, + { "command" : "subtitle/save" }, + { "command" : "subtitle/save/as" }, + { "command" : "tool/export" }, + { "recent" : "Subtitle" }, + {}, + { "command" : "subtitle/properties" }, + { "command" : "subtitle/attachment" }, + { "command" : "tool/font_collector" }, + {}, + { "command" : "app/new_window" }, + { "command" : "app/exit", "special" : "exit" } + ], + "main/edit" : [ + { "command" : "edit/undo" }, + { "command" : "edit/redo" }, + {}, + { "command" : "edit/line/cut" }, + { "command" : "edit/line/copy" }, + { "command" : "edit/line/paste" }, + { "command" : "edit/line/paste/over" }, + {}, + { "command" : "subtitle/find" }, + { "command" : "subtitle/find/next" }, + { "command" : "edit/search_replace" } + ], + "main/subtitle" : [ + { "command" : "tool/style/manager" }, + { "command" : "tool/style/assistant" }, + { "command" : "tool/translation_assistant" }, + { "command" : "tool/resampleres" }, + { "command" : "subtitle/spellcheck" }, + {}, + { "command" : "tool/assdraw" }, + { "submenu" : "main/subtitle/insert lines", "text" : "&Insert Lines" }, + { "command" : "edit/line/duplicate" }, + { "command" : "edit/line/duplicate/shift" }, + { "command" : "edit/line/delete" }, + {}, + { "submenu" : "main/subtitle/join lines", "text" : "Join Lines" }, + { "command" : "edit/line/recombine" }, + { "command" : "edit/line/split/by_karaoke" }, + {}, + { "submenu" : "main/subtitle/sort lines", "text" : "Sort Lines" }, + { "command" : "edit/line/swap" }, + { "command" : "tool/line/select" } + ], + "main/subtitle/insert lines" : [ + { "command" : "subtitle/insert/before" }, + { "command" : "subtitle/insert/after" }, + { "command" : "subtitle/insert/before/videotime" }, + { "command" : "subtitle/insert/after/videotime" } + ], + "main/subtitle/join lines" : [ + { "command" : "edit/line/join/concatenate" }, + { "command" : "edit/line/join/keep_first" }, + { "command" : "edit/line/join/as_karaoke" } + ], + "main/subtitle/sort lines" : [ + { "command" : "time/sort/start" }, + { "command" : "time/sort/end" }, + { "command" : "time/sort/style" } + ], + "main/timing" : [ + { "command" : "time/shift" }, + { "command" : "tool/time/postprocess" }, + { "command" : "tool/time/kanji" }, + {}, + { "command" : "time/snap/start_video" }, + { "command" : "time/snap/end_video" }, + { "command" : "time/snap/scene" }, + { "command" : "time/frame/current" }, + {}, + { "submenu" : "main/timing/make times continuous", "text" : "Make Times Continuous" } + ], + "main/timing/make times continuous" : [ + { "command" : "time/continuous/start" }, + { "command" : "time/continuous/end" } + ], + "main/video" : [ + { "command" : "video/open" }, + { "command" : "video/close" }, + { "recent" : "Video" }, + { "command" : "video/open/dummy" }, + { "command" : "video/details" }, + {}, + { "command" : "timecode/open" }, + { "command" : "timecode/save" }, + { "command" : "timecode/close" }, + { "recent" : "Timecodes" }, + {}, + { "command" : "keyframe/open" }, + { "command" : "keyframe/save" }, + { "command" : "keyframe/close" }, + { "recent" : "Keyframes" }, + {}, + { "command" : "video/detach" }, + { "submenu" : "main/video/set zoom", "text" : "Set Zoom" }, + { "submenu" : "main/video/override ar", "text" : "Override AR" }, + { "command" : "video/show_overscan" }, + {}, + { "command" : "video/jump" }, + { "command" : "video/jump/start" }, + { "command" : "video/jump/end" } + ], + "main/video/set zoom" : [ + { "command" : "video/zoom/50" }, + { "command" : "video/zoom/100" }, + { "command" : "video/zoom/200" } + ], + "main/video/override ar" : [ + { "command" : "video/aspect/default" }, + { "command" : "video/aspect/full" }, + { "command" : "video/aspect/wide" }, + { "command" : "video/aspect/cinematic" }, + { "command" : "video/aspect/custom" } + ], + "main/audio" : [ + { "command" : "audio/open" }, + { "command" : "audio/open/video" }, + { "command" : "audio/close" }, + { "recent" : "Audio" }, + {}, + { "command" : "audio/view/spectrum" }, + { "command" : "audio/view/waveform" }, + { "command" : "audio/open/blank" }, + { "command" : "audio/open/noise" } + ], + "main/automation" : [ + { "command" : "am/manager" }, + {} + ], + "main/view" : [ + { "command" : "app/language" }, + { "command" : "app/options", "special" : "options" }, + {}, + { "command" : "app/display/subs" }, + { "command" : "app/display/video_subs" }, + { "command" : "app/display/audio_subs" }, + { "command" : "app/display/full" }, + {}, + { "command" : "grid/tags/show" }, + { "command" : "grid/tags/simplify" }, + { "command" : "grid/tags/hide" } + ], + "main/help" : [ + { "command" : "help/contents" }, + {}, + { "command" : "help/files" }, + { "command" : "help/website" }, + { "command" : "help/forums" }, + { "command" : "help/bugs" }, + {}, + { "command" : "help/irc" }, + { "command" : "app/updates" }, + { "command" : "app/about", "special" : "about" }, + { "command" : "app/log" } + ] } diff --git a/aegisub/src/main.cpp b/aegisub/src/main.cpp index 8450267d7..155a8c55c 100644 --- a/aegisub/src/main.cpp +++ b/aegisub/src/main.cpp @@ -195,9 +195,6 @@ bool AegisubApp::OnInit() { // Init icons. icon::icon_init(); - // Generate menus. - menu::menu = new menu::Menu(); - // Install assertion handler // wxSetAssertHandler(wxAssertHandler); @@ -312,10 +309,12 @@ bool AegisubApp::OnInit() { return false; } +#ifndef _DEBUG catch (...) { wxMessageBox(_T("Unhandled exception"),_T("Fatal error while initializing")); return false; } +#endif StartupLog(_T("Initialization complete")); return true; @@ -334,7 +333,6 @@ int AegisubApp::OnExit() { delete config::mru; delete agi::hotkey::hotkey; delete config::path; - delete menu::menu; cmd::clear(); #ifdef WITH_AUTOMATION diff --git a/aegisub/src/menu.cpp b/aegisub/src/menu.cpp index cd8806549..4801924bb 100644 --- a/aegisub/src/menu.cpp +++ b/aegisub/src/menu.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2010, Amar Takhar +// Copyright (c) 2011, Thomas Goyne // // Permission to use, copy, modify, and distribute this software for any // purpose with or without fee is hereby granted, provided that the above @@ -20,144 +20,339 @@ #include "config.h" -#ifndef AGI_PRE -#include - -#include - -#include -#endif - #include "include/aegisub/menu.h" -#include +#include "include/aegisub/hotkey.h" + +#include "command/command.h" +#include "compat.h" +#include "libresrc/libresrc.h" +#include "main.h" +#include "standard_paths.h" + #include #include -#include "include/aegisub/hotkey.h" -#include "command/command.h" -#include "libresrc/libresrc.h" -#include "main.h" +#ifndef AGI_PRE +#include +#include +#include -namespace menu { +#include +#include +#endif -menu::Menu *menu; +namespace { +/// Window ID of first menu item +static const int MENU_ID_BASE = 10000; -Menu::Menu() { +using std::tr1::placeholders::_1; +using std::tr1::bind; - main_menu = new wxMenuBar(); +/// Get the menu text for a command along with hotkey +inline wxString get_menu_text(cmd::Command *command, agi::Context *c) { + return command->StrMenu(c) + "\t" + hotkey::get_hotkey_str_first("Default", command->name()); +} - std::istringstream *stream = new std::istringstream(GET_DEFAULT_CONFIG(default_menu)); - json::UnknownElement menu_root = agi::json_util::parse(stream); +class MruMenu : public wxMenu { + std::string type; + std::vector items; + std::vector *cmds; - LOG_D("menu/init") << "Generating Menus"; - json::Object object = menu_root; - - for (json::Object::const_iterator index(object.Begin()); index != object.End(); index++) { - const json::Object::Member& member = *index; - const json::Array& array = member.element; - delete BuildMenu(member.name, array); + void Resize(size_t new_size) { + for (size_t i = GetMenuItemCount(); i > new_size; --i) { + Remove(FindItemByPosition(i - 1)); } + + for (size_t i = GetMenuItemCount(); i < new_size; ++i) { + if (i >= items.size()) { + items.push_back(new wxMenuItem(this, MENU_ID_BASE + cmds->size(), "_")); + cmds->push_back(cmd::get(STD_STR(wxString::Format("recent/%s/%d", lagi_wxString(type).Lower(), i)))); } + Append(items[i]); + } + } -Menu::~Menu() {} +public: + MruMenu(std::string const& type, std::vector *cmds) + : type(type) + , cmds(cmds) + { + } + ~MruMenu() { + // Append all items to ensure that they're all cleaned up + Resize(items.size()); + } -wxMenu* Menu::GetMenu(std::string name) { + void Update() { + const agi::MRUManager::MRUListMap *mru = config::mru->Get(type); - MTMap::iterator index; + Resize(mru->size()); - if ((index = map.find(name)) != map.end()) { - return index->second; + if (mru->empty()) { + Resize(1); + items[0]->Enable(false); + items[0]->SetItemLabel(_("Empty")); + return; } - LOG_E("menu/invalid") << "Invalid index name: " << name; - throw MenuInvalidName("Unknown index"); + int i = 0; + for (agi::MRUManager::MRUListMap::const_iterator it = mru->begin(); + it != mru->end(); + ++it, ++i) + { + items[i]->SetItemLabel(wxString::Format("%s%d %s", + i <= 9 ? "&" : "", i + 1, + wxFileName(lagi_wxString(*it)).GetFullName())); + items[i]->Enable(true); + } + } +}; + +/// @class CommandManager +/// @brief Event dispatcher to update menus on open and handle click events +/// +/// Some of what the class does could be dumped off on wx, but wxEVT_MENU_OPEN +/// is super buggy (GetMenu() often returns NULL and it outright doesn't trigger +/// on submenus in many cases, and registering large numbers of wxEVT_UPDATE_UI +/// handlers makes everything involves events unusably slow. +class CommandManager { + /// Menu items which need to do something on menu open + std::deque > dynamic_items; + /// window id -> command map + std::vector items; + /// MRU menus which need to be updated on menu open + std::deque mru; + + /// Project context + agi::Context *context; + + /// Update a single dynamic menu item + void UpdateItem(std::pair const& item) { + int flags = item.first->Type(); + if (flags & cmd::COMMAND_DYNAMIC_NAME) + item.second->SetItemLabel(get_menu_text(item.first, context)); + if (flags & cmd::COMMAND_VALIDATE) + item.second->Enable(item.first->Validate(context)); + if (flags & cmd::COMMAND_RADIO || flags & cmd::COMMAND_TOGGLE) + item.second->Check(item.first->IsActive(context)); } +public: + CommandManager(agi::Context *context) : context(context) { } + /// Append a command to a menu and register the needed handlers + int AddCommand(cmd::Command *co, wxMenu *parent, std::string const& text) { + int flags = co->Type(); + wxItemKind kind = + flags & cmd::COMMAND_RADIO ? wxITEM_RADIO : + flags & cmd::COMMAND_TOGGLE ? wxITEM_CHECK : + wxITEM_NORMAL; + wxString menu_text = text.empty() ? + get_menu_text(co, context) : + lagi_wxString(text) + "\t" + hotkey::get_hotkey_str_first("Default", co->name()); -wxMenu* Menu::BuildMenu(std::string name, const json::Array& array, int submenu) { - wxMenu *menu = new wxMenu(); + wxMenuItem *item = new wxMenuItem(parent, MENU_ID_BASE + items.size(), menu_text, co->StrHelp(), kind); +#ifndef __WXMAC__ + /// @todo Maybe make this a configuration option instead? + item->SetBitmap(co->Icon(16)); +#endif + parent->Append(item); + items.push_back(co); + if (flags != cmd::COMMAND_NORMAL) + dynamic_items.push_back(std::make_pair(co, item)); - for (json::Array::const_iterator index(array.Begin()); index != array.End(); index++) { - std::string name_sub = name; - - const json::Object& obj = *index; - const json::Number& type_number = obj["type"]; - int type = type_number.Value(); - - if (type == Menu::Spacer) { - menu->AppendSeparator(); - continue; + return item->GetId(); } + /// Create a MRU menu and register the needed handlers + /// @param name MRU type + /// @param parent Menu to append the new MRU menu to + void AddRecent(std::string const& name, wxMenu *parent) { + mru.push_back(new MruMenu(name, &items)); + parent->AppendSubMenu(mru.back(), _("Recent")); + } - const json::String& command = obj["command"]; - std::string name_submenu = name_sub + "/" + command.Value(); + void OnMenuOpen(wxMenuEvent &) { + for_each(dynamic_items.begin(), dynamic_items.end(), bind(&CommandManager::UpdateItem, this, _1)); + for_each(mru.begin(), mru.end(), std::mem_fun(&MruMenu::Update)); + } + void OnMenuClick(wxCommandEvent &evt) { + // This also gets clicks on unrelated things such as the toolbar, so + // the window ID ranges really need to be unique + size_t id = static_cast(evt.GetId() - MENU_ID_BASE); + if (id < items.size()) + (*items[id])(context); + } +}; + +/// Wrapper for wxMenu to add a command manager +struct CommandMenu : public wxMenu { + CommandManager cm; + CommandMenu(agi::Context *c) : cm(c) { } +}; + +/// Wrapper for wxMenuBar to add a command manager +struct CommandMenuBar : public wxMenuBar { + CommandManager cm; + CommandMenuBar(agi::Context *c) : cm(c) { } +}; + +/// Read a string from a json object +/// @param obj Object to read from +/// @param name Index to read from +/// @param[out] value Output value to write to +/// @return Was the requested index found +bool read_entry(json::Object const& obj, const char *name, std::string *value) { + json::Object::const_iterator it = obj.Find(name); + if (it == obj.End()) return false; + *value = static_cast(it->element); + return true; +} + +typedef json::Array menu_items; +typedef json::Object menu_map; + +/// Get the root object of the menu configuration +menu_map const& get_menus_root() { + static menu_map root; + if (!root.Empty()) return root; - std::string cmd_name = type == Menu::Submenu ? name_submenu : command.Value(); - cmd::Command *cmd; try { - cmd = cmd::get(cmd_name); + root = agi::json_util::file(StandardPaths::DecodePath("?user/menu.json").utf8_str().data(), GET_DEFAULT_CONFIG(default_menu)); + return root; } - catch (CommandNotFound const&) { - LOG_W("menu/command/not_found") << "Command '" << cmd_name << "' not found; skipping"; - continue; + catch (json::Reader::ParseException const& e) { + LOG_E("menu/parse") << "json::ParseException: " << e.what() << ", Line/offset: " << e.m_locTokenBegin.m_nLine + 1 << '/' << e.m_locTokenBegin.m_nLineOffset + 1; + throw; } - - wxString display = cmd->StrMenu() + "\t" + hotkey::get_hotkey_str_first("Default", cmd_name); - wxString descr = cmd->StrHelp(); - - switch (type) { - case Menu::Option: { - wxMenuItem *menu_item = new wxMenuItem(menu, cmd::id(command.Value()), display, descr, wxITEM_NORMAL); - menu->Append(menu_item); + catch (std::exception const& e) { + LOG_E("menu/parse") << e.what(); + throw; } - break; - - case Menu::Check: { - menu->AppendCheckItem(cmd::id(command.Value()), display, descr); } - break; - case Menu::Radio: { - menu->AppendRadioItem(cmd::id(command.Value()), display, descr); +/// Get the menu with the specified name +/// @param name Name of menu to get +/// @return Array of menu items +menu_items const& get_menu(std::string const& name) { + menu_map const& root = get_menus_root(); + + menu_map::const_iterator it = root.Find(name); + if (it == root.End()) throw menu::UnknownMenu("Menu named " + name + " not found"); + return it->element; +} + +wxMenu *build_menu(std::string const& name, agi::Context *c, CommandManager *cm, wxMenu *menu = 0); + +/// Recursively process a single entry in the menu json +/// @param parent Menu to add the item(s) from this entry to +/// @param c Project context to bind the menu to +/// @param ele json object to process +/// @param cm Command manager for this menu +void process_menu_item(wxMenu *parent, agi::Context *c, json::Object const& ele, CommandManager *cm) { + if (ele.Empty()) { + parent->AppendSeparator(); + return; } - break; - case Menu::Recent: { - wxMenu *menu_new = new wxMenu(); - wxMenuItem *menu_item = new wxMenuItem(menu, cmd::id(command.Value()), display, descr, wxITEM_NORMAL, menu_new); - menu->Append(menu_item); - map.insert(MTPair(command.Value(), menu_new)); + std::string submenu, recent, command, text, special; + read_entry(ele, "special", &special); + if (read_entry(ele, "submenu", &submenu) && read_entry(ele, "text", &text)) { + parent->AppendSubMenu(build_menu(submenu, c, cm), lagi_wxString(text)); +#ifdef __WXMAC__ + if (special == "help") + wxApp::s_macHelpMenuTitleName = lagi_wxString(text); +#endif + return; } - break; - case Menu::Submenu: { + if (read_entry(ele, "recent", &recent)) { + cm->AddRecent(recent, parent); + return; + } - const json::Array& arr = obj["contents"]; - - wxMenu *menu_new = BuildMenu(name_sub.append("/").append(command), arr, 1); - map.insert(MTPair(name_submenu, menu_new)); - - if (submenu) { - wxMenuItem *menu_item = new wxMenuItem(menu, cmd::id(name_sub), display, descr, wxITEM_NORMAL, menu_new); - menu->Append(menu_item); - } else { - main_menu->Append(menu_new, display); + if (special == "automation") { +#ifdef WITH_AUTOMATION + /// @todo Actually implement this + parent->Append(-1, _("No Automation macros loaded"))->Enable(false); +#endif + return; } + + if (!read_entry(ele, "command", &command)) + return; + + read_entry(ele, "text", &text); + + try { + int id = cm->AddCommand(cmd::get(command), parent, text); +#ifdef __WXMAC__ + if (!special.empty()) { + if (special == "about") + wxApp::s_macAboutMenuItemId = id; + else if (special == "exit") + wxApp::s_macExitMenuItemId = id; + else if (special == "options") + wxApp::s_macPreferencesMenuItemId = id; + } +#endif + } + catch (agi::Exception const& e) { +#ifdef _DEBUG + parent->Append(-1, lagi_wxString(e.GetMessage()))->Enable(false); +#endif + LOG_W("menu/command/not_found") << "Skipping command " << command << ": " << e.GetMessage(); } - break; } - } // for index +/// Build the menu with the given name +/// @param name Name of the menu +/// @param c Project context to bind the menu to +wxMenu *build_menu(std::string const& name, agi::Context *c, CommandManager *cm, wxMenu *menu) { + menu_items const& items = get_menu(name); + if (!menu) menu = new wxMenu; + for_each(items.Begin(), items.End(), bind(process_menu_item, menu, c, _1, cm)); return menu; } -} // namespace menu +} + +namespace menu { + void GetMenuBar(std::string const& name, wxFrame *window, agi::Context *c) { + menu_items const& items = get_menu(name); + + CommandMenuBar *menu = new CommandMenuBar(c); + for (menu_items::const_iterator it = items.Begin(); it != items.End(); ++it) { + 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)); + } + + window->SetMenuBar(menu); + window->Bind(wxEVT_MENU_OPEN, &CommandManager::OnMenuOpen, &menu->cm); + window->Bind(wxEVT_COMMAND_MENU_SELECTED, &CommandManager::OnMenuClick, &menu->cm); + } + + wxMenu *GetMenu(std::string const& name, agi::Context *c) { + CommandMenu *menu = new CommandMenu(c); + build_menu(name, c, &menu->cm, menu); + menu->Bind(wxEVT_MENU_OPEN, &CommandManager::OnMenuOpen, &menu->cm); + menu->Bind(wxEVT_COMMAND_MENU_SELECTED, &CommandManager::OnMenuClick, &menu->cm); + return menu; + } + + void OpenPopupMenu(wxMenu *menu, wxWindow *parent_window) { + wxMenuEvent evt; + evt.SetEventType(wxEVT_MENU_OPEN); + menu->ProcessEvent(evt); + parent_window->PopupMenu(menu); + } +} diff --git a/aegisub/src/subs_grid.cpp b/aegisub/src/subs_grid.cpp index c7689ed17..cd160f270 100644 --- a/aegisub/src/subs_grid.cpp +++ b/aegisub/src/subs_grid.cpp @@ -134,7 +134,7 @@ void SubtitlesGrid::OnCommand(wxCommandEvent& event) { static inline void append_command(wxMenu &menu, const char *name, const agi::Context *context) { cmd::Command *c = cmd::get(name); - menu.Append(cmd::id(name), c->StrMenu(), c->StrHelp())->Enable(c->Validate(context)); + menu.Append(cmd::id(name), c->StrMenu(context), c->StrHelp())->Enable(c->Validate(context)); } /// @brief Popup menu diff --git a/aegisub/src/toolbar.cpp b/aegisub/src/toolbar.cpp index 4e64f404d..3a67769ff 100644 --- a/aegisub/src/toolbar.cpp +++ b/aegisub/src/toolbar.cpp @@ -143,7 +143,7 @@ namespace { } if (hotkeys.size()) tooltip += ")"; - AddTool(TOOL_ID_BASE + commands.size(), command->StrDisplay(), icon, tooltip, kind); + AddTool(TOOL_ID_BASE + commands.size(), command->StrDisplay(context), icon, tooltip, kind); commands.push_back(command); needs_onidle = needs_onidle || flags != cmd::COMMAND_NORMAL; diff --git a/aegisub/src/utils.h b/aegisub/src/utils.h index 2c54fad17..67e6b02bd 100644 --- a/aegisub/src/utils.h +++ b/aegisub/src/utils.h @@ -60,7 +60,6 @@ wxString DecodeRelativePath(wxString path,wxString reference); wxString AegiFloatToString(double value); wxString AegiIntegerToString(int value); wxString PrettySize(int bytes); -wxMenuItem *AppendBitmapMenuItem (wxMenu* parentMenu,int id,wxString text,wxString help,wxBitmap bmp,int pos=-1); int SmallestPowerOf2(int x); void GetWordBoundaries(const wxString text,IntPairVector &results,int start=0,int end=-1); bool IsWhitespace(wchar_t c);