18#include <glibmm/convert.h>
19#include <glibmm/i18n.h>
20#include <glibmm/miscutils.h>
21#include <glibmm/regex.h>
22#include <glibmm/variant.h>
23#include <giomm/file.h>
24#include <giomm/simpleaction.h>
25#include <gtkmm/accelerator.h>
26#include <gtkmm/actionable.h>
27#include <gtkmm/application.h>
28#include <gtkmm/eventcontrollerkey.h>
29#include <gtkmm/shortcut.h>
30#include <gtkmm/window.h>
57 std::cerr <<
"Shortcuts::Shortcuts: No app! Shortcuts cannot be used without a Gtk::Application!" << std::endl;
82 path = prefs->
getString(
"/options/kbshortcuts/shortcutfile");
85 if (!Glib::path_is_absolute(path)) {
90 Glib::RefPtr<Gio::File> file = Gio::File::create_for_path(path);
91 success =
_read(file);
93 std::cerr <<
"Shortcut::Shortcut: Unable to read shortcut file listed in preferences: " + path << std::endl;
100 prefs->
setString(
"/options/kbshortcuts/shortcutfile", relative_path.c_str());
108 success =
_read(file);
112 std::cerr <<
"Shortcut::Shortcut: Failed to read file default.xml, trying inkscape.xml" << std::endl;
115 success =
_read(file);
119 std::cerr <<
"Shortcut::Shortcut: Failed to read file inkscape.xml; giving up!" << std::endl;
125 if (file->query_exists()) {
132 if (file->query_exists()) {
153 detailed_action_name,
154 trigger.get_abbrev(),
164 std::cerr <<
"Shortcut::add_user_shortcut: Failed to add: " << detailed_action_name.raw()
165 <<
" with shortcut " << trigger.get_abbrev().raw() << std::endl;
174 bool user_shortcut =
is_user_set(detailed_action_name);
176 if (!user_shortcut) {
191 std::cerr <<
"Shortcuts::remove_user_shortcut: Failed to remove shortcut for: "
192 << detailed_action_name.raw() << std::endl;
207 document->appendChild(
node);
223 auto it =
_shortcuts.find(detailed_action_name);
227 return (it->second.user_set);
251 if (
auto const actionable =
dynamic_cast<Gtk::Actionable *
>(widget)) {
252 if (
auto action = actionable->get_action_name(); !action.empty()) {
253 Glib::ustring variant;
254 if (
auto const value = actionable->get_action_target_value()) {
255 auto const type = value.get_type_string();
257 variant =
static_cast<Glib::Variant<Glib::ustring>
const &
>(value).get();
258 action +=
"('" + variant +
"')";
259 }
else if (type ==
"i") {
260 variant = std::to_string(
static_cast<Glib::Variant<std::int32_t>
const &
>(value).get());
261 action +=
"(" + variant +
")";
263 std::cerr <<
"Shortcuts::update_gui_text_recursive: unhandled variant type: " << type << std::endl;
269 Glib::ustring tooltip;
272 tooltip = iapp->get_action_extra_data().get_tooltip_for_action(action,
true,
true);
276 if (triggers.size() > 0) {
278 if (!tooltip.empty()) {
283 unsigned int key = 0;
284 Gdk::ModifierType mod{};
285 Gtk::Accelerator::parse(triggers[0],
key, mod);
286 tooltip +=
"(" + Gtk::Accelerator::get_label(
key, mod) +
")";
290 widget->set_tooltip_markup(tooltip);
306 Glib::ustring accel = Gtk::Accelerator::name(shortcut.get_key(), shortcut.get_mod());
309 if (!actions.empty()) {
310 Glib::ustring
const &action = actions[0];
312 Glib::VariantBase value;
313 Gio::SimpleAction::parse_detailed_name_variant(action.substr(4),
action_name, value);
314 if (action.compare(0, 4,
"app.") == 0) {
320 window->activate_action(action, value);
341 unsigned const keyval,
unsigned const keycode,
342 GdkModifierType
const state)
344 auto const shortcut =
get_from(controller, keyval, keycode, state);
353std::vector<Glib::ustring>
356 std::vector<Glib::ustring> triggers;
357 auto matches =
_shortcuts.equal_range(detailed_action_name);
358 for (
auto it = matches.first; it != matches.second; ++it) {
359 triggers.push_back(it->second.trigger_string);
367std::vector<Glib::ustring>
370 std::vector<Glib::ustring> actions;
371 for (
auto const &[detailed_action_name, value] :
_shortcuts) {
372 if (trigger == value.trigger_string) {
373 actions.emplace_back(detailed_action_name);
384 if (!shortcut.is_null()) {
387 if (shortcut.get_abbrev().find(
"KP") != Glib::ustring::npos) {
388 label += _(
"Numpad");
392 label += Gtk::Accelerator::get_label(shortcut.get_key(), shortcut.get_mod());
400 GdkModifierType
const event_state,
unsigned const event_group,
405 auto const default_mod_mask = Gtk::Accelerator::get_default_mod_mask();
406 auto const initial_modifiers =
static_cast<Gdk::ModifierType
>(event_state) & default_mod_mask;
408 auto consumed_modifiers = 0u;
410 event_keyval, event_keycode, event_state, event_group, &consumed_modifiers);
414 bool is_case_convertible = !(gdk_keyval_is_upper(keyval) && gdk_keyval_is_lower(keyval));
415 if (is_case_convertible) {
416 keyval = gdk_keyval_to_lower(keyval);
417 consumed_modifiers &= ~static_cast<unsigned>(Gdk::ModifierType::SHIFT_MASK);
425 keyval = event_keyval;
428 auto const unused_modifiers = Gdk::ModifierType(
static_cast<unsigned>(initial_modifiers)
429 & ~consumed_modifiers
436 return (Gtk::AccelKey(keyval, unused_modifiers));
456 unsigned const keyval,
unsigned const keycode, GdkModifierType
const state,
460 auto const mcontroller =
const_cast<GtkEventControllerKey *
>(controller);
461 auto const group = controller ? gtk_event_controller_key_get_group(mcontroller) : 0u;
467 unsigned keyval,
unsigned keycode, Gdk::ModifierType state,
bool fix)
469 return get_from_event_impl(keyval, keycode,
static_cast<GdkModifierType
>(state), controller.get_group(), fix);
481std::vector<Glib::ustring>
491std::vector<Glib::ustring>
494 std::vector<Glib::ustring> all_actions;
496 auto actions =
app->list_actions();
497 std::sort(actions.begin(), actions.end());
498 for (
auto &&action: std::move(actions)) {
499 all_actions.push_back(
"app." + std::move(action));
502 auto gwindow =
app->get_active_window();
505 actions = window->list_actions();
506 std::sort(actions.begin(), actions.end());
507 for (
auto &&action: std::move(actions)) {
508 all_actions.push_back(
"win." + std::move(action));
511 auto document = window->get_document();
513 auto map = document->getActionGroup();
515 actions =
map->list_actions();
516 std::sort(actions.begin(), actions.end());
517 for (
auto &&action: std::move(actions)) {
518 all_actions.push_back(
"doc." + std::move(action));
521 std::cerr <<
"Shortcuts::list_all_actions: No document map!" << std::endl;
530static void append(std::vector<T> &target, std::vector<T> &&source)
532 target.insert(target.end(), std::move_iterator{source.begin()}, std::move_iterator{source.end()});
538std::vector<std::pair<Glib::ustring, std::string>>
550 std::vector<std::pair<Glib::ustring, std::string>> names_and_paths;
551 for (
auto const &filename : filenames) {
552 Glib::ustring
label = Glib::path_get_basename(filename);
557 std::cerr <<
"Shortcut::get_file_names: could not parse file: " << filename << std::endl;
562 for ( ; iter ; ++iter ) {
563 if (strcmp(iter->name(),
"keys") == 0) {
564 char const *
const name = iter->attribute(
"name");
568 names_and_paths.emplace_back(std::move(
label), std::move(filename_relative));
573 std::cerr <<
"Shortcuts::get_File_names: not a shortcut keys file: " << filename << std::endl;
580 std::sort(names_and_paths.begin(), names_and_paths.end(),
581 [](
auto const &pair1,
auto const &pair2) {
582 return pair1.first < pair2.first;
585 auto it_default = std::find_if(names_and_paths.begin(), names_and_paths.end(),
586 [](
auto const &pair) {
587 return pair.second ==
"default.xml";
589 if (it_default != names_and_paths.end()) {
590 std::rotate(names_and_paths.begin(), it_default, it_default+1);
593 return names_and_paths;
605 Gtk::Window* window =
app->get_active_window();
610 static std::vector<std::pair<Glib::ustring, Glib::ustring>>
const filters {
611 {_(
"Inkscape shortcuts (*.xml)"),
"*.xml"}
623 if (!
_read(file,
true)) {
624 std::cerr <<
"Shortcuts::import_shortcuts: Failed to read file!" << std::endl;
638 Gtk::Window* window =
app->get_active_window();
655 std::cerr <<
"Shortcuts::export_shortcuts: Failed to save file!" << std::endl;
670[[nodiscard]]
static Glib::ustring
671join(std::vector<Glib::ustring>
const &accels,
char const separator)
673 auto const capacity = std::accumulate(accels.begin(), accels.end(), std::size_t{0},
674 [](std::size_t capacity,
auto const &accel){ return capacity += accel.size() + 1; });
677 for (
auto const &accel: accels) {
687 Gdk::ModifierType modifiers{};
688 if (modifiers_string) {
689 std::vector<Glib::ustring> mod_vector = Glib::Regex::split_simple(
"\\s*,\\s*", modifiers_string);
691 for (
auto const &mod : mod_vector) {
692 if (mod ==
"Control" || mod ==
"Ctrl") {
693 modifiers |= Gdk::ModifierType::CONTROL_MASK;
694 }
else if (mod ==
"Shift") {
695 modifiers |= Gdk::ModifierType::SHIFT_MASK;
696 }
else if (mod ==
"Alt") {
697 modifiers |= Gdk::ModifierType::ALT_MASK;
698 }
else if (mod ==
"Super") {
699 modifiers |= Gdk::ModifierType::SUPER_MASK;
700 }
else if (mod ==
"Hyper") {
701 modifiers |= Gdk::ModifierType::HYPER_MASK;
702 }
else if (mod ==
"Meta") {
703 modifiers |= Gdk::ModifierType::META_MASK;
704 }
else if (mod ==
"Primary") {
706 modifiers |= Gdk::ModifierType::META_MASK;
708 modifiers |= Gdk::ModifierType::CONTROL_MASK;
711 std::cerr <<
"Shortcut::read: Unknown GDK modifier: " << mod.c_str() << std::endl;
726 if (!file->query_exists()) {
727 std::cerr <<
"Shortcut::read: file does not exist: " << file->get_path() << std::endl;
733 std::cerr <<
"Shortcut::read: could not parse file: " << file->get_path() << std::endl;
738 for ( ; iter ; ++iter ) {
739 if (strcmp(iter->name(),
"keys") == 0) {
745 std::cerr <<
"Shortcuts::read: File in wrong format: " << file->get_path() << std::endl;
750 _read(*iter, user_set);
764 bool cache_action_list =
false;
766 for ( ; iter ; ++iter ) {
767 if (strcmp(iter->name(),
"modifier") == 0) {
768 char const *
const mod_name = iter->
attribute(
"action");
770 std::cerr <<
"Shortcuts::read: Missing modifier for action!" << std::endl;
775 if (mod ==
nullptr) {
776 std::cerr <<
"Shortcuts::read: Can't find modifier: " << mod_name << std::endl;
783 char const *
const mod_attr = iter->attribute(
"modifiers");
790 char const *
const not_attr = iter->attribute(
"not_modifiers");
795 char const *
const disabled_attr = iter->attribute(
"disabled");
796 if (disabled_attr && strcmp(disabled_attr,
"true") == 0) {
797 and_modifier =
NEVER;
802 mod->set_user(and_modifier, not_modifier);
804 mod->set_keys(and_modifier, not_modifier);
808 }
else if (strcmp(iter->name(),
"keys") == 0) {
809 _read(*iter, user_set);
811 }
else if (strcmp(iter->name(),
"bind") != 0) {
817 char const *
const gaction = iter->attribute(
"gaction");
818 char const *
const keys = iter->attribute(
"keys");
819 if (gaction && keys) {
822 Glib::ustring Keys = keys;
823 auto p = Keys.find_first_not_of(
" ");
824 Keys = Keys.erase(0, p);
826 std::vector<Glib::ustring> key_vector = Glib::Regex::split_simple(
"\\s*,\\s*", Keys);
827 std::reverse(key_vector.begin(), key_vector.end());
830 for (
auto const &
key : key_vector) {
836 cache_action_list =
true;
871 document->appendChild(
node);
876 if ( (what ==
All) ||
877 (what ==
System && !user_set) ||
878 (what ==
User && user_set) )
881 if (!triggers.empty()) {
886 auto const keys =
join(triggers,
',');
889 document->root()->appendChild(
node);
895 if (what ==
User && modifier->is_set_user()) {
899 if (modifier->get_config_user_disabled()) {
903 auto not_mask = modifier->get_config_user_not();
904 if (!not_mask.empty() and not_mask !=
"-") {
909 document->root()->appendChild(
node);
931 bool cache_action_names)
934 auto str = trigger_string.raw();
939 static std::string
const primary =
"<primary>";
940 auto const pos = str.find(primary);
941 if (pos != std::string::npos) {
942 str.replace(pos, pos + primary.length(),
"<meta>");
946 Gtk::AccelKey
key(str);
948 auto trigger_normalized =
key.get_abbrev();
952 Glib::VariantBase target;
953 Gio::SimpleAction::parse_detailed_name_variant(detailed_action_name,
action_name, target);
974 auto const trigger = Gtk::ShortcutTrigger::parse_string(trigger_normalized);
977 auto const action = Gtk::NamedAction::create(
action_name);
980 auto shortcut = Gtk::Shortcut::create(trigger, action);
983 shortcut->set_arguments(target);
988 auto value =
ShortcutValue{std::move(trigger_normalized), std::move(shortcut), user};
989 _shortcuts.emplace(detailed_action_name.raw(), std::move(value));
1001 bool changed =
false;
1003 if (it->second.trigger_string.raw() == trigger.raw()) {
1005 auto shortcut = it->second.
shortcut;
1006 for (
int i = 0; i <
_liststore->get_n_items(); ++i) {
1033 bool removed =
false;
1035 if (it->first == detailed_action_name.raw()) {
1036 auto const &shortcut = it->second.shortcut;
1040 for (
int i = 0; i <
_liststore->get_n_items(); ++i) {
1076 Glib::ustring action_name_short;
1077 Glib::VariantBase
unused;
1078 Gio::SimpleAction::parse_detailed_name_variant(action_name_detailed, action_name_short,
unused);
1100 static std::vector<Gdk::ModifierType>
const modifiers{
1101 Gdk::ModifierType{},
1102 Gdk::ModifierType::SHIFT_MASK,
1103 Gdk::ModifierType::CONTROL_MASK,
1104 Gdk::ModifierType::ALT_MASK,
1105 Gdk::ModifierType::SHIFT_MASK | Gdk::ModifierType::CONTROL_MASK,
1106 Gdk::ModifierType::SHIFT_MASK | Gdk::ModifierType::ALT_MASK,
1107 Gdk::ModifierType::CONTROL_MASK | Gdk::ModifierType::ALT_MASK,
1108 Gdk::ModifierType::SHIFT_MASK | Gdk::ModifierType::CONTROL_MASK | Gdk::ModifierType::ALT_MASK
1111 for (
auto mod : modifiers) {
1112 for (
char key =
'!';
key <=
'~'; ++
key) {
1113 Glib::ustring action;
1114 Glib::ustring accel = Gtk::Accelerator::name(
key, mod);
1116 if (!actions.empty()) {
1117 action = actions[0];
1120 std::cout <<
" shortcut:"
1121 <<
" " << std::setw( 8) << std::hex << static_cast<int>(mod)
1122 <<
" " << std::setw( 8) << std::hex <<
key
1123 <<
" " << std::setw(30) << std::left << accel
1130 for (
int i = 0; i < count; ++i) {
1132 auto trigger = shortcut->get_trigger();
1133 auto action = shortcut->get_action();
1134 auto variant = shortcut->get_arguments();
1136 std::cout << action->to_string();
1138 std::cout <<
"(" << variant.print() <<
")";
1140 std::cout <<
": " << trigger->to_string() << std::endl;
1147 static unsigned int indent = 0;
1149 for (
int i = 0; i < indent; ++i) std::cout <<
" ";
1151 auto const actionable =
dynamic_cast<Gtk::Actionable *
>(widget);
1152 auto const action = actionable ? actionable->get_action_name() :
"";
1154 std::cout << widget->get_name()
1155 <<
": actionable: " << std::boolalpha << static_cast<bool>(actionable)
1156 <<
": " << widget->get_tooltip_text()
static InkscapeApplication * instance()
Singleton instance.
A class to represent ways functionality is driven by shift modifiers.
static Modifier * get(Type index)
A function to turn an enum index into a modifier object.
static std::vector< Modifier const * > getList()
List all the modifiers available.
Preference storage class.
Glib::ustring getString(Glib::ustring const &pref_path, Glib::ustring const &def="")
Retrieve an UTF-8 string.
static Preferences * get()
Access the singleton Preferences object.
void setString(Glib::ustring const &pref_path, Glib::ustring const &value)
Set an UTF-8 string value.
std::vector< Glib::ustring > list_all_actions()
bool _add_shortcut(Glib::ustring const &detailed_action_name, Glib::ustring const &trigger_string, bool user, bool cache_action_names)
Add a shortcut.
bool invoke_action(Gtk::AccelKey const &shortcut)
Trigger action from a shortcut.
bool clear_user_shortcuts()
Remove all user's shortcuts (simply overwrites existing file).
std::unordered_multimap< std::string, ShortcutValue > _shortcuts
static Glib::ustring get_label(const Gtk::AccelKey &shortcut)
static std::vector< std::pair< Glib::ustring, std::string > > get_file_names()
Get a list of filenames to populate menu in preferences dialog.
std::vector< Glib::ustring > list_all_detailed_action_names()
std::set< std::string > _list_action_names_cache
void _dump_all_recursive(Gtk::Widget *widget)
static Gtk::AccelKey get_from(GtkEventControllerKey const *controller, unsigned keyval, unsigned keycode, GdkModifierType state, bool fix=false)
Controller provides the group. It can be nullptr; if so, we use group 0.
void update_gui_text_recursive(Gtk::Widget *widget)
void _clear()
Clear all shortcuts.
bool _read(Glib::RefPtr< Gio::File > const &file, bool user_set=false)
Read a shortcut file.
bool write_user()
Write user shortcuts to file.
const std::set< std::string > & _list_action_names(bool cached)
Get a sorted list of the non-detailed names of all actions.
sigc::signal< void()> _changed
bool _write(Glib::RefPtr< Gio::File > const &file, What what=User)
std::vector< Glib::ustring > get_triggers(Glib::ustring const &action_name) const
Returns a vector of triggers for a given detailed_action_name.
bool add_user_shortcut(Glib::ustring const &detailed_action_name, Gtk::AccelKey const &trigger)
Glib::RefPtr< Gio::ListStore< Gtk::Shortcut > > _liststore
bool is_user_set(Glib::ustring const &detailed_action_name)
Return if user set shortcut for Gio::Action.
bool remove_user_shortcut(Glib::ustring const &detailed_action_name)
sigc::connection connect_changed(sigc::slot< void()> const &slot)
Connects to a signal emitted whenever the shortcuts change.
bool _remove_shortcut_trigger(Glib::ustring const &trigger)
Remove shortcuts via AccelKey.
Gtk::Application *const app
static Gtk::AccelKey get_from_event(KeyEvent const &event, bool fix=false)
std::vector< Glib::ustring > get_actions(Glib::ustring const &trigger) const
Returns a vector of detailed_action_names for a given trigger.
bool _remove_shortcuts(Glib::ustring const &detailed_action_name)
Remove all shortcuts for a detailed action.
Interface for refcounted XML nodes.
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
virtual Node * firstChild()=0
Get the first child of this node.
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
std::unordered_map< std::string, std::unique_ptr< SPDocument > > map
std::string sp_relative_path_from_path(std::string const &path, std::string const &base)
Returns a form of path relative to base if that is easy to construct (eg if path appears to be in the...
TODO: insert short description here.
auto absolute(Geom::Point const &a)
Inkscape::XML::Node * node
Inkscape - An SVG editor.
static R & release(R &r)
Decrements the reference count of a anchored object.
std::string get_path_string(Domain domain, Type type, char const *filename, char const *extra)
std::vector< std::string > get_filenames(Type type, std::vector< const char * > const &extensions, std::vector< const char * > const &exclusions)
std::vector< Gtk::Widget * > get_children(Gtk::Widget &widget)
Get a vector of the widgetʼs children, from get_first_child() through each get_next_sibling().
Helper class to stream background task notifications as a series of messages.
Gdk::ModifierType parse_modifier_string(char const *const modifiers_string)
static Gtk::AccelKey get_from_event_impl(unsigned const event_keyval, unsigned const event_keycode, GdkModifierType const event_state, unsigned const event_group, bool const fix)
static Glib::ustring join(std::vector< Glib::ustring > const &accels, char const separator)
static void append(std::vector< T > &target, std::vector< T > &&source)
Glib::RefPtr< Gio::File > choose_file_save(Glib::ustring const &title, Gtk::Window *parent, Glib::RefPtr< Gio::ListStore< Gtk::FileFilter > > const &filters_model, std::string const &file_name, std::string ¤t_folder)
Synchronously run a Gtk::FileDialog to select a file for saving data.
Glib::RefPtr< Gio::File > choose_file_open(Glib::ustring const &title, Gtk::Window *parent, Glib::RefPtr< Gio::ListStore< Gtk::FileFilter > > const &filters_model, std::string ¤t_folder, Glib::ustring const &accept)
Synchronously run a Gtk::FileDialog to open a single file for reading data.
static cairo_user_data_key_t key
Singleton class to access the preferences file in a convenient way.
Document * sp_repr_read_file(const gchar *filename, const gchar *default_ns, bool xinclude)
Reads XML from a file, and returns the Document.
bool sp_repr_save_file(Document *doc, gchar const *const filename, gchar const *default_ns)
Returns true iff file successfully saved.
Inkscape::IO::Resource - simple resource API.
Inkscape::XML::SimpleDocument - generic XML document implementation.
unsigned modifiers
The modifiers mask immediately before the event.
uint32_t keyval
The key that was pressed/released. (Matches gdkkeysyms.h.)
int group
The keyboard group.
uint16_t keycode
The raw code of the key that was pressed/released.
Glib::RefPtr< Gtk::Shortcut > shortcut
Interface for XML documents.
std::vector< Texture > unused