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"
>
</File>
<File
RelativePath="..\..\src\command\menu_.cpp"
>
</File>
<File
RelativePath="..\..\src\command\recent.cpp"
>

View file

@ -19,6 +19,7 @@
/// @ingroup command
#include "command.h"
#include "icon.h"
#include <libaegisub/log.h>
@ -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();

View file

@ -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

View file

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

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_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<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_timecode_entry>(i));
reg(new mru_wrapper<recent_timecodes_entry>(i));
reg(new mru_wrapper<recent_video_entry>(i));
}
}

View file

@ -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<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) {
// Stop audio and video
context->videoController->Stop();

View file

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

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
// purpose with or without fee is hereby granted, provided that the above
@ -19,51 +19,44 @@
/// @ingroup menu toolbar
#ifndef AGI_PRE
#include <map>
#include <wx/menu.h>
#include <string>
#endif
#include <libaegisub/exception.h>
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<std::string, wxMenu*> MTMap;
typedef std::pair<std::string, wxMenu*> 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);
}

View file

@ -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"
}
]
}
{ "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" }
]
}

View file

@ -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

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
// purpose with or without fee is hereby granted, provided that the above
@ -20,144 +20,339 @@
#include "config.h"
#ifndef AGI_PRE
#include <math.h>
#include <memory>
#include <wx/menuitem.h>
#endif
#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/log.h>
#include "include/aegisub/hotkey.h"
#include "command/command.h"
#include "libresrc/libresrc.h"
#include "main.h"
#ifndef AGI_PRE
#include <deque>
#include <sstream>
#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));
json::UnknownElement menu_root = agi::json_util::parse(stream);
class MruMenu : public wxMenu {
std::string type;
std::vector<wxMenuItem *> items;
std::vector<cmd::Command*> *cmds;
LOG_D("menu/init") << "Generating Menus";
json::Object object = menu_root;
void Resize(size_t new_size) {
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() {}
wxMenu* Menu::GetMenu(std::string name) {
MTMap::iterator index;
if ((index = map.find(name)) != map.end()) {
return index->second;
public:
MruMenu(std::string const& type, std::vector<cmd::Command*> *cmds)
: type(type)
, cmds(cmds)
{
}
LOG_E("menu/invalid") << "Invalid index name: " << name;
throw MenuInvalidName("Unknown index");
~MruMenu() {
// Append all items to ensure that they're all cleaned up
Resize(items.size());
}
void Update() {
const agi::MRUManager::MRUListMap *mru = config::mru->Get(type);
Resize(mru->size());
wxMenu* Menu::BuildMenu(std::string name, const json::Array& array, int submenu) {
wxMenu *menu = new wxMenu();
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;
if (mru->empty()) {
Resize(1);
items[0]->Enable(false);
items[0]->SetItemLabel(_("Empty"));
return;
}
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);
}
}
};
const json::String& command = obj["command"];
std::string name_submenu = name_sub + "/" + command.Value();
/// @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());
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));
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"));
}
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<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 {
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);
}
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));
}
break;
case Menu::Submenu: {
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);
}
}
break;
}
} // for index
/// 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;
}
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;
}
if (read_entry(ele, "recent", &recent)) {
cm->AddRecent(recent, parent);
return;
}
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();
}
}
/// 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);
}
}

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) {
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

View file

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

View file

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