/[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 3225 - (show annotations) (download)
Fri May 26 22:10:16 2017 UTC (6 years, 10 months ago) by schoenebeck
File size: 20023 byte(s)
* Assigned more useful default dimensions (and default position) for various
  windows and dialogs (if auto-restore of user's own custom window
  dimensions is disabled).
* Bumped version (1.0.0.svn51).

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

  ViewVC Help
Powered by ViewVC