Rewrite the dynamic menu generation code

Remove hardcoded assumptions about where in the menu items are and
instead bind menu items directly to commands so that customizing the
menu actually works.

Add support for user menu files that override the default one.

Add better support for multiple menus so that all of the menus can
potentially be created by the dynamic menu system rather than just the
main menu bar.

Add support for commands whose names change based on the current project
state so that undo and redo can work properly.

Simplify the menu json format and make commands responsible for
controlling what type of menu item is created rather than allowing
nonsensical configurations.

The Automation menu is currently not implemented.

Originally committed to SVN as r5554.
This commit is contained in:
Thomas Goyne 2011-08-27 06:29:36 +00:00
parent 319b454bb8
commit 626df4db05
15 changed files with 544 additions and 1059 deletions

View file

@ -1915,10 +1915,6 @@
RelativePath="..\..\src\command\keyframe.cpp" RelativePath="..\..\src\command\keyframe.cpp"
> >
</File> </File>
<File
RelativePath="..\..\src\command\menu_.cpp"
>
</File>
<File <File
RelativePath="..\..\src\command\recent.cpp" RelativePath="..\..\src\command\recent.cpp"
> >

View file

@ -19,6 +19,7 @@
/// @ingroup command /// @ingroup command
#include "command.h" #include "command.h"
#include "icon.h" #include "icon.h"
#include <libaegisub/log.h> #include <libaegisub/log.h>
@ -85,7 +86,6 @@ namespace cmd {
void init_grid(); void init_grid();
void init_help(); void init_help();
void init_keyframe(); void init_keyframe();
void init_menu();
void init_recent(); void init_recent();
void init_subtitle(); void init_subtitle();
void init_time(); void init_time();
@ -102,7 +102,6 @@ namespace cmd {
init_grid(); init_grid();
init_help(); init_help();
init_keyframe(); init_keyframe();
init_menu();
init_recent(); init_recent();
init_subtitle(); init_subtitle();
init_time(); init_time();

View file

@ -37,8 +37,8 @@ DEFINE_SIMPLE_EXCEPTION_NOINNER(CommandIconNone, CommandError, "command/icon")
DEFINE_SIMPLE_EXCEPTION_NOINNER(CommandIconInvalid, CommandError, "command/icon/invalid") DEFINE_SIMPLE_EXCEPTION_NOINNER(CommandIconInvalid, CommandError, "command/icon/invalid")
#define CMD_NAME(a) const char* name() { return a; } #define CMD_NAME(a) const char* name() { return a; }
#define STR_MENU(a) wxString StrMenu() const { return a; } #define STR_MENU(a) wxString StrMenu(const agi::Context *) const { return a; }
#define STR_DISP(a) wxString StrDisplay() 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 STR_HELP(a) wxString StrHelp() const { return a; }
#define CMD_TYPE(a) int Type() const { using namespace cmd; return a; } #define CMD_TYPE(a) int Type() const { using namespace cmd; return a; }
@ -85,10 +85,15 @@ namespace cmd {
/// Holds an individual Command /// Holds an individual Command
class Command { class Command {
public: public:
virtual const char* name()=0; ///< Command name. /// Command name
virtual wxString StrMenu() const=0; ///< String for menu purposes including accelerators. virtual const char* name()=0;
virtual wxString StrDisplay() const=0; ///< Plain string for display purposes. /// String for menu purposes including accelerators, but not hotkeys
virtual wxString StrHelp() const=0; ///< Short help string descripting the command purpose. 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 /// Get this command's type flags
/// @return Bitmask of CommandFlags /// @return Bitmask of CommandFlags

View file

@ -347,11 +347,16 @@ struct edit_line_swap : public Command {
/// Redoes last action. /// Redoes last action.
struct edit_redo : public Command { struct edit_redo : public Command {
CMD_NAME("edit/redo") CMD_NAME("edit/redo")
STR_MENU("&Redo")
STR_DISP("Redo")
STR_HELP("Redoes last action.") STR_HELP("Redoes last action.")
CMD_TYPE(COMMAND_VALIDATE | COMMAND_DYNAMIC_NAME) 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) { bool Validate(const agi::Context *c) {
return !c->ass->IsRedoStackEmpty(); return !c->ass->IsRedoStackEmpty();
} }
@ -380,11 +385,16 @@ struct edit_search_replace : public Command {
/// Undoes last action. /// Undoes last action.
struct edit_undo : public Command { struct edit_undo : public Command {
CMD_NAME("edit/undo") CMD_NAME("edit/undo")
STR_MENU("&Undo")
STR_DISP("Undo")
STR_HELP("Undoes last action.") STR_HELP("Undoes last action.")
CMD_TYPE(COMMAND_VALIDATE | COMMAND_DYNAMIC_NAME) 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) { bool Validate(const agi::Context *c) {
return !c->ass->IsUndoStackEmpty(); return !c->ass->IsUndoStackEmpty();
} }

View file

@ -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);
}
}

View file

@ -57,9 +57,9 @@ namespace {
/// @{ /// @{
COMMAND_GROUP(recent_audio, "recent/audio", "Recent", "Recent", "Open recent audio."); 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_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."); COMMAND_GROUP(recent_video, "recent/video", "Recent", "Recent", "Open recent video.");
struct recent_audio_entry : public Command { struct recent_audio_entry : public Command {
@ -73,8 +73,8 @@ struct recent_audio_entry : public Command {
} }
}; };
struct recent_keyframe_entry : public Command { struct recent_keyframes_entry : public Command {
CMD_NAME("recent/keyframe/") CMD_NAME("recent/keyframes/")
STR_MENU("Recent") STR_MENU("Recent")
STR_DISP("Recent") STR_DISP("Recent")
STR_HELP("Open recent keyframes.") STR_HELP("Open recent keyframes.")
@ -95,8 +95,8 @@ struct recent_subtitle_entry : public Command {
} }
}; };
struct recent_timecode_entry : public Command { struct recent_timecodes_entry : public Command {
CMD_NAME("recent/timecode/") CMD_NAME("recent/timecodes/")
STR_MENU("Recent") STR_MENU("Recent")
STR_DISP("Recent") STR_DISP("Recent")
STR_HELP("Open recent timecodes.") STR_HELP("Open recent timecodes.")
@ -141,17 +141,17 @@ public:
namespace cmd { namespace cmd {
void init_recent() { void init_recent() {
reg(new recent_audio); reg(new recent_audio);
reg(new recent_keyframe); reg(new recent_keyframes);
reg(new recent_subtitle); reg(new recent_subtitle);
reg(new recent_timecode); reg(new recent_timecodes);
reg(new recent_video); reg(new recent_video);
/// @todo 16 is an implementation detail that maybe needs to be exposed /// @todo 16 is an implementation detail that maybe needs to be exposed
for (int i = 0; i < 16; ++i) { for (int i = 0; i < 16; ++i) {
reg(new mru_wrapper<recent_audio_entry>(i)); reg(new mru_wrapper<recent_audio_entry>(i));
reg(new mru_wrapper<recent_keyframe_entry>(i)); reg(new mru_wrapper<recent_keyframes_entry>(i));
reg(new mru_wrapper<recent_subtitle_entry>(i)); reg(new mru_wrapper<recent_subtitle_entry>(i));
reg(new mru_wrapper<recent_timecode_entry>(i)); reg(new mru_wrapper<recent_timecodes_entry>(i));
reg(new mru_wrapper<recent_video_entry>(i)); reg(new mru_wrapper<recent_video_entry>(i));
} }
} }

View file

@ -144,10 +144,6 @@ FrameMain::FrameMain (wxArrayString args)
context->previousFocus = 0; context->previousFocus = 0;
AegisubApp::Get()->frame = this; 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__ #ifdef __WXMAC__
// Bind(FrameMain::OnAbout, &FrameMain::cmd_call, this, cmd::id("app/about")); // Bind(FrameMain::OnAbout, &FrameMain::cmd_call, this, cmd::id("app/about"));
#endif #endif
@ -163,7 +159,7 @@ FrameMain::FrameMain (wxArrayString args)
InitToolbar(); InitToolbar();
StartupLog("Initialize menu bar"); StartupLog("Initialize menu bar");
InitMenu(); menu::GetMenuBar("main", this, context.get());
StartupLog("Create status bar"); StartupLog("Create status bar");
CreateStatusBar(2); CreateStatusBar(2);
@ -248,29 +244,26 @@ void FrameMain::cmd_call(wxCommandEvent& event) {
LOG_D("event/select") << "Id: " << id; LOG_D("event/select") << "Id: " << id;
if (id < cmd::count()) if (id < cmd::count())
cmd::call(context.get(), id); 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 () { void FrameMain::InitToolbar () {
wxSystemOptions::SetOption("msw.remap", 0); wxSystemOptions::SetOption("msw.remap", 0);
toolbar::AttachToolbar(this, "main", context.get(), "Default"); toolbar::AttachToolbar(this, "main", context.get(), "Default");
GetToolBar()->Realize(); 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() { void FrameMain::InitContents() {
StartupLog("Create background panel"); StartupLog("Create background panel");
Panel = new wxPanel(this,-1,wxDefaultPosition,wxDefaultSize,wxTAB_TRAVERSAL | wxCLIP_CHILDREN); 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_SASH_DRAGGED(ID_SASH_MAIN_AUDIO, FrameMain::OnAudioBoxResize)
EVT_MENU_OPEN(FrameMain::OnMenuOpen)
EVT_KEY_DOWN(FrameMain::OnKeyDown) EVT_KEY_DOWN(FrameMain::OnKeyDown)
#ifdef __WXMAC__ #ifdef __WXMAC__
@ -616,208 +608,6 @@ BEGIN_EVENT_TABLE(FrameMain, wxFrame)
#endif #endif
END_EVENT_TABLE() 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<Automation4::FeatureMacro*> &macros) {
#ifdef WITH_AUTOMATION
if (macros.empty()) {
return 0;
}
int id = activeMacroItems.size();;
for (std::vector<Automation4::FeatureMacro*>::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<int> 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) { void FrameMain::OnCloseWindow (wxCloseEvent &event) {
// Stop audio and video // Stop audio and video
context->videoController->Stop(); context->videoController->Stop();

View file

@ -99,7 +99,6 @@ class FrameMain: public wxFrame {
void InitToolbar(); void InitToolbar();
void InitContents(); void InitContents();
void InitMenu();
bool LoadList(wxArrayString list); bool LoadList(wxArrayString list);
void UpdateTitle(); void UpdateTitle();
@ -112,15 +111,11 @@ class FrameMain: public wxFrame {
void OnAutoSave(wxTimerEvent &event); void OnAutoSave(wxTimerEvent &event);
void OnStatusClear(wxTimerEvent &event); void OnStatusClear(wxTimerEvent &event);
void OnCloseWindow (wxCloseEvent &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 /// 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? /// @param enableCancel Should the user be able to cancel the close?
int TryToCloseSubs(bool enableCancel=true); int TryToCloseSubs(bool enableCancel=true);
void RebuildRecentList(const char *root_command, const char *mru_name);
// AudioControllerAudioEventListener implementation // AudioControllerAudioEventListener implementation
void OnAudioOpen(AudioProvider *provider); void OnAudioOpen(AudioProvider *provider);
void OnAudioClose(); void OnAudioClose();

View file

@ -1,4 +1,4 @@
// Copyright (c) 2010, Amar Takhar <verm@aegisub.org> // Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
// //
// Permission to use, copy, modify, and distribute this software for any // Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above // purpose with or without fee is hereby granted, provided that the above
@ -19,51 +19,44 @@
/// @ingroup menu toolbar /// @ingroup menu toolbar
#ifndef AGI_PRE #ifndef AGI_PRE
#include <map> #include <string>
#include <wx/menu.h>
#endif #endif
#include <libaegisub/exception.h> #include <libaegisub/exception.h>
namespace json { class Array; } namespace agi { struct Context; }
class wxMenu;
class wxMenuBar;
namespace menu { namespace menu {
DEFINE_BASE_EXCEPTION_NOINNER(MenuError, agi::Exception) DEFINE_BASE_EXCEPTION_NOINNER(Error, agi::Exception)
DEFINE_SIMPLE_EXCEPTION_NOINNER(MenuJsonValueArray, MenuError, "menu/value/array") DEFINE_SIMPLE_EXCEPTION_NOINNER(UnknownMenu, Error, "menu/unknown")
DEFINE_SIMPLE_EXCEPTION_NOINNER(MenuJsonValueSingle, MenuError, "menu/value") DEFINE_SIMPLE_EXCEPTION_NOINNER(InvalidMenu, Error, "menu/invalid")
DEFINE_SIMPLE_EXCEPTION_NOINNER(MenuJsonValueNull, MenuError, "menu/value")
DEFINE_SIMPLE_EXCEPTION_NOINNER(MenuInvalidName, MenuError, "menu/invalid")
class Menu; /// @brief Get the menu with the specified name as a wxMenuBar
extern Menu *menu; /// @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 { /// @brief Get the menu with the specified name as a wxMenu
public: /// @param name Name of the menu
Menu(); ///
~Menu(); /// Throws:
wxMenuBar* GetMainMenu() { return main_menu; } /// UnknownMenu if no menu with the given name was found
wxMenu* GetMenu(std::string name); /// BadMenu if there is a menu with the given name, but it is invalid
wxMenu *GetMenu(std::string const& name, agi::Context *c);
private: /// @brief Open a popup menu at the mouse
/// @param menu Menu to open
typedef std::map<std::string, wxMenu*> MTMap; /// @param parent_window Parent window for the menu; cannot be NULL
typedef std::pair<std::string, wxMenu*> MTPair; ///
/// This function should be used rather than wxWindow::PopupMenu due to
enum MenuTypes { /// that PopupMenu does not trigger menu open events and triggers update
Option = 1, /// ui events on the opening window rather than the menu for some bizarre
Check = 2, /// reason
Radio = 3, void OpenPopupMenu(wxMenu *menu, wxWindow *parent_window);
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

View file

@ -1,580 +1,175 @@
{ {
"main" : [ "main" : [
{ { "submenu" : "main/file", "text" : "&File" },
"type" : 4, { "submenu" : "main/edit", "text" : "&Edit" },
"command" : "file", { "submenu" : "main/subtitle", "text" : "&Subtitle" },
"contents" : [ { "submenu" : "main/timing", "text" : "&Timing" },
{ { "submenu" : "main/video", "text" : "&Video" },
"type" : 1, { "submenu" : "main/audio", "text" : "&Audio" },
"command" : "subtitle/new" { "submenu" : "main/automation", "text" : "A&utomation" },
}, { "submenu" : "main/view", "text" : "&View" },
{ { "submenu" : "main/help", "text" : "&Help", "special" : "help" }
"type" : 1, ],
"command" : "subtitle/open" "main/file" : [
}, { "command" : "subtitle/new" },
{ { "command" : "subtitle/open" },
"type" : 1, { "command" : "subtitle/open/charset" },
"command" : "subtitle/open/charset" { "command" : "subtitle/open/video" },
}, { "command" : "subtitle/save" },
{ { "command" : "subtitle/save/as" },
"type" : 1, { "command" : "tool/export" },
"command" : "subtitle/open/video" { "recent" : "Subtitle" },
}, {},
{ { "command" : "subtitle/properties" },
"type" : 1, { "command" : "subtitle/attachment" },
"command" : "subtitle/save" { "command" : "tool/font_collector" },
}, {},
{ { "command" : "app/new_window" },
"type" : 1, { "command" : "app/exit", "special" : "exit" }
"command" : "subtitle/save/as" ],
}, "main/edit" : [
{ { "command" : "edit/undo" },
"type" : 1, { "command" : "edit/redo" },
"command" : "tool/export" {},
}, { "command" : "edit/line/cut" },
{ { "command" : "edit/line/copy" },
"type" : 5, { "command" : "edit/line/paste" },
"command" : "recent/subtitle" { "command" : "edit/line/paste/over" },
}, {},
{ { "command" : "subtitle/find" },
"type" : 100 { "command" : "subtitle/find/next" },
}, { "command" : "edit/search_replace" }
{ ],
"type" : 1, "main/subtitle" : [
"command" : "subtitle/properties" { "command" : "tool/style/manager" },
}, { "command" : "tool/style/assistant" },
{ { "command" : "tool/translation_assistant" },
"type" : 1, { "command" : "tool/resampleres" },
"command" : "subtitle/attachment" { "command" : "subtitle/spellcheck" },
}, {},
{ { "command" : "tool/assdraw" },
"type" : 1, { "submenu" : "main/subtitle/insert lines", "text" : "&Insert Lines" },
"command" : "tool/font_collector" { "command" : "edit/line/duplicate" },
}, { "command" : "edit/line/duplicate/shift" },
{ { "command" : "edit/line/delete" },
"type" : 100 {},
}, { "submenu" : "main/subtitle/join lines", "text" : "Join Lines" },
{ { "command" : "edit/line/recombine" },
"type" : 1, { "command" : "edit/line/split/by_karaoke" },
"command" : "app/new_window" {},
}, { "submenu" : "main/subtitle/sort lines", "text" : "Sort Lines" },
{ { "command" : "edit/line/swap" },
"type" : 1, { "command" : "tool/line/select" }
"command" : "app/exit" ],
} "main/subtitle/insert lines" : [
] { "command" : "subtitle/insert/before" },
}, { "command" : "subtitle/insert/after" },
{ { "command" : "subtitle/insert/before/videotime" },
"type" : 4, { "command" : "subtitle/insert/after/videotime" }
"command" : "edit", ],
"contents" : [ "main/subtitle/join lines" : [
{ { "command" : "edit/line/join/concatenate" },
"type" : 1, { "command" : "edit/line/join/keep_first" },
"command" : "edit/undo" { "command" : "edit/line/join/as_karaoke" }
}, ],
{ "main/subtitle/sort lines" : [
"type" : 1, { "command" : "time/sort/start" },
"command" : "edit/redo" { "command" : "time/sort/end" },
}, { "command" : "time/sort/style" }
{ ],
"type" : 100 "main/timing" : [
}, { "command" : "time/shift" },
{ { "command" : "tool/time/postprocess" },
"type" : 1, { "command" : "tool/time/kanji" },
"command" : "edit/line/cut" {},
}, { "command" : "time/snap/start_video" },
{ { "command" : "time/snap/end_video" },
"type" : 1, { "command" : "time/snap/scene" },
"command" : "edit/line/copy" { "command" : "time/frame/current" },
}, {},
{ { "submenu" : "main/timing/make times continuous", "text" : "Make Times Continuous" }
"type" : 1, ],
"command" : "edit/line/paste" "main/timing/make times continuous" : [
}, { "command" : "time/continuous/start" },
{ { "command" : "time/continuous/end" }
"type" : 1, ],
"command" : "edit/line/paste/over" "main/video" : [
}, { "command" : "video/open" },
{ { "command" : "video/close" },
"type" : 100 { "recent" : "Video" },
}, { "command" : "video/open/dummy" },
{ { "command" : "video/details" },
"type" : 1, {},
"command" : "subtitle/find" { "command" : "timecode/open" },
}, { "command" : "timecode/save" },
{ { "command" : "timecode/close" },
"type" : 1, { "recent" : "Timecodes" },
"command" : "subtitle/find/next" {},
}, { "command" : "keyframe/open" },
{ { "command" : "keyframe/save" },
"type" : 1, { "command" : "keyframe/close" },
"command" : "edit/search_replace" { "recent" : "Keyframes" },
} {},
] { "command" : "video/detach" },
}, { "submenu" : "main/video/set zoom", "text" : "Set Zoom" },
{ { "submenu" : "main/video/override ar", "text" : "Override AR" },
"type" : 4, { "command" : "video/show_overscan" },
"command" : "subtitle", {},
"contents" : [ { "command" : "video/jump" },
{ { "command" : "video/jump/start" },
"type" : 1, { "command" : "video/jump/end" }
"command" : "tool/style/manager" ],
}, "main/video/set zoom" : [
{ { "command" : "video/zoom/50" },
"type" : 1, { "command" : "video/zoom/100" },
"command" : "tool/style/assistant" { "command" : "video/zoom/200" }
}, ],
{ "main/video/override ar" : [
"type" : 1, { "command" : "video/aspect/default" },
"command" : "tool/translation_assistant" { "command" : "video/aspect/full" },
}, { "command" : "video/aspect/wide" },
{ { "command" : "video/aspect/cinematic" },
"type" : 1, { "command" : "video/aspect/custom" }
"command" : "tool/resampleres" ],
}, "main/audio" : [
{ { "command" : "audio/open" },
"type" : 1, { "command" : "audio/open/video" },
"command" : "subtitle/spellcheck" { "command" : "audio/close" },
}, { "recent" : "Audio" },
{ {},
"type" : 100 { "command" : "audio/view/spectrum" },
}, { "command" : "audio/view/waveform" },
{ { "command" : "audio/open/blank" },
"type" : 1, { "command" : "audio/open/noise" }
"command" : "tool/assdraw" ],
}, "main/automation" : [
{ { "command" : "am/manager" },
"type" : 4, {}
"command" : "insert lines", ],
"contents" : [ "main/view" : [
{ { "command" : "app/language" },
"type" : 1, { "command" : "app/options", "special" : "options" },
"command" : "subtitle/insert/before" {},
}, { "command" : "app/display/subs" },
{ { "command" : "app/display/video_subs" },
"type" : 1, { "command" : "app/display/audio_subs" },
"command" : "subtitle/insert/after" { "command" : "app/display/full" },
}, {},
{ { "command" : "grid/tags/show" },
"type" : 1, { "command" : "grid/tags/simplify" },
"command" : "subtitle/insert/before/videotime" { "command" : "grid/tags/hide" }
}, ],
{ "main/help" : [
"type" : 1, { "command" : "help/contents" },
"command" : "subtitle/insert/after/videotime" {},
} { "command" : "help/files" },
] { "command" : "help/website" },
}, { "command" : "help/forums" },
{ { "command" : "help/bugs" },
"type" : 1, {},
"command" : "edit/line/duplicate" { "command" : "help/irc" },
}, { "command" : "app/updates" },
{ { "command" : "app/about", "special" : "about" },
"type" : 1, { "command" : "app/log" }
"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"
}
]
}
]
} }

View file

@ -195,9 +195,6 @@ bool AegisubApp::OnInit() {
// Init icons. // Init icons.
icon::icon_init(); icon::icon_init();
// Generate menus.
menu::menu = new menu::Menu();
// Install assertion handler // Install assertion handler
// wxSetAssertHandler(wxAssertHandler); // wxSetAssertHandler(wxAssertHandler);
@ -312,10 +309,12 @@ bool AegisubApp::OnInit() {
return false; return false;
} }
#ifndef _DEBUG
catch (...) { catch (...) {
wxMessageBox(_T("Unhandled exception"),_T("Fatal error while initializing")); wxMessageBox(_T("Unhandled exception"),_T("Fatal error while initializing"));
return false; return false;
} }
#endif
StartupLog(_T("Initialization complete")); StartupLog(_T("Initialization complete"));
return true; return true;
@ -334,7 +333,6 @@ int AegisubApp::OnExit() {
delete config::mru; delete config::mru;
delete agi::hotkey::hotkey; delete agi::hotkey::hotkey;
delete config::path; delete config::path;
delete menu::menu;
cmd::clear(); cmd::clear();
#ifdef WITH_AUTOMATION #ifdef WITH_AUTOMATION

View file

@ -1,4 +1,4 @@
// Copyright (c) 2010, Amar Takhar <verm@aegisub.org> // Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
// //
// Permission to use, copy, modify, and distribute this software for any // Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above // purpose with or without fee is hereby granted, provided that the above
@ -20,144 +20,339 @@
#include "config.h" #include "config.h"
#ifndef AGI_PRE
#include <math.h>
#include <memory>
#include <wx/menuitem.h>
#endif
#include "include/aegisub/menu.h" #include "include/aegisub/menu.h"
#include <libaegisub/io.h> #include "include/aegisub/hotkey.h"
#include "command/command.h"
#include "compat.h"
#include "libresrc/libresrc.h"
#include "main.h"
#include "standard_paths.h"
#include <libaegisub/json.h> #include <libaegisub/json.h>
#include <libaegisub/log.h> #include <libaegisub/log.h>
#include "include/aegisub/hotkey.h" #ifndef AGI_PRE
#include "command/command.h" #include <deque>
#include "libresrc/libresrc.h" #include <sstream>
#include "main.h" #include <vector>
namespace menu { #include <wx/app.h>
#include <wx/menuitem.h>
#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)); class MruMenu : public wxMenu {
json::UnknownElement menu_root = agi::json_util::parse(stream); std::string type;
std::vector<wxMenuItem *> items;
std::vector<cmd::Command*> *cmds;
LOG_D("menu/init") << "Generating Menus"; void Resize(size_t new_size) {
json::Object object = menu_root; for (size_t i = GetMenuItemCount(); i > new_size; --i) {
Remove(FindItemByPosition(i - 1));
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);
} }
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<cmd::Command*> *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()) { if (mru->empty()) {
return index->second; Resize(1);
items[0]->Enable(false);
items[0]->SetItemLabel(_("Empty"));
return;
} }
LOG_E("menu/invalid") << "Invalid index name: " << name; int i = 0;
throw MenuInvalidName("Unknown index"); 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<std::pair<cmd::Command*, wxMenuItem*> > dynamic_items;
/// window id -> command map
std::vector<cmd::Command*> items;
/// MRU menus which need to be updated on menu open
std::deque<MruMenu*> mru;
/// Project context
agi::Context *context;
/// Update a single dynamic menu item
void UpdateItem(std::pair<cmd::Command*, wxMenuItem*> 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) { wxMenuItem *item = new wxMenuItem(parent, MENU_ID_BASE + items.size(), menu_text, co->StrHelp(), kind);
wxMenu *menu = new wxMenu(); #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++) { return item->GetId();
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;
} }
/// 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"]; void OnMenuOpen(wxMenuEvent &) {
std::string name_submenu = name_sub + "/" + command.Value(); 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<size_t>(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<json::String const&>(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 { 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&) { catch (json::Reader::ParseException const& e) {
LOG_W("menu/command/not_found") << "Command '" << cmd_name << "' not found; skipping"; LOG_E("menu/parse") << "json::ParseException: " << e.what() << ", Line/offset: " << e.m_locTokenBegin.m_nLine + 1 << '/' << e.m_locTokenBegin.m_nLineOffset + 1;
continue; throw;
} }
catch (std::exception const& e) {
wxString display = cmd->StrMenu() + "\t" + hotkey::get_hotkey_str_first("Default", cmd_name); LOG_E("menu/parse") << e.what();
wxString descr = cmd->StrHelp(); throw;
switch (type) {
case Menu::Option: {
wxMenuItem *menu_item = new wxMenuItem(menu, cmd::id(command.Value()), display, descr, wxITEM_NORMAL);
menu->Append(menu_item);
} }
break;
case Menu::Check: {
menu->AppendCheckItem(cmd::id(command.Value()), display, descr);
} }
break;
case Menu::Radio: { /// Get the menu with the specified name
menu->AppendRadioItem(cmd::id(command.Value()), display, descr); /// @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: { std::string submenu, recent, command, text, special;
wxMenu *menu_new = new wxMenu(); read_entry(ele, "special", &special);
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));
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"]; if (special == "automation") {
#ifdef WITH_AUTOMATION
wxMenu *menu_new = BuildMenu(name_sub.append("/").append(command), arr, 1); /// @todo Actually implement this
map.insert(MTPair(name_submenu, menu_new)); parent->Append(-1, _("No Automation macros loaded"))->Enable(false);
#endif
if (submenu) { return;
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 (!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; 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);
}
}

View file

@ -134,7 +134,7 @@ void SubtitlesGrid::OnCommand(wxCommandEvent& event) {
static inline void append_command(wxMenu &menu, const char *name, const agi::Context *context) { static inline void append_command(wxMenu &menu, const char *name, const agi::Context *context) {
cmd::Command *c = cmd::get(name); 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 /// @brief Popup menu

View file

@ -143,7 +143,7 @@ namespace {
} }
if (hotkeys.size()) tooltip += ")"; 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); commands.push_back(command);
needs_onidle = needs_onidle || flags != cmd::COMMAND_NORMAL; needs_onidle = needs_onidle || flags != cmd::COMMAND_NORMAL;

View file

@ -60,7 +60,6 @@ wxString DecodeRelativePath(wxString path,wxString reference);
wxString AegiFloatToString(double value); wxString AegiFloatToString(double value);
wxString AegiIntegerToString(int value); wxString AegiIntegerToString(int value);
wxString PrettySize(int bytes); wxString PrettySize(int bytes);
wxMenuItem *AppendBitmapMenuItem (wxMenu* parentMenu,int id,wxString text,wxString help,wxBitmap bmp,int pos=-1);
int SmallestPowerOf2(int x); int SmallestPowerOf2(int x);
void GetWordBoundaries(const wxString text,IntPairVector &results,int start=0,int end=-1); void GetWordBoundaries(const wxString text,IntPairVector &results,int start=0,int end=-1);
bool IsWhitespace(wchar_t c); bool IsWhitespace(wchar_t c);