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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3187 - (show annotations) (download)
Wed May 17 16:14:20 2017 UTC (6 years, 11 months ago) by schoenebeck
File size: 19909 byte(s)
* Macro Editor: Handle boolean parameter types appropriately, i.e. show
  'Yes' or 'NO' instead of '1' or '0'.
* Bumped version (1.0.0.svn47).

1 /*
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 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 m_applyButton(_("_Apply"), true),
18 m_cancelButton(_("_Cancel"), true),
19 m_altKeyDown(false),
20 m_primaryKeyDown(false)
21 {
22 add(m_vbox);
23
24 set_default_size(800, 600);
25
26 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 _("A macro is a list of parameters and corresponding values which "
32 "should be applied to the instrument editor when the macro is "
33 "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 );
39 m_vbox.pack_start(m_labelIntro, Gtk::PACK_SHRINK);
40
41 // 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 //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 {
57 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 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 m_treeViewMacro.set_headers_visible(true);
80 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
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 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 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 m_footerHBox.pack_start(m_buttonBoxL, Gtk::PACK_SHRINK);
119 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 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 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 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 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 show_all_children();
159 updateStatus();
160 }
161
162 MacroEditor::~MacroEditor() {
163 printf("MacroEditor destruct\n");
164 }
165
166 void MacroEditor::setMacro(Serialization::Archive* macro, bool isClipboard) {
167 m_macroOriginal = macro;
168 if (!macro) {
169 set_title(_("No Macro"));
170 return;
171 }
172
173 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
182 // copy for non-destructive editing
183 m_macro = *macro;
184
185 reloadTreeView();
186 }
187
188 sigc::signal<void>& MacroEditor::signal_changes_applied() {
189 return m_changes_applied;
190 }
191
192 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 inline static Serialization::String _boolToStr(bool b) {
200 // 'NO' intentional all uper case in contrast to 'Yes', simply because I
201 // find them easier distinguishable that way on quick readings
202 return b ? "Yes" : "NO";
203 }
204
205 static const char* _boolOptions[] = { "Yes", "NO", NULL };
206
207 void MacroEditor::buildTreeView(const Gtk::TreeModel::Row& parentRow, const Serialization::Object& parentObject) {
208 for (int iMember = 0; iMember < parentObject.members().size(); ++iMember) {
209 const Serialization::Member& member = parentObject.members()[iMember];
210 const Serialization::Object& object = m_macro.objectByUID(member.uid());
211 Gtk::TreeModel::iterator iterRow = m_treeStoreMacro->append(parentRow.children());
212 Gtk::TreeModel::Row row = *iterRow;
213 row[m_treeModelMacro.m_col_name] = gig_to_utf8(member.name());
214 row[m_treeModelMacro.m_col_type] = gig_to_utf8(member.type().asLongDescr());
215 row[m_treeModelMacro.m_col_uid] = object.uid();
216 row[m_treeModelMacro.m_col_allowTextEntry] = true;
217
218 if (object.type().isClass()) {
219 row[m_treeModelMacro.m_col_value] = "(class)";
220 row[m_treeModelMacro.m_col_editable] = false;
221 buildTreeView(row, object);
222 } else if (object.type().isEnum()) {
223 const char* key = gig::enumKey(
224 object.type().customTypeName(), m_macro.valueAsInt(object)
225 );
226 row[m_treeModelMacro.m_col_value] = key ? key : m_macro.valueAsString(object);
227 row[m_treeModelMacro.m_col_editable] = true;
228 const char** allKeys = gig::enumKeys(object.type().customTypeName());
229 if (allKeys) {
230 Glib::RefPtr<Gtk::ListStore> refOptions = createComboOptions(allKeys);
231 row[m_treeModelMacro.m_col_options] = refOptions;
232 }
233 } else if (object.type().isBool()) {
234 row[m_treeModelMacro.m_col_value] = _boolToStr( m_macro.valueAsBool(object) );
235 row[m_treeModelMacro.m_col_editable] = true;
236 Glib::RefPtr<Gtk::ListStore> refOptions = createComboOptions(_boolOptions);
237 row[m_treeModelMacro.m_col_options] = refOptions;
238 row[m_treeModelMacro.m_col_allowTextEntry] = false;
239 } else {
240 row[m_treeModelMacro.m_col_value] = m_macro.valueAsString(object);
241 row[m_treeModelMacro.m_col_editable] = true;
242 }
243 }
244 }
245
246 void MacroEditor::reloadTreeView() {
247 m_ignoreTreeViewValueChange = true;
248
249 m_treeStoreMacro->clear();
250
251 const Serialization::Object& rootObject = m_macro.rootObject();
252
253 Gtk::TreeModel::iterator iterRoot = m_treeStoreMacro->append();
254 Gtk::TreeModel::Row rowRoot = *iterRoot;
255 rowRoot[m_treeModelMacro.m_col_name] = "(Root)";
256 rowRoot[m_treeModelMacro.m_col_type] = gig_to_utf8(rootObject.type().asLongDescr());
257 rowRoot[m_treeModelMacro.m_col_value] = "";
258 rowRoot[m_treeModelMacro.m_col_uid] = rootObject.uid();
259 rowRoot[m_treeModelMacro.m_col_allowTextEntry] = false;
260 rowRoot[m_treeModelMacro.m_col_editable] = false;
261
262 buildTreeView(rowRoot, rootObject);
263
264 m_treeViewMacro.expand_all();
265
266 updateStatus();
267
268 m_ignoreTreeViewValueChange = false;
269 }
270
271 void MacroEditor::onTreeViewSelectionChanged() {
272 std::vector<Gtk::TreeModel::Path> v = m_treeViewMacro.get_selection()->get_selected_rows();
273 const bool bValidSelection = !v.empty();
274 m_deleteButton.set_sensitive(bValidSelection);
275 m_inverseDeleteButton.set_sensitive(bValidSelection);
276 }
277
278 // Cmd key on Mac, Ctrl key on all other OSs
279 static const guint primaryKeyL =
280 #if defined(__APPLE__)
281 GDK_KEY_Meta_L;
282 #else
283 GDK_KEY_Control_L;
284 #endif
285
286 static const guint primaryKeyR =
287 #if defined(__APPLE__)
288 GDK_KEY_Meta_R;
289 #else
290 GDK_KEY_Control_R;
291 #endif
292
293 bool MacroEditor::onKeyPressed(GdkEventKey* key) {
294 //printf("key down 0x%x\n", key->keyval);
295 if (key->keyval == GDK_KEY_Alt_L || key->keyval == GDK_KEY_Alt_R)
296 m_altKeyDown = true;
297 if (key->keyval == primaryKeyL || key->keyval == primaryKeyR)
298 m_primaryKeyDown = true;
299 return false;
300 }
301
302 bool MacroEditor::onKeyReleased(GdkEventKey* key) {
303 //printf("key up 0x%x\n", key->keyval);
304 if (key->keyval == GDK_KEY_Alt_L || key->keyval == GDK_KEY_Alt_R)
305 m_altKeyDown = false;
306 if (key->keyval == primaryKeyL || key->keyval == primaryKeyR)
307 m_primaryKeyDown = false;
308 return false;
309 }
310
311 void MacroEditor::onMacroTreeViewKeyRelease(GdkEventKey* key) {
312 if (key->keyval == GDK_KEY_BackSpace || key->keyval == GDK_KEY_Delete) {
313 if (m_altKeyDown)
314 inverseDeleteSelectedRows();
315 else if (m_primaryKeyDown)
316 deleteSelectedRows();
317 }
318 }
319
320 void MacroEditor::onValueCellEdited(const Glib::ustring& sPath, const Glib::ustring& text) {
321 Gtk::TreePath path(sPath);
322 Gtk::TreeModel::iterator iter = m_treeStoreMacro->get_iter(path);
323 onMacroTreeViewRowValueChangedImpl(path, iter, text);
324 }
325
326 void MacroEditor::onMacroTreeViewRowValueChanged(const Gtk::TreeModel::Path& path,
327 const Gtk::TreeModel::iterator& iter)
328 {
329 if (!iter) return;
330 Gtk::TreeModel::Row row = *iter;
331 Glib::ustring value = row[m_treeModelMacro.m_col_value];
332 onMacroTreeViewRowValueChangedImpl(path, iter, value);
333 }
334
335 void MacroEditor::onMacroTreeViewRowValueChangedImpl(const Gtk::TreeModel::Path& path,
336 const Gtk::TreeModel::iterator& iter,
337 const Glib::ustring& value)
338 {
339 if (m_ignoreTreeViewValueChange) return;
340 if (!iter) return;
341 Gtk::TreeModel::Row row = *iter;
342 Serialization::UID uid = row[m_treeModelMacro.m_col_uid];
343 Serialization::String gigvalue(gig_from_utf8(value));
344 Serialization::Object& object = m_macro.objectByUID(uid);
345 std::string errorText;
346 try {
347 if (object.type().isEnum() &&
348 gig::enumKey(object.type().customTypeName(), gigvalue))
349 {
350 size_t iValue = gig::enumValue(gigvalue);
351 m_macro.setAutoValue(object, ToString(iValue));
352 // no auto correct here yet (due to numeric vs. textual values)
353 if (row[m_treeModelMacro.m_col_value] != value)
354 row[m_treeModelMacro.m_col_value] = value;
355 } else if (object.type().isBool()) {
356 m_macro.setAutoValue(object, gigvalue);
357 Serialization::String sBoolean = _boolToStr( m_macro.valueAsBool(object) );
358 // potentially auto correct (i.e. when type is bool, user entered '5' -> yields 'Yes')
359 if (row[m_treeModelMacro.m_col_value] != sBoolean)
360 row[m_treeModelMacro.m_col_value] = sBoolean;
361 } else {
362 m_macro.setAutoValue(object, gigvalue);
363 // potentially auto correct (i.e. when type is bool, user entered 5 -> yields 1)
364 if (row[m_treeModelMacro.m_col_value] != m_macro.valueAsString(object))
365 row[m_treeModelMacro.m_col_value] = m_macro.valueAsString(object);
366 }
367 updateStatus();
368 } catch (Serialization::Exception e) {
369 errorText = e.Message;
370 } catch (...) {
371 errorText = _("Unknown exception during object value change");
372 }
373 if (!errorText.empty()) {
374 Glib::ustring txt = _("Couldn't change value:\n") + errorText;
375 Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
376 msg.run();
377 }
378 }
379
380 void MacroEditor::deleteSelectedRows() {
381 Glib::RefPtr<Gtk::TreeSelection> sel = m_treeViewMacro.get_selection();
382 std::vector<Gtk::TreeModel::Path> rows = sel->get_selected_rows();
383 deleteRows(rows);
384 }
385
386 void MacroEditor::deleteRows(const std::vector<Gtk::TreeModel::Path>& rows) {
387 for (int r = rows.size() - 1; r >= 0; --r) {
388 Gtk::TreeModel::iterator it = m_treeStoreMacro->get_iter(rows[r]);
389 if (!it) continue;
390 Gtk::TreeModel::Row row = *it;
391 Serialization::UID uid = row[m_treeModelMacro.m_col_uid];
392 if (uid == m_macro.rootObject().uid()) continue; // prohibit deleting root object
393 Gtk::TreeModel::iterator itParent = row.parent();
394 if (!itParent) continue;
395 Gtk::TreeModel::Row rowParent = *itParent;
396 Serialization::UID uidParent = rowParent[m_treeModelMacro.m_col_uid];
397 //Serialization::Object& object = m_macro.objectByUID(uid);
398 Serialization::Object& parentObject = m_macro.objectByUID(uidParent);
399 const Serialization::Member& member = parentObject.memberByUID(uid);
400 m_macro.removeMember(parentObject, member);
401 //m_macro.remove(object);
402 }
403 reloadTreeView();
404 }
405
406 static bool _onEachTreeRow(const Gtk::TreeModel::Path& input, std::vector<Gtk::TreeModel::Path>* output) {
407 output->push_back(input);
408 return false; // continue walking the tree
409 }
410
411 void MacroEditor::inverseDeleteSelectedRows() {
412 // get all rows of tree view
413 std::vector<Gtk::TreeModel::Path> rows;
414 m_treeViewMacro.get_model()->foreach_path(
415 sigc::bind(
416 sigc::ptr_fun(&_onEachTreeRow),
417 &rows
418 )
419 );
420
421 // erase all entries from "rows" which are currently selected
422 std::vector<Gtk::TreeModel::Path> vSelected = m_treeViewMacro.get_selection()->get_selected_rows();
423 for (int i = rows.size() - 1; i >= 0; --i) {
424 bool bIsSelected = std::find(vSelected.begin(), vSelected.end(),
425 rows[i]) != vSelected.end();
426 if (bIsSelected)
427 rows.erase(rows.begin() + i);
428 }
429
430 // delete those 'inverse' selected rows
431 deleteRows(rows);
432 }
433
434 void MacroEditor::updateStatus() {
435 m_applyButton.set_sensitive(isModified());
436 updateStatusBar();
437 }
438
439 void MacroEditor::updateStatusBar() {
440 // update status text
441 std::string txt;
442 m_statusLabel.set_markup(txt);
443 }
444
445 bool MacroEditor::onWindowDelete(GdkEventAny* e) {
446 //printf("onWindowDelete\n");
447
448 if (!isModified()) return false; // propagate event further (which will close this window)
449
450 //gchar* msg = g_strdup_printf(_("Apply changes to macro \"%s\" before closing?"),
451 // m_macroOriginal->Name.c_str());
452 gchar* msg = g_strdup_printf(_("Apply changes to macro before closing?"));
453 Gtk::MessageDialog dialog(*this, msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE);
454 g_free(msg);
455 dialog.set_secondary_text(_("If you close without applying, your changes will be lost."));
456 dialog.add_button(_("Close _Without Applying"), Gtk::RESPONSE_NO);
457 dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL);
458 dialog.add_button(_("_Apply"), Gtk::RESPONSE_YES);
459 dialog.set_default_response(Gtk::RESPONSE_YES);
460 int response = dialog.run();
461 dialog.hide();
462
463 // user decided to close macro editor without saving
464 if (response == Gtk::RESPONSE_NO)
465 return false; // propagate event further (which will close this window)
466
467 // user cancelled dialog, thus don't close macro editor
468 if (response == Gtk::RESPONSE_CANCEL) {
469 show();
470 return true; // drop event (prevents closing this window)
471 }
472
473 // user wants to apply the changes, afterwards close window
474 if (response == Gtk::RESPONSE_YES) {
475 onButtonApply();
476 return false; // propagate event further (which will close this window)
477 }
478
479 // should never ever make it to this point actually
480 return false;
481 }
482
483 bool MacroEditor::isModified() const {
484 return m_macro.isModified();
485 }
486
487 void MacroEditor::onButtonCancel() {
488 bool dropEvent = onWindowDelete(NULL);
489 if (dropEvent) return;
490 hide();
491 }
492
493 void MacroEditor::onButtonApply() {
494 std::string errorText;
495 try {
496 // enforce re-encoding the abstract object model and resetting the
497 // 'modified' state
498 m_macro.rawData();
499 // replace actual effective Archive object which is effectively used
500 // for macro apply operations
501 *m_macroOriginal = m_macro;
502 } catch (Serialization::Exception e) {
503 errorText = e.Message;
504 } catch (...) {
505 errorText = _("Unknown exception while applying macro changes");
506 }
507 if (!errorText.empty()) {
508 Glib::ustring txt = _("Couldn't apply macro changes:\n") + errorText;
509 Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
510 msg.run();
511 }
512 updateStatus();
513 m_changes_applied.emit();
514 }
515
516 void MacroEditor::onWindowHide() {
517 delete this; // this is the end, my friend
518 }

  ViewVC Help
Powered by ViewVC