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

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 //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 // 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 //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 {
53 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 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 m_treeViewMacro.set_headers_visible(true);
76 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
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 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 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 m_footerHBox.pack_start(m_buttonBoxL, Gtk::PACK_SHRINK);
115 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 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 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 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 show_all_children();
152 updateStatus();
153 }
154
155 MacroEditor::~MacroEditor() {
156 printf("MacroEditor destruct\n");
157 }
158
159 void MacroEditor::setMacro(Serialization::Archive* macro, bool isClipboard) {
160 m_macroOriginal = macro;
161 if (!macro) {
162 set_title(_("No Macro"));
163 return;
164 }
165
166 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
175 // copy for non-destructive editing
176 m_macro = *macro;
177
178 reloadTreeView();
179 }
180
181 sigc::signal<void>& MacroEditor::signal_changes_applied() {
182 return m_changes_applied;
183 }
184
185 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 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 row[m_treeModelMacro.m_col_allowTextEntry] = false;
202
203 if (object.type().isClass()) {
204 row[m_treeModelMacro.m_col_value] = "(class)";
205 row[m_treeModelMacro.m_col_editable] = false;
206 buildTreeView(row, object);
207 } 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 } else {
219 row[m_treeModelMacro.m_col_value] = m_macro.valueAsString(object);
220 row[m_treeModelMacro.m_col_editable] = true;
221 }
222 }
223 }
224
225 void MacroEditor::reloadTreeView() {
226 m_ignoreTreeViewValueChange = true;
227
228 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 rowRoot[m_treeModelMacro.m_col_allowTextEntry] = false;
239 rowRoot[m_treeModelMacro.m_col_editable] = false;
240
241 buildTreeView(rowRoot, rootObject);
242
243 m_treeViewMacro.expand_all();
244
245 updateStatus();
246
247 m_ignoreTreeViewValueChange = false;
248 }
249
250 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 // 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 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 if (key->keyval == primaryKeyL || key->keyval == primaryKeyR)
277 m_primaryKeyDown = true;
278 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 if (key->keyval == primaryKeyL || key->keyval == primaryKeyR)
286 m_primaryKeyDown = false;
287 return false;
288 }
289
290 void MacroEditor::onMacroTreeViewKeyRelease(GdkEventKey* key) {
291 if (key->keyval == GDK_KEY_BackSpace || key->keyval == GDK_KEY_Delete) {
292 if (m_altKeyDown)
293 inverseDeleteSelectedRows();
294 else if (m_primaryKeyDown)
295 deleteSelectedRows();
296 }
297 }
298
299 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 void MacroEditor::onMacroTreeViewRowValueChanged(const Gtk::TreeModel::Path& path,
306 const Gtk::TreeModel::iterator& iter)
307 {
308 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 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 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 } 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 deleteRows(rows);
357 }
358
359 void MacroEditor::deleteRows(const std::vector<Gtk::TreeModel::Path>& rows) {
360 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 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 void MacroEditor::inverseDeleteSelectedRows() {
385 // 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 }
406
407 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 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 m_changes_applied.emit();
487 }
488
489 void MacroEditor::onWindowHide() {
490 delete this; // this is the end, my friend
491 }

  ViewVC Help
Powered by ViewVC