From f48f17cd0bb1463bd6fd69ac50437039f5cd2f4a Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 28 Oct 2011 20:40:43 +0000 Subject: [PATCH] Implement the hotkey page of the preferences dialog Originally committed to SVN as r5794. --- .../aegisub_vs2008/aegisub_vs2008.vcproj | 44 ++- aegisub/build/msbuild/Aegisub/Aegisub.vcxproj | 2 + .../msbuild/Aegisub/Aegisub.vcxproj.filters | 6 + aegisub/src/Makefile | 1 + aegisub/src/agi_pre.h | 2 + aegisub/src/hotkey.cpp | 30 +- aegisub/src/hotkey_data_view_model.cpp | 339 ++++++++++++++++++ aegisub/src/hotkey_data_view_model.h | 66 ++++ aegisub/src/include/aegisub/hotkey.h | 12 +- aegisub/src/preferences.cpp | 248 ++++++++++++- aegisub/src/preferences.h | 13 + aegisub/src/preferences_base.cpp | 102 ------ aegisub/src/preferences_base.h | 22 -- 13 files changed, 730 insertions(+), 157 deletions(-) create mode 100644 aegisub/src/hotkey_data_view_model.cpp create mode 100644 aegisub/src/hotkey_data_view_model.h diff --git a/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj b/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj index a040ae26a..4d501a70e 100644 --- a/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj +++ b/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj @@ -1219,22 +1219,6 @@ RelativePath="..\..\src\dialog_video_details.h" > - - - - - - - - + + + + + + + + + + + + + + diff --git a/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj b/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj index 8592e9a3c..faa9cb2e1 100644 --- a/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj +++ b/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj @@ -145,6 +145,7 @@ + @@ -332,6 +333,7 @@ + diff --git a/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj.filters b/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj.filters index f0420b8b1..9eede2120 100644 --- a/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj.filters +++ b/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj.filters @@ -558,6 +558,9 @@ Utilities + + Preferences + Main UI @@ -1073,6 +1076,9 @@ Utilities + + Preferences + Main UI diff --git a/aegisub/src/Makefile b/aegisub/src/Makefile index 6b8370d59..cc3c2168c 100644 --- a/aegisub/src/Makefile +++ b/aegisub/src/Makefile @@ -192,6 +192,7 @@ SRC += \ gl_wrap.cpp \ help_button.cpp \ hotkey.cpp \ + hotkey_data_view_model.cpp \ kana_table.cpp \ main.cpp \ menu.cpp \ diff --git a/aegisub/src/agi_pre.h b/aegisub/src/agi_pre.h index 4865d750f..8fd6a7f3d 100644 --- a/aegisub/src/agi_pre.h +++ b/aegisub/src/agi_pre.h @@ -115,6 +115,7 @@ #include #include #include +#include #include #include #include @@ -169,6 +170,7 @@ #include #include #include +#include #include #include #include diff --git a/aegisub/src/hotkey.cpp b/aegisub/src/hotkey.cpp index f0e13e3c4..0443e64c6 100644 --- a/aegisub/src/hotkey.cpp +++ b/aegisub/src/hotkey.cpp @@ -26,10 +26,23 @@ #include "libresrc/libresrc.h" #include "command/command.h" +#include "compat.h" #include "main.h" +#include "standard_paths.h" namespace hotkey { +agi::hotkey::Hotkey *inst = 0; +void init() { + inst = new agi::hotkey::Hotkey( + STD_STR(StandardPaths::DecodePath("?user/hotkey.json")), + GET_DEFAULT_CONFIG(default_hotkey)); +} + +void clear() { + delete inst; +} + static std::vector keycode_names; static std::string const& get_keycode_name(int code); @@ -47,7 +60,7 @@ static std::string const& keycode_name(int code) { return keycode_names[code]; } -bool check(std::string const& context, agi::Context *c, int key_code, wchar_t key_char, int modifier) { +std::string keypress_to_str(int key_code, wchar_t key_char, int modifier) { std::string combo; if ((modifier != wxMOD_NONE)) { if ((modifier & wxMOD_CMD) != 0) combo.append("Ctrl-"); @@ -56,10 +69,16 @@ bool check(std::string const& context, agi::Context *c, int key_code, wchar_t ke } combo += keycode_name(key_code); + + return combo; +} + +bool check(std::string const& context, agi::Context *c, int key_code, wchar_t key_char, int modifier) { + std::string combo = keypress_to_str(key_code, key_char, modifier); if (combo.empty()) return false; std::string command; - if (agi::hotkey::hotkey->Scan(context, combo, OPT_GET("Audio/Medusa Timing Hotkeys")->GetBool(), command)) { + if (inst->Scan(context, combo, OPT_GET("Audio/Medusa Timing Hotkeys")->GetBool(), command)) { /// The bottom line should be removed after all the hotkey commands are fixed. /// This is to avoid pointless exceptions. if (command.find("/") != std::string::npos) { @@ -71,14 +90,13 @@ bool check(std::string const& context, agi::Context *c, int key_code, wchar_t ke } std::vector get_hotkey_strs(std::string const& context, std::string const& command) { - return agi::hotkey::hotkey->GetHotkeys(context, command); + return inst->GetHotkeys(context, command); } std::string get_hotkey_str_first(std::string const& context, std::string const& command) { - return agi::hotkey::hotkey->GetHotkey(context, command); + return inst->GetHotkey(context, command); } - static inline void set_kc(std::vector &vec, int code, std::string const& str) { if (static_cast(code) >= vec.size()) vec.resize(code * 2, ""); vec[code] = str; @@ -176,6 +194,4 @@ static void init_keycode_names() { set_kc(keycode_names, WXK_NUMPAD_DIVIDE, "KP_Divide"); } - } // namespace hotkey - diff --git a/aegisub/src/hotkey_data_view_model.cpp b/aegisub/src/hotkey_data_view_model.cpp new file mode 100644 index 000000000..d838db751 --- /dev/null +++ b/aegisub/src/hotkey_data_view_model.cpp @@ -0,0 +1,339 @@ +// Copyright (c) 2011, Thomas Goyne +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file hotkey_data_view_model.cpp +/// @see hotkey_data_view_model.h +/// @ingroup hotkey configuration_ui +/// + +#include "config.h" + +#include "hotkey_data_view_model.h" + +#include +#include + +#include "command/command.h" +#include "command/icon.h" +#include "compat.h" +#include "include/aegisub/hotkey.h" +#include "preferences.h" + +#ifndef AGI_PRE +#include +#include + +#include +#endif + +using namespace agi::hotkey; + +/// @class HotkeyModelItem +/// @brief A base class for things exposed by HotkeyDataViewModel +class HotkeyModelItem { +public: + virtual unsigned int GetChildren(wxDataViewItemArray &children) const=0; + virtual wxDataViewItem GetParent() const=0; + virtual void GetValue(wxVariant &variant, unsigned int col) const=0; + virtual bool IsContainer() const=0; + virtual bool SetValue(wxVariant const& variant, unsigned int col)=0; +}; + +class HotkeyModelRoot; +class HotkeyModelCategory; + +/// @class HotkeyModelCombo +/// @brief A single hotkey exposed in the data view +/// +/// All actual mutation of hotkeys happens through this class +class HotkeyModelCombo : public HotkeyModelItem { + HotkeyModelCategory *parent; ///< The containing category + Combo combo; ///< The actual hotkey + wxString cmd_name; + wxString cmd_str; +public: + HotkeyModelCombo(HotkeyModelCategory *parent, Combo const& combo) + : parent(parent) + , combo(combo) + , cmd_name(lagi_wxString(combo.CmdName())) + , cmd_str(lagi_wxString(combo.Str())) + { + } + + bool IsVisible(wxRegEx const& filter) const { + return filter.Matches(cmd_name) || filter.Matches(cmd_str); + } + + void Apply(Hotkey::HotkeyMap *hk_map) { + hk_map->insert(make_pair(combo.CmdName(), combo)); + } + + unsigned int GetChildren(wxDataViewItemArray &children) const { return 0; } + wxDataViewItem GetParent() const { return wxDataViewItem(parent); } + bool IsContainer() const { return false; } + + void GetValue(wxVariant &variant, unsigned int col) const { + if (col == 0) + variant = combo.Str(); + else if (col == 1) { + wxIcon icon; + icon.CopyFromBitmap(icon::get(combo.CmdName(), 16)); + variant << wxDataViewIconText(combo.CmdName(), icon); + } + else if (col == 2) { + try { + variant = cmd::get(combo.CmdName())->StrHelp(); + } + catch (agi::Exception const& e) { + variant = lagi_wxString(e.GetChainedMessage()); + } + } + else + throw agi::InternalError("HotkeyDataViewModel asked for an invalid column number", 0); + } + + bool SetValue(wxVariant const& variant, unsigned int col) { + if (col == 0) { + wxArrayString toks = wxSplit(variant.GetString(), '-'); + std::vector keys; + keys.reserve(toks.size()); + for (size_t i = 0; i < toks.size(); ++i) + keys[i] = STD_STR(toks[i]); + combo = Combo(combo.Context(), combo.CmdName(), keys); + cmd_str = combo.Str(); + return true; + } + else if (col == 1) { + wxDataViewIconText text; + text << variant; + combo = Combo(combo.Context(), STD_STR(text.GetText()), combo.Get()); + cmd_name = text.GetText(); + return true; + } + return false; + } +}; + +/// A hotkey context exposed in the data view +class HotkeyModelCategory : public HotkeyModelItem { + std::list children; + wxDataViewModel *model; + wxString name; + wxDataViewItemArray visible_items; +public: + HotkeyModelCategory(wxDataViewModel *model, wxString const& name) + : model(model) + , name(name) + { + } + + void AddChild(Combo const& combo) { + children.push_back(HotkeyModelCombo(this, combo)); + visible_items.push_back(wxDataViewItem(&children.back())); + model->ItemAdded(wxDataViewItem(this), wxDataViewItem(&children.back())); + } + + void Delete(wxDataViewItem const& item) { + for (std::list::iterator it = children.begin(); it != children.end(); ++it) { + if (&*it == item.GetID()) { + model->ItemDeleted(wxDataViewItem(this), wxDataViewItem((void*)&*it)); + children.erase(it); + return; + } + } + } + + void Apply(Hotkey::HotkeyMap *hk_map) { + for_each(children.begin(), children.end(), + bind(&HotkeyModelCombo::Apply, std::tr1::placeholders::_1, hk_map)); + } + + void SetFilter(wxRegEx const& new_filter) { + std::set old_visible; + for (size_t i = 0; i < visible_items.size(); ++i) + old_visible.insert(static_cast(visible_items[i].GetID())); + + visible_items.clear(); + + wxDataViewItemArray added; + wxDataViewItemArray removed; + + for (std::list::iterator it = children.begin(); it != children.end(); ++it) { + bool was_visible = old_visible.count(&*it) > 0; + bool is_visible = it->IsVisible(new_filter); + + if (is_visible) + visible_items.push_back(wxDataViewItem(&*it)); + if (was_visible && !is_visible) + removed.push_back(wxDataViewItem(&*it)); + if (is_visible && !was_visible) + added.push_back(wxDataViewItem(&*it)); + } + + if (!added.empty()) + model->ItemsAdded(wxDataViewItem(this), added); + if (!removed.empty()) + model->ItemsDeleted(wxDataViewItem(this), removed); + } + + + wxDataViewItem GetParent() const { return wxDataViewItem(0); } + bool IsContainer() const { return true; } + bool SetValue(wxVariant const& variant, unsigned int col) { return false; } + void GetValue(wxVariant &variant, unsigned int col) const { + if (col == 1) + variant << wxDataViewIconText(name); + else + variant = name; + } + + unsigned int GetChildren(wxDataViewItemArray &out) const { + out = visible_items; + return out.size(); + } +}; + +/// The root containing the hotkey contexts +class HotkeyModelRoot : public HotkeyModelItem { + std::list categories; +public: + HotkeyModelRoot(wxDataViewModel *model) { + Hotkey::HotkeyMap const& hk_map = hotkey::inst->GetHotkeyMap(); + std::map cat_map; + + for (Hotkey::HotkeyMap::const_iterator it = hk_map.begin(); it != hk_map.end(); ++it) { + std::string cat_name = it->second.Context(); + HotkeyModelCategory *cat; + std::map::iterator cat_it = cat_map.find(cat_name); + if (cat_it != cat_map.end()) + cat = cat_it->second; + else { + categories.push_back(HotkeyModelCategory(model, cat_name)); + cat = cat_map[cat_name] = &categories.back(); + } + + cat->AddChild(it->second); + } + } + + void Apply(Hotkey::HotkeyMap *hk_map) { + for_each(categories.begin(), categories.end(), + bind(&HotkeyModelCategory::Apply, std::tr1::placeholders::_1, hk_map)); + } + + void SetFilter(wxString filter) { + // Escape any regular-expression special characters + static wxRegEx escape_meta("[-[\\]{}()*+?.,\\\\^$|#]", wxRE_ADVANCED); + escape_meta.Replace(&filter, "\\\\&"); + + // Using wxRegEx for case-insensitive contains + wxRegEx re(filter, wxRE_ADVANCED | wxRE_ICASE | wxRE_NOSUB); + for_each(categories.begin(), categories.end(), + bind(&HotkeyModelCategory::SetFilter, std::tr1::placeholders::_1, std::tr1::ref(re))); + } + + wxDataViewItem GetParent() const { return wxDataViewItem(0); } + bool IsContainer() const { return true; } + bool SetValue(wxVariant const& variant, unsigned int col) { return false; } + void GetValue(wxVariant &variant, unsigned int col) const { } + + unsigned int GetChildren(wxDataViewItemArray &out) const { + out.reserve(categories.size()); + for (std::list::const_iterator it = categories.begin(); it != categories.end(); ++it) + out.push_back(wxDataViewItem((void*)&*it)); + return out.size(); + } +}; + + +HotkeyDataViewModel::HotkeyDataViewModel(Preferences *parent) +: root(new HotkeyModelRoot(this)) +, parent(parent) +, has_pending_changes(false) +{ +} + +const HotkeyModelItem * HotkeyDataViewModel::get(wxDataViewItem const& item) const { + if (item.IsOk()) + return static_cast(item.GetID()); + return root.get(); +} + +HotkeyModelItem * HotkeyDataViewModel::get(wxDataViewItem const& item) { + if (item.IsOk()) + return static_cast(item.GetID()); + return root.get(); +} + +unsigned int HotkeyDataViewModel::GetChildren(wxDataViewItem const& item, wxDataViewItemArray &children) const { + return get(item)->GetChildren(children); +} + +wxDataViewItem HotkeyDataViewModel::GetParent(wxDataViewItem const& item) const { + return get(item)->GetParent(); +} + +void HotkeyDataViewModel::GetValue(wxVariant &variant, wxDataViewItem const& item, unsigned int col) const { + get(item)->GetValue(variant, col); +} + +bool HotkeyDataViewModel::IsContainer(wxDataViewItem const& item) const { + return get(item)->IsContainer(); +} + +bool HotkeyDataViewModel::SetValue(wxVariant const& variant, wxDataViewItem const& item, unsigned int col) { + if (!has_pending_changes) { + has_pending_changes = true; + parent->AddPendingChange(std::tr1::bind(&HotkeyDataViewModel::Apply, this)); + } + return get(item)->SetValue(variant, col); +} + +void HotkeyDataViewModel::New(wxDataViewItem item) { + if (!item.IsOk()) return; + + if (!IsContainer(item)) + item = GetParent(item); + + HotkeyModelCategory *ctx = static_cast(item.GetID()); + wxVariant name; + ctx->GetValue(name, 0); + ctx->AddChild(Combo(STD_STR(name.GetString()), "", std::vector())); +} + +void HotkeyDataViewModel::Delete(wxDataViewItem const& item) { + if (!item.IsOk() || IsContainer(item)) return; + + static_cast(GetParent(item).GetID())->Delete(item); + + if (!has_pending_changes) { + has_pending_changes = true; + parent->AddPendingChange(std::tr1::bind(&HotkeyDataViewModel::Apply, this)); + } +} + +void HotkeyDataViewModel::Apply() { + Hotkey::HotkeyMap hk_map; + root->Apply(&hk_map); + hotkey::inst->SetHotkeyMap(hk_map); + has_pending_changes = false; +} + +void HotkeyDataViewModel::SetFilter(wxString const& filter) { + root->SetFilter(filter); +} diff --git a/aegisub/src/hotkey_data_view_model.h b/aegisub/src/hotkey_data_view_model.h new file mode 100644 index 000000000..62dcfba62 --- /dev/null +++ b/aegisub/src/hotkey_data_view_model.h @@ -0,0 +1,66 @@ +// Copyright (c) 2011, Thomas Goyne +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file hotkey_data_view_model.h +/// @see hotkey_data_view_model.cpp +/// @ingroup hotkey configuration_ui +/// + +#ifndef AGI_PRE +#include +#endif + +#include + +class HotkeyModelItem; +class HotkeyModelRoot; +class Preferences; + +/// @class HotkeyDataViewModel +/// @brief A wxDataViewModel for hotkeys +class HotkeyDataViewModel : public wxDataViewModel { + agi::scoped_ptr root; + Preferences *parent; + bool has_pending_changes; + + /// Get the real item from the wrapper, or root if it's wrapping NULL + const HotkeyModelItem *get(wxDataViewItem const& item) const; + /// Get the real item from the wrapper, or root if it's wrapping NULL + HotkeyModelItem *get(wxDataViewItem const& item); +public: + HotkeyDataViewModel(Preferences *parent); + + /// Create a new hotkey in the current context + void New(wxDataViewItem item); + /// Delete the currently selected hotkey + void Delete(wxDataViewItem const& item); + /// Update the hotkeys with changes made to the model + void Apply(); + + /// Only display hotkeys containing filter, or all if filter is empty + void SetFilter(wxString const& filter); + + unsigned int GetColumnCount() const { return 3; } + wxString GetColumnType(unsigned int) const { return "string"; } + + unsigned int GetChildren(wxDataViewItem const& item, wxDataViewItemArray &children) const; + wxDataViewItem GetParent(wxDataViewItem const& item) const; + void GetValue(wxVariant &variant, wxDataViewItem const& item, unsigned int col) const; + bool IsContainer(wxDataViewItem const& item) const; + bool SetValue(wxVariant const& variant, wxDataViewItem const& item, unsigned int col); +}; diff --git a/aegisub/src/include/aegisub/hotkey.h b/aegisub/src/include/aegisub/hotkey.h index b04e59cbc..754450894 100644 --- a/aegisub/src/include/aegisub/hotkey.h +++ b/aegisub/src/include/aegisub/hotkey.h @@ -23,12 +23,22 @@ #include #endif -namespace agi { struct Context; } +namespace agi { + struct Context; + namespace hotkey { class Hotkey; } +} namespace hotkey { +extern agi::hotkey::Hotkey *inst; + +void init(); +void clear(); + bool check(std::string const& context, agi::Context *c, int key_code, wchar_t key_char, int modifier); +std::string keypress_to_str(int key_code, wchar_t key_char, int modifier); std::string get_hotkey_str_first(std::string const& context, std::string const& command); std::vector get_hotkey_strs(std::string const& context, std::string const& command); + } // namespace hotkey diff --git a/aegisub/src/preferences.cpp b/aegisub/src/preferences.cpp index ff2cebe9c..a9e223e67 100644 --- a/aegisub/src/preferences.cpp +++ b/aegisub/src/preferences.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -41,15 +42,53 @@ #include "preferences.h" #include "colour_button.h" +#include "command/command.h" #include "compat.h" +#include "hotkey_data_view_model.h" #include "include/aegisub/audio_player.h" #include "include/aegisub/audio_provider.h" +#include "include/aegisub/hotkey.h" #include "include/aegisub/subtitles_provider.h" #include "libresrc/libresrc.h" #include "main.h" #include "preferences_base.h" #include "video_provider_manager.h" +#define CLASS_PAGE(name) \ +class name: public OptionPage { \ +public: \ + name(wxTreebook *book, Preferences *parent); \ +}; + +CLASS_PAGE(General) +CLASS_PAGE(Subtitles) +CLASS_PAGE(Audio) +CLASS_PAGE(Video) +CLASS_PAGE(Interface) +CLASS_PAGE(Interface_Colours) +CLASS_PAGE(Paths) +CLASS_PAGE(File_Associations) +CLASS_PAGE(Backup) +CLASS_PAGE(Automation) +CLASS_PAGE(Advanced) +CLASS_PAGE(Advanced_Interface) +CLASS_PAGE(Advanced_Audio) +CLASS_PAGE(Advanced_Video) + +class Interface_Hotkeys : public OptionPage { + wxDataViewCtrl *dvc; + wxObjectDataPtr model; + wxSearchCtrl *quick_search; + + void OnNewButton(wxCommandEvent&); + void OnEditButton(wxCommandEvent&); + void OnDeleteButton(wxCommandEvent&); + void OnUpdateFilter(wxCommandEvent&); + void OnClearFilter(wxCommandEvent&); +public: + Interface_Hotkeys(wxTreebook *book, Preferences *parent); +}; + /// General preferences page General::General(wxTreebook *book, Preferences *parent): OptionPage(book, parent, _("General")) { @@ -213,28 +252,114 @@ Interface_Colours::Interface_Colours(wxTreebook *book, Preferences *parent): Opt SetSizerAndFit(sizer); } +/// wxDataViewIconTextRenderer with command name autocompletion +class CommandRenderer : public wxDataViewIconTextRenderer { + wxArrayString autocomplete; +public: + CommandRenderer() + : wxDataViewIconTextRenderer("wxDataViewIconText", wxDATAVIEW_CELL_EDITABLE) + { + std::vector cmd_names = cmd::get_registered_commands(); + autocomplete.reserve(cmd_names.size()); + copy(cmd_names.begin(), cmd_names.end(), std::back_inserter(autocomplete)); + } + + wxWindow *CreateEditorCtrl(wxWindow *parent, wxRect label_rect, wxVariant const& value) { + wxTextCtrl *ctrl = static_cast(wxDataViewIconTextRenderer::CreateEditorCtrl(parent, label_rect, value)); + ctrl->AutoComplete(autocomplete); + return ctrl; + } +}; + +class HotkeyRenderer : public wxDataViewTextRenderer { + wxTextCtrl *ctrl; +public: + HotkeyRenderer() : wxDataViewTextRenderer("string", wxDATAVIEW_CELL_EDITABLE) { } + + wxWindow *CreateEditorCtrl(wxWindow *parent, wxRect label_rect, wxVariant const& value) { + ctrl = static_cast(wxDataViewTextRenderer::CreateEditorCtrl(parent, label_rect, value)); + ctrl->Bind(wxEVT_KEY_DOWN, &HotkeyRenderer::OnKeyDown, this); + return ctrl; + } + + void OnKeyDown(wxKeyEvent &evt) { + ctrl->ChangeValue(lagi_wxString(hotkey::keypress_to_str(evt.GetKeyCode(), evt.GetUnicodeKey(), evt.GetModifiers()))); + } +}; /// Interface Hotkeys preferences subpage -Interface_Hotkeys::Interface_Hotkeys(wxTreebook *book, Preferences *parent): OptionPage(book, parent, _("Hotkeys"), PAGE_SUB) { - wxFlexGridSizer *hotkeys = PageSizer(_("Hotkeys")); - hotkeys->Add(new wxStaticText(this, wxID_ANY, _T("To be added after hotkey rewrite.")), 0, wxALL, 5); +Interface_Hotkeys::Interface_Hotkeys(wxTreebook *book, Preferences *parent) +: OptionPage(book, parent, _("Hotkeys"), PAGE_SUB) +, model(new HotkeyDataViewModel(parent)) +{ + quick_search = new wxSearchCtrl(this, -1); + wxButton *new_button = new wxButton(this, -1, _("New")); + wxButton *edit_button = new wxButton(this, -1, _("Edit")); + wxButton *delete_button = new wxButton(this, -1, _("Delete")); + + new_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &Interface_Hotkeys::OnNewButton, this); + edit_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &Interface_Hotkeys::OnEditButton, this); + delete_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &Interface_Hotkeys::OnDeleteButton, this); + + quick_search->Bind(wxEVT_COMMAND_TEXT_UPDATED, &Interface_Hotkeys::OnUpdateFilter, this); + quick_search->Bind(wxEVT_COMMAND_SEARCHCTRL_CANCEL_BTN, &Interface_Hotkeys::OnClearFilter, this); + + dvc = new wxDataViewCtrl(this, -1); + dvc->AssociateModel(model.get()); + dvc->AppendColumn(new wxDataViewColumn("Hotkey", new HotkeyRenderer, 0, 125, wxALIGN_LEFT, wxCOL_SORTABLE | wxCOL_RESIZABLE)); + dvc->AppendColumn(new wxDataViewColumn("Command", new CommandRenderer, 1, 250, wxALIGN_LEFT, wxCOL_SORTABLE | wxCOL_RESIZABLE)); + dvc->AppendTextColumn("Description", 2, wxDATAVIEW_CELL_INERT, 300, wxALIGN_LEFT, wxCOL_SORTABLE | wxCOL_RESIZABLE); + + wxSizer *buttons = new wxBoxSizer(wxHORIZONTAL); + buttons->Add(quick_search, wxSizerFlags().Border()); + buttons->AddStretchSpacer(1); + buttons->Add(new_button, wxSizerFlags().Border().Right()); + buttons->Add(edit_button, wxSizerFlags().Border().Right()); + buttons->Add(delete_button, wxSizerFlags().Border().Right()); + + sizer->Add(buttons, wxSizerFlags().Expand()); + sizer->Add(dvc, wxSizerFlags(1).Expand().Border(wxLEFT | wxRIGHT)); + SetSizerAndFit(sizer); } +void Interface_Hotkeys::OnNewButton(wxCommandEvent&) { + model->New(dvc->GetSelection()); +} -/// Paths preferences page class Paths: public OptionPage { public: +void Interface_Hotkeys::OnEditButton(wxCommandEvent&) { + dvc->StartEditor(dvc->GetSelection(), 0); +} + +void Interface_Hotkeys::OnDeleteButton(wxCommandEvent&) { + model->Delete(dvc->GetSelection()); +} + +void Interface_Hotkeys::OnUpdateFilter(wxCommandEvent&) { + model->SetFilter(quick_search->GetValue()); + + if (!quick_search->GetValue().empty()) { + wxDataViewItemArray contexts; + model->GetChildren(wxDataViewItem(0), contexts); + for (size_t i = 0; i < contexts.size(); ++i) + dvc->Expand(contexts[i]); + } +} + +void Interface_Hotkeys::OnClearFilter(wxCommandEvent &evt) { + quick_search->SetValue(""); +} + +/// Paths preferences page Paths::Paths(wxTreebook *book, Preferences *parent): OptionPage(book, parent, _("Paths")) { - wxFlexGridSizer *general = PageSizer(_("General")); general->Add(new wxStaticText(this, wxID_ANY, "TBD..."), 0, wxALL, 5); SetSizerAndFit(sizer); } - /// File Associations preferences page File_Associations::File_Associations(wxTreebook *book, Preferences *parent): OptionPage(book, parent, _("File Associations")) { - wxFlexGridSizer *assoc = PageSizer(_("General")); assoc->Add(new wxStaticText(this, wxID_ANY, "TBD..."), 0, wxALL, 5); @@ -258,8 +383,6 @@ Backup::Backup(wxTreebook *book, Preferences *parent): OptionPage(book, parent, SetSizerAndFit(sizer); } - - /// Automation preferences page Automation::Automation(wxTreebook *book, Preferences *parent): OptionPage(book, parent, _("Automation")) { wxFlexGridSizer *general = PageSizer(_("General")); @@ -372,3 +495,110 @@ Advanced_Video::Advanced_Video(wxTreebook *book, Preferences *parent): OptionPag SetSizerAndFit(sizer); } + +void Preferences::SetOption(std::string const& name, wxAny value) { + pending_changes[name] = value; + if (IsEnabled()) + applyButton->Enable(true); +} + +void Preferences::AddPendingChange(Thunk const& callback) { + pending_callbacks.push_back(callback); + if (IsEnabled()) + applyButton->Enable(true); +} + +void Preferences::OnOK(wxCommandEvent &event) { + OnApply(event); + EndModal(0); +} + +void Preferences::OnApply(wxCommandEvent &event) { + for (std::map::iterator cur = pending_changes.begin(); cur != pending_changes.end(); ++cur) { + agi::OptionValue *opt = OPT_SET(cur->first); + switch (opt->GetType()) { + case agi::OptionValue::Type_Bool: + opt->SetBool(cur->second.As()); + break; + case agi::OptionValue::Type_Colour: + opt->SetColour(cur->second.As()); + break; + case agi::OptionValue::Type_Double: + opt->SetDouble(cur->second.As()); + break; + case agi::OptionValue::Type_Int: + opt->SetInt(cur->second.As()); + break; + case agi::OptionValue::Type_String: + opt->SetString(cur->second.As()); + break; + default: + throw PreferenceNotSupported("Unsupported type"); + } + } + pending_changes.clear(); + + for (std::deque::iterator it = pending_callbacks.begin(); it != pending_callbacks.end(); ++it) + (*it)(); + pending_callbacks.clear(); + + applyButton->Enable(false); + config::opt->Flush(); +} + +static void PageChanged(wxBookCtrlEvent& evt) { + OPT_SET("Tool/Preferences/Page")->SetInt(evt.GetSelection()); +} + +Preferences::Preferences(wxWindow *parent): wxDialog(parent, -1, _("Preferences"), wxDefaultPosition, wxSize(-1, 500)) { + // SetIcon(BitmapToIcon(GETIMAGE(options_button_24))); + + book = new wxTreebook(this, -1, wxDefaultPosition, wxDefaultSize); + new General(book, this); + new Subtitles(book, this); + new Audio(book, this); + new Video(book, this); + new Interface(book, this); + new Interface_Colours(book, this); + new Interface_Hotkeys(book, this); + new Paths(book, this); + new File_Associations(book, this); + new Backup(book, this); + new Automation(book, this); + new Advanced(book, this); + new Advanced_Interface(book, this); + new Advanced_Audio(book, this); + new Advanced_Video(book, this); + + book->Fit(); + + book->ChangeSelection(OPT_GET("Tool/Preferences/Page")->GetInt()); + book->Bind(wxEVT_COMMAND_TREEBOOK_PAGE_CHANGED, &PageChanged); + + // Bottom Buttons + wxStdDialogButtonSizer *stdButtonSizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL | wxAPPLY); + applyButton = stdButtonSizer->GetApplyButton(); + wxSizer *buttonSizer = new wxBoxSizer(wxHORIZONTAL); + wxButton *defaultButton = new wxButton(this, -1, _("Restore Defaults")); + buttonSizer->Add(defaultButton, wxSizerFlags(0).Expand()); + buttonSizer->AddStretchSpacer(1); + buttonSizer->Add(stdButtonSizer, wxSizerFlags(0).Expand()); + + // Main Sizer + wxSizer *mainSizer = new wxBoxSizer(wxVERTICAL); + mainSizer->Add(book, wxSizerFlags(1).Expand().Border()); + mainSizer->Add(buttonSizer, wxSizerFlags(0).Expand().Border(wxALL & ~wxTOP)); + + SetSizerAndFit(mainSizer); + SetMinSize(wxSize(-1, 500)); + SetMaxSize(wxSize(-1, 500)); + CenterOnParent(); + + applyButton->Enable(false); + + Bind(wxEVT_COMMAND_BUTTON_CLICKED, &Preferences::OnOK, this, wxID_OK); + Bind(wxEVT_COMMAND_BUTTON_CLICKED, &Preferences::OnApply, this, wxID_APPLY); +} + +Preferences::~Preferences() { +} diff --git a/aegisub/src/preferences.h b/aegisub/src/preferences.h index 1a93fdcf8..3d08a8219 100644 --- a/aegisub/src/preferences.h +++ b/aegisub/src/preferences.h @@ -20,20 +20,32 @@ /// @ingroup configuration_ui #ifndef AGI_PRE +#include +#include #include #include #endif +#include + class wxAny; class wxButton; class wxTreebook; +DEFINE_BASE_EXCEPTION_NOINNER(PreferencesError, agi::Exception) +DEFINE_SIMPLE_EXCEPTION_NOINNER(PreferenceIncorrectType, PreferencesError, "preferences/incorrect_type") +DEFINE_SIMPLE_EXCEPTION_NOINNER(PreferenceNotSupported, PreferencesError, "preferences/not_supported") + class Preferences: public wxDialog { +public: + typedef std::tr1::function Thunk; +private: wxTreebook *book; wxButton *applyButton; std::map pending_changes; + std::deque pending_callbacks; void OnOK(wxCommandEvent &event); void OnCancel(wxCommandEvent &event); @@ -44,4 +56,5 @@ public: ~Preferences(); void SetOption(std::string const& name, wxAny value); + void AddPendingChange(Thunk const& callback); }; diff --git a/aegisub/src/preferences_base.cpp b/aegisub/src/preferences_base.cpp index 978856a81..023a0cd1a 100644 --- a/aegisub/src/preferences_base.cpp +++ b/aegisub/src/preferences_base.cpp @@ -36,8 +36,6 @@ #include #endif -#include - #include "preferences_base.h" #include "colour_button.h" @@ -50,10 +48,6 @@ #include "standard_paths.h" #include "video_provider_manager.h" -DEFINE_BASE_EXCEPTION_NOINNER(PreferencesError, agi::Exception) -DEFINE_SIMPLE_EXCEPTION_NOINNER(PreferenceIncorrectType, PreferencesError, "preferences/incorrect_type") -DEFINE_SIMPLE_EXCEPTION_NOINNER(PreferenceNotSupported, PreferencesError, "preferences/not_supported") - #define OPTION_UPDATER(type, evttype, body) \ class type { \ std::string name; \ @@ -254,99 +248,3 @@ void OptionPage::OptionFont(wxSizer *sizer, std::string opt_prefix) { Add(sizer, _("Font Face"), button_sizer); Add(sizer, _("Font Size"), font_size); } - -void Preferences::SetOption(std::string const& name, wxAny value) { - pending_changes[name] = value; - if (IsEnabled()) - applyButton->Enable(true); -} - -void Preferences::OnOK(wxCommandEvent &event) { - OnApply(event); - EndModal(0); -} - -void Preferences::OnApply(wxCommandEvent &event) { - for (std::map::iterator cur = pending_changes.begin(); cur != pending_changes.end(); ++cur) { - agi::OptionValue *opt = OPT_SET(cur->first); - switch (opt->GetType()) { - case agi::OptionValue::Type_Bool: - opt->SetBool(cur->second.As()); - break; - case agi::OptionValue::Type_Colour: - opt->SetColour(cur->second.As()); - break; - case agi::OptionValue::Type_Double: - opt->SetDouble(cur->second.As()); - break; - case agi::OptionValue::Type_Int: - opt->SetInt(cur->second.As()); - break; - case agi::OptionValue::Type_String: - opt->SetString(cur->second.As()); - break; - default: - throw PreferenceNotSupported("Unsupported type"); - } - } - pending_changes.clear(); - applyButton->Enable(false); - config::opt->Flush(); -} - -static void PageChanged(wxBookCtrlEvent& evt) { - OPT_SET("Tool/Preferences/Page")->SetInt(evt.GetSelection()); -} - -Preferences::Preferences(wxWindow *parent): wxDialog(parent, -1, _("Preferences"), wxDefaultPosition, wxSize(-1, 500)) { -// SetIcon(BitmapToIcon(GETIMAGE(options_button_24))); - - book = new wxTreebook(this, -1, wxDefaultPosition, wxDefaultSize); - new General(book, this); - new Subtitles(book, this); - new Audio(book, this); - new Video(book, this); - new Interface(book, this); - new Interface_Colours(book, this); - new Interface_Hotkeys(book, this); - new Paths(book, this); - new File_Associations(book, this); - new Backup(book, this); - new Automation(book, this); - new Advanced(book, this); - new Advanced_Interface(book, this); - new Advanced_Audio(book, this); - new Advanced_Video(book, this); - - book->Fit(); - - book->ChangeSelection(OPT_GET("Tool/Preferences/Page")->GetInt()); - book->Bind(wxEVT_COMMAND_TREEBOOK_PAGE_CHANGED, &PageChanged); - - // Bottom Buttons - wxStdDialogButtonSizer *stdButtonSizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL | wxAPPLY); - applyButton = stdButtonSizer->GetApplyButton(); - wxSizer *buttonSizer = new wxBoxSizer(wxHORIZONTAL); - wxButton *defaultButton = new wxButton(this, -1, _("Restore Defaults")); - buttonSizer->Add(defaultButton, wxSizerFlags(0).Expand()); - buttonSizer->AddStretchSpacer(1); - buttonSizer->Add(stdButtonSizer, wxSizerFlags(0).Expand()); - - // Main Sizer - wxSizer *mainSizer = new wxBoxSizer(wxVERTICAL); - mainSizer->Add(book, wxSizerFlags(1).Expand().Border()); - mainSizer->Add(buttonSizer, wxSizerFlags(0).Expand().Border(wxALL & ~wxTOP)); - - SetSizerAndFit(mainSizer); - SetMinSize(wxSize(-1, 500)); - SetMaxSize(wxSize(-1, 500)); - CenterOnParent(); - - applyButton->Enable(false); - - Bind(wxEVT_COMMAND_BUTTON_CLICKED, &Preferences::OnOK, this, wxID_OK); - Bind(wxEVT_COMMAND_BUTTON_CLICKED, &Preferences::OnApply, this, wxID_APPLY); -} - -Preferences::~Preferences() { -} diff --git a/aegisub/src/preferences_base.h b/aegisub/src/preferences_base.h index cd333c1ab..c5afdab26 100644 --- a/aegisub/src/preferences_base.h +++ b/aegisub/src/preferences_base.h @@ -43,25 +43,3 @@ public: void OptionBrowse(wxFlexGridSizer *flex, const wxString &name, const char *opt_name); void OptionFont(wxSizer *sizer, std::string opt_prefix); }; - -#define CLASS_PAGE(name) \ - class name: public OptionPage { \ - public: \ - name(wxTreebook *book, Preferences *parent); \ - }; - -CLASS_PAGE(General) -CLASS_PAGE(Subtitles) -CLASS_PAGE(Audio) -CLASS_PAGE(Video) -CLASS_PAGE(Interface) -CLASS_PAGE(Interface_Colours) -CLASS_PAGE(Interface_Hotkeys) -CLASS_PAGE(Paths) -CLASS_PAGE(File_Associations) -CLASS_PAGE(Backup) -CLASS_PAGE(Automation) -CLASS_PAGE(Advanced) -CLASS_PAGE(Advanced_Interface) -CLASS_PAGE(Advanced_Audio) -CLASS_PAGE(Advanced_Video)