1 /*
2 * Copyright (c) 2008-2009, Thomas Jaeger <ThJaeger@gmail.com>
3 *
4 * Permission to use, copy, modify, and/or distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
11 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
13 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
14 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16 #include "actions.h"
17 #include "actiondb.h"
18 #include "win.h"
19 #include "main.h"
20 #include "prefdb.h"
21 #include <glibmm/i18n.h>
22 #include <X11/XKBlib.h>
23 #include "grabber.h"
24 #include "cellrenderertextish.h"
25
26 #include <typeinfo>
27
on_button_press_event(GdkEventButton * event)28 bool TreeViewMulti::on_button_press_event(GdkEventButton* event) {
29 int cell_x, cell_y;
30 Gtk::TreeViewColumn *column;
31 pending = (get_path_at_pos(event->x, event->y, path, column, cell_x, cell_y))
32 && (get_selection()->is_selected(path))
33 && !(event->state & (GDK_CONTROL_MASK|GDK_SHIFT_MASK));
34 return Gtk::TreeView::on_button_press_event(event);
35 }
36
on_button_release_event(GdkEventButton * event)37 bool TreeViewMulti::on_button_release_event(GdkEventButton* event) {
38 if (pending) {
39 pending = false;
40 get_selection()->unselect_all();
41 get_selection()->select(path);
42 }
43 return Gtk::TreeView::on_button_release_event(event);
44 }
45
on_drag_begin(const Glib::RefPtr<Gdk::DragContext> & context)46 void TreeViewMulti::on_drag_begin(const Glib::RefPtr<Gdk::DragContext> &context) {
47 pending = false;
48 if (get_selection()->count_selected_rows() <= 1)
49 return Gtk::TreeView::on_drag_begin(context);
50 Glib::RefPtr<Gdk::Pixbuf> pb = render_icon_pixbuf(Gtk::Stock::DND_MULTIPLE, Gtk::ICON_SIZE_DND);
51 context->set_icon(pb, pb->get_width(), pb->get_height());
52 }
53
TreeViewMulti()54 TreeViewMulti::TreeViewMulti() : Gtk::TreeView(), pending(false) {
55 get_selection()->set_select_function(sigc::mem_fun(*this, &TreeViewMulti::negate_pending));
56 }
57
58 enum Type { COMMAND, KEY, TEXT, SCROLL, IGNORE, BUTTON, MISC };
59
60 struct TypeInfo {
61 Type type;
62 const char *name;
63 const std::type_info *type_info;
64 const CellRendererTextishMode mode;
65 };
66
67 TypeInfo all_types[8] = {
68 { COMMAND, N_("Command"), &typeid(Command), CELL_RENDERER_TEXTISH_MODE_Text },
69 { KEY, N_("Key"), &typeid(SendKey), CELL_RENDERER_TEXTISH_MODE_Key },
70 { TEXT, N_("Text"), &typeid(SendText), CELL_RENDERER_TEXTISH_MODE_Text },
71 { SCROLL, N_("Scroll"), &typeid(Scroll), CELL_RENDERER_TEXTISH_MODE_Key },
72 { IGNORE, N_("Ignore"), &typeid(Ignore), CELL_RENDERER_TEXTISH_MODE_Key },
73 { BUTTON, N_("Button"), &typeid(Button), CELL_RENDERER_TEXTISH_MODE_Popup },
74 { MISC, N_("Misc"), &typeid(Misc), CELL_RENDERER_TEXTISH_MODE_Combo },
75 { COMMAND, 0, 0, CELL_RENDERER_TEXTISH_MODE_Text }
76 };
77
from_name(Glib::ustring name)78 const Type from_name(Glib::ustring name) {
79 for (TypeInfo *i = all_types;; i++)
80 if (!i->name || _(i->name) == name)
81 return i->type;
82 }
83
type_info_to_name(const std::type_info * info)84 const char *type_info_to_name(const std::type_info *info) {
85 for (TypeInfo *i = all_types; i->name; i++)
86 if (i->type_info == info)
87 return _(i->name);
88 return "";
89 }
90
on_actions_cell_data_arg(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * tree_model,GtkTreeIter * iter,gpointer data)91 static void on_actions_cell_data_arg(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) {
92 GtkTreePath *path = gtk_tree_model_get_path(tree_model, iter);
93 gchar *path_string = gtk_tree_path_to_string(path);
94 ((Actions *)data)->on_cell_data_arg(cell, path_string);
95 g_free(path_string);
96 gtk_tree_path_free(path);
97 }
98
on_actions_accel_edited(CellRendererTextish *,gchar * path,GdkModifierType mods,guint code,gpointer data)99 static void on_actions_accel_edited(CellRendererTextish *, gchar *path, GdkModifierType mods, guint code, gpointer data) {
100 guint key = XkbKeycodeToKeysym(dpy, code, 0, 0);
101 ((Actions *)data)->on_accel_edited(path, key, mods, code);
102 }
103
on_actions_combo_edited(CellRendererTextish *,gchar * path,guint row,gpointer data)104 static void on_actions_combo_edited(CellRendererTextish *, gchar *path, guint row, gpointer data) {
105 ((Actions *)data)->on_combo_edited(path, row);
106 }
107
on_actions_text_edited(GtkCellRendererText *,gchar * path,gchar * new_text,gpointer data)108 static void on_actions_text_edited(GtkCellRendererText *, gchar *path, gchar *new_text, gpointer data) {
109 ((Actions *)data)->on_text_edited(path, new_text);
110 }
111
on_actions_editing_started(GtkCellRenderer *,GtkCellEditable * editable,const gchar * path,gpointer data)112 static void on_actions_editing_started(GtkCellRenderer *, GtkCellEditable *editable, const gchar *path, gpointer data) {
113 ((Actions *)data)->on_arg_editing_started(editable, path);
114 }
115
Actions()116 Actions::Actions() :
117 apps_view(0),
118 vpaned_position(-1),
119 editing_new(false),
120 editing(false),
121 action_list(actions.get_root())
122 {
123 Gtk::ScrolledWindow *sw;
124 widgets->get_widget("scrolledwindow_actions", sw);
125 widgets->get_widget("treeview_apps", apps_view);
126 sw->add(tv);
127 tv.show();
128
129 Gtk::Button *button_add, *button_add_app, *button_add_group;
130 widgets->get_widget("button_add_action", button_add);
131 widgets->get_widget("button_delete_action", button_delete);
132 widgets->get_widget("button_record", button_record);
133 widgets->get_widget("button_add_app", button_add_app);
134 widgets->get_widget("button_add_group", button_add_group);
135 widgets->get_widget("button_remove_app", button_remove_app);
136 widgets->get_widget("button_reset_actions", button_reset_actions);
137 widgets->get_widget("check_show_deleted", check_show_deleted);
138 widgets->get_widget("expander_apps", expander_apps);
139 widgets->get_widget("vpaned_apps", vpaned_apps);
140 button_record->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_button_record));
141 button_delete->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_button_delete));
142 button_add->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_button_new));
143 button_add_app->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_add_app));
144 button_add_group->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_add_group));
145 button_remove_app->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_remove_app));
146 button_reset_actions->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_reset_actions));
147
148 tv.signal_row_activated().connect(sigc::mem_fun(*this, &Actions::on_row_activated));
149 tv.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &Actions::on_selection_changed));
150
151 tm = Store::create(cols, this);
152
153 tv.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE);
154
155 int n;
156 n = tv.append_column(_("Stroke"), cols.stroke);
157 tv.get_column(n-1)->set_sort_column(cols.id);
158 tm->set_sort_func(cols.id, sigc::mem_fun(*this, &Actions::compare_ids));
159 tm->set_default_sort_func(sigc::mem_fun(*this, &Actions::compare_ids));
160 tm->set_sort_column(Gtk::TreeSortable::DEFAULT_SORT_COLUMN_ID, Gtk::SORT_ASCENDING);
161
162 n = tv.append_column(_("Name"), cols.name);
163 Gtk::CellRendererText *name_renderer = dynamic_cast<Gtk::CellRendererText *>(tv.get_column_cell_renderer(n-1));
164 name_renderer->property_editable() = true;
165 name_renderer->signal_edited().connect(sigc::mem_fun(*this, &Actions::on_name_edited));
166 name_renderer->signal_editing_started().connect(sigc::mem_fun(*this, &Actions::on_something_editing_started));
167 name_renderer->signal_editing_canceled().connect(sigc::mem_fun(*this, &Actions::on_something_editing_canceled));
168 Gtk::TreeView::Column *col_name = tv.get_column(n-1);
169 col_name->set_sort_column(cols.name);
170 col_name->set_cell_data_func(*name_renderer, sigc::mem_fun(*this, &Actions::on_cell_data_name));
171 col_name->set_resizable();
172
173 type_store = Gtk::ListStore::create(type);
174 for (TypeInfo *i = all_types; i->name; i++)
175 (*(type_store->append()))[type.type] = _(i->name);
176
177 Gtk::CellRendererCombo *type_renderer = Gtk::manage(new Gtk::CellRendererCombo);
178 type_renderer->property_model() = type_store;
179 type_renderer->property_editable() = true;
180 type_renderer->property_text_column() = 0;
181 type_renderer->property_has_entry() = false;
182 type_renderer->signal_edited().connect(sigc::mem_fun(*this, &Actions::on_type_edited));
183 type_renderer->signal_editing_started().connect(sigc::mem_fun(*this, &Actions::on_something_editing_started));
184 type_renderer->signal_editing_canceled().connect(sigc::mem_fun(*this, &Actions::on_something_editing_canceled));
185
186 n = tv.append_column(_("Type"), *type_renderer);
187 Gtk::TreeView::Column *col_type = tv.get_column(n-1);
188 col_type->add_attribute(type_renderer->property_text(), cols.type);
189 col_type->set_cell_data_func(*type_renderer, sigc::mem_fun(*this, &Actions::on_cell_data_type));
190
191 int i = 0;
192 while (Misc::types[i]) i++;
193 CellRendererTextish *arg_renderer = cell_renderer_textish_new_with_items ((gchar**)Misc::types, i);
194 GtkTreeViewColumn *col_arg = gtk_tree_view_column_new_with_attributes(_("Details"), GTK_CELL_RENDERER (arg_renderer), "text", cols.arg.index(), NULL);
195 gtk_tree_view_append_column(tv.gobj(), col_arg);
196
197 gtk_tree_view_column_set_cell_data_func (col_arg, GTK_CELL_RENDERER (arg_renderer), on_actions_cell_data_arg, this, NULL);
198 gtk_tree_view_column_set_resizable(col_arg, true);
199 g_object_set(arg_renderer, "editable", true, NULL);
200 g_signal_connect(arg_renderer, "key-edited", G_CALLBACK(on_actions_accel_edited), this);
201 g_signal_connect(arg_renderer, "combo-edited", G_CALLBACK(on_actions_combo_edited), this);
202 g_signal_connect(arg_renderer, "edited", G_CALLBACK(on_actions_text_edited), this);
203 g_signal_connect(arg_renderer, "editing-started", G_CALLBACK(on_actions_editing_started), this);
204
205 update_action_list();
206 tv.set_model(tm);
207 tv.enable_model_drag_source();
208 tv.enable_model_drag_dest();
209
210 check_show_deleted->signal_toggled().connect(sigc::mem_fun(*this, &Actions::update_action_list));
211 expander_apps->property_expanded().signal_changed().connect(sigc::mem_fun(*this, &Actions::on_apps_selection_changed));
212 expander_apps->property_expanded().signal_changed().connect(sigc::mem_fun(*this, &Actions::on_expanded));
213 apps_view->get_selection()->signal_changed().connect(sigc::mem_fun(*this, &Actions::on_apps_selection_changed));
214 apps_model = AppsStore::create(ca, this);
215
216 load_app_list(apps_model->children(), actions.get_root());
217 update_counts();
218
219 apps_view->append_column_editable(_("Application"), ca.app);
220 apps_view->get_column(0)->set_expand(true);
221 apps_view->get_column(0)->set_cell_data_func(
222 *apps_view->get_column_cell_renderer(0), sigc::mem_fun(*this, &Actions::on_cell_data_apps));
223 Gtk::CellRendererText *app_name_renderer =
224 dynamic_cast<Gtk::CellRendererText *>(apps_view->get_column_cell_renderer(0));
225 app_name_renderer->signal_edited().connect(sigc::mem_fun(*this, &Actions::on_group_name_edited));
226 apps_view->append_column(_("Actions"), ca.count);
227
228 apps_view->set_model(apps_model);
229 apps_view->enable_model_drag_dest();
230 apps_view->expand_all();
231 }
232
load_app_list(const Gtk::TreeNodeChildren & ch,ActionListDiff * actions)233 void Actions::load_app_list(const Gtk::TreeNodeChildren &ch, ActionListDiff *actions) {
234 Gtk::TreeRow row = *(apps_model->append(ch));
235 row[ca.app] = app_name_hr(actions->name);
236 row[ca.actions] = actions;
237 for (ActionListDiff::iterator i = actions->begin(); i != actions->end(); i++)
238 load_app_list(row.children(), &(*i));
239 }
240
on_cell_data_name(Gtk::CellRenderer * cell,const Gtk::TreeModel::iterator & iter)241 void Actions::on_cell_data_name(Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter) {
242 bool bold = (*iter)[cols.name_bold];
243 bool deactivated = (*iter)[cols.deactivated];
244 Gtk::CellRendererText *renderer = dynamic_cast<Gtk::CellRendererText *>(cell);
245 if (renderer)
246 renderer->property_weight().set_value(bold ? 700 : 400);
247 cell->property_sensitive().set_value(!deactivated);
248 }
249
on_cell_data_type(Gtk::CellRenderer * cell,const Gtk::TreeModel::iterator & iter)250 void Actions::on_cell_data_type(Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter) {
251 bool bold = (*iter)[cols.action_bold];
252 bool deactivated = (*iter)[cols.deactivated];
253 Gtk::CellRendererText *renderer = dynamic_cast<Gtk::CellRendererText *>(cell);
254 if (renderer)
255 renderer->property_weight().set_value(bold ? 700 : 400);
256 cell->property_sensitive().set_value(!deactivated);
257 }
258
on_cell_data_arg(GtkCellRenderer * cell,gchar * path)259 void Actions::on_cell_data_arg(GtkCellRenderer *cell, gchar *path) {
260 Gtk::TreeModel::iterator iter = tm->get_iter(path);
261 bool bold = (*iter)[cols.action_bold];
262 bool deactivated = (*iter)[cols.deactivated];
263 g_object_set(cell, "sensitive", !deactivated, "weight", bold ? 700 : 400, NULL);
264 CellRendererTextish *renderer = CELL_RENDERER_TEXTISH (cell);
265 if (!renderer)
266 return;
267 Glib::ustring str = (*iter)[cols.type];
268 renderer->mode = all_types[from_name(str)].mode;
269 }
270
compare_ids(const Gtk::TreeModel::iterator & a,const Gtk::TreeModel::iterator & b)271 int Actions::compare_ids(const Gtk::TreeModel::iterator &a, const Gtk::TreeModel::iterator &b) {
272 Unique *x = (*a)[cols.id];
273 Unique *y = (*b)[cols.id];
274 if (x->level == y->level) {
275 if (x->i == y->i)
276 return 0;
277 if (x->i < y->i)
278 return -1;
279 else
280 return 1;
281 }
282 if (x->level < y->level)
283 return -1;
284 else
285 return 1;
286 }
287
row_drop_possible_vfunc(const Gtk::TreeModel::Path & dest,const Gtk::SelectionData & selection) const288 bool Actions::AppsStore::row_drop_possible_vfunc(const Gtk::TreeModel::Path &dest,
289 const Gtk::SelectionData &selection) const {
290 static bool expecting = false;
291 static Gtk::TreePath expected;
292 if (expecting && expected != dest)
293 expecting = false;
294 if (!expecting) {
295 if (gtk_tree_path_get_depth((GtkTreePath *)dest.gobj()) < 2 || dest.back() != 0)
296 return false;
297 expected = dest;
298 expected.up();
299 expecting = true;
300 return false;
301 }
302 expecting = false;
303 Gtk::TreePath src;
304 Glib::RefPtr<TreeModel> model;
305 if (!Gtk::TreeModel::Path::get_from_selection_data(selection, model, src))
306 return false;
307 if (model != parent->tm)
308 return false;
309 Gtk::TreeIter dest_iter = parent->apps_model->get_iter(dest);
310 ActionListDiff *actions = dest_iter ? (*dest_iter)[parent->ca.actions] : (ActionListDiff *)NULL;
311 return actions && actions != parent->action_list;
312 }
313
drag_data_received_vfunc(const Gtk::TreeModel::Path & dest,const Gtk::SelectionData & selection)314 bool Actions::AppsStore::drag_data_received_vfunc(const Gtk::TreeModel::Path &dest, const Gtk::SelectionData &selection) {
315 Gtk::TreePath src;
316 Glib::RefPtr<TreeModel> model;
317 if (!Gtk::TreeModel::Path::get_from_selection_data(selection, model, src))
318 return false;
319 if (model != parent->tm)
320 return false;
321 Unique *src_id = (*parent->tm->get_iter(src))[parent->cols.id];
322 Gtk::TreeIter dest_iter = parent->apps_model->get_iter(dest);
323 ActionListDiff *actions = dest_iter ? (*dest_iter)[parent->ca.actions] : (ActionListDiff *)NULL;
324 if (!actions || actions == parent->action_list)
325 return false;
326 Glib::RefPtr<Gtk::TreeSelection> sel = parent->tv.get_selection();
327 if (sel->count_selected_rows() <= 1) {
328 RStrokeInfo si = parent->action_list->get_info(src_id);
329 parent->action_list->remove(src_id);
330 actions->add(*si);
331 } else {
332 std::vector<Gtk::TreePath> paths = sel->get_selected_rows();
333 for (std::vector<Gtk::TreePath>::iterator i = paths.begin(); i != paths.end(); ++i) {
334 Unique *id = (*parent->tm->get_iter(*i))[parent->cols.id];
335 RStrokeInfo si = parent->action_list->get_info(id);
336 parent->action_list->remove(id);
337 actions->add(*si);
338 }
339 }
340 parent->update_action_list();
341 update_actions();
342 return true;
343 }
344
row_draggable_vfunc(const Gtk::TreeModel::Path & path) const345 bool Actions::Store::row_draggable_vfunc(const Gtk::TreeModel::Path &path) const {
346 int col;
347 Gtk::SortType sort;
348 parent->tm->get_sort_column_id(col, sort);
349 if (col != Gtk::TreeSortable::DEFAULT_SORT_COLUMN_ID)
350 return false;
351 if (sort != Gtk::SORT_ASCENDING)
352 return false;
353 Glib::RefPtr<Gtk::TreeSelection> sel = parent->tv.get_selection();
354 if (sel->count_selected_rows() <= 1) {
355 Unique *id = (*parent->tm->get_iter(path))[parent->cols.id];
356 return id->level == parent->action_list->level;
357 } else {
358 std::vector<Gtk::TreePath> paths = sel->get_selected_rows();
359 for (std::vector<Gtk::TreePath>::iterator i = paths.begin(); i != paths.end(); ++i) {
360 Unique *id = (*parent->tm->get_iter(*i))[parent->cols.id];
361 if (id->level != parent->action_list->level)
362 return false;
363 }
364 return true;
365 }
366 }
367
row_drop_possible_vfunc(const Gtk::TreeModel::Path & dest,const Gtk::SelectionData & selection) const368 bool Actions::Store::row_drop_possible_vfunc(const Gtk::TreeModel::Path &dest, const Gtk::SelectionData &selection) const {
369 static bool ignore_next = false;
370 if (gtk_tree_path_get_depth((GtkTreePath *)dest.gobj()) > 1) {
371 ignore_next = true;
372 return false;
373 }
374 if (ignore_next) {
375 ignore_next = false;
376 return false;
377 }
378 Gtk::TreePath src;
379 Glib::RefPtr<TreeModel> model;
380 if (!Gtk::TreeModel::Path::get_from_selection_data(selection, model, src))
381 return false;
382 if (model != parent->tm)
383 return false;
384 Unique *src_id = (*parent->tm->get_iter(src))[parent->cols.id];
385 Gtk::TreeIter dest_iter = parent->tm->get_iter(dest);
386 Unique *dest_id = dest_iter ? (*dest_iter)[parent->cols.id] : (Unique *)0;
387 if (dest_id && src_id->level != dest_id->level)
388 return false;
389 return true;
390 }
391
drag_data_received_vfunc(const Gtk::TreeModel::Path & dest,const Gtk::SelectionData & selection)392 bool Actions::Store::drag_data_received_vfunc(const Gtk::TreeModel::Path &dest, const Gtk::SelectionData &selection) {
393 Gtk::TreePath src;
394 Glib::RefPtr<TreeModel> model;
395 if (!Gtk::TreeModel::Path::get_from_selection_data(selection, model, src))
396 return false;
397 if (model != parent->tm)
398 return false;
399 Unique *src_id = (*parent->tm->get_iter(src))[parent->cols.id];
400 Gtk::TreeIter dest_iter = parent->tm->get_iter(dest);
401 Unique *dest_id = dest_iter ? (*dest_iter)[parent->cols.id] : (Unique *)0;
402 if (dest_id && src_id->level != dest_id->level)
403 return false;
404 Glib::RefPtr<Gtk::TreeSelection> sel = parent->tv.get_selection();
405 if (sel->count_selected_rows() <= 1) {
406 if (parent->action_list->move(src_id, dest_id)) {
407 (*parent->tm->get_iter(src))[parent->cols.id] = src_id;
408 update_actions();
409 }
410 } else {
411 std::vector<Gtk::TreePath> paths = sel->get_selected_rows();
412 bool updated = false;
413 for (std::vector<Gtk::TreePath>::iterator i = paths.begin(); i != paths.end(); ++i) {
414 Unique *id = (*parent->tm->get_iter(*i))[parent->cols.id];
415 if (parent->action_list->move(id, dest_id))
416 updated = true;
417 }
418 if (updated) {
419 parent->update_action_list();
420 update_actions();
421 }
422 }
423 return false;
424 }
425
on_type_edited(const Glib::ustring & path,const Glib::ustring & new_text)426 void Actions::on_type_edited(const Glib::ustring &path, const Glib::ustring &new_text) {
427 tv.grab_focus();
428 Gtk::TreeRow row(*tm->get_iter(path));
429 Type new_type = from_name(new_text);
430 Type old_type = from_name(row[cols.type]);
431 bool edit = true;
432 if (old_type == new_type) {
433 edit = editing_new;
434 } else {
435 row[cols.type] = new_text;
436 RAction new_action;
437 if (new_type == COMMAND) {
438 Glib::ustring cmd_save = row[cols.cmd_save];
439 if (cmd_save != "")
440 edit = false;
441 new_action = Command::create(cmd_save);
442 }
443 if (old_type == COMMAND) {
444 row[cols.cmd_save] = (Glib::ustring)row[cols.arg];
445 }
446 if (new_type == KEY) {
447 new_action = SendKey::create(0, (Gdk::ModifierType)0);
448 edit = true;
449 }
450 if (new_type == TEXT) {
451 new_action = SendText::create(Glib::ustring());
452 edit = true;
453 }
454 if (new_type == SCROLL) {
455 new_action = Scroll::create((Gdk::ModifierType)0);
456 edit = false;
457 }
458 if (new_type == IGNORE) {
459 new_action = Ignore::create((Gdk::ModifierType)0);
460 edit = false;
461 }
462 if (new_type == BUTTON) {
463 new_action = Button::create((Gdk::ModifierType)0, 0);
464 edit = true;
465 }
466 if (new_type == MISC) {
467 new_action = Misc::create(Misc::NONE);
468 edit = true;
469 }
470 action_list->set_action(row[cols.id], new_action);
471 update_row(row);
472 update_actions();
473 }
474 editing_new = false;
475 focus(row[cols.id], 3, edit);
476 }
477
on_button_delete()478 void Actions::on_button_delete() {
479 int n = tv.get_selection()->count_selected_rows();
480
481 Glib::ustring str;
482 if (n == 1)
483 str = Glib::ustring::compose(_("Action \"%1\" is about to be deleted."), get_selected_row()[cols.name]);
484 else
485 str = Glib::ustring::compose(ngettext("One action is about to be deleted.",
486 "%1 actions are about to be deleted", n), n);
487
488 Gtk::MessageDialog *dialog;
489 widgets->get_widget("dialog_delete", dialog);
490 dialog->set_message(ngettext("Delete an Action", "Delete Actions", n));
491 dialog->set_secondary_text(str);
492 Gtk::Button *del;
493 widgets->get_widget("button_delete_delete", del);
494
495 dialog->show();
496 del->grab_focus();
497 bool ok = dialog->run() == 1;
498 dialog->hide();
499 if (!ok)
500 return;
501
502 std::vector<Gtk::TreePath> paths = tv.get_selection()->get_selected_rows();
503 for (std::vector<Gtk::TreePath>::iterator i = paths.begin(); i != paths.end(); ++i) {
504 Gtk::TreeRow row(*tm->get_iter(*i));
505 action_list->remove(row[cols.id]);
506 }
507 update_action_list();
508 update_actions();
509 update_counts();
510 }
511
on_cell_data_apps(Gtk::CellRenderer * cell,const Gtk::TreeModel::iterator & iter)512 void Actions::on_cell_data_apps(Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter) {
513 ActionListDiff *as = (*iter)[ca.actions];
514 Gtk::CellRendererText *renderer = dynamic_cast<Gtk::CellRendererText *>(cell);
515 if (renderer)
516 renderer->property_editable().set_value(actions.get_root() != as && !as->app);
517 }
518
select_app(const Gtk::TreeModel::Path & path,const Gtk::TreeModel::iterator & iter,ActionListDiff * actions)519 bool Actions::select_app(const Gtk::TreeModel::Path& path, const Gtk::TreeModel::iterator& iter, ActionListDiff *actions) {
520 if ((*iter)[ca.actions] == actions) {
521 apps_view->expand_to_path(path);
522 apps_view->set_cursor(path);
523 return true;
524 }
525 return false;
526 }
527
on_add_app()528 void Actions::on_add_app() {
529 std::string name = grabber->select_window();
530 if (actions.apps.count(name)) {
531 apps_model->foreach(sigc::bind(sigc::mem_fun(*this, &Actions::select_app), actions.apps[name]));
532 return;
533 }
534 ActionListDiff *parent = action_list->app ? actions.get_root() : action_list;
535 ActionListDiff *child = parent->add_child(name, true);
536 const Gtk::TreeNodeChildren &ch = parent == actions.get_root() ?
537 apps_model->children().begin()->children() :
538 apps_view->get_selection()->get_selected()->children();
539 Gtk::TreeRow row = *(apps_model->append(ch));
540 row[ca.app] = app_name_hr(name);
541 row[ca.actions] = child;
542 actions.apps[name] = child;
543 Gtk::TreePath path = apps_model->get_path(row);
544 apps_view->expand_to_path(path);
545 apps_view->set_cursor(path);
546 update_actions();
547 }
548
on_remove_app()549 void Actions::on_remove_app() {
550 if (action_list == actions.get_root())
551 return;
552 int size = action_list->size_rec();
553 if (size) {
554 Gtk::MessageDialog *dialog;
555 widgets->get_widget("dialog_delete", dialog);
556 Glib::ustring str = Glib::ustring::compose(_("%1 \"%2\" (containing %3 %4) is about to be deleted."),
557 action_list->app ? _("The application") : _("The group"),
558 action_list->name,
559 size,
560 ngettext("action", "actions", size));
561 dialog->set_message(action_list->app ? _("Delete an Application") : _("Delete an Application Group"));
562 dialog->set_secondary_text(str);
563 Gtk::Button *del;
564 widgets->get_widget("button_delete_delete", del);
565 dialog->show();
566 del->grab_focus();
567 bool ok = dialog->run() == 1;
568 dialog->hide();
569 if (!ok)
570 return;
571 }
572 if (!action_list->remove())
573 return;
574 apps_model->erase(*apps_view->get_selection()->get_selected());
575 update_actions();
576 }
577
on_reset_actions()578 void Actions::on_reset_actions() {
579 std::vector<Gtk::TreePath> paths = tv.get_selection()->get_selected_rows();
580 for (std::vector<Gtk::TreePath>::iterator i = paths.begin(); i != paths.end(); ++i) {
581 Gtk::TreeRow row(*tm->get_iter(*i));
582 action_list->reset(row[cols.id]);
583 }
584 update_action_list();
585 on_selection_changed();
586 update_actions();
587 }
588
on_add_group()589 void Actions::on_add_group() {
590 ActionListDiff *parent = action_list->app ? actions.get_root() : action_list;
591 Glib::ustring name = _("Group");
592 ActionListDiff *child = parent->add_child(name, false);
593 const Gtk::TreeNodeChildren &ch = parent == actions.get_root() ?
594 apps_model->children().begin()->children() :
595 apps_view->get_selection()->get_selected()->children();
596 Gtk::TreeRow row = *(apps_model->append(ch));
597 row[ca.app] = name;
598 row[ca.actions] = child;
599 actions.apps[name] = child;
600 Gtk::TreePath path = apps_model->get_path(row);
601 apps_view->expand_to_path(path);
602 apps_view->set_cursor(path, *apps_view->get_column(0), true);
603 update_actions();
604 }
605
on_group_name_edited(const Glib::ustring & path,const Glib::ustring & new_text)606 void Actions::on_group_name_edited(const Glib::ustring& path, const Glib::ustring& new_text) {
607 Gtk::TreeRow row(*apps_model->get_iter(path));
608 row[ca.app] = new_text;
609 ActionListDiff *as = row[ca.actions];
610 as->name = new_text;
611 update_actions();
612 }
613
on_expanded()614 void Actions::on_expanded() {
615 if (expander_apps->get_expanded()) {
616 vpaned_apps->set_position(vpaned_position);
617 } else {
618 if(vpaned_apps->property_position_set().get_value())
619 vpaned_position = vpaned_apps->get_position();
620 else
621 vpaned_position = -1;
622 vpaned_apps->property_position_set().set_value(false);
623 }
624 }
625
on_apps_selection_changed()626 void Actions::on_apps_selection_changed() {
627 ActionListDiff *new_action_list = actions.get_root();
628 if (expander_apps->property_expanded().get_value()) {
629 if (apps_view->get_selection()->count_selected_rows()) {
630 Gtk::TreeIter i = apps_view->get_selection()->get_selected();
631 new_action_list = (*i)[ca.actions];
632 }
633 button_remove_app->set_sensitive(new_action_list != actions.get_root());
634 }
635 if (action_list != new_action_list) {
636 action_list = new_action_list;
637 update_action_list();
638 on_selection_changed();
639 }
640 }
641
count_app_actions(const Gtk::TreeIter & i)642 bool Actions::count_app_actions(const Gtk::TreeIter &i) {
643 (*i)[ca.count] = ((ActionListDiff*)(*i)[ca.actions])->count_actions();
644 return false;
645 }
646
update_counts()647 void Actions::update_counts() {
648 apps_model->foreach_iter(sigc::mem_fun(*this, &Actions::count_app_actions));
649 }
650
update_action_list()651 void Actions::update_action_list() {
652 check_show_deleted->set_sensitive(action_list != actions.get_root());
653 boost::shared_ptr<std::set<Unique *> > ids = action_list->get_ids(check_show_deleted->get_active());
654 const Gtk::TreeNodeChildren &ch = tm->children();
655
656 std::list<Gtk::TreeRowReference> refs;
657 for (Gtk::TreeIter i = ch.begin(); i != ch.end(); i++) {
658 Gtk::TreeRowReference ref(tm, Gtk::TreePath(*i));
659 refs.push_back(ref);
660 }
661
662 for (std::list<Gtk::TreeRowReference>::iterator ref = refs.begin(); ref != refs.end(); ref++) {
663 Gtk::TreeIter i = tm->get_iter(ref->get_path());
664 std::set<Unique *>::iterator id = ids->find((*i)[cols.id]);
665 if (id == ids->end()) {
666 tm->erase(i);
667 } else {
668 ids->erase(id);
669 update_row(*i);
670 }
671 }
672 for (std::set<Unique *>::const_iterator i = ids->begin(); i != ids->end(); i++) {
673 Gtk::TreeRow row = *tm->append();
674 row[cols.id] = *i;
675 update_row(row);
676 }
677 }
678
update_row(const Gtk::TreeRow & row)679 void Actions::update_row(const Gtk::TreeRow &row) {
680 bool deleted, stroke, name, action;
681 RStrokeInfo si = action_list->get_info(row[cols.id], &deleted, &stroke, &name, &action);
682 row[cols.stroke] = !si->strokes.empty() && *si->strokes.begin() ?
683 (*si->strokes.begin())->draw(STROKE_SIZE, stroke ? 4.0 : 2.0) : Stroke::drawEmpty(STROKE_SIZE);
684 row[cols.name] = si->name;
685 row[cols.type] = si->action ? type_info_to_name(&typeid(*si->action)) : "";
686 row[cols.arg] = si->action ? si->action->get_label() : "";
687 row[cols.deactivated] = deleted;
688 row[cols.name_bold] = name;
689 row[cols.action_bold] = action;
690 }
691
692 extern boost::shared_ptr<sigc::slot<void, RStroke> > stroke_action;
693 Source<bool> recording(false);
694
695 class Actions::OnStroke {
696 Actions *parent;
697 Gtk::Dialog *dialog;
698 Gtk::TreeRow &row;
699 RStroke stroke;
run()700 bool run() {
701 if (stroke->button == 0 && stroke->trivial()) {
702 grabber->queue_suspend();
703 Glib::ustring msg = Glib::ustring::compose(
704 _("You are about to bind an action to a single click. "
705 "This might make it difficult to use Button %1 in the future. "
706 "Are you sure you want to continue?"),
707 stroke->button ? stroke->button : prefs.button.ref().button);
708 Gtk::MessageDialog md(*dialog, msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_YES_NO, true);
709 bool abort = md.run() != Gtk::RESPONSE_YES;
710 grabber->queue_resume();
711 if (abort)
712 return false;
713 }
714 StrokeSet strokes;
715 strokes.insert(stroke);
716 parent->action_list->set_strokes(row[parent->cols.id], strokes);
717 parent->update_row(row);
718 parent->on_selection_changed();
719 update_actions();
720 dialog->response(0);
721 return false;
722 }
723 public:
OnStroke(Actions * parent_,Gtk::Dialog * dialog_,Gtk::TreeRow & row_)724 OnStroke(Actions *parent_, Gtk::Dialog *dialog_, Gtk::TreeRow &row_) : parent(parent_), dialog(dialog_), row(row_) {}
delayed_run(RStroke stroke_)725 void delayed_run(RStroke stroke_) {
726 stroke = stroke_;
727 Glib::signal_idle().connect(sigc::mem_fun(*this, &OnStroke::run));
728 stroke_action.reset();
729 recording.set(false);
730 }
731 };
732
on_row_activated(const Gtk::TreeModel::Path & path,Gtk::TreeViewColumn * column)733 void Actions::on_row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column) {
734 Gtk::TreeRow row(*tm->get_iter(path));
735 Gtk::MessageDialog *dialog;
736 widgets->get_widget("dialog_record", dialog);
737 dialog->set_message(_("Record a New Stroke"));
738 dialog->set_secondary_text(Glib::ustring::compose(_("The next stroke will be associated with the action \"%1\". You can draw it anywhere on the screen (except for the two buttons below)."), row[cols.name]));
739
740 static Gtk::Button *del = 0, *cancel = 0;
741 if (!del) {
742 widgets->get_widget("button_record_delete", del);
743 del->signal_enter().connect(sigc::mem_fun(*grabber, &Grabber::queue_suspend));
744 del->signal_leave().connect(sigc::mem_fun(*grabber, &Grabber::queue_resume));
745 }
746 if (!cancel) {
747 widgets->get_widget("button_record_cancel", cancel);
748 cancel->signal_enter().connect(sigc::mem_fun(*grabber, &Grabber::queue_suspend));
749 cancel->signal_leave().connect(sigc::mem_fun(*grabber, &Grabber::queue_resume));
750 }
751 RStrokeInfo si = action_list->get_info(row[cols.id]);
752 if (si)
753 del->set_sensitive(si->strokes.size());
754
755 OnStroke ps(this, dialog, row);
756 stroke_action.reset(new sigc::slot<void, RStroke>(sigc::mem_fun(ps, &OnStroke::delayed_run)));
757 recording.set(true);
758
759 dialog->show();
760 cancel->grab_focus();
761 int response = dialog->run();
762 dialog->hide();
763 stroke_action.reset();
764 recording.set(false);
765 if (response != 1)
766 return;
767
768 action_list->set_strokes(row[cols.id], StrokeSet());
769 update_row(row);
770 on_selection_changed();
771 update_actions();
772 }
773
on_button_record()774 void Actions::on_button_record() {
775 Gtk::TreeModel::Path path;
776 Gtk::TreeViewColumn *col;
777 tv.get_cursor(path, col);
778 on_row_activated(path, col);
779 }
780
get_selected_row()781 Gtk::TreeRow Actions::get_selected_row() {
782 std::vector<Gtk::TreePath> paths = tv.get_selection()->get_selected_rows();
783 return Gtk::TreeRow(*tm->get_iter(*paths.begin()));
784 }
785
on_selection_changed()786 void Actions::on_selection_changed() {
787 int n = tv.get_selection()->count_selected_rows();
788 button_record->set_sensitive(n == 1);
789 button_delete->set_sensitive(n >= 1);
790 bool resettable = false;
791 if (n) {
792 std::vector<Gtk::TreePath> paths = tv.get_selection()->get_selected_rows();
793 for (std::vector<Gtk::TreePath>::iterator i = paths.begin(); i != paths.end(); ++i) {
794 Gtk::TreeRow row(*tm->get_iter(*i));
795 if (action_list->resettable(row[cols.id])) {
796 resettable = true;
797 break;
798 }
799 }
800 }
801 button_reset_actions->set_sensitive(resettable);
802 }
803
on_button_new()804 void Actions::on_button_new() {
805 editing_new = true;
806 Unique *before = 0;
807 if (tv.get_selection()->count_selected_rows()) {
808 std::vector<Gtk::TreePath> paths = tv.get_selection()->get_selected_rows();
809 Gtk::TreeIter i = tm->get_iter(paths[paths.size()-1]);
810 i++;
811 if (i != tm->children().end())
812 before = (*i)[cols.id];
813 }
814
815 Gtk::TreeModel::Row row = *(tm->append());
816 StrokeInfo si;
817 si.action = Command::create("");
818 Unique *id = action_list->add(si, before);
819 row[cols.id] = id;
820 std::string name;
821 if (action_list != actions.get_root())
822 name = action_list->name + " ";
823 name += Glib::ustring::compose(_("Gesture %1"), action_list->order_size());
824 action_list->set_name(id, name);
825
826 update_row(row);
827 focus(id, 1, true);
828 update_actions();
829 update_counts();
830 }
831
do_focus(Unique * id,Gtk::TreeViewColumn * col,bool edit)832 bool Actions::do_focus(Unique *id, Gtk::TreeViewColumn *col, bool edit) {
833 if (!editing) {
834 Gtk::TreeModel::Children chs = tm->children();
835 for (Gtk::TreeIter i = chs.begin(); i != chs.end(); ++i)
836 if ((*i)[cols.id] == id) {
837 tv.set_cursor(Gtk::TreePath(*i), *col, edit);
838 }
839 }
840 return false;
841 }
842
focus(Unique * id,int col,bool edit)843 void Actions::focus(Unique *id, int col, bool edit) {
844 editing = false;
845 Glib::signal_idle().connect(sigc::bind(sigc::mem_fun(*this, &Actions::do_focus), id, tv.get_column(col), edit));
846 }
847
on_name_edited(const Glib::ustring & path,const Glib::ustring & new_text)848 void Actions::on_name_edited(const Glib::ustring& path, const Glib::ustring& new_text) {
849 Gtk::TreeRow row(*tm->get_iter(path));
850 action_list->set_name(row[cols.id], new_text);
851 update_actions();
852 update_row(row);
853 focus(row[cols.id], 2, editing_new);
854 }
855
on_text_edited(const gchar * path,const gchar * new_text)856 void Actions::on_text_edited(const gchar *path, const gchar *new_text) {
857 Gtk::TreeRow row(*tm->get_iter(path));
858 Type type = from_name(row[cols.type]);
859 if (type == COMMAND) {
860 action_list->set_action(row[cols.id], Command::create(new_text));
861 } else if (type == TEXT) {
862 action_list->set_action(row[cols.id], SendText::create(new_text));
863 } else return;
864 update_row(row);
865 update_actions();
866 }
867
on_accel_edited(const gchar * path_string,guint accel_key,GdkModifierType mods,guint hardware_keycode)868 void Actions::on_accel_edited(const gchar *path_string, guint accel_key, GdkModifierType mods, guint hardware_keycode) {
869 Gdk::ModifierType accel_mods = (Gdk::ModifierType)mods;
870 Gtk::TreeRow row(*tm->get_iter(path_string));
871 Type type = from_name(row[cols.type]);
872 if (type == KEY) {
873 RSendKey send_key = SendKey::create(accel_key, accel_mods);
874 Glib::ustring str = send_key->get_label();
875 if (row[cols.arg] == str)
876 return;
877 action_list->set_action(row[cols.id], boost::static_pointer_cast<Action>(send_key));
878 } else if (type == SCROLL) {
879 RScroll scroll = Scroll::create(accel_mods);
880 Glib::ustring str = scroll->get_label();
881 if (row[cols.arg] == str)
882 return;
883 action_list->set_action(row[cols.id], boost::static_pointer_cast<Action>(scroll));
884 } else if (type == IGNORE) {
885 RIgnore ignore = Ignore::create(accel_mods);
886 Glib::ustring str = ignore->get_label();
887 if (row[cols.arg] == str)
888 return;
889 action_list->set_action(row[cols.id], boost::static_pointer_cast<Action>(ignore));
890 } else return;
891 update_row(row);
892 update_actions();
893 }
894
on_combo_edited(const gchar * path_string,guint item)895 void Actions::on_combo_edited(const gchar *path_string, guint item) {
896 RMisc misc = Misc::create((Misc::Type)item);
897 Glib::ustring str = misc->get_label();
898 Gtk::TreeRow row(*tm->get_iter(path_string));
899 if (row[cols.arg] == str)
900 return;
901 action_list->set_action(row[cols.id], boost::static_pointer_cast<Action>(misc));
902 update_row(row);
903 update_actions();
904 }
905
on_something_editing_canceled()906 void Actions::on_something_editing_canceled() {
907 editing_new = false;
908 }
909
on_something_editing_started(Gtk::CellEditable * editable,const Glib::ustring & path)910 void Actions::on_something_editing_started(Gtk::CellEditable* editable, const Glib::ustring& path) {
911 editing = true;
912 }
913
on_arg_editing_started(GtkCellEditable * editable,const gchar * path)914 void Actions::on_arg_editing_started(GtkCellEditable *editable, const gchar *path) {
915 tv.grab_focus();
916 Gtk::TreeRow row(*tm->get_iter(path));
917 if (from_name(row[cols.type]) != BUTTON)
918 return;
919 ButtonInfo bi;
920 RButton bt = boost::static_pointer_cast<Button>(action_list->get_info(row[cols.id])->action);
921 if (bt)
922 bi = bt->get_button_info();
923 SelectButton sb(bi, false, false);
924 if (!sb.run())
925 return;
926 bt = boost::static_pointer_cast<Button>(Button::create(Gdk::ModifierType(sb.event.state), sb.event.button));
927 action_list->set_action(row[cols.id], bt);
928 update_row(row);
929 update_actions();
930 }
931
get_label() const932 const Glib::ustring SendKey::get_label() const {
933 return Gtk::AccelGroup::get_label(key, mods);
934 }
935
get_label() const936 const Glib::ustring ModAction::get_label() const {
937 if (!mods)
938 return _("No Modifiers");
939 Glib::ustring label = Gtk::AccelGroup::get_label(0, mods);
940 return label.substr(0,label.size()-1);
941 }
942
get_button_text() const943 Glib::ustring ButtonInfo::get_button_text() const {
944 Glib::ustring str;
945 if (instant)
946 str += _("(Instantly) ");
947 if (click_hold)
948 str += _("(Click & Hold) ");
949 if (state == AnyModifier)
950 str += Glib::ustring() + "(" + _("Any Modifier") + " +) ";
951 else
952 str += Gtk::AccelGroup::get_label(0, (Gdk::ModifierType)state);
953 return str + Glib::ustring::compose(_("Button %1"), button);
954 }
955
get_label() const956 const Glib::ustring Scroll::get_label() const {
957 if (mods)
958 return ModAction::get_label() + _(" + Scroll");
959 else
960 return _("Scroll");
961 }
962
get_label() const963 const Glib::ustring Ignore::get_label() const {
964 if (mods)
965 return ModAction::get_label();
966 else
967 return _("Ignore");
968 }
969