/[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 3184 - (show annotations) (download)
Wed May 17 12:28:39 2017 UTC (6 years, 11 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 /*
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 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 row[m_treeModelMacro.m_col_allowTextEntry] = false;
209
210 if (object.type().isClass()) {
211 row[m_treeModelMacro.m_col_value] = "(class)";
212 row[m_treeModelMacro.m_col_editable] = false;
213 buildTreeView(row, object);
214 } 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 } else {
226 row[m_treeModelMacro.m_col_value] = m_macro.valueAsString(object);
227 row[m_treeModelMacro.m_col_editable] = true;
228 }
229 }
230 }
231
232 void MacroEditor::reloadTreeView() {
233 m_ignoreTreeViewValueChange = true;
234
235 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 rowRoot[m_treeModelMacro.m_col_allowTextEntry] = false;
246 rowRoot[m_treeModelMacro.m_col_editable] = false;
247
248 buildTreeView(rowRoot, rootObject);
249
250 m_treeViewMacro.expand_all();
251
252 updateStatus();
253
254 m_ignoreTreeViewValueChange = false;
255 }
256
257 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 // 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 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 if (key->keyval == primaryKeyL || key->keyval == primaryKeyR)
284 m_primaryKeyDown = true;
285 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 if (key->keyval == primaryKeyL || key->keyval == primaryKeyR)
293 m_primaryKeyDown = false;
294 return false;
295 }
296
297 void MacroEditor::onMacroTreeViewKeyRelease(GdkEventKey* key) {
298 if (key->keyval == GDK_KEY_BackSpace || key->keyval == GDK_KEY_Delete) {
299 if (m_altKeyDown)
300 inverseDeleteSelectedRows();
301 else if (m_primaryKeyDown)
302 deleteSelectedRows();
303 }
304 }
305
306 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 void MacroEditor::onMacroTreeViewRowValueChanged(const Gtk::TreeModel::Path& path,
313 const Gtk::TreeModel::iterator& iter)
314 {
315 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 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 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 } 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 deleteRows(rows);
364 }
365
366 void MacroEditor::deleteRows(const std::vector<Gtk::TreeModel::Path>& rows) {
367 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 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 void MacroEditor::inverseDeleteSelectedRows() {
392 // 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 }
413
414 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 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 m_changes_applied.emit();
494 }
495
496 void MacroEditor::onWindowHide() {
497 delete this; // this is the end, my friend
498 }

  ViewVC Help
Powered by ViewVC