Implement the hotkey page of the preferences dialog

Originally committed to SVN as r5794.
This commit is contained in:
Thomas Goyne 2011-10-28 20:40:43 +00:00
parent 6c995e7780
commit f48f17cd0b
13 changed files with 730 additions and 157 deletions

View file

@ -1219,22 +1219,6 @@
RelativePath="..\..\src\dialog_video_details.h" RelativePath="..\..\src\dialog_video_details.h"
> >
</File> </File>
<File
RelativePath="..\..\src\preferences.cpp"
>
</File>
<File
RelativePath="..\..\src\preferences.h"
>
</File>
<File
RelativePath="..\..\src\preferences_base.cpp"
>
</File>
<File
RelativePath="..\..\src\preferences_base.h"
>
</File>
</Filter> </Filter>
<Filter <Filter
Name="Core" Name="Core"
@ -1944,6 +1928,34 @@
> >
</File> </File>
</Filter> </Filter>
<Filter
Name="Preferences"
>
<File
RelativePath="..\..\src\hotkey_data_view_model.cpp"
>
</File>
<File
RelativePath="..\..\src\hotkey_data_view_model.h"
>
</File>
<File
RelativePath="..\..\src\preferences.cpp"
>
</File>
<File
RelativePath="..\..\src\preferences.h"
>
</File>
<File
RelativePath="..\..\src\preferences_base.cpp"
>
</File>
<File
RelativePath="..\..\src\preferences_base.h"
>
</File>
</Filter>
</Files> </Files>
<Globals> <Globals>
</Globals> </Globals>

View file

@ -145,6 +145,7 @@
<ClInclude Include="$(SrcDir)gl_wrap.h" /> <ClInclude Include="$(SrcDir)gl_wrap.h" />
<ClInclude Include="$(SrcDir)help_button.h" /> <ClInclude Include="$(SrcDir)help_button.h" />
<ClInclude Include="$(SrcDir)hotkeys.h" /> <ClInclude Include="$(SrcDir)hotkeys.h" />
<ClInclude Include="$(SrcDir)hotkey_data_view_model.h" />
<ClInclude Include="$(SrcDir)kana_table.h" /> <ClInclude Include="$(SrcDir)kana_table.h" />
<ClInclude Include="$(SrcDir)keyframe.h" /> <ClInclude Include="$(SrcDir)keyframe.h" />
<ClInclude Include="$(SrcDir)main.h" /> <ClInclude Include="$(SrcDir)main.h" />
@ -332,6 +333,7 @@
<ClCompile Include="$(SrcDir)gl_wrap.cpp" /> <ClCompile Include="$(SrcDir)gl_wrap.cpp" />
<ClCompile Include="$(SrcDir)help_button.cpp" /> <ClCompile Include="$(SrcDir)help_button.cpp" />
<ClCompile Include="$(SrcDir)hotkey.cpp" /> <ClCompile Include="$(SrcDir)hotkey.cpp" />
<ClCompile Include="$(SrcDir)hotkey_data_view_model.cpp" />
<ClCompile Include="$(SrcDir)kana_table.cpp" /> <ClCompile Include="$(SrcDir)kana_table.cpp" />
<ClCompile Include="$(SrcDir)main.cpp" /> <ClCompile Include="$(SrcDir)main.cpp" />
<ClCompile Include="$(SrcDir)MatroskaParser.c"> <ClCompile Include="$(SrcDir)MatroskaParser.c">

View file

@ -558,6 +558,9 @@
<ClInclude Include="$(SrcDir)factory_manager.h"> <ClInclude Include="$(SrcDir)factory_manager.h">
<Filter>Utilities</Filter> <Filter>Utilities</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="$(SrcDir)hotkey_data_view_model.h">
<Filter>Preferences</Filter>
</ClInclude>
<ClInclude Include="$(SrcDir)hotkeys.h"> <ClInclude Include="$(SrcDir)hotkeys.h">
<Filter>Main UI</Filter> <Filter>Main UI</Filter>
</ClInclude> </ClInclude>
@ -1073,6 +1076,9 @@
<ClCompile Include="$(SrcDir)compat.cpp"> <ClCompile Include="$(SrcDir)compat.cpp">
<Filter>Utilities</Filter> <Filter>Utilities</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="$(SrcDir)hotkey_data_view_model.cpp">
<Filter>Preferences</Filter>
</ClCompile>
<ClCompile Include="$(SrcDir)hotkey.cpp"> <ClCompile Include="$(SrcDir)hotkey.cpp">
<Filter>Main UI</Filter> <Filter>Main UI</Filter>
</ClCompile> </ClCompile>

View file

@ -192,6 +192,7 @@ SRC += \
gl_wrap.cpp \ gl_wrap.cpp \
help_button.cpp \ help_button.cpp \
hotkey.cpp \ hotkey.cpp \
hotkey_data_view_model.cpp \
kana_table.cpp \ kana_table.cpp \
main.cpp \ main.cpp \
menu.cpp \ menu.cpp \

View file

@ -115,6 +115,7 @@
#include <wx/config.h> #include <wx/config.h>
#include <wx/control.h> #include <wx/control.h>
#include <wx/dataobj.h> #include <wx/dataobj.h>
#include <wx/dataview.h>
#include <wx/datetime.h> #include <wx/datetime.h>
#include <wx/dc.h> #include <wx/dc.h>
#include <wx/dcbuffer.h> #include <wx/dcbuffer.h>
@ -169,6 +170,7 @@
#include <wx/regex.h> #include <wx/regex.h>
#include <wx/sashwin.h> #include <wx/sashwin.h>
#include <wx/scrolbar.h> #include <wx/scrolbar.h>
#include <wx/srchctrl.h>
#include <wx/settings.h> #include <wx/settings.h>
#include <wx/sizer.h> #include <wx/sizer.h>
#include <wx/slider.h> #include <wx/slider.h>

View file

@ -26,10 +26,23 @@
#include "libresrc/libresrc.h" #include "libresrc/libresrc.h"
#include "command/command.h" #include "command/command.h"
#include "compat.h"
#include "main.h" #include "main.h"
#include "standard_paths.h"
namespace hotkey { 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<std::string> keycode_names; static std::vector<std::string> keycode_names;
static std::string const& get_keycode_name(int code); 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]; 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; std::string combo;
if ((modifier != wxMOD_NONE)) { if ((modifier != wxMOD_NONE)) {
if ((modifier & wxMOD_CMD) != 0) combo.append("Ctrl-"); 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); 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; if (combo.empty()) return false;
std::string command; 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. /// The bottom line should be removed after all the hotkey commands are fixed.
/// This is to avoid pointless exceptions. /// This is to avoid pointless exceptions.
if (command.find("/") != std::string::npos) { 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<std::string> get_hotkey_strs(std::string const& context, std::string const& command) { std::vector<std::string> 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) { 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<std::string> &vec, int code, std::string const& str) { static inline void set_kc(std::vector<std::string> &vec, int code, std::string const& str) {
if (static_cast<size_t>(code) >= vec.size()) vec.resize(code * 2, ""); if (static_cast<size_t>(code) >= vec.size()) vec.resize(code * 2, "");
vec[code] = str; vec[code] = str;
@ -176,6 +194,4 @@ static void init_keycode_names() {
set_kc(keycode_names, WXK_NUMPAD_DIVIDE, "KP_Divide"); set_kc(keycode_names, WXK_NUMPAD_DIVIDE, "KP_Divide");
} }
} // namespace hotkey } // namespace hotkey

View file

@ -0,0 +1,339 @@
// 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
// 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 <libaegisub/exception.h>
#include <libaegisub/hotkey.h>
#include "command/command.h"
#include "command/icon.h"
#include "compat.h"
#include "include/aegisub/hotkey.h"
#include "preferences.h"
#ifndef AGI_PRE
#include <wx/dataview.h>
#include <wx/regex.h>
#include <set>
#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<std::string> 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<HotkeyModelCombo> 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<HotkeyModelCombo>::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<HotkeyModelCombo*> old_visible;
for (size_t i = 0; i < visible_items.size(); ++i)
old_visible.insert(static_cast<HotkeyModelCombo*>(visible_items[i].GetID()));
visible_items.clear();
wxDataViewItemArray added;
wxDataViewItemArray removed;
for (std::list<HotkeyModelCombo>::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<HotkeyModelCategory> categories;
public:
HotkeyModelRoot(wxDataViewModel *model) {
Hotkey::HotkeyMap const& hk_map = hotkey::inst->GetHotkeyMap();
std::map<std::string, HotkeyModelCategory*> 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<std::string, HotkeyModelCategory*>::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<HotkeyModelCategory>::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<HotkeyModelItem*>(item.GetID());
return root.get();
}
HotkeyModelItem * HotkeyDataViewModel::get(wxDataViewItem const& item) {
if (item.IsOk())
return static_cast<HotkeyModelItem*>(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<HotkeyModelCategory*>(item.GetID());
wxVariant name;
ctx->GetValue(name, 0);
ctx->AddChild(Combo(STD_STR(name.GetString()), "", std::vector<std::string>()));
}
void HotkeyDataViewModel::Delete(wxDataViewItem const& item) {
if (!item.IsOk() || IsContainer(item)) return;
static_cast<HotkeyModelCategory*>(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);
}

View file

@ -0,0 +1,66 @@
// 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
// 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 <wx/dataview.h>
#endif
#include <libaegisub/scoped_ptr.h>
class HotkeyModelItem;
class HotkeyModelRoot;
class Preferences;
/// @class HotkeyDataViewModel
/// @brief A wxDataViewModel for hotkeys
class HotkeyDataViewModel : public wxDataViewModel {
agi::scoped_ptr<HotkeyModelRoot> 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);
};

View file

@ -23,12 +23,22 @@
#include <vector> #include <vector>
#endif #endif
namespace agi { struct Context; } namespace agi {
struct Context;
namespace hotkey { class Hotkey; }
}
namespace 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); 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::string get_hotkey_str_first(std::string const& context, std::string const& command);
std::vector<std::string> get_hotkey_strs(std::string const& context, std::string const& command); std::vector<std::string> get_hotkey_strs(std::string const& context, std::string const& command);
} // namespace hotkey } // namespace hotkey

View file

@ -28,6 +28,7 @@
#include <wx/event.h> #include <wx/event.h>
#include <wx/filefn.h> #include <wx/filefn.h>
#include <wx/listctrl.h> #include <wx/listctrl.h>
#include <wx/srchctrl.h>
#include <wx/sizer.h> #include <wx/sizer.h>
#include <wx/spinctrl.h> #include <wx/spinctrl.h>
#include <wx/stattext.h> #include <wx/stattext.h>
@ -41,15 +42,53 @@
#include "preferences.h" #include "preferences.h"
#include "colour_button.h" #include "colour_button.h"
#include "command/command.h"
#include "compat.h" #include "compat.h"
#include "hotkey_data_view_model.h"
#include "include/aegisub/audio_player.h" #include "include/aegisub/audio_player.h"
#include "include/aegisub/audio_provider.h" #include "include/aegisub/audio_provider.h"
#include "include/aegisub/hotkey.h"
#include "include/aegisub/subtitles_provider.h" #include "include/aegisub/subtitles_provider.h"
#include "libresrc/libresrc.h" #include "libresrc/libresrc.h"
#include "main.h" #include "main.h"
#include "preferences_base.h" #include "preferences_base.h"
#include "video_provider_manager.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<HotkeyDataViewModel> 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 preferences page
General::General(wxTreebook *book, Preferences *parent): OptionPage(book, parent, _("General")) { 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); SetSizerAndFit(sizer);
} }
/// wxDataViewIconTextRenderer with command name autocompletion
class CommandRenderer : public wxDataViewIconTextRenderer {
wxArrayString autocomplete;
public:
CommandRenderer()
: wxDataViewIconTextRenderer("wxDataViewIconText", wxDATAVIEW_CELL_EDITABLE)
{
std::vector<std::string> 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<wxTextCtrl*>(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<wxTextCtrl*>(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 preferences subpage
Interface_Hotkeys::Interface_Hotkeys(wxTreebook *book, Preferences *parent): OptionPage(book, parent, _("Hotkeys"), PAGE_SUB) { Interface_Hotkeys::Interface_Hotkeys(wxTreebook *book, Preferences *parent)
wxFlexGridSizer *hotkeys = PageSizer(_("Hotkeys")); : OptionPage(book, parent, _("Hotkeys"), PAGE_SUB)
hotkeys->Add(new wxStaticText(this, wxID_ANY, _T("To be added after hotkey rewrite.")), 0, wxALL, 5); , 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); 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")) { Paths::Paths(wxTreebook *book, Preferences *parent): OptionPage(book, parent, _("Paths")) {
wxFlexGridSizer *general = PageSizer(_("General")); wxFlexGridSizer *general = PageSizer(_("General"));
general->Add(new wxStaticText(this, wxID_ANY, "TBD..."), 0, wxALL, 5); general->Add(new wxStaticText(this, wxID_ANY, "TBD..."), 0, wxALL, 5);
SetSizerAndFit(sizer); SetSizerAndFit(sizer);
} }
/// File Associations preferences page /// File Associations preferences page
File_Associations::File_Associations(wxTreebook *book, Preferences *parent): OptionPage(book, parent, _("File Associations")) { File_Associations::File_Associations(wxTreebook *book, Preferences *parent): OptionPage(book, parent, _("File Associations")) {
wxFlexGridSizer *assoc = PageSizer(_("General")); wxFlexGridSizer *assoc = PageSizer(_("General"));
assoc->Add(new wxStaticText(this, wxID_ANY, "TBD..."), 0, wxALL, 5); 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); SetSizerAndFit(sizer);
} }
/// Automation preferences page /// Automation preferences page
Automation::Automation(wxTreebook *book, Preferences *parent): OptionPage(book, parent, _("Automation")) { Automation::Automation(wxTreebook *book, Preferences *parent): OptionPage(book, parent, _("Automation")) {
wxFlexGridSizer *general = PageSizer(_("General")); wxFlexGridSizer *general = PageSizer(_("General"));
@ -372,3 +495,110 @@ Advanced_Video::Advanced_Video(wxTreebook *book, Preferences *parent): OptionPag
SetSizerAndFit(sizer); 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<std::string, wxAny>::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<bool>());
break;
case agi::OptionValue::Type_Colour:
opt->SetColour(cur->second.As<agi::Colour>());
break;
case agi::OptionValue::Type_Double:
opt->SetDouble(cur->second.As<double>());
break;
case agi::OptionValue::Type_Int:
opt->SetInt(cur->second.As<int>());
break;
case agi::OptionValue::Type_String:
opt->SetString(cur->second.As<std::string>());
break;
default:
throw PreferenceNotSupported("Unsupported type");
}
}
pending_changes.clear();
for (std::deque<Thunk>::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() {
}

View file

@ -20,20 +20,32 @@
/// @ingroup configuration_ui /// @ingroup configuration_ui
#ifndef AGI_PRE #ifndef AGI_PRE
#include <deque>
#include <tr1/functional>
#include <map> #include <map>
#include <wx/dialog.h> #include <wx/dialog.h>
#endif #endif
#include <libaegisub/exception.h>
class wxAny; class wxAny;
class wxButton; class wxButton;
class wxTreebook; 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 { class Preferences: public wxDialog {
public:
typedef std::tr1::function<void ()> Thunk;
private:
wxTreebook *book; wxTreebook *book;
wxButton *applyButton; wxButton *applyButton;
std::map<std::string, wxAny> pending_changes; std::map<std::string, wxAny> pending_changes;
std::deque<Thunk> pending_callbacks;
void OnOK(wxCommandEvent &event); void OnOK(wxCommandEvent &event);
void OnCancel(wxCommandEvent &event); void OnCancel(wxCommandEvent &event);
@ -44,4 +56,5 @@ public:
~Preferences(); ~Preferences();
void SetOption(std::string const& name, wxAny value); void SetOption(std::string const& name, wxAny value);
void AddPendingChange(Thunk const& callback);
}; };

View file

@ -36,8 +36,6 @@
#include <wx/treebook.h> #include <wx/treebook.h>
#endif #endif
#include <libaegisub/exception.h>
#include "preferences_base.h" #include "preferences_base.h"
#include "colour_button.h" #include "colour_button.h"
@ -50,10 +48,6 @@
#include "standard_paths.h" #include "standard_paths.h"
#include "video_provider_manager.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) \ #define OPTION_UPDATER(type, evttype, body) \
class type { \ class type { \
std::string name; \ 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 Face"), button_sizer);
Add(sizer, _("Font Size"), font_size); 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<std::string, wxAny>::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<bool>());
break;
case agi::OptionValue::Type_Colour:
opt->SetColour(cur->second.As<agi::Colour>());
break;
case agi::OptionValue::Type_Double:
opt->SetDouble(cur->second.As<double>());
break;
case agi::OptionValue::Type_Int:
opt->SetInt(cur->second.As<int>());
break;
case agi::OptionValue::Type_String:
opt->SetString(cur->second.As<std::string>());
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() {
}

View file

@ -43,25 +43,3 @@ public:
void OptionBrowse(wxFlexGridSizer *flex, const wxString &name, const char *opt_name); void OptionBrowse(wxFlexGridSizer *flex, const wxString &name, const char *opt_name);
void OptionFont(wxSizer *sizer, std::string opt_prefix); 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)