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