/[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 3170 - (show annotations) (download)
Wed May 10 21:21:14 2017 UTC (3 years, 3 months ago) by schoenebeck
File size: 18508 byte(s)
* Macro Editor: Fixed deleting rows when delete button was hit
  without Ctrl being pressed.
* Macro Editor: Show combo box with textual options for enum types.
* Bumped version (1.0.0.svn42).

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

  ViewVC Help
Powered by ViewVC