/[svn]/gigedit/trunk/src/gigedit/MacroEditor.cpp
ViewVC logotype

Annotation of /gigedit/trunk/src/gigedit/MacroEditor.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3170 - (hide annotations) (download)
Wed May 10 21:21:14 2017 UTC (6 years, 11 months ago) by schoenebeck
File size: 18508 byte(s)
* Macro Editor: Fixed deleting rows when delete button was hit
  without Ctrl being pressed.
* Macro Editor: Show combo box with textual options for enum types.
* Bumped version (1.0.0.svn42).

1 schoenebeck 3151 /*
2     Copyright (c) MMXVII Christian Schoenebeck
3    
4     This file is part of "gigedit" and released under the terms of the
5     GNU General Public License version 2.
6     */
7    
8     #include "MacroEditor.h"
9     #include "global.h"
10     #include <assert.h>
11    
12     MacroEditor::MacroEditor() :
13     m_macroOriginal(NULL),
14     m_statusLabel("", Gtk::ALIGN_START),
15 schoenebeck 3155 m_deleteButton(Glib::ustring(_("Delete")) + " " + UNICODE_PRIMARY_KEY_SYMBOL + UNICODE_ERASE_KEY_SYMBOL),
16     m_inverseDeleteButton(Glib::ustring(_("Inverse Delete")) + " " + UNICODE_ALT_KEY_SYMBOL + UNICODE_ERASE_KEY_SYMBOL),
17 schoenebeck 3151 m_applyButton(_("_Apply"), true),
18 schoenebeck 3155 m_cancelButton(_("_Cancel"), true),
19 schoenebeck 3170 m_altKeyDown(false),
20     m_primaryKeyDown(false)
21 schoenebeck 3151 {
22     add(m_vbox);
23    
24     set_default_size(800, 600);
25    
26 schoenebeck 3162 //FIXME: Commented out since Gtk::Label is a disaster when it comes to multi line content.
27     /*m_labelIntro.set_text(
28     _("A macro is a list of parameters and corresponding values which "
29     "should be applied to the instrument editor when the macro is "
30     "triggered by the user. A macro is triggered either by selecting "
31     "the macro from the \"Macro\" menu, or by hitting the macro's "
32     "respective keyboard accelerator (F1 to F12).")
33     );
34     m_labelIntro.set_line_wrap();
35     m_vbox.pack_start(m_labelIntro, Gtk::PACK_SHRINK);*/
36    
37 schoenebeck 3151 // create Macro treeview (including its data model)
38     m_treeStoreMacro = MacroTreeStore::create(m_treeModelMacro);
39     m_treeViewMacro.set_model(m_treeStoreMacro);
40     m_treeViewMacro.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE);
41     //m_treeViewMacro.set_tooltip_text(_(""));
42     m_treeViewMacro.append_column(_("Key"), m_treeModelMacro.m_col_name);
43     m_treeViewMacro.append_column(_("Type"), m_treeModelMacro.m_col_type);
44 schoenebeck 3170 //m_treeViewMacro.append_column_editable(_("Value"), m_treeModelMacro.m_col_value);
45     //m_treeViewMacro.append_column(_("Value"), m_valueCellRenderer);
46     Gtk::TreeViewColumn* valueColumn = new Gtk::TreeViewColumn(_("Value"));
47     valueColumn->pack_start(m_valueCellRenderer);
48     m_treeViewMacro.append_column(*valueColumn);
49     // m_valueCellRenderer.property_model() = m_comboBoxModel;
50     // m_valueCellRenderer.property_text_column() = 0;
51     //m_valueCellRenderer.property_editable() = true;
52 schoenebeck 3154 {
53 schoenebeck 3170 Gtk::TreeView::Column* column = valueColumn;// m_treeViewMacro.get_column(2);
54     //column->set_renderer(m_valueCellRenderer, m_treeModelMacro.m_col_value);
55     column->add_attribute(m_valueCellRenderer.property_text(),
56     m_treeModelMacro.m_col_value);
57     //column->add_attribute(m_valueCellRenderer.property_has_entry(),
58     // m_treeModelMacro.m_col_allowTextEntry);
59     column->add_attribute(m_valueCellRenderer.property_editable(),
60     m_treeModelMacro.m_col_editable);
61     column->add_attribute(m_valueCellRenderer.property_model(),
62     m_treeModelMacro.m_col_options);
63     }
64     m_valueCellRenderer.property_text_column() = 0;
65     m_valueCellRenderer.signal_edited().connect(
66     sigc::mem_fun(*this, &MacroEditor::onValueCellEdited)
67     );
68    
69     {
70 schoenebeck 3154 Gtk::TreeViewColumn* column = m_treeViewMacro.get_column(1);
71     Gtk::CellRendererText* cellrenderer =
72     dynamic_cast<Gtk::CellRendererText*>(column->get_first_cell());
73     cellrenderer->property_foreground().set_value("#bababa");
74     }
75 schoenebeck 3151 m_treeViewMacro.set_headers_visible(true);
76 schoenebeck 3154 m_treeViewMacro.get_selection()->signal_changed().connect(
77     sigc::mem_fun(*this, &MacroEditor::onTreeViewSelectionChanged)
78     );
79     m_treeViewMacro.signal_key_release_event().connect_notify(
80     sigc::mem_fun(*this, &MacroEditor::onMacroTreeViewKeyRelease)
81     );
82     m_treeStoreMacro->signal_row_changed().connect(
83     sigc::mem_fun(*this, &MacroEditor::onMacroTreeViewRowValueChanged)
84     );
85     m_ignoreTreeViewValueChange = false;
86 schoenebeck 3151
87     m_scrolledWindow.add(m_treeViewMacro);
88     m_scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
89     m_vbox.pack_start(m_scrolledWindow);
90    
91 schoenebeck 3154 m_buttonBoxL.set_layout(Gtk::BUTTONBOX_START);
92     m_buttonBoxL.pack_start(m_deleteButton);
93     m_buttonBoxL.pack_start(m_inverseDeleteButton);
94     m_deleteButton.set_sensitive(false);
95     m_inverseDeleteButton.set_sensitive(false);
96    
97 schoenebeck 3151 m_buttonBox.set_layout(Gtk::BUTTONBOX_END);
98     m_buttonBox.pack_start(m_applyButton);
99     m_buttonBox.pack_start(m_cancelButton);
100     m_applyButton.set_can_default();
101     m_applyButton.set_sensitive(false);
102     m_applyButton.grab_focus();
103    
104     #if GTKMM_MAJOR_VERSION >= 3
105     m_statusLabel.set_margin_left(6);
106     m_statusLabel.set_margin_right(6);
107     #else
108     m_statusHBox.set_spacing(6);
109     #endif
110    
111     m_statusHBox.pack_start(m_statusLabel);
112     m_statusHBox.show_all_children();
113    
114 schoenebeck 3154 m_footerHBox.pack_start(m_buttonBoxL, Gtk::PACK_SHRINK);
115 schoenebeck 3151 m_footerHBox.pack_start(m_statusHBox);
116     m_footerHBox.pack_start(m_buttonBox, Gtk::PACK_SHRINK);
117    
118     m_vbox.pack_start(m_footerHBox, Gtk::PACK_SHRINK);
119    
120     m_applyButton.signal_clicked().connect(
121     sigc::mem_fun(*this, &MacroEditor::onButtonApply)
122     );
123    
124     m_cancelButton.signal_clicked().connect(
125     sigc::mem_fun(*this, &MacroEditor::onButtonCancel)
126     );
127    
128 schoenebeck 3154 m_deleteButton.signal_clicked().connect(
129     sigc::mem_fun(*this, &MacroEditor::deleteSelectedRows)
130     );
131    
132     m_inverseDeleteButton.signal_clicked().connect(
133     sigc::mem_fun(*this, &MacroEditor::inverseDeleteSelectedRows)
134     );
135    
136 schoenebeck 3151 signal_hide().connect(
137     sigc::mem_fun(*this, &MacroEditor::onWindowHide)
138     );
139    
140     signal_delete_event().connect(
141     sigc::mem_fun(*this, &MacroEditor::onWindowDelete)
142     );
143    
144 schoenebeck 3155 signal_key_press_event().connect(
145     sigc::mem_fun(*this, &MacroEditor::onKeyPressed)
146     );
147     signal_key_release_event().connect(
148     sigc::mem_fun(*this, &MacroEditor::onKeyReleased)
149     );
150    
151 schoenebeck 3151 show_all_children();
152     updateStatus();
153     }
154    
155     MacroEditor::~MacroEditor() {
156     printf("MacroEditor destruct\n");
157     }
158    
159 schoenebeck 3162 void MacroEditor::setMacro(Serialization::Archive* macro, bool isClipboard) {
160 schoenebeck 3151 m_macroOriginal = macro;
161     if (!macro) {
162     set_title(_("No Macro"));
163     return;
164     }
165    
166 schoenebeck 3162 if (isClipboard)
167     set_title(std::string(_("Macro Editor:")) + " " + _("Clipboard Content"));
168     else {
169     if (macro->name().empty())
170     set_title(std::string(_("Macro Editor:")) + " " + _("Unnamed Macro"));
171     else
172     set_title(std::string(_("Macro Editor:")) + " \"" + macro->name() + "\"");
173     }
174 schoenebeck 3151
175     // copy for non-destructive editing
176     m_macro = *macro;
177    
178     reloadTreeView();
179     }
180    
181 schoenebeck 3162 sigc::signal<void>& MacroEditor::signal_changes_applied() {
182     return m_changes_applied;
183     }
184    
185 schoenebeck 3170 Glib::RefPtr<Gtk::ListStore> MacroEditor::createComboOptions(const char** options) {
186     Glib::RefPtr<Gtk::ListStore> refOptions = Gtk::ListStore::create(m_comboOptionsModel);
187     for (size_t i = 0; options[i]; ++i)
188     (*refOptions->append())[m_comboOptionsModel.m_col_choice] = options[i];
189     return refOptions;
190     }
191    
192 schoenebeck 3151 void MacroEditor::buildTreeView(const Gtk::TreeModel::Row& parentRow, const Serialization::Object& parentObject) {
193     for (int iMember = 0; iMember < parentObject.members().size(); ++iMember) {
194     const Serialization::Member& member = parentObject.members()[iMember];
195     const Serialization::Object& object = m_macro.objectByUID(member.uid());
196     Gtk::TreeModel::iterator iterRow = m_treeStoreMacro->append(parentRow.children());
197     Gtk::TreeModel::Row row = *iterRow;
198     row[m_treeModelMacro.m_col_name] = gig_to_utf8(member.name());
199     row[m_treeModelMacro.m_col_type] = gig_to_utf8(member.type().asLongDescr());
200     row[m_treeModelMacro.m_col_uid] = object.uid();
201 schoenebeck 3170 row[m_treeModelMacro.m_col_allowTextEntry] = false;
202    
203 schoenebeck 3151 if (object.type().isClass()) {
204     row[m_treeModelMacro.m_col_value] = "(class)";
205 schoenebeck 3170 row[m_treeModelMacro.m_col_editable] = false;
206 schoenebeck 3151 buildTreeView(row, object);
207 schoenebeck 3170 } else if (object.type().isEnum()) {
208     const char* key = gig::enumKey(
209     object.type().customTypeName(), m_macro.valueAsInt(object)
210     );
211     row[m_treeModelMacro.m_col_value] = key ? key : m_macro.valueAsString(object);
212     row[m_treeModelMacro.m_col_editable] = true;
213     const char** allKeys = gig::enumKeys(object.type().customTypeName());
214     if (allKeys) {
215     Glib::RefPtr<Gtk::ListStore> refOptions = createComboOptions(allKeys);
216     row[m_treeModelMacro.m_col_options] = refOptions;
217     }
218 schoenebeck 3151 } else {
219     row[m_treeModelMacro.m_col_value] = m_macro.valueAsString(object);
220 schoenebeck 3170 row[m_treeModelMacro.m_col_editable] = true;
221 schoenebeck 3151 }
222     }
223     }
224    
225     void MacroEditor::reloadTreeView() {
226 schoenebeck 3154 m_ignoreTreeViewValueChange = true;
227    
228 schoenebeck 3151 m_treeStoreMacro->clear();
229    
230     const Serialization::Object& rootObject = m_macro.rootObject();
231    
232     Gtk::TreeModel::iterator iterRoot = m_treeStoreMacro->append();
233     Gtk::TreeModel::Row rowRoot = *iterRoot;
234     rowRoot[m_treeModelMacro.m_col_name] = "(Root)";
235     rowRoot[m_treeModelMacro.m_col_type] = gig_to_utf8(rootObject.type().asLongDescr());
236     rowRoot[m_treeModelMacro.m_col_value] = "";
237     rowRoot[m_treeModelMacro.m_col_uid] = rootObject.uid();
238 schoenebeck 3170 rowRoot[m_treeModelMacro.m_col_allowTextEntry] = false;
239     rowRoot[m_treeModelMacro.m_col_editable] = false;
240 schoenebeck 3151
241     buildTreeView(rowRoot, rootObject);
242    
243     m_treeViewMacro.expand_all();
244    
245     updateStatus();
246 schoenebeck 3154
247     m_ignoreTreeViewValueChange = false;
248 schoenebeck 3151 }
249    
250 schoenebeck 3154 void MacroEditor::onTreeViewSelectionChanged() {
251     std::vector<Gtk::TreeModel::Path> v = m_treeViewMacro.get_selection()->get_selected_rows();
252     const bool bValidSelection = !v.empty();
253     m_deleteButton.set_sensitive(bValidSelection);
254     m_inverseDeleteButton.set_sensitive(bValidSelection);
255     }
256    
257 schoenebeck 3170 // Cmd key on Mac, Ctrl key on all other OSs
258     static const guint primaryKeyL =
259     #if defined(__APPLE__)
260     GDK_KEY_Meta_L;
261     #else
262     GDK_KEY_Control_L;
263     #endif
264    
265     static const guint primaryKeyR =
266     #if defined(__APPLE__)
267     GDK_KEY_Meta_R;
268     #else
269     GDK_KEY_Control_R;
270     #endif
271    
272 schoenebeck 3155 bool MacroEditor::onKeyPressed(GdkEventKey* key) {
273     //printf("key down 0x%x\n", key->keyval);
274     if (key->keyval == GDK_KEY_Alt_L || key->keyval == GDK_KEY_Alt_R)
275     m_altKeyDown = true;
276 schoenebeck 3170 if (key->keyval == primaryKeyL || key->keyval == primaryKeyR)
277     m_primaryKeyDown = true;
278 schoenebeck 3155 return false;
279     }
280    
281     bool MacroEditor::onKeyReleased(GdkEventKey* key) {
282     //printf("key up 0x%x\n", key->keyval);
283     if (key->keyval == GDK_KEY_Alt_L || key->keyval == GDK_KEY_Alt_R)
284     m_altKeyDown = false;
285 schoenebeck 3170 if (key->keyval == primaryKeyL || key->keyval == primaryKeyR)
286     m_primaryKeyDown = false;
287 schoenebeck 3155 return false;
288     }
289    
290 schoenebeck 3154 void MacroEditor::onMacroTreeViewKeyRelease(GdkEventKey* key) {
291 schoenebeck 3155 if (key->keyval == GDK_KEY_BackSpace || key->keyval == GDK_KEY_Delete) {
292     if (m_altKeyDown)
293     inverseDeleteSelectedRows();
294 schoenebeck 3170 else if (m_primaryKeyDown)
295 schoenebeck 3155 deleteSelectedRows();
296     }
297 schoenebeck 3154 }
298    
299 schoenebeck 3170 void MacroEditor::onValueCellEdited(const Glib::ustring& sPath, const Glib::ustring& text) {
300     printf("asdf\n");
301     Gtk::TreePath path(sPath);
302     Gtk::TreeModel::iterator iter = m_treeStoreMacro->get_iter(path);
303     onMacroTreeViewRowValueChangedImpl(path, iter, text);
304     }
305    
306 schoenebeck 3154 void MacroEditor::onMacroTreeViewRowValueChanged(const Gtk::TreeModel::Path& path,
307     const Gtk::TreeModel::iterator& iter)
308     {
309 schoenebeck 3170 if (!iter) return;
310     Gtk::TreeModel::Row row = *iter;
311     Glib::ustring value = row[m_treeModelMacro.m_col_value];
312     onMacroTreeViewRowValueChangedImpl(path, iter, value);
313     }
314    
315     void MacroEditor::onMacroTreeViewRowValueChangedImpl(const Gtk::TreeModel::Path& path,
316     const Gtk::TreeModel::iterator& iter,
317     const Glib::ustring& value)
318     {
319 schoenebeck 3154 if (m_ignoreTreeViewValueChange) return;
320     if (!iter) return;
321     Gtk::TreeModel::Row row = *iter;
322     Serialization::UID uid = row[m_treeModelMacro.m_col_uid];
323     Serialization::String gigvalue(gig_from_utf8(value));
324     Serialization::Object& object = m_macro.objectByUID(uid);
325     std::string errorText;
326     try {
327 schoenebeck 3170 if (object.type().isEnum() &&
328     gig::enumKey(object.type().customTypeName(), gigvalue))
329     {
330     size_t iValue = gig::enumValue(gigvalue);
331     m_macro.setAutoValue(object, ToString(iValue));
332     // no auto correct here yet (due to numeric vs. textual values)
333     if (row[m_treeModelMacro.m_col_value] != value)
334     row[m_treeModelMacro.m_col_value] = value;
335     } else {
336     m_macro.setAutoValue(object, gigvalue);
337     // potentially auto correct (i.e. when type is bool, user entered 5 -> yields 1)
338     if (row[m_treeModelMacro.m_col_value] != m_macro.valueAsString(object))
339     row[m_treeModelMacro.m_col_value] = m_macro.valueAsString(object);
340     }
341     updateStatus();
342 schoenebeck 3154 } catch (Serialization::Exception e) {
343     errorText = e.Message;
344     } catch (...) {
345     errorText = _("Unknown exception during object value change");
346     }
347     if (!errorText.empty()) {
348     Glib::ustring txt = _("Couldn't change value:\n") + errorText;
349     Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
350     msg.run();
351     }
352     }
353    
354     void MacroEditor::deleteSelectedRows() {
355     Glib::RefPtr<Gtk::TreeSelection> sel = m_treeViewMacro.get_selection();
356     std::vector<Gtk::TreeModel::Path> rows = sel->get_selected_rows();
357 schoenebeck 3155 deleteRows(rows);
358     }
359    
360     void MacroEditor::deleteRows(const std::vector<Gtk::TreeModel::Path>& rows) {
361 schoenebeck 3154 for (int r = rows.size() - 1; r >= 0; --r) {
362     Gtk::TreeModel::iterator it = m_treeStoreMacro->get_iter(rows[r]);
363     if (!it) continue;
364     Gtk::TreeModel::Row row = *it;
365     Serialization::UID uid = row[m_treeModelMacro.m_col_uid];
366     if (uid == m_macro.rootObject().uid()) continue; // prohibit deleting root object
367     Gtk::TreeModel::iterator itParent = row.parent();
368     if (!itParent) continue;
369     Gtk::TreeModel::Row rowParent = *itParent;
370     Serialization::UID uidParent = rowParent[m_treeModelMacro.m_col_uid];
371     //Serialization::Object& object = m_macro.objectByUID(uid);
372     Serialization::Object& parentObject = m_macro.objectByUID(uidParent);
373     const Serialization::Member& member = parentObject.memberByUID(uid);
374     m_macro.removeMember(parentObject, member);
375     //m_macro.remove(object);
376     }
377     reloadTreeView();
378     }
379    
380 schoenebeck 3155 static bool _onEachTreeRow(const Gtk::TreeModel::Path& input, std::vector<Gtk::TreeModel::Path>* output) {
381     output->push_back(input);
382     return false; // continue walking the tree
383     }
384    
385 schoenebeck 3154 void MacroEditor::inverseDeleteSelectedRows() {
386 schoenebeck 3155 // get all rows of tree view
387     std::vector<Gtk::TreeModel::Path> rows;
388     m_treeViewMacro.get_model()->foreach_path(
389     sigc::bind(
390     sigc::ptr_fun(&_onEachTreeRow),
391     &rows
392     )
393     );
394    
395     // erase all entries from "rows" which are currently selected
396     std::vector<Gtk::TreeModel::Path> vSelected = m_treeViewMacro.get_selection()->get_selected_rows();
397     for (int i = rows.size() - 1; i >= 0; --i) {
398     bool bIsSelected = std::find(vSelected.begin(), vSelected.end(),
399     rows[i]) != vSelected.end();
400     if (bIsSelected)
401     rows.erase(rows.begin() + i);
402     }
403    
404     // delete those 'inverse' selected rows
405     deleteRows(rows);
406 schoenebeck 3154 }
407    
408 schoenebeck 3151 void MacroEditor::updateStatus() {
409     m_applyButton.set_sensitive(isModified());
410     updateStatusBar();
411     }
412    
413     void MacroEditor::updateStatusBar() {
414     // update status text
415     std::string txt;
416     m_statusLabel.set_markup(txt);
417     }
418    
419     bool MacroEditor::onWindowDelete(GdkEventAny* e) {
420     //printf("onWindowDelete\n");
421    
422     if (!isModified()) return false; // propagate event further (which will close this window)
423    
424     //gchar* msg = g_strdup_printf(_("Apply changes to macro \"%s\" before closing?"),
425     // m_macroOriginal->Name.c_str());
426     gchar* msg = g_strdup_printf(_("Apply changes to macro before closing?"));
427     Gtk::MessageDialog dialog(*this, msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE);
428     g_free(msg);
429     dialog.set_secondary_text(_("If you close without applying, your changes will be lost."));
430     dialog.add_button(_("Close _Without Applying"), Gtk::RESPONSE_NO);
431     dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL);
432     dialog.add_button(_("_Apply"), Gtk::RESPONSE_YES);
433     dialog.set_default_response(Gtk::RESPONSE_YES);
434     int response = dialog.run();
435     dialog.hide();
436    
437     // user decided to close macro editor without saving
438     if (response == Gtk::RESPONSE_NO)
439     return false; // propagate event further (which will close this window)
440    
441     // user cancelled dialog, thus don't close macro editor
442     if (response == Gtk::RESPONSE_CANCEL) {
443     show();
444     return true; // drop event (prevents closing this window)
445     }
446    
447     // user wants to apply the changes, afterwards close window
448     if (response == Gtk::RESPONSE_YES) {
449     onButtonApply();
450     return false; // propagate event further (which will close this window)
451     }
452    
453     // should never ever make it to this point actually
454     return false;
455     }
456    
457     bool MacroEditor::isModified() const {
458     return m_macro.isModified();
459     }
460    
461     void MacroEditor::onButtonCancel() {
462     bool dropEvent = onWindowDelete(NULL);
463     if (dropEvent) return;
464     hide();
465     }
466    
467     void MacroEditor::onButtonApply() {
468 schoenebeck 3155 std::string errorText;
469     try {
470     // enforce re-encoding the abstract object model and resetting the
471     // 'modified' state
472     m_macro.rawData();
473     // replace actual effective Archive object which is effectively used
474     // for macro apply operations
475     *m_macroOriginal = m_macro;
476     } catch (Serialization::Exception e) {
477     errorText = e.Message;
478     } catch (...) {
479     errorText = _("Unknown exception while applying macro changes");
480     }
481     if (!errorText.empty()) {
482     Glib::ustring txt = _("Couldn't apply macro changes:\n") + errorText;
483     Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
484     msg.run();
485     }
486     updateStatus();
487 schoenebeck 3162 m_changes_applied.emit();
488 schoenebeck 3151 }
489    
490     void MacroEditor::onWindowHide() {
491     delete this; // this is the end, my friend
492     }

  ViewVC Help
Powered by ViewVC