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