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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3458 - (show annotations) (download)
Thu Jan 31 18:43:09 2019 UTC (5 years, 1 month ago) by persson
File size: 30336 byte(s)
* Avoid non-standard offsetof usage (to get rid of gcc warnings)

1 /*
2 Copyright (c) MMXVII - MMXIX 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 "MacrosSetup.h"
9 #include "compat.h"
10 #include "global.h"
11 #include <assert.h>
12 #include <set>
13 #include <math.h>
14 #if HAS_GTKMM_STOCK
15 # include <gtkmm/stock.h>
16 #endif
17 #include "MacroEditor.h"
18
19 MacrosSetup::MacrosSetup() :
20 m_modified(false),
21 m_clipboardContent(NULL),
22 m_addFromClipboardButton(" " + Glib::ustring(_("From Clipboard")) + " " + UNICODE_PRIMARY_KEY_SYMBOL + "B"),
23 m_addFromSelectionButton(" " + Glib::ustring(_("From Selection")) + " " + UNICODE_PRIMARY_KEY_SYMBOL + "S"),
24 #if HAS_GTKMM_STOCK
25 m_buttonUp(Gtk::Stock::GO_UP),
26 m_buttonDown(Gtk::Stock::GO_DOWN),
27 m_buttonEdit(Gtk::Stock::EDIT),
28 #else
29 m_buttonUp(_("Up"), true),
30 m_buttonDown(_("Down"), true),
31 m_buttonEdit(_("Edit"), true),
32 #endif
33 m_buttonDuplicate(_("Duplicate")),
34 m_statusLabel("", Gtk::ALIGN_START),
35 m_labelComment(_("Comment"), Gtk::ALIGN_START),
36 m_deleteButton(" " + Glib::ustring(_("Delete")) + " " + UNICODE_PRIMARY_KEY_SYMBOL + UNICODE_ERASE_KEY_SYMBOL),
37 m_inverseDeleteButton(" " + Glib::ustring(_("Inverse Delete")) + " " + UNICODE_ALT_KEY_SYMBOL + UNICODE_ERASE_KEY_SYMBOL),
38 #if HAS_GTKMM_STOCK
39 m_applyButton(Gtk::Stock::APPLY),
40 m_cancelButton(Gtk::Stock::CANCEL),
41 #else
42 m_applyButton(_("Apply"), true),
43 m_cancelButton(_("_Cancel"), true),
44 #endif
45 m_altKeyDown(false),
46 m_primaryKeyDown(false)
47 {
48 add(m_vbox);
49
50 set_title(_("Setup Macros"));
51
52 #if !HAS_GTKMM_STOCK
53 // see : https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
54 m_buttonUp.set_icon_name("go-up");
55 m_buttonDown.set_icon_name("go-down");
56 m_buttonEdit.set_icon_name("insert-text");
57 #endif
58
59 if (!Settings::singleton()->autoRestoreWindowDimension) {
60 set_default_size(680, 500);
61 set_position(Gtk::WIN_POS_CENTER);
62 }
63
64 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION >= 12)
65 m_labelIntro.set_margin_start(10);
66 m_labelIntro.set_margin_end(10);
67 #else
68 m_labelIntro.set_padding(10, 10);
69 #endif
70
71 #if GTKMM_MAJOR_VERSION >= 3
72 m_labelIntro.set_line_wrap();
73 #endif
74 m_labelIntro.set_text(
75 _("A macro is a list of parameters and corresponding values which "
76 "should be applied to the instrument editor when the macro is "
77 "triggered by the user. A macro is triggered either by selecting "
78 "the macro from the \"Macro\" menu, or by hitting the macro's "
79 "respective keyboard accelerator (F1 to F12), or by applying a "
80 "previously copied macro from the clipboard.")
81 );
82 m_vbox.pack_start(m_labelIntro, Gtk::PACK_SHRINK);
83
84 #if HAS_GTKMM_STOCK
85 m_addFromClipboardButton.set_image(
86 *new Gtk::Image(Gtk::Stock::ADD, Gtk::ICON_SIZE_BUTTON)
87 );
88 m_addFromSelectionButton.set_image(
89 *new Gtk::Image(Gtk::Stock::ADD, Gtk::ICON_SIZE_BUTTON)
90 );
91 m_buttonDuplicate.set_image(
92 *new Gtk::Image(Gtk::Stock::COPY, Gtk::ICON_SIZE_BUTTON)
93 );
94 m_deleteButton.set_image(
95 *new Gtk::Image(Gtk::Stock::DELETE, Gtk::ICON_SIZE_BUTTON)
96 );
97 m_inverseDeleteButton.set_image(
98 *new Gtk::Image(Gtk::Stock::DELETE, Gtk::ICON_SIZE_BUTTON)
99 );
100 #else // since GTKMM 3.90 ...
101 // see : https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
102 m_addFromClipboardButton.set_icon_name("list-add");
103 m_addFromSelectionButton.set_icon_name("list-add");
104 m_buttonDuplicate.set_icon_name("edit-copy");
105 m_deleteButton.set_icon_name("edit-delete");
106 m_inverseDeleteButton.set_icon_name("edit-delete");
107 #endif
108 m_addFromClipboardButton.set_tooltip_text(_("Create a new macro from the content currently available on the clipboard."));
109 m_addFromSelectionButton.set_tooltip_text(_("Create a new macro from the currently selected dimension region's parameters currently shown on the main window."));
110 m_buttonDuplicate.set_tooltip_text(_("Duplicate the selected macro(s). The new macro(s) will be appended to the end of the list."));
111 m_buttonUp.set_tooltip_text(
112 _("Move the selected macro up in the list, which changes its order of "
113 "appearance in the main window's \"Macro\" menu and changes to which "
114 "keyboard accelerator key (F1 to F12) the macro is assigned to."));
115 m_buttonDown.set_tooltip_text(
116 _("Move the selected macro down in the list, which changes its order of "
117 "appearance in the main window's \"Macro\" menu and changes to which "
118 "keyboard accelerator key (F1 to F12) the macro is assigned to."));
119 m_buttonEdit.set_tooltip_text(_("Open the Macro Editor for the selected macro."));
120 m_deleteButton.set_tooltip_text(_("Delete the selected macros."));
121 m_inverseDeleteButton.set_tooltip_text(_("Delete all macros except the selected ones."));
122 m_addHBox.pack_start(m_addFromClipboardButton, Gtk::PACK_EXPAND_WIDGET/*, 15*/);
123 m_addHBox.pack_start(m_addFromSelectionButton, Gtk::PACK_EXPAND_WIDGET/*, 15*/);
124 m_vbox.pack_start(m_addHBox, Gtk::PACK_SHRINK);
125
126 m_vbox.pack_start(m_mainHBox);
127 m_vbox.set_spacing(5);
128
129 // create Macro list treeview (including its data model)
130 m_treeStoreMacros = MacroListTreeStore::create(m_treeModelMacros);
131 m_treeViewMacros.set_model(m_treeStoreMacros);
132 m_treeViewMacros.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE);
133 //m_treeViewMacro.set_tooltip_text(_(""));
134 m_treeViewMacros.append_column(_("Key"), m_treeModelMacros.m_col_key);
135 m_treeViewMacros.append_column_editable(_("Macro Name"), m_treeModelMacros.m_col_name);
136 m_treeViewMacros.append_column(_("Created"), m_treeModelMacros.m_col_created);
137 m_treeViewMacros.append_column(_("Modified"), m_treeModelMacros.m_col_modified);
138 m_treeViewMacros.set_tooltip_column(m_treeModelMacros.m_col_comment.index());
139 // make all rows gray text, except of "Name" column
140 for (int i = 0; i <= 3; ++i) {
141 if (i == m_treeModelMacros.m_col_name.index())
142 continue;
143 Gtk::TreeViewColumn* column = m_treeViewMacros.get_column(i);
144 Gtk::CellRendererText* cellrenderer =
145 dynamic_cast<Gtk::CellRendererText*>(column->get_first_cell());
146 cellrenderer->property_foreground().set_value("#bababa");
147 }
148 /*{
149 Gtk::TreeViewColumn* column = m_treeViewMacro.get_column(0);
150 Gtk::CellRendererText* cellrenderer =
151 dynamic_cast<Gtk::CellRendererText*>(column->get_first_cell());
152 column->add_attribute(
153 cellrenderer->property_foreground(), m_SamplesModel.m_color
154 );
155 }*/
156 /*{
157 Gtk::TreeViewColumn* column = m_treeViewMacro.get_column(1);
158 Gtk::CellRendererText* cellrenderer =
159 dynamic_cast<Gtk::CellRendererText*>(column->get_first_cell());
160 column->add_attribute(
161 cellrenderer->property_foreground(), m_SamplesModel.m_color
162 );
163 }*/
164 m_treeViewMacros.set_headers_visible(true);
165 m_treeViewMacros.get_selection()->signal_changed().connect(
166 sigc::mem_fun(*this, &MacrosSetup::onTreeViewSelectionChanged)
167 );
168 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2
169 m_treeViewMacros.signal_key_release_event().connect(
170 #else
171 m_treeViewMacros.signal_key_release_event().connect_notify(
172 #endif
173 sigc::mem_fun(*this, &MacrosSetup::onMacroTreeViewKeyRelease)
174 );
175 m_treeStoreMacros->signal_row_changed().connect(
176 sigc::mem_fun(*this, &MacrosSetup::onMacroTreeViewRowValueChanged)
177 );
178 m_ignoreTreeViewValueChange = false;
179 m_ignoreCommentTextViewChange = false;
180
181 m_scrolledWindow.add(m_treeViewMacros);
182 m_scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
183 m_mainHBox.pack_start(m_scrolledWindow);
184
185 m_rvbox.set_spacing(5);
186
187 m_mainHBox.pack_start(m_rvbox, Gtk::PACK_SHRINK);
188 m_mainHBox.set_spacing(5),
189 m_rvbox.set_spacing(5);
190 m_rvbox.pack_start(m_buttonDuplicate, Gtk::PACK_SHRINK);
191 m_rvbox.pack_start(m_detailsButtonBox, Gtk::PACK_SHRINK);
192
193 //m_textViewComment.set_left_margin(3);
194 //m_textViewComment.set_right_margin(3);
195 m_textViewComment.set_indent(2);
196 m_textViewComment.set_tooltip_text(
197 _("Write arbitrary comments for the selected macro which help you to "
198 "remember the purpose of your macro. The comment will be shown as "
199 "tooltip in the main window's \"Macro\" menu for example.")
200 );
201 m_scrolledWindowComment.add(m_textViewComment);
202 m_scrolledWindowComment.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
203 m_labelComment.set_markup(
204 "<b>" + m_labelComment.get_text() + "</b>"
205 );
206 m_rvbox.pack_start(m_labelComment, Gtk::PACK_SHRINK);
207 m_rvbox.pack_start(m_scrolledWindowComment);
208
209 m_detailsButtonBox.pack_start(m_buttonUp);
210 m_detailsButtonBox.pack_start(m_buttonDown);
211 m_detailsButtonBox.pack_start(m_buttonEdit);
212
213 m_buttonBoxL.set_layout(Gtk::BUTTONBOX_START);
214 m_buttonBoxL.pack_start(m_deleteButton);
215 m_buttonBoxL.pack_start(m_inverseDeleteButton);
216 m_deleteButton.set_sensitive(false);
217 m_inverseDeleteButton.set_sensitive(false);
218 m_buttonDuplicate.set_sensitive(false);
219 m_buttonUp.set_sensitive(false);
220 m_buttonDown.set_sensitive(false);
221
222 m_buttonBox.set_layout(Gtk::BUTTONBOX_END);
223 m_buttonBox.pack_start(m_applyButton);
224 m_buttonBox.pack_start(m_cancelButton);
225 m_applyButton.set_can_default();
226 m_applyButton.set_sensitive(false);
227 m_applyButton.grab_focus();
228
229 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION >= 12)
230 m_statusLabel.set_margin_start(6);
231 m_statusLabel.set_margin_end(6);
232 #elif GTKMM_MAJOR_VERSION >= 3
233 m_statusLabel.set_margin_left(6);
234 m_statusLabel.set_margin_right(6);
235 #else
236 m_statusHBox.set_spacing(6);
237 #endif
238
239 m_statusHBox.pack_start(m_statusLabel);
240 #if HAS_GTKMM_SHOW_ALL_CHILDREN
241 m_statusHBox.show_all_children();
242 #endif
243
244 m_footerHBox.pack_start(m_buttonBoxL, Gtk::PACK_SHRINK);
245 m_footerHBox.pack_start(m_statusHBox);
246 m_footerHBox.pack_start(m_buttonBox, Gtk::PACK_SHRINK);
247
248 m_vbox.pack_start(m_footerHBox, Gtk::PACK_SHRINK);
249
250 m_addFromClipboardButton.signal_clicked().connect(
251 sigc::mem_fun(*this, &MacrosSetup::onButtonAddFromClipboard)
252 );
253
254 m_addFromSelectionButton.signal_clicked().connect(
255 sigc::mem_fun(*this, &MacrosSetup::onButtonAddFromSelection)
256 );
257
258 m_buttonUp.signal_clicked().connect(
259 sigc::mem_fun(*this, &MacrosSetup::onButtonUp)
260 );
261
262 m_buttonDown.signal_clicked().connect(
263 sigc::mem_fun(*this, &MacrosSetup::onButtonDown)
264 );
265
266 m_buttonEdit.signal_clicked().connect(
267 sigc::mem_fun(*this, &MacrosSetup::onButtonEdit)
268 );
269
270 m_buttonDuplicate.signal_clicked().connect(
271 sigc::mem_fun(*this, &MacrosSetup::onButtonDuplicate)
272 );
273
274 m_textViewComment.get_buffer()->signal_changed().connect(
275 sigc::mem_fun(*this, &MacrosSetup::onCommentTextViewChanged)
276 );
277
278 m_applyButton.signal_clicked().connect(
279 sigc::mem_fun(*this, &MacrosSetup::onButtonApply)
280 );
281
282 m_cancelButton.signal_clicked().connect(
283 sigc::mem_fun(*this, &MacrosSetup::onButtonCancel)
284 );
285
286 m_deleteButton.signal_clicked().connect(
287 sigc::mem_fun(*this, &MacrosSetup::deleteSelectedRows)
288 );
289
290 m_inverseDeleteButton.signal_clicked().connect(
291 sigc::mem_fun(*this, &MacrosSetup::inverseDeleteSelectedRows)
292 );
293
294 signal_hide().connect(
295 sigc::mem_fun(*this, &MacrosSetup::onWindowHide)
296 );
297
298 signal_delete_event().connect(
299 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2
300 sigc::mem_fun(*this, &MacrosSetup::onWindowDelete)
301 #else
302 sigc::mem_fun(*this, &MacrosSetup::onWindowDeleteP)
303 #endif
304 );
305
306 signal_key_press_event().connect(
307 sigc::mem_fun(*this, &MacrosSetup::onKeyPressed)
308 );
309 signal_key_release_event().connect(
310 sigc::mem_fun(*this, &MacrosSetup::onKeyReleased)
311 );
312
313 #if HAS_GTKMM_SHOW_ALL_CHILDREN
314 show_all_children();
315 #endif
316 updateStatus();
317 }
318
319 MacrosSetup::~MacrosSetup() {
320 printf("MacrosSetup destruct\n");
321 }
322
323 void MacrosSetup::setMacros(const std::vector<Serialization::Archive>& macros,
324 Serialization::Archive* pClipboardContent,
325 gig::DimensionRegion* pSelectedDimRgn)
326 {
327 // copy for non-destructive editing
328 m_macros = macros;
329
330 m_clipboardContent = pClipboardContent;
331 m_selectedDimRgn = pSelectedDimRgn;
332
333 reloadTreeView();
334 }
335
336 void MacrosSetup::onButtonAddFromClipboard() {
337 printf("+fromClipboard\n");
338 if (!m_clipboardContent) return;
339 if (!m_clipboardContent->rootObject()) return;
340 m_macros.push_back(*m_clipboardContent);
341 m_modified = true;
342 reloadTreeView();
343 }
344
345 void MacrosSetup::onButtonAddFromSelection() {
346 printf("+fromSelection\n");
347 if (!m_selectedDimRgn) return;
348 std::string errorText;
349 try {
350 Serialization::Archive archive;
351 archive.serialize(m_selectedDimRgn);
352 //archive.setName("Unnamed Macro");
353 m_macros.push_back(archive);
354 m_modified = true;
355 reloadTreeView();
356 } catch (Serialization::Exception e) {
357 errorText = e.Message;
358 } catch (...) {
359 errorText = _("Unknown exception while creating macro");
360 }
361 if (!errorText.empty()) {
362 Glib::ustring txt = _("Couldn't create macro:\n") + errorText;
363 Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
364 msg.run();
365 }
366 }
367
368 void MacrosSetup::moveByDir(int d) {
369 if (d < -1 || d > 1) return;
370 int index = getSelectedMacroIndex();
371 if (index < 0) return;
372 if (d == -1 && index == 0) return;
373 if (d == +1 && index >= m_macros.size() - 1) return;
374
375 // swap macros
376 std::swap(m_macros[index + d], m_macros[index]);
377
378 // swap tree view rows
379 Gtk::TreePath p1(ToString(index + d));
380 Gtk::TreePath p2(ToString(index));
381 Gtk::TreeModel::iterator it1 = m_treeStoreMacros->get_iter(p1);
382 Gtk::TreeModel::iterator it2 = m_treeStoreMacros->get_iter(p2);
383 m_treeStoreMacros->iter_swap(it1, it2);
384 int idx1 = (*it1)[m_treeModelMacros.m_col_index];
385 int idx2 = (*it2)[m_treeModelMacros.m_col_index];
386 (*it1)[m_treeModelMacros.m_col_index] = idx2;
387 (*it2)[m_treeModelMacros.m_col_index] = idx1;
388 Glib::ustring s1 = (*it1)[m_treeModelMacros.m_col_key];
389 Glib::ustring s2 = (*it2)[m_treeModelMacros.m_col_key];
390 (*it1)[m_treeModelMacros.m_col_key] = s2;
391 (*it2)[m_treeModelMacros.m_col_key] = s1;
392
393 m_modified = true;
394 }
395
396 void MacrosSetup::onButtonUp() {
397 moveByDir(-1);
398 }
399
400 void MacrosSetup::onButtonDown() {
401 moveByDir(+1);
402 }
403
404 void MacrosSetup::onButtonDuplicate() {
405 Glib::RefPtr<Gtk::TreeSelection> sel = m_treeViewMacros.get_selection();
406 std::vector<Gtk::TreeModel::Path> rows = sel->get_selected_rows();
407 duplicateRows(rows);
408 }
409
410 void MacrosSetup::onButtonEdit() {
411 Serialization::Archive* macro = getSelectedMacro();
412 if (!macro) return;
413
414 m_modifiedBeforeMacroEditor = isModified();
415
416 MacroEditor* editor = new MacroEditor();
417 editor->setMacro(macro, false);
418 editor->signal_changes_applied().connect(
419 sigc::mem_fun(*this, &MacrosSetup::onMacroEditorAppliedChanges)
420 );
421 editor->show();
422 }
423
424 void MacrosSetup::onMacroEditorAppliedChanges() {
425 // so that the user does not need to click on a Apply buttons twice
426 if (!m_modifiedBeforeMacroEditor)
427 onButtonApply();
428 updateStatus();
429 }
430
431 void MacrosSetup::onCommentTextViewChanged() {
432 if (m_ignoreCommentTextViewChange) return;
433 //printf("textChanged\n");
434 Serialization::Archive* macro = getSelectedMacro();
435 if (!macro) return;
436 macro->setComment(
437 m_textViewComment.get_buffer()->get_text()
438 );
439 updateStatus();
440 }
441
442 int MacrosSetup::getSelectedMacroIndex() const {
443 std::vector<Gtk::TreeModel::Path> v = m_treeViewMacros.get_selection()->get_selected_rows();
444 if (v.empty()) return -1;
445 Gtk::TreeModel::iterator it = m_treeStoreMacros->get_iter(v[0]);
446 if (!it) return -1;
447 const Gtk::TreeModel::Row& row = *it;
448 int index = row[m_treeModelMacros.m_col_index];
449 if (index < 0 || index >= m_macros.size()) return -1;
450 return index;
451 }
452
453 Serialization::Archive* MacrosSetup::getSelectedMacro() {
454 int index = getSelectedMacroIndex();
455 if (index < 0) return NULL;
456 return &m_macros[index];
457 }
458
459 static Glib::ustring indexToAccKey(uint index) {
460 if (index >= 19) return "";
461 return "F" + ToString(index+1);
462 }
463
464 static int daysAgo(const tm& t) {
465 time_t now;
466 time(&now);
467 tm* pNow = localtime(&now);
468 if (!pNow) return 0;
469 if (pNow->tm_year == t.tm_year &&
470 pNow->tm_mon == t.tm_mon &&
471 pNow->tm_mday == t.tm_mday) return 0;
472 time_t past = mktime((tm*)&t);
473 return ceil(difftime(now, past) / 60.0 / 60.0 / 24.0);
474 }
475
476 static Glib::ustring humanShortStr(const tm& t) {
477 int iDaysAgo = daysAgo(t);
478 char buf[70];
479 if (iDaysAgo == 0) {
480 // C-Time specification for a time somewhere today (see 'man strftime()').
481 if (strftime(buf, sizeof buf, _("%R"), &t))
482 return buf;
483 } else if (iDaysAgo == 1) {
484 // C-Time specification for a time somewhere yesterday (see 'man strftime()').
485 if (strftime(buf, sizeof buf, _("Yesterday %R"), &t))
486 return buf;
487 } else if (iDaysAgo == 2) {
488 // C-Time specification for a time somewhere 2 days ago (see 'man strftime()').
489 if (strftime(buf, sizeof buf, _("2 days ago %R"), &t))
490 return buf;
491 } else {
492 // C-Time specification for a time far more than 2 days ago (see 'man strftime()').
493 if (strftime(buf, sizeof buf, "%d %b %Y", &t))
494 return buf;
495 }
496 return "";
497 }
498
499 void MacrosSetup::reloadTreeView() {
500 m_ignoreTreeViewValueChange = true;
501
502 m_treeStoreMacros->clear();
503
504 for (int iMacro = 0; iMacro < m_macros.size(); ++iMacro) {
505 const Serialization::Archive& macro = m_macros[iMacro];
506
507 Gtk::TreeModel::iterator iter = m_treeStoreMacros->append();
508 Gtk::TreeModel::Row row = *iter;
509 row[m_treeModelMacros.m_col_key] = indexToAccKey(iMacro);
510 row[m_treeModelMacros.m_col_name] = macro.name().empty() ? _("Unnamed Macro") : gig_to_utf8(macro.name());
511 row[m_treeModelMacros.m_col_comment] = macro.comment().empty() ? _("No comment assigned to this macro yet.") : gig_to_utf8(macro.comment());
512 row[m_treeModelMacros.m_col_created] = humanShortStr(macro.dateTimeCreated());
513 row[m_treeModelMacros.m_col_modified] = humanShortStr(macro.dateTimeModified());
514 row[m_treeModelMacros.m_col_index] = iMacro;
515 }
516
517 m_treeViewMacros.expand_all();
518
519 updateStatus();
520
521 m_ignoreTreeViewValueChange = false;
522 }
523
524 void MacrosSetup::onTreeViewSelectionChanged() {
525 std::vector<Gtk::TreeModel::Path> v = m_treeViewMacros.get_selection()->get_selected_rows();
526 const bool bValidSelection = !v.empty();
527 m_deleteButton.set_sensitive(bValidSelection);
528 m_inverseDeleteButton.set_sensitive(bValidSelection);
529 m_buttonEdit.set_sensitive(bValidSelection);
530 m_buttonDuplicate.set_sensitive(bValidSelection);
531 m_buttonUp.set_sensitive(bValidSelection);
532 m_buttonDown.set_sensitive(bValidSelection);
533
534 // update comment text view
535 std::string sComment;
536 Serialization::Archive* macro = getSelectedMacro();
537 if (macro)
538 sComment = macro->comment();
539 m_ignoreCommentTextViewChange = true;
540 m_textViewComment.get_buffer()->set_text(sComment);
541 m_ignoreCommentTextViewChange = false;
542 m_textViewComment.set_sensitive(bValidSelection);
543 }
544
545 // Cmd key on Mac, Ctrl key on all other OSs
546 static const guint primaryKeyL =
547 #if defined(__APPLE__)
548 GDK_KEY_Meta_L;
549 #else
550 GDK_KEY_Control_L;
551 #endif
552
553 static const guint primaryKeyR =
554 #if defined(__APPLE__)
555 GDK_KEY_Meta_R;
556 #else
557 GDK_KEY_Control_R;
558 #endif
559
560 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2
561 bool MacrosSetup::onKeyPressed(Gdk::EventKey& _key) {
562 GdkEventKey* key = _key.gobj();
563 #else
564 bool MacrosSetup::onKeyPressed(GdkEventKey* key) {
565 #endif
566 //printf("key down 0x%x\n", key->keyval);
567 if (key->keyval == GDK_KEY_Alt_L || key->keyval == GDK_KEY_Alt_R)
568 m_altKeyDown = true;
569 if (key->keyval == primaryKeyL || key->keyval == primaryKeyR)
570 m_primaryKeyDown = true;
571 return false;
572 }
573
574 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2
575 bool MacrosSetup::onKeyReleased(Gdk::EventKey& _key) {
576 GdkEventKey* key = _key.gobj();
577 #else
578 bool MacrosSetup::onKeyReleased(GdkEventKey* key) {
579 #endif
580 //printf("key up 0x%x\n", key->keyval);
581 if (key->keyval == GDK_KEY_Alt_L || key->keyval == GDK_KEY_Alt_R)
582 m_altKeyDown = false;
583 if (key->keyval == primaryKeyL || key->keyval == primaryKeyR)
584 m_primaryKeyDown = false;
585 if (m_primaryKeyDown && key->keyval == GDK_KEY_b)
586 onButtonAddFromClipboard();
587 if (m_primaryKeyDown && key->keyval == GDK_KEY_s)
588 onButtonAddFromSelection();
589 return false;
590 }
591
592 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2
593 bool MacrosSetup::onMacroTreeViewKeyRelease(Gdk::EventKey& _key) {
594 GdkEventKey* key = _key.gobj();
595 #else
596 void MacrosSetup::onMacroTreeViewKeyRelease(GdkEventKey* key) {
597 #endif
598 if (key->keyval == GDK_KEY_BackSpace || key->keyval == GDK_KEY_Delete) {
599 if (m_altKeyDown)
600 inverseDeleteSelectedRows();
601 else if (m_primaryKeyDown)
602 deleteSelectedRows();
603 }
604 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2
605 return false;
606 #endif
607 }
608
609 void MacrosSetup::onMacroTreeViewRowValueChanged(const Gtk::TreeModel::Path& path,
610 const Gtk::TreeModel::iterator& iter)
611 {
612 if (m_ignoreTreeViewValueChange) return;
613 if (!iter) return;
614 Gtk::TreeModel::Row row = *iter;
615 Glib::ustring name = row[m_treeModelMacros.m_col_name];
616 int index = row[m_treeModelMacros.m_col_index];
617 m_macros[index].setName(name);
618 //reloadTreeView();
619 m_modified = true;
620 updateStatus();
621 }
622
623 void MacrosSetup::deleteSelectedRows() {
624 Glib::RefPtr<Gtk::TreeSelection> sel = m_treeViewMacros.get_selection();
625 std::vector<Gtk::TreeModel::Path> rows = sel->get_selected_rows();
626 deleteRows(rows);
627 }
628
629 void MacrosSetup::duplicateRows(const std::vector<Gtk::TreeModel::Path>& rows) {
630 if (!rows.empty()) m_modified = true;
631 bool bError = false;
632 for (int r = 0; r < rows.size(); ++r) {
633 Gtk::TreeModel::iterator it = m_treeStoreMacros->get_iter(rows[r]);
634 if (!it) continue;
635 Gtk::TreeModel::Row row = *it;
636 int index = row[m_treeModelMacros.m_col_index];
637 if (index < 0 || index >= m_macros.size()) continue;
638
639 Serialization::Archive clone = m_macros[index];
640 if (!endsWith(clone.name(), "COPY", true)) {
641 clone.setName(
642 (clone.name().empty()) ? "Unnamed COPY" : (clone.name() + " COPY")
643 );
644 }
645 try {
646 // enforce re-encoding the abstract object model and resetting the
647 // 'modified' state
648 clone.rawData();
649 } catch (Serialization::Exception e) {
650 bError = true;
651 e.PrintMessage();
652 continue;
653 } catch (...) {
654 bError = true;
655 std::cerr << "Unknown exception while cloning macro." << std::endl;
656 continue;
657 }
658 // finally add new cloned macro
659 m_macros.push_back(clone);
660 }
661 reloadTreeView();
662 if (bError) {
663 Glib::ustring txt = _("At least one of the macros could not be cloned due to an error (check console output).");
664 Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
665 msg.run();
666 }
667 }
668
669 void MacrosSetup::deleteRows(const std::vector<Gtk::TreeModel::Path>& rows) {
670 if (!rows.empty()) m_modified = true;
671 std::set<int> macros;
672 for (int r = rows.size() - 1; r >= 0; --r) {
673 Gtk::TreeModel::iterator it = m_treeStoreMacros->get_iter(rows[r]);
674 if (!it) continue;
675 Gtk::TreeModel::Row row = *it;
676 macros.insert(
677 row[m_treeModelMacros.m_col_index]
678 );
679 }
680 for (std::set<int>::const_reverse_iterator it = macros.rbegin();
681 it != macros.rend(); ++it)
682 {
683 m_macros.erase(m_macros.begin() + *it);
684 }
685 reloadTreeView();
686 }
687
688 static bool _onEachTreeRow(const Gtk::TreeModel::Path& input, std::vector<Gtk::TreeModel::Path>* output) {
689 output->push_back(input);
690 return false; // continue walking the tree
691 }
692
693 void MacrosSetup::inverseDeleteSelectedRows() {
694 // get all rows of tree view
695 std::vector<Gtk::TreeModel::Path> rows;
696 m_treeViewMacros.get_model()->foreach_path(
697 sigc::bind(
698 sigc::ptr_fun(&_onEachTreeRow),
699 &rows
700 )
701 );
702
703 // erase all entries from "rows" which are currently selected
704 std::vector<Gtk::TreeModel::Path> vSelected = m_treeViewMacros.get_selection()->get_selected_rows();
705 for (int i = rows.size() - 1; i >= 0; --i) {
706 bool bIsSelected = std::find(vSelected.begin(), vSelected.end(),
707 rows[i]) != vSelected.end();
708 if (bIsSelected)
709 rows.erase(rows.begin() + i);
710 }
711
712 // delete those 'inverse' selected rows
713 deleteRows(rows);
714 }
715
716 void MacrosSetup::updateStatus() {
717 bool bValidSelection = !m_treeViewMacros.get_selection()->get_selected_rows().empty();
718 m_addFromClipboardButton.set_sensitive(
719 m_clipboardContent && m_clipboardContent->rootObject()
720 );
721 m_addFromSelectionButton.set_sensitive(m_selectedDimRgn);
722 m_buttonEdit.set_sensitive(bValidSelection);
723 m_buttonDuplicate.set_sensitive(bValidSelection);
724 m_buttonUp.set_sensitive(bValidSelection);
725 m_buttonDown.set_sensitive(bValidSelection);
726 m_applyButton.set_sensitive(isModified());
727 m_textViewComment.set_sensitive(bValidSelection);
728 updateStatusBar();
729 }
730
731 void MacrosSetup::updateStatusBar() {
732 // update status text
733 std::string txt;
734 m_statusLabel.set_markup(txt);
735 }
736
737 sigc::signal<void, const std::vector<Serialization::Archive>& >& MacrosSetup::signal_macros_changed()
738 {
739 return m_macros_changed;
740 }
741
742 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2
743 bool MacrosSetup::onWindowDelete(Gdk::Event& e) {
744 return onWindowDeleteP(NULL);
745 }
746 #endif
747
748 bool MacrosSetup::onWindowDeleteP(GdkEventAny* /*e*/) {
749 //printf("onWindowDelete\n");
750
751 if (!isModified()) return false; // propagate event further (which will close this window)
752
753 //gchar* msg = g_strdup_printf(_("Apply changes to macro \"%s\" before closing?"),
754 // m_macroOriginal->Name.c_str());
755 gchar* msg = g_strdup(_("Apply changes to macro list before closing?"));
756 Gtk::MessageDialog dialog(*this, msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE);
757 g_free(msg);
758 dialog.set_secondary_text(_("If you close without applying, your changes will be lost."));
759 dialog.add_button(_("Close _Without Applying"), Gtk::RESPONSE_NO);
760 dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL);
761 dialog.add_button(_("_Apply"), Gtk::RESPONSE_YES);
762 dialog.set_default_response(Gtk::RESPONSE_YES);
763 int response = dialog.run();
764 dialog.hide();
765
766 // user decided to close this window without saving
767 if (response == Gtk::RESPONSE_NO)
768 return false; // propagate event further (which will close this window)
769
770 // user cancelled dialog, thus don't close this window
771 if (response == Gtk::RESPONSE_CANCEL) {
772 show();
773 return true; // drop event (prevents closing this window)
774 }
775
776 // user wants to apply the changes, afterwards close window
777 if (response == Gtk::RESPONSE_YES) {
778 onButtonApply();
779 return false; // propagate event further (which will close this window)
780 }
781
782 // should never ever make it to this point actually
783 return false;
784 }
785
786 bool MacrosSetup::isModified() const {
787 if (m_modified) return true;
788 bool bModified = false;
789 for (int i = 0; i < m_macros.size(); ++i) {
790 if (m_macros[i].isModified()) {
791 bModified = true;
792 break;
793 }
794 }
795 return bModified;
796 }
797
798 void MacrosSetup::onButtonCancel() {
799 bool dropEvent = onWindowDeleteP(NULL);
800 if (dropEvent) return;
801 hide();
802 }
803
804 void MacrosSetup::onButtonApply() {
805 std::string errorText;
806 try {
807 for (int i = 0; i < m_macros.size(); ++i) {
808 if (!m_macros[i].isModified()) continue;
809 // enforce re-encoding the abstract object model and resetting the
810 // 'modified' state
811 m_macros[i].rawData();
812 }
813 m_modified = false;
814 } catch (Serialization::Exception e) {
815 errorText = e.Message;
816 } catch (...) {
817 errorText = _("Unknown exception while applying macro changes");
818 }
819 if (!errorText.empty()) {
820 Glib::ustring txt = _("Couldn't apply macro changes:\n") + errorText;
821 Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
822 msg.run();
823 } else {
824 // update MainWindow with edited list of macros
825 m_macros_changed.emit(m_macros);
826 }
827 updateStatus();
828 }
829
830 void MacrosSetup::onWindowHide() {
831 delete this; // this is the end, my friend
832 }

  ViewVC Help
Powered by ViewVC