/[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 3176 - (hide annotations) (download)
Thu May 11 11:36:33 2017 UTC (3 years, 2 months ago) by schoenebeck
File size: 18486 byte(s)
* Macros Setup: Implemented duplicating macros.
* Bumped version (1.0.0.svn44).

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     Gtk::TreePath path(sPath);
301     Gtk::TreeModel::iterator iter = m_treeStoreMacro->get_iter(path);
302     onMacroTreeViewRowValueChangedImpl(path, iter, text);
303     }
304    
305 schoenebeck 3154 void MacroEditor::onMacroTreeViewRowValueChanged(const Gtk::TreeModel::Path& path,
306     const Gtk::TreeModel::iterator& iter)
307     {
308 schoenebeck 3170 if (!iter) return;
309     Gtk::TreeModel::Row row = *iter;
310     Glib::ustring value = row[m_treeModelMacro.m_col_value];
311     onMacroTreeViewRowValueChangedImpl(path, iter, value);
312     }
313    
314     void MacroEditor::onMacroTreeViewRowValueChangedImpl(const Gtk::TreeModel::Path& path,
315     const Gtk::TreeModel::iterator& iter,
316     const Glib::ustring& value)
317     {
318 schoenebeck 3154 if (m_ignoreTreeViewValueChange) return;
319     if (!iter) return;
320     Gtk::TreeModel::Row row = *iter;
321     Serialization::UID uid = row[m_treeModelMacro.m_col_uid];
322     Serialization::String gigvalue(gig_from_utf8(value));
323     Serialization::Object& object = m_macro.objectByUID(uid);
324     std::string errorText;
325     try {
326 schoenebeck 3170 if (object.type().isEnum() &&
327     gig::enumKey(object.type().customTypeName(), gigvalue))
328     {
329     size_t iValue = gig::enumValue(gigvalue);
330     m_macro.setAutoValue(object, ToString(iValue));
331     // no auto correct here yet (due to numeric vs. textual values)
332     if (row[m_treeModelMacro.m_col_value] != value)
333     row[m_treeModelMacro.m_col_value] = value;
334     } else {
335     m_macro.setAutoValue(object, gigvalue);
336     // potentially auto correct (i.e. when type is bool, user entered 5 -> yields 1)
337     if (row[m_treeModelMacro.m_col_value] != m_macro.valueAsString(object))
338     row[m_treeModelMacro.m_col_value] = m_macro.valueAsString(object);
339     }
340     updateStatus();
341 schoenebeck 3154 } catch (Serialization::Exception e) {
342     errorText = e.Message;
343     } catch (...) {
344     errorText = _("Unknown exception during object value change");
345     }
346     if (!errorText.empty()) {
347     Glib::ustring txt = _("Couldn't change value:\n") + errorText;
348     Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
349     msg.run();
350     }
351     }
352    
353     void MacroEditor::deleteSelectedRows() {
354     Glib::RefPtr<Gtk::TreeSelection> sel = m_treeViewMacro.get_selection();
355     std::vector<Gtk::TreeModel::Path> rows = sel->get_selected_rows();
356 schoenebeck 3155 deleteRows(rows);
357     }
358    
359     void MacroEditor::deleteRows(const std::vector<Gtk::TreeModel::Path>& rows) {
360 schoenebeck 3154 for (int r = rows.size() - 1; r >= 0; --r) {
361     Gtk::TreeModel::iterator it = m_treeStoreMacro->get_iter(rows[r]);
362     if (!it) continue;
363     Gtk::TreeModel::Row row = *it;
364     Serialization::UID uid = row[m_treeModelMacro.m_col_uid];
365     if (uid == m_macro.rootObject().uid()) continue; // prohibit deleting root object
366     Gtk::TreeModel::iterator itParent = row.parent();
367     if (!itParent) continue;
368     Gtk::TreeModel::Row rowParent = *itParent;
369     Serialization::UID uidParent = rowParent[m_treeModelMacro.m_col_uid];
370     //Serialization::Object& object = m_macro.objectByUID(uid);
371     Serialization::Object& parentObject = m_macro.objectByUID(uidParent);
372     const Serialization::Member& member = parentObject.memberByUID(uid);
373     m_macro.removeMember(parentObject, member);
374     //m_macro.remove(object);
375     }
376     reloadTreeView();
377     }
378    
379 schoenebeck 3155 static bool _onEachTreeRow(const Gtk::TreeModel::Path& input, std::vector<Gtk::TreeModel::Path>* output) {
380     output->push_back(input);
381     return false; // continue walking the tree
382     }
383    
384 schoenebeck 3154 void MacroEditor::inverseDeleteSelectedRows() {
385 schoenebeck 3155 // get all rows of tree view
386     std::vector<Gtk::TreeModel::Path> rows;
387     m_treeViewMacro.get_model()->foreach_path(
388     sigc::bind(
389     sigc::ptr_fun(&_onEachTreeRow),
390     &rows
391     )
392     );
393    
394     // erase all entries from "rows" which are currently selected
395     std::vector<Gtk::TreeModel::Path> vSelected = m_treeViewMacro.get_selection()->get_selected_rows();
396     for (int i = rows.size() - 1; i >= 0; --i) {
397     bool bIsSelected = std::find(vSelected.begin(), vSelected.end(),
398     rows[i]) != vSelected.end();
399     if (bIsSelected)
400     rows.erase(rows.begin() + i);
401     }
402    
403     // delete those 'inverse' selected rows
404     deleteRows(rows);
405 schoenebeck 3154 }
406    
407 schoenebeck 3151 void MacroEditor::updateStatus() {
408     m_applyButton.set_sensitive(isModified());
409     updateStatusBar();
410     }
411    
412     void MacroEditor::updateStatusBar() {
413     // update status text
414     std::string txt;
415     m_statusLabel.set_markup(txt);
416     }
417    
418     bool MacroEditor::onWindowDelete(GdkEventAny* e) {
419     //printf("onWindowDelete\n");
420    
421     if (!isModified()) return false; // propagate event further (which will close this window)
422    
423     //gchar* msg = g_strdup_printf(_("Apply changes to macro \"%s\" before closing?"),
424     // m_macroOriginal->Name.c_str());
425     gchar* msg = g_strdup_printf(_("Apply changes to macro before closing?"));
426     Gtk::MessageDialog dialog(*this, msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE);
427     g_free(msg);
428     dialog.set_secondary_text(_("If you close without applying, your changes will be lost."));
429     dialog.add_button(_("Close _Without Applying"), Gtk::RESPONSE_NO);
430     dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL);
431     dialog.add_button(_("_Apply"), Gtk::RESPONSE_YES);
432     dialog.set_default_response(Gtk::RESPONSE_YES);
433     int response = dialog.run();
434     dialog.hide();
435    
436     // user decided to close macro editor without saving
437     if (response == Gtk::RESPONSE_NO)
438     return false; // propagate event further (which will close this window)
439    
440     // user cancelled dialog, thus don't close macro editor
441     if (response == Gtk::RESPONSE_CANCEL) {
442     show();
443     return true; // drop event (prevents closing this window)
444     }
445    
446     // user wants to apply the changes, afterwards close window
447     if (response == Gtk::RESPONSE_YES) {
448     onButtonApply();
449     return false; // propagate event further (which will close this window)
450     }
451    
452     // should never ever make it to this point actually
453     return false;
454     }
455    
456     bool MacroEditor::isModified() const {
457     return m_macro.isModified();
458     }
459    
460     void MacroEditor::onButtonCancel() {
461     bool dropEvent = onWindowDelete(NULL);
462     if (dropEvent) return;
463     hide();
464     }
465    
466     void MacroEditor::onButtonApply() {
467 schoenebeck 3155 std::string errorText;
468     try {
469     // enforce re-encoding the abstract object model and resetting the
470     // 'modified' state
471     m_macro.rawData();
472     // replace actual effective Archive object which is effectively used
473     // for macro apply operations
474     *m_macroOriginal = m_macro;
475     } catch (Serialization::Exception e) {
476     errorText = e.Message;
477     } catch (...) {
478     errorText = _("Unknown exception while applying macro changes");
479     }
480     if (!errorText.empty()) {
481     Glib::ustring txt = _("Couldn't apply macro changes:\n") + errorText;
482     Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
483     msg.run();
484     }
485     updateStatus();
486 schoenebeck 3162 m_changes_applied.emit();
487 schoenebeck 3151 }
488    
489     void MacroEditor::onWindowHide() {
490     delete this; // this is the end, my friend
491     }

  ViewVC Help
Powered by ViewVC