Back to index

easystroke  0.5.5.1
actions.cc
Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 2008-2009, Thomas Jaeger <ThJaeger@gmail.com>
00003  *
00004  * Permission to use, copy, modify, and/or distribute this software for any
00005  * purpose with or without fee is hereby granted, provided that the above
00006  * copyright notice and this permission notice appear in all copies.
00007  *
00008  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
00009  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
00010  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
00011  * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
00012  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
00013  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
00014  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
00015  */
00016 #include "actions.h"
00017 #include "actiondb.h"
00018 #include "win.h"
00019 #include "main.h"
00020 #include "prefdb.h"
00021 #include <glibmm/i18n.h>
00022 #include "grabber.h"
00023 
00024 #include <typeinfo>
00025 
00026 bool TreeViewMulti::on_button_press_event(GdkEventButton* event) {
00027        int cell_x, cell_y;
00028        Gtk::TreeViewColumn *column;
00029        pending = (get_path_at_pos(event->x, event->y, path, column, cell_x, cell_y))
00030               && (get_selection()->is_selected(path))
00031               && !(event->state & (GDK_CONTROL_MASK|GDK_SHIFT_MASK));
00032        return Gtk::TreeView::on_button_press_event(event);
00033 }
00034 
00035 bool TreeViewMulti::on_button_release_event(GdkEventButton* event) {
00036        if (pending) {
00037               pending = false;
00038               get_selection()->unselect_all();
00039               get_selection()->select(path);
00040        }
00041        return Gtk::TreeView::on_button_release_event(event);
00042 }
00043 
00044 void TreeViewMulti::on_drag_begin(const Glib::RefPtr<Gdk::DragContext> &context) {
00045        pending = false;
00046        if (get_selection()->count_selected_rows() <= 1)
00047               return Gtk::TreeView::on_drag_begin(context);
00048        Glib::RefPtr<Gdk::Pixbuf> pb = render_icon(Gtk::Stock::DND_MULTIPLE, Gtk::ICON_SIZE_DND);
00049        context->set_icon(pb, pb->get_width(), pb->get_height());
00050 }
00051 
00052 bool negate(bool b) { return !b; }
00053 
00054 TreeViewMulti::TreeViewMulti() : Gtk::TreeView(), pending(false) {
00055        get_selection()->set_select_function(sigc::group(&negate, sigc::ref(pending)));
00056 }
00057 
00058 class CellEditableAccel : public Gtk::EventBox, public Gtk::CellEditable {
00059        CellRendererTextish *parent;
00060        Glib::ustring path;
00061 public:
00062        CellEditableAccel(CellRendererTextish *parent_, const Glib::ustring &path_, Gtk::Widget &widget) :
00063               Glib::ObjectBase(typeid(CellEditableAccel)),
00064               parent(parent_), path(path_)
00065        {
00066               WIDGET(Gtk::Label, label, _("Key combination..."));
00067               label.set_alignment(0.0, 0.5);
00068               add(label);
00069               modify_bg(Gtk::STATE_NORMAL, widget.get_style()->get_bg(Gtk::STATE_SELECTED));
00070               label.modify_fg(Gtk::STATE_NORMAL, widget.get_style()->get_fg(Gtk::STATE_SELECTED));
00071               show_all();
00072        }
00073 protected:
00074 
00075        virtual void start_editing_vfunc(GdkEvent *event) {
00076               add_modal_grab();
00077               get_window()->keyboard_grab(false, gdk_event_get_time(event));
00078               signal_key_press_event().connect(sigc::mem_fun(*this, &CellEditableAccel::on_key));
00079        }
00080 
00081        bool on_key(GdkEventKey* event) {
00082               if (event->is_modifier)
00083                      return true;
00084               switch (event->keyval) {
00085                      case GDK_Super_L:
00086                      case GDK_Super_R:
00087                      case GDK_Hyper_L:
00088                      case GDK_Hyper_R:
00089                             return true;
00090               }
00091               guint mods = event->state & gtk_accelerator_get_default_mod_mask();
00092               guint key;
00093               if (mods & ~GDK_SHIFT_MASK) {
00094                      key = XKeycodeToKeysym(dpy, event->hardware_keycode, 0);
00095               } else {
00096                      key = gdk_keyval_to_lower(event->keyval);
00097                      if (key == event->keyval)
00098                             mods = 0;
00099               }
00100 
00101               editing_done();
00102               remove_widget();
00103 
00104               parent->signal_key_edited().emit(path, key, (Gdk::ModifierType)mods, event->hardware_keycode);
00105               return true;
00106        }
00107 
00108        virtual void on_editing_done() {
00109               remove_modal_grab();
00110               get_window()->keyboard_ungrab(CurrentTime);
00111               Gtk::CellEditable::on_editing_done();
00112        }
00113 };
00114 
00115 class CellEditableCombo : public Gtk::ComboBoxText, public Gtk::CellEditable {
00116        CellRendererTextish *parent;
00117        Glib::ustring path;
00118 public:
00119        CellEditableCombo(CellRendererTextish *parent_, const Glib::ustring &path_, Gtk::Widget &widget, const char **items) :
00120               Glib::ObjectBase(typeid(CellEditableCombo)),
00121               parent(parent_), path(path_)
00122        {
00123               while (*items)
00124                      append_text(_(*(items++)));
00125        }
00126 protected:
00127        virtual void on_changed() {
00128               parent->signal_combo_edited().emit(path, get_active_row_number());
00129        }
00130 };
00131 
00132 class CellEditableDummy : public Gtk::EventBox, public Gtk::CellEditable {
00133 public:
00134        CellEditableDummy() : Glib::ObjectBase(typeid(CellEditableDummy)) {}
00135 protected:
00136        virtual void start_editing_vfunc(GdkEvent *event) {
00137               editing_done();
00138               remove_widget();
00139        }
00140 };
00141 
00142 Gtk::CellEditable* CellRendererTextish::start_editing_vfunc(GdkEvent *event, Gtk::Widget &widget, const Glib::ustring &path,
00143               const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) {
00144        if (!property_editable())
00145                   return 0;
00146        switch (mode) {
00147               case TEXT:
00148                      return Gtk::CellRendererText::start_editing_vfunc(event, widget, path, background_area, cell_area, flags);
00149               case KEY:
00150                      return Gtk::manage(new CellEditableAccel(this, path, widget));
00151               case COMBO:
00152                      return Gtk::manage(new CellEditableCombo(this, path, widget, items));
00153               case POPUP:
00154                      return Gtk::manage(new CellEditableDummy);
00155        }
00156        return NULL;
00157 }
00158 
00159 enum Type { COMMAND, KEY, TEXT, SCROLL, IGNORE, BUTTON, MISC };
00160 
00161 struct TypeInfo {
00162        Type type;
00163        const char *name;
00164        const std::type_info *type_info;
00165        const CellRendererTextish::Mode mode;
00166 };
00167 
00168 TypeInfo all_types[8] = {
00169        { COMMAND, N_("Command"), &typeid(Command),  CellRendererTextish::TEXT  },
00170        { KEY,     N_("Key"),     &typeid(SendKey),  CellRendererTextish::KEY   },
00171        { TEXT,    N_("Text"),    &typeid(SendText), CellRendererTextish::TEXT  },
00172        { SCROLL,  N_("Scroll"),  &typeid(Scroll),   CellRendererTextish::KEY   },
00173        { IGNORE,  N_("Ignore"),  &typeid(Ignore),   CellRendererTextish::KEY   },
00174        { BUTTON,  N_("Button"),  &typeid(Button),   CellRendererTextish::POPUP },
00175        { MISC,    N_("Misc"),    &typeid(Misc),     CellRendererTextish::COMBO },
00176        { COMMAND, 0,             0,                 CellRendererTextish::TEXT  }
00177 };
00178 
00179 const Type from_name(Glib::ustring name) {
00180        for (TypeInfo *i = all_types;; i++)
00181               if (!i->name || _(i->name) == name)
00182                      return i->type;
00183 }
00184 
00185 const char *type_info_to_name(const std::type_info *info) {
00186        for (TypeInfo *i = all_types; i->name; i++)
00187               if (i->type_info == info)
00188                      return _(i->name);
00189        return "";
00190 }
00191 
00192 Actions::Actions() :
00193        apps_view(0),
00194        editing_new(false),
00195        editing(false),
00196        action_list(actions.get_root())
00197 {
00198        Gtk::ScrolledWindow *sw;
00199        widgets->get_widget("scrolledwindow_actions", sw);
00200        widgets->get_widget("treeview_apps", apps_view);
00201        sw->add(tv);
00202        tv.show();
00203 
00204        Gtk::Button *button_add, *button_add_app, *button_add_group;
00205        widgets->get_widget("button_add_action", button_add);
00206        widgets->get_widget("button_delete_action", button_delete);
00207        widgets->get_widget("button_record", button_record);
00208        widgets->get_widget("button_add_app", button_add_app);
00209        widgets->get_widget("button_add_group", button_add_group);
00210        widgets->get_widget("button_remove_app", button_remove_app);
00211        widgets->get_widget("button_reset_actions", button_reset_actions);
00212        widgets->get_widget("check_show_deleted", check_show_deleted);
00213        widgets->get_widget("expander_apps", expander_apps);
00214        button_record->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_button_record));
00215        button_delete->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_button_delete));
00216        button_add->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_button_new));
00217        button_add_app->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_add_app));
00218        button_add_group->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_add_group));
00219        button_remove_app->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_remove_app));
00220        button_reset_actions->signal_clicked().connect(sigc::mem_fun(*this, &Actions::on_reset_actions));
00221 
00222        tv.signal_cursor_changed().connect(sigc::mem_fun(*this, &Actions::on_cursor_changed));
00223        tv.signal_row_activated().connect(sigc::mem_fun(*this, &Actions::on_row_activated));
00224        tv.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &Actions::on_selection_changed));
00225 
00226        tm = Store::create(cols, this);
00227 
00228        tv.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE);
00229 
00230        int n;
00231        n = tv.append_column(_("Stroke"), cols.stroke);
00232        tv.get_column(n-1)->set_sort_column(cols.id);
00233        tm->set_sort_func(cols.id, sigc::mem_fun(*this, &Actions::compare_ids));
00234        tm->set_default_sort_func(sigc::mem_fun(*this, &Actions::compare_ids));
00235        tm->set_sort_column(Gtk::TreeSortable::DEFAULT_SORT_COLUMN_ID, Gtk::SORT_ASCENDING);
00236 
00237        n = tv.append_column(_("Name"), cols.name);
00238        Gtk::CellRendererText *name_renderer = dynamic_cast<Gtk::CellRendererText *>(tv.get_column_cell_renderer(n-1));
00239        name_renderer->property_editable() = true;
00240        name_renderer->signal_edited().connect(sigc::mem_fun(*this, &Actions::on_name_edited));
00241        name_renderer->signal_editing_started().connect(sigc::mem_fun(*this, &Actions::on_something_editing_started));
00242        name_renderer->signal_editing_canceled().connect(sigc::mem_fun(*this, &Actions::on_something_editing_canceled));
00243        Gtk::TreeView::Column *col_name = tv.get_column(n-1);
00244        col_name->set_sort_column(cols.name);
00245        col_name->set_cell_data_func(*name_renderer, sigc::mem_fun(*this, &Actions::on_cell_data_name));
00246        col_name->set_resizable();
00247 
00248        type_store = Gtk::ListStore::create(type);
00249        for (TypeInfo *i = all_types; i->name; i++)
00250               (*(type_store->append()))[type.type] = _(i->name);
00251 
00252        Gtk::CellRendererCombo *type_renderer = Gtk::manage(new Gtk::CellRendererCombo);
00253        type_renderer->property_model() = type_store;
00254        type_renderer->property_editable() = true;
00255        type_renderer->property_text_column() = 0;
00256        type_renderer->property_has_entry() = false;
00257        type_renderer->signal_edited().connect(sigc::mem_fun(*this, &Actions::on_type_edited));
00258        type_renderer->signal_editing_started().connect(sigc::mem_fun(*this, &Actions::on_something_editing_started));
00259        type_renderer->signal_editing_canceled().connect(sigc::mem_fun(*this, &Actions::on_something_editing_canceled));
00260 
00261        n = tv.append_column(_("Type"), *type_renderer);
00262        Gtk::TreeView::Column *col_type = tv.get_column(n-1);
00263        col_type->add_attribute(type_renderer->property_text(), cols.type);
00264        col_type->set_cell_data_func(*type_renderer, sigc::mem_fun(*this, &Actions::on_cell_data_type));
00265 
00266        CellRendererTextish *arg_renderer = Gtk::manage(new CellRendererTextish);
00267        n = tv.append_column(_("Details"), *arg_renderer);
00268        Gtk::TreeView::Column *col_arg = tv.get_column(n-1);
00269        col_arg->add_attribute(arg_renderer->property_text(), cols.arg);
00270        col_arg->set_cell_data_func(*arg_renderer, sigc::mem_fun(*this, &Actions::on_cell_data_arg));
00271        col_arg->set_resizable();
00272        arg_renderer->property_editable() = true;
00273        arg_renderer->signal_key_edited().connect(sigc::mem_fun(*this, &Actions::on_accel_edited));
00274        arg_renderer->signal_combo_edited().connect(sigc::mem_fun(*this, &Actions::on_combo_edited));
00275        arg_renderer->signal_edited().connect(sigc::mem_fun(*this, &Actions::on_text_edited));
00276        arg_renderer->signal_editing_started().connect(sigc::mem_fun(*this, &Actions::on_arg_editing_started));
00277        arg_renderer->items = Misc::types;
00278 
00279        update_action_list();
00280        tv.set_model(tm);
00281        tv.enable_model_drag_source();
00282        tv.enable_model_drag_dest();
00283 
00284        check_show_deleted->signal_toggled().connect(sigc::mem_fun(*this, &Actions::update_action_list));
00285        expander_apps->property_expanded().signal_changed().connect(sigc::mem_fun(*this, &Actions::on_apps_selection_changed));
00286        apps_view->get_selection()->signal_changed().connect(sigc::mem_fun(*this, &Actions::on_apps_selection_changed));
00287        apps_model = AppsStore::create(ca, this);
00288 
00289        load_app_list(apps_model->children(), actions.get_root());
00290        update_counts();
00291 
00292        apps_view->append_column_editable(_("Application"), ca.app);
00293        apps_view->get_column(0)->set_expand(true);
00294        apps_view->get_column(0)->set_cell_data_func(
00295                      *apps_view->get_column_cell_renderer(0), sigc::mem_fun(*this, &Actions::on_cell_data_apps));
00296        Gtk::CellRendererText *app_name_renderer =
00297               dynamic_cast<Gtk::CellRendererText *>(apps_view->get_column_cell_renderer(0));
00298        app_name_renderer->signal_edited().connect(sigc::mem_fun(*this, &Actions::on_group_name_edited));
00299        apps_view->append_column(_("Actions"), ca.count);
00300 
00301        apps_view->set_model(apps_model);
00302        apps_view->enable_model_drag_dest();
00303        apps_view->expand_all();
00304 }
00305 
00306 void Actions::load_app_list(const Gtk::TreeNodeChildren &ch, ActionListDiff *actions) {
00307        Gtk::TreeRow row = *(apps_model->append(ch));
00308        row[ca.app] = app_name_hr(actions->name);
00309        row[ca.actions] = actions;
00310        for (ActionListDiff::iterator i = actions->begin(); i != actions->end(); i++)
00311               load_app_list(row.children(), &(*i));
00312 }
00313 
00314 void Actions::on_cell_data_name(Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter) {
00315        bool bold = (*iter)[cols.name_bold];
00316        bool deactivated = (*iter)[cols.deactivated];
00317        Gtk::CellRendererText *renderer = dynamic_cast<Gtk::CellRendererText *>(cell);
00318        if (renderer)
00319               renderer->property_weight().set_value(bold ? 700 : 400);
00320        cell->property_sensitive().set_value(!deactivated);
00321 }
00322 
00323 void Actions::on_cell_data_type(Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter) {
00324        bool bold = (*iter)[cols.action_bold];
00325        bool deactivated = (*iter)[cols.deactivated];
00326        Gtk::CellRendererText *renderer = dynamic_cast<Gtk::CellRendererText *>(cell);
00327        if (renderer)
00328               renderer->property_weight().set_value(bold ? 700 : 400);
00329        cell->property_sensitive().set_value(!deactivated);
00330 }
00331 
00332 void Actions::on_cell_data_arg(Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter) {
00333        bool bold = (*iter)[cols.action_bold];
00334        bool deactivated = (*iter)[cols.deactivated];
00335        cell->property_sensitive().set_value(!deactivated);
00336        CellRendererTextish *renderer = dynamic_cast<CellRendererTextish *>(cell);
00337        if (!renderer)
00338               return;
00339        renderer->property_weight().set_value(bold ? 700 : 400);
00340 
00341        Glib::ustring str = (*iter)[cols.type];
00342        renderer->mode = all_types[from_name(str)].mode;
00343 }
00344 
00345 int Actions::compare_ids(const Gtk::TreeModel::iterator &a, const Gtk::TreeModel::iterator &b) {
00346        Unique *x = (*a)[cols.id];
00347        Unique *y = (*b)[cols.id];
00348        if (x->level == y->level) {
00349               if (x->i == y->i)
00350                      return 0;
00351               if (x->i < y->i)
00352                      return -1;
00353               else
00354                      return 1;
00355        }
00356        if (x->level < y->level)
00357               return -1;
00358        else
00359               return 1;
00360 }
00361 
00362 bool Actions::AppsStore::row_drop_possible_vfunc(const Gtk::TreeModel::Path &dest,
00363               const Gtk::SelectionData &selection) const {
00364        static bool expecting = false;
00365        static Gtk::TreePath expected;
00366        if (expecting && expected != dest)
00367               expecting = false;
00368        if (!expecting) {
00369               if (dest.get_depth() < 2 || dest.back() != 0)
00370                      return false;
00371               expected = dest;
00372               expected.up();
00373               expecting = true;
00374               return false;
00375        }
00376        expecting = false;
00377        Gtk::TreePath src;
00378        Glib::RefPtr<TreeModel> model;
00379        if (!Gtk::TreeModel::Path::get_from_selection_data(selection, model, src))
00380               return false;
00381        if (model != parent->tm)
00382               return false;
00383        Gtk::TreeIter dest_iter = parent->apps_model->get_iter(dest);
00384        ActionListDiff *actions = dest_iter ? (*dest_iter)[parent->ca.actions] : (ActionListDiff *)NULL;
00385        return actions && actions != parent->action_list;
00386 }
00387 
00388 bool Actions::AppsStore::drag_data_received_vfunc(const Gtk::TreeModel::Path &dest, const Gtk::SelectionData &selection) {
00389        Gtk::TreePath src;
00390        Glib::RefPtr<TreeModel> model;
00391        if (!Gtk::TreeModel::Path::get_from_selection_data(selection, model, src))
00392               return false;
00393        if (model != parent->tm)
00394               return false;
00395        Unique *src_id = (*parent->tm->get_iter(src))[parent->cols.id];
00396        Gtk::TreeIter dest_iter = parent->apps_model->get_iter(dest);
00397        ActionListDiff *actions = dest_iter ? (*dest_iter)[parent->ca.actions] : (ActionListDiff *)NULL;
00398        if (!actions || actions == parent->action_list)
00399               return false;
00400        Glib::RefPtr<Gtk::TreeSelection> sel = parent->tv.get_selection();
00401        if (sel->count_selected_rows() <= 1) {
00402               RStrokeInfo si = parent->action_list->get_info(src_id);
00403               parent->action_list->remove(src_id);
00404               actions->add(*si);
00405        } else {
00406               std::vector<Gtk::TreePath> paths = sel->get_selected_rows();
00407               for (std::vector<Gtk::TreePath>::iterator i = paths.begin(); i != paths.end(); ++i) {
00408                      Unique *id = (*parent->tm->get_iter(*i))[parent->cols.id];
00409                      RStrokeInfo si = parent->action_list->get_info(id);
00410                      parent->action_list->remove(id);
00411                      actions->add(*si);
00412               }
00413        }
00414        parent->update_action_list();
00415        update_actions();
00416        return true;
00417 }
00418 
00419 bool Actions::Store::row_draggable_vfunc(const Gtk::TreeModel::Path &path) const {
00420        int col;
00421        Gtk::SortType sort;
00422        parent->tm->get_sort_column_id(col, sort);
00423        if (col != Gtk::TreeSortable::DEFAULT_SORT_COLUMN_ID)
00424               return false;
00425        if (sort != Gtk::SORT_ASCENDING)
00426               return false;
00427        Glib::RefPtr<Gtk::TreeSelection> sel = parent->tv.get_selection();
00428        if (sel->count_selected_rows() <= 1) {
00429               Unique *id = (*parent->tm->get_iter(path))[parent->cols.id];
00430               return id->level == parent->action_list->level;
00431        } else {
00432               std::vector<Gtk::TreePath> paths = sel->get_selected_rows();
00433               for (std::vector<Gtk::TreePath>::iterator i = paths.begin(); i != paths.end(); ++i) {
00434                      Unique *id = (*parent->tm->get_iter(*i))[parent->cols.id];
00435                      if (id->level != parent->action_list->level)
00436                             return false;
00437               }
00438               return true;
00439        }
00440 }
00441 
00442 bool Actions::Store::row_drop_possible_vfunc(const Gtk::TreeModel::Path &dest, const Gtk::SelectionData &selection) const {
00443        static bool ignore_next = false;
00444        if (dest.get_depth() > 1) {
00445               ignore_next = true;
00446               return false;
00447        }
00448        if (ignore_next) {
00449               ignore_next = false;
00450               return false;
00451        }
00452        Gtk::TreePath src;
00453        Glib::RefPtr<TreeModel> model;
00454        if (!Gtk::TreeModel::Path::get_from_selection_data(selection, model, src))
00455               return false;
00456        if (model != parent->tm)
00457               return false;
00458        Unique *src_id = (*parent->tm->get_iter(src))[parent->cols.id];
00459        Gtk::TreeIter dest_iter = parent->tm->get_iter(dest);
00460        Unique *dest_id = dest_iter ? (*dest_iter)[parent->cols.id] : (Unique *)0;
00461        if (dest_id && src_id->level != dest_id->level)
00462               return false;
00463        return true;
00464 }
00465 
00466 bool Actions::Store::drag_data_received_vfunc(const Gtk::TreeModel::Path &dest, const Gtk::SelectionData &selection) {
00467        Gtk::TreePath src;
00468        Glib::RefPtr<TreeModel> model;
00469        if (!Gtk::TreeModel::Path::get_from_selection_data(selection, model, src))
00470               return false;
00471        if (model != parent->tm)
00472               return false;
00473        Unique *src_id = (*parent->tm->get_iter(src))[parent->cols.id];
00474        Gtk::TreeIter dest_iter = parent->tm->get_iter(dest);
00475        Unique *dest_id = dest_iter ? (*dest_iter)[parent->cols.id] : (Unique *)0;
00476        if (dest_id && src_id->level != dest_id->level)
00477               return false;
00478        Glib::RefPtr<Gtk::TreeSelection> sel = parent->tv.get_selection();
00479        if (sel->count_selected_rows() <= 1) {
00480               if (parent->action_list->move(src_id, dest_id)) {
00481                      (*parent->tm->get_iter(src))[parent->cols.id] = src_id;
00482                      update_actions();
00483               }
00484        } else {
00485               std::vector<Gtk::TreePath> paths = sel->get_selected_rows();
00486               bool updated = false;
00487               for (std::vector<Gtk::TreePath>::iterator i = paths.begin(); i != paths.end(); ++i) {
00488                      Unique *id = (*parent->tm->get_iter(*i))[parent->cols.id];
00489                      if (parent->action_list->move(id, dest_id))
00490                             updated = true;
00491               }
00492               if (updated) {
00493                      parent->update_action_list();
00494                      update_actions();
00495               }
00496        }
00497        return false;
00498 }
00499 
00500 void Actions::on_type_edited(const Glib::ustring &path, const Glib::ustring &new_text) {
00501        tv.grab_focus();
00502        Gtk::TreeRow row(*tm->get_iter(path));
00503        Type new_type = from_name(new_text);
00504        Type old_type = from_name(row[cols.type]);
00505        bool edit = true;
00506        if (old_type == new_type) {
00507               edit = editing_new;
00508        } else {
00509               row[cols.type] = new_text;
00510               RAction new_action;
00511               if (new_type == COMMAND) {
00512                      Glib::ustring cmd_save = row[cols.cmd_save];
00513                      if (cmd_save != "")
00514                             edit = false;
00515                      new_action = Command::create(cmd_save);
00516               }
00517               if (old_type == COMMAND) {
00518                      row[cols.cmd_save] = (Glib::ustring)row[cols.arg];
00519               }
00520               if (new_type == KEY) {
00521                      new_action = SendKey::create(0, (Gdk::ModifierType)0);
00522                      edit = true;
00523               }
00524               if (new_type == TEXT) {
00525                      new_action = SendText::create(Glib::ustring());
00526                      edit = true;
00527               }
00528               if (new_type == SCROLL) {
00529                      new_action = Scroll::create((Gdk::ModifierType)0);
00530                      edit = false;
00531               }
00532               if (new_type == IGNORE) {
00533                      new_action = Ignore::create((Gdk::ModifierType)0);
00534                      edit = false;
00535               }
00536               if (new_type == BUTTON) {
00537                      new_action = Button::create((Gdk::ModifierType)0, 0);
00538                      edit = true;
00539               }
00540               if (new_type == MISC) {
00541                      new_action = Misc::create(Misc::NONE);
00542                      edit = true;
00543               }
00544               action_list->set_action(row[cols.id], new_action);
00545               update_row(row);
00546               update_actions();
00547        }
00548        editing_new = false;
00549        focus(row[cols.id], 3, edit);
00550 }
00551 
00552 void Actions::on_button_delete() {
00553        int n = tv.get_selection()->count_selected_rows();
00554 
00555        Glib::ustring str;
00556        if (n == 1)
00557               str = Glib::ustring::compose(_("Action \"%1\" is about to be deleted."), get_selected_row()[cols.name]);
00558        else
00559               str = Glib::ustring::compose(ngettext("One action is about to be deleted.",
00560                                    "%1 actions are about to be deleted", n), n);
00561 
00562        Gtk::MessageDialog *dialog;
00563        widgets->get_widget("dialog_delete", dialog);
00564        dialog->set_message(ngettext("Delete an Action", "Delete Actions", n));
00565        dialog->set_secondary_text(str);
00566        Gtk::Button *del;
00567        widgets->get_widget("button_delete_delete", del);
00568 
00569        dialog->show();
00570        del->grab_focus();
00571        bool ok = dialog->run() == 1;
00572        dialog->hide();
00573        if (!ok)
00574               return;
00575 
00576        std::vector<Gtk::TreePath> paths = tv.get_selection()->get_selected_rows();
00577        for (std::vector<Gtk::TreePath>::iterator i = paths.begin(); i != paths.end(); ++i) {
00578               Gtk::TreeRow row(*tm->get_iter(*i));
00579               action_list->remove(row[cols.id]);
00580        }
00581        update_action_list();
00582        update_actions();
00583        update_counts();
00584 }
00585 
00586 void Actions::on_cell_data_apps(Gtk::CellRenderer* cell, const Gtk::TreeModel::iterator& iter) {
00587        ActionListDiff *as = (*iter)[ca.actions];
00588        Gtk::CellRendererText *renderer = dynamic_cast<Gtk::CellRendererText *>(cell);
00589        if (renderer)
00590               renderer->property_editable().set_value(actions.get_root() != as && !as->app);
00591 }
00592 
00593 bool Actions::select_app(const Gtk::TreeModel::Path& path, const Gtk::TreeModel::iterator& iter, ActionListDiff *actions) {
00594        if ((*iter)[ca.actions] == actions) {
00595               apps_view->expand_to_path(path);
00596               apps_view->set_cursor(path);
00597               return true;
00598        }
00599        return false;
00600 }
00601 
00602 void Actions::on_add_app() {
00603        std::string name = select_window();
00604        if (actions.apps.count(name)) {
00605               apps_model->foreach(sigc::bind(sigc::mem_fun(*this, &Actions::select_app), actions.apps[name]));
00606               return;
00607        }
00608        ActionListDiff *parent = action_list->app ? actions.get_root() : action_list;
00609        ActionListDiff *child = parent->add_child(name, true);
00610        const Gtk::TreeNodeChildren &ch = parent == actions.get_root() ?
00611               apps_model->children().begin()->children() :
00612               apps_view->get_selection()->get_selected()->children();
00613        Gtk::TreeRow row = *(apps_model->append(ch));
00614        row[ca.app] = app_name_hr(name);
00615        row[ca.actions] = child;
00616        actions.apps[name] = child;
00617        Gtk::TreePath path = apps_model->get_path(row);
00618        apps_view->expand_to_path(path);
00619        apps_view->set_cursor(path);
00620        update_actions();
00621 }
00622 
00623 void Actions::on_remove_app() {
00624        if (action_list == actions.get_root())
00625               return;
00626        int size = action_list->size_rec();
00627        if (size) {
00628               Gtk::MessageDialog *dialog;
00629               widgets->get_widget("dialog_delete", dialog);
00630               Glib::ustring str = Glib::ustring::compose(_("%1 \"%2\" (containing %3 %4) is about to be deleted."),
00631                             action_list->app ? _("The application") : _("The group"),
00632                             action_list->name,
00633                             size,
00634                             ngettext("action", "actions", size));
00635               dialog->set_message(action_list->app ? _("Delete an Application") : _("Delete an Application Group"));
00636               dialog->set_secondary_text(str);
00637               Gtk::Button *del;
00638               widgets->get_widget("button_delete_delete", del);
00639               dialog->show();
00640               del->grab_focus();
00641               bool ok = dialog->run() == 1;
00642               dialog->hide();
00643               if (!ok)
00644                      return;
00645        }
00646        if (!action_list->remove())
00647               return;
00648        apps_model->erase(*apps_view->get_selection()->get_selected());
00649        update_actions();
00650 }
00651 
00652 void Actions::on_reset_actions() {
00653        std::vector<Gtk::TreePath> paths = tv.get_selection()->get_selected_rows();
00654        for (std::vector<Gtk::TreePath>::iterator i = paths.begin(); i != paths.end(); ++i) {
00655               Gtk::TreeRow row(*tm->get_iter(*i));
00656               action_list->reset(row[cols.id]);
00657        }
00658        update_action_list();
00659        on_selection_changed();
00660        update_actions();
00661 }
00662 
00663 void Actions::on_add_group() {
00664        ActionListDiff *parent = action_list->app ? actions.get_root() : action_list;
00665        Glib::ustring name = _("Group");
00666        ActionListDiff *child = parent->add_child(name, false);
00667        const Gtk::TreeNodeChildren &ch = parent == actions.get_root() ?
00668               apps_model->children().begin()->children() :
00669               apps_view->get_selection()->get_selected()->children();
00670        Gtk::TreeRow row = *(apps_model->append(ch));
00671        row[ca.app] = name;
00672        row[ca.actions] = child;
00673        actions.apps[name] = child;
00674        Gtk::TreePath path = apps_model->get_path(row);
00675        apps_view->expand_to_path(path);
00676        apps_view->set_cursor(path, *apps_view->get_column(0), true);
00677        update_actions();
00678 }
00679 
00680 void Actions::on_group_name_edited(const Glib::ustring& path, const Glib::ustring& new_text) {
00681        Gtk::TreeRow row(*apps_model->get_iter(path));
00682        row[ca.app] = new_text;
00683        ActionListDiff *as = row[ca.actions];
00684        as->name = new_text;
00685        update_actions();
00686 }
00687 
00688 void Actions::on_apps_selection_changed() {
00689        ActionListDiff *new_action_list = actions.get_root();
00690        if (expander_apps->property_expanded().get_value()) {
00691               if (apps_view->get_selection()->count_selected_rows()) {
00692                      Gtk::TreeIter i = apps_view->get_selection()->get_selected();
00693                      new_action_list = (*i)[ca.actions];
00694               }
00695               button_remove_app->set_sensitive(new_action_list != actions.get_root());
00696        }
00697        if (action_list != new_action_list) {
00698               action_list = new_action_list;
00699               update_action_list();
00700               on_selection_changed();
00701        }
00702 }
00703 
00704 bool Actions::count_app_actions(const Gtk::TreeIter &i) {
00705        (*i)[ca.count] = ((ActionListDiff*)(*i)[ca.actions])->count_actions();
00706        return false;
00707 }
00708 
00709 void Actions::update_counts() {
00710        apps_model->foreach_iter(sigc::mem_fun(*this, &Actions::count_app_actions));
00711 }
00712 
00713 void Actions::update_action_list() {
00714        boost::shared_ptr<std::set<Unique *> > ids = action_list->get_ids(check_show_deleted->get_active());
00715        const Gtk::TreeNodeChildren &ch = tm->children();
00716 
00717        std::list<Gtk::TreeRowReference> refs;
00718        for (Gtk::TreeIter i = ch.begin(); i != ch.end(); i++) {
00719               Gtk::TreeRowReference ref(tm, Gtk::TreePath(*i));
00720               refs.push_back(ref);
00721        }
00722 
00723        for (std::list<Gtk::TreeRowReference>::iterator ref = refs.begin(); ref != refs.end(); ref++) {
00724               Gtk::TreeIter i = tm->get_iter(ref->get_path());
00725               std::set<Unique *>::iterator id = ids->find((*i)[cols.id]);
00726               if (id == ids->end()) {
00727                      tm->erase(i);
00728               } else {
00729                      ids->erase(id);
00730                      update_row(*i);
00731               }
00732        }
00733        for (std::set<Unique *>::const_iterator i = ids->begin(); i != ids->end(); i++) {
00734               Gtk::TreeRow row = *tm->append();
00735               row[cols.id] = *i;
00736               update_row(row);
00737        }
00738 }
00739 
00740 void Actions::update_row(const Gtk::TreeRow &row) {
00741        bool deleted, stroke, name, action;
00742        RStrokeInfo si = action_list->get_info(row[cols.id], &deleted, &stroke, &name, &action);
00743        row[cols.stroke] = !si->strokes.empty() && *si->strokes.begin() ? 
00744               (*si->strokes.begin())->draw(STROKE_SIZE, stroke ? 4.0 : 2.0) : Stroke::drawEmpty(STROKE_SIZE);
00745        row[cols.name] = si->name;
00746        row[cols.type] = si->action ? type_info_to_name(&typeid(*si->action)) : "";
00747        row[cols.arg]  = si->action ? si->action->get_label() : "";
00748        row[cols.deactivated] = deleted;
00749        row[cols.name_bold] = name;
00750        row[cols.action_bold] = action;
00751 }
00752 
00753 extern boost::shared_ptr<sigc::slot<void, RStroke> > stroke_action;
00754 Source<ActionListDiff *> stroke_app(NULL);
00755 
00756 class Actions::OnStroke {
00757        Actions *parent;
00758        Gtk::Dialog *dialog;
00759        Gtk::TreeRow &row;
00760        RStroke stroke;
00761        bool run() {
00762               if (stroke->button == 0 && stroke->trivial()) {
00763                      grabber->queue_suspend();
00764                      Glib::ustring msg = Glib::ustring::compose(
00765                                    _("You are about to bind an action to a single click.  "
00766                                           "This might make it difficult to use Button %1 in the future.  "
00767                                           "Are you sure you want to continue?"),
00768                                    stroke->button ? stroke->button : prefs.button.ref().button);
00769                      Gtk::MessageDialog md(*dialog, msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_YES_NO, true);
00770                      bool abort = md.run() != Gtk::RESPONSE_YES;
00771                      grabber->queue_resume();
00772                      if (abort)
00773                             return false;
00774               }
00775               StrokeSet strokes;
00776               strokes.insert(stroke);
00777               parent->action_list->set_strokes(row[parent->cols.id], strokes);
00778               parent->update_row(row);
00779               parent->on_selection_changed();
00780               update_actions();
00781               dialog->response(0);
00782               return false;
00783        }
00784 public:
00785        OnStroke(Actions *parent_, Gtk::Dialog *dialog_, Gtk::TreeRow &row_) : parent(parent_), dialog(dialog_), row(row_) {}
00786        void delayed_run(RStroke stroke_) {
00787               stroke = stroke_;
00788               Glib::signal_idle().connect(sigc::mem_fun(*this, &OnStroke::run));
00789               stroke_action.reset();
00790               stroke_app.set(NULL);
00791        }
00792 };
00793 
00794 void Actions::on_row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column) {
00795        Gtk::TreeRow row(*tm->get_iter(path));
00796        Gtk::MessageDialog *dialog;
00797        widgets->get_widget("dialog_record", dialog);
00798        dialog->set_message(_("Record a New Stroke"));
00799        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]));
00800 
00801        static Gtk::Button *del = 0, *cancel = 0;
00802        if (!del) {
00803               widgets->get_widget("button_record_delete", del);
00804               del->signal_enter().connect(sigc::mem_fun(*grabber, &Grabber::queue_suspend));
00805               del->signal_leave().connect(sigc::mem_fun(*grabber, &Grabber::queue_resume));
00806        }
00807        if (!cancel) {
00808               widgets->get_widget("button_record_cancel", cancel);
00809               cancel->signal_enter().connect(sigc::mem_fun(*grabber, &Grabber::queue_suspend));
00810               cancel->signal_leave().connect(sigc::mem_fun(*grabber, &Grabber::queue_resume));
00811        }
00812        RStrokeInfo si = action_list->get_info(row[cols.id]);
00813        if (si)
00814               del->set_sensitive(si->strokes.size());
00815 
00816        OnStroke ps(this, dialog, row);
00817        stroke_action.reset(new sigc::slot<void, RStroke>(sigc::mem_fun(ps, &OnStroke::delayed_run)));
00818        stroke_app.set(action_list);
00819 
00820        dialog->show();
00821        cancel->grab_focus();
00822        int response = dialog->run();
00823        dialog->hide();
00824        stroke_action.reset();
00825        stroke_app.set(NULL);
00826        if (response != 1)
00827               return;
00828 
00829        action_list->set_strokes(row[cols.id], StrokeSet());
00830        update_row(row);
00831        on_selection_changed();
00832        update_actions();
00833 }
00834 
00835 void Actions::on_button_record() {
00836        Gtk::TreeModel::Path path;
00837        Gtk::TreeViewColumn *col;
00838        tv.get_cursor(path, col);
00839        on_row_activated(path, col);
00840 }
00841 
00842 void Actions::on_cursor_changed() {
00843        Gtk::TreeModel::Path path;
00844        Gtk::TreeViewColumn *col;
00845        tv.get_cursor(path, col);
00846        Gtk::TreeRow row(*tm->get_iter(path));
00847 }
00848 
00849 Gtk::TreeRow Actions::get_selected_row() {
00850        std::vector<Gtk::TreePath> paths = tv.get_selection()->get_selected_rows();
00851        return Gtk::TreeRow(*tm->get_iter(*paths.begin()));
00852 }
00853 
00854 void Actions::on_selection_changed() {
00855        int n = tv.get_selection()->count_selected_rows();
00856        button_record->set_sensitive(n == 1);
00857        button_delete->set_sensitive(n >= 1);
00858        bool resettable = false;
00859        if (n) {
00860               std::vector<Gtk::TreePath> paths = tv.get_selection()->get_selected_rows();
00861               for (std::vector<Gtk::TreePath>::iterator i = paths.begin(); i != paths.end(); ++i) {
00862                      Gtk::TreeRow row(*tm->get_iter(*i));
00863                      if (action_list->resettable(row[cols.id])) {
00864                             resettable = true;
00865                             break;
00866                      }
00867               }
00868        }
00869        button_reset_actions->set_sensitive(resettable);
00870 }
00871 
00872 void Actions::on_button_new() {
00873        editing_new = true;
00874        Unique *before = 0;
00875        if (tv.get_selection()->count_selected_rows()) {
00876               std::vector<Gtk::TreePath> paths = tv.get_selection()->get_selected_rows();
00877               Gtk::TreeIter i = tm->get_iter(paths[paths.size()-1]);
00878               i++;
00879               if (i != tm->children().end())
00880                      before = (*i)[cols.id];
00881        }
00882 
00883        Gtk::TreeModel::Row row = *(tm->append());
00884        StrokeInfo si;
00885        si.action = Command::create("");
00886        Unique *id = action_list->add(si, before);
00887        row[cols.id] = id;
00888        std::string name;
00889        if (action_list != actions.get_root())
00890               name = action_list->name + " ";
00891        name += Glib::ustring::compose(_("Gesture %1"), action_list->order_size());
00892        action_list->set_name(id, name);
00893 
00894        update_row(row);
00895        focus(id, 1, true);
00896        update_actions();
00897        update_counts();
00898 }
00899 
00900 bool Actions::do_focus(Unique *id, Gtk::TreeViewColumn *col, bool edit) {
00901        if (!editing) {
00902               Gtk::TreeModel::Children chs = tm->children();
00903               for (Gtk::TreeIter i = chs.begin(); i != chs.end(); ++i)
00904                      if ((*i)[cols.id] == id) {
00905                             tv.set_cursor(Gtk::TreePath(*i), *col, edit);
00906                      }
00907        }
00908        return false;
00909 }
00910 
00911 void Actions::focus(Unique *id, int col, bool edit) {
00912        editing = false;
00913        Glib::signal_idle().connect(sigc::bind(sigc::mem_fun(*this, &Actions::do_focus), id, tv.get_column(col), edit));
00914 }
00915 
00916 void Actions::on_name_edited(const Glib::ustring& path, const Glib::ustring& new_text) {
00917        Gtk::TreeRow row(*tm->get_iter(path));
00918        action_list->set_name(row[cols.id], new_text);
00919        update_actions();
00920        update_row(row);
00921        focus(row[cols.id], 2, editing_new);
00922 }
00923 
00924 void Actions::on_text_edited(const Glib::ustring& path, const Glib::ustring& new_text) {
00925        Gtk::TreeRow row(*tm->get_iter(path));
00926        Type type = from_name(row[cols.type]);
00927        if (type == COMMAND) {
00928               action_list->set_action(row[cols.id], Command::create(new_text));
00929        } else if (type == TEXT) {
00930               action_list->set_action(row[cols.id], SendText::create(new_text));
00931        } else return;
00932        update_row(row);
00933        update_actions();
00934 }
00935 
00936 void Actions::on_accel_edited(const Glib::ustring& path_string, guint accel_key, Gdk::ModifierType accel_mods, guint hardware_keycode) {
00937        Gtk::TreeRow row(*tm->get_iter(path_string));
00938        Type type = from_name(row[cols.type]);
00939        if (type == KEY) {
00940               RSendKey send_key = SendKey::create(accel_key, accel_mods);
00941               Glib::ustring str = send_key->get_label();
00942               if (row[cols.arg] == str)
00943                      return;
00944               action_list->set_action(row[cols.id], boost::static_pointer_cast<Action>(send_key));
00945        } else if (type == SCROLL) {
00946               RScroll scroll = Scroll::create(accel_mods);
00947               Glib::ustring str = scroll->get_label();
00948               if (row[cols.arg] == str)
00949                      return;
00950               action_list->set_action(row[cols.id], boost::static_pointer_cast<Action>(scroll));
00951        } else if (type == IGNORE) {
00952               RIgnore ignore = Ignore::create(accel_mods);
00953               Glib::ustring str = ignore->get_label();
00954               if (row[cols.arg] == str)
00955                      return;
00956               action_list->set_action(row[cols.id], boost::static_pointer_cast<Action>(ignore));
00957        } else return;
00958        update_row(row);
00959        update_actions();
00960 }
00961 
00962 void Actions::on_combo_edited(const Glib::ustring& path_string, guint item) {
00963        if (item < 0)
00964               item = 0;
00965        RMisc misc = Misc::create((Misc::Type)item);
00966        Glib::ustring str = misc->get_label();
00967        Gtk::TreeRow row(*tm->get_iter(path_string));
00968        if (row[cols.arg] == str)
00969               return;
00970        action_list->set_action(row[cols.id], boost::static_pointer_cast<Action>(misc));
00971        update_row(row);
00972        update_actions();
00973 }
00974 
00975 void Actions::on_something_editing_canceled() {
00976        editing_new = false;
00977 }
00978 
00979 void Actions::on_something_editing_started(Gtk::CellEditable* editable, const Glib::ustring& path) {
00980        editing = true;
00981 }
00982 
00983 void Actions::on_arg_editing_started(Gtk::CellEditable* editable, const Glib::ustring& path) {
00984        tv.grab_focus();
00985        Gtk::TreeRow row(*tm->get_iter(path));
00986        if (from_name(row[cols.type]) != BUTTON)
00987               return;
00988        ButtonInfo bi;
00989        RButton bt = boost::static_pointer_cast<Button>(action_list->get_info(row[cols.id])->action);
00990        if (bt)
00991               bi = bt->get_button_info();
00992        SelectButton sb(bi, false, false);
00993        if (!sb.run())
00994               return;
00995        bt = boost::static_pointer_cast<Button>(Button::create(Gdk::ModifierType(sb.event.state), sb.event.button));
00996        action_list->set_action(row[cols.id], bt);
00997        update_row(row);
00998        update_actions();
00999 }
01000 
01001 const Glib::ustring SendKey::get_label() const {
01002        return Gtk::AccelGroup::get_label(key, mods);
01003 }
01004 
01005 const Glib::ustring ModAction::get_label() const {
01006        if (!mods)
01007               return _("No Modifiers");
01008        Glib::ustring label = Gtk::AccelGroup::get_label(0, mods);
01009        return label.substr(0,label.size()-1);
01010 }
01011 
01012 Glib::ustring ButtonInfo::get_button_text() const {
01013        Glib::ustring str;
01014        if (instant)
01015               str += _("(Instantly) ");
01016        if (click_hold)
01017               str += _("(Click & Hold) ");
01018        if (state == AnyModifier)
01019               str += Glib::ustring() + "(" + _("Any Modifier") + " +) ";
01020        else
01021               str += Gtk::AccelGroup::get_label(0, (Gdk::ModifierType)state);
01022        return str + Glib::ustring::compose(_("Button %1"), button);
01023 }
01024 
01025 const Glib::ustring Scroll::get_label() const {
01026        if (mods)
01027               return ModAction::get_label() + _(" + Scroll");
01028        else
01029               return _("Scroll");
01030 }
01031 
01032 const Glib::ustring Ignore::get_label() const {
01033        if (mods)
01034               return ModAction::get_label();
01035        else
01036               return _("Ignore");
01037 }