/[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 3184 - (hide annotations) (download)
Wed May 17 12:28:39 2017 UTC (3 years, 2 months ago) by schoenebeck
File size: 18804 byte(s)
* Added bunch of help text and tooltips for the new
  "Macro Setup" and "Macro Editor" windows.
* wrapLabel: Fixed wrong dimensions when using
  padding.
* Bumped version (1.0.0.svn46).

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

  ViewVC Help
Powered by ViewVC