/[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 3157 - (show annotations) (download)
Mon May 8 17:30:10 2017 UTC (6 years, 10 months ago) by schoenebeck
File size: 13639 byte(s)
* Fixed potentially unhandled exceptions when app is loading
  its config file.
* WIP: Introduced user configurable list of macros which are
  auto assigned to keyboard accelerators (F1 ... F12) and
  saved along with the app's config file (added under new
  main menu bar section "Macro").
* Bumped version (1.0.0.svn38).

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 "MacrosSetup.h"
9 #include "global.h"
10 #include <assert.h>
11 #include <set>
12
13 MacrosSetup::MacrosSetup() :
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 {
21 add(m_vbox);
22
23 set_title(_("Setup Macros"));
24
25 set_default_size(800, 600);
26
27 // create Macro list treeview (including its data model)
28 m_treeStoreMacros = MacroListTreeStore::create(m_treeModelMacros);
29 m_treeViewMacros.set_model(m_treeStoreMacros);
30 m_treeViewMacros.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE);
31 //m_treeViewMacro.set_tooltip_text(_(""));
32 m_treeViewMacros.append_column(_("Key"), m_treeModelMacros.m_col_key);
33 m_treeViewMacros.append_column_editable(_("Name"), m_treeModelMacros.m_col_name);
34 m_treeViewMacros.append_column(_("Created"), m_treeModelMacros.m_col_created);
35 m_treeViewMacros.append_column(_("Modified"), m_treeModelMacros.m_col_modified);
36 m_treeViewMacros.set_tooltip_column(m_treeModelMacros.m_col_comment.index());
37 // make all rows gray text, except of "Name" column
38 for (int i = 0; i <= 3; ++i) {
39 if (i == m_treeModelMacros.m_col_name.index())
40 continue;
41 Gtk::TreeViewColumn* column = m_treeViewMacros.get_column(i);
42 Gtk::CellRendererText* cellrenderer =
43 dynamic_cast<Gtk::CellRendererText*>(column->get_first_cell());
44 cellrenderer->property_foreground().set_value("#bababa");
45 }
46 /*{
47 Gtk::TreeViewColumn* column = m_treeViewMacro.get_column(0);
48 Gtk::CellRendererText* cellrenderer =
49 dynamic_cast<Gtk::CellRendererText*>(column->get_first_cell());
50 column->add_attribute(
51 cellrenderer->property_foreground(), m_SamplesModel.m_color
52 );
53 }*/
54 /*{
55 Gtk::TreeViewColumn* column = m_treeViewMacro.get_column(1);
56 Gtk::CellRendererText* cellrenderer =
57 dynamic_cast<Gtk::CellRendererText*>(column->get_first_cell());
58 column->add_attribute(
59 cellrenderer->property_foreground(), m_SamplesModel.m_color
60 );
61 }*/
62 m_treeViewMacros.set_headers_visible(true);
63 m_treeViewMacros.get_selection()->signal_changed().connect(
64 sigc::mem_fun(*this, &MacrosSetup::onTreeViewSelectionChanged)
65 );
66 /*m_treeViewMacros.signal_key_release_event().connect_notify(
67 sigc::mem_fun(*this, &MacrosSetup::onMacroTreeViewKeyRelease)
68 );*/
69 m_treeStoreMacros->signal_row_changed().connect(
70 sigc::mem_fun(*this, &MacrosSetup::onMacroTreeViewRowValueChanged)
71 );
72 m_ignoreTreeViewValueChange = false;
73
74 m_scrolledWindow.add(m_treeViewMacros);
75 m_scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
76 m_vbox.pack_start(m_scrolledWindow);
77
78 m_buttonBoxL.set_layout(Gtk::BUTTONBOX_START);
79 m_buttonBoxL.pack_start(m_deleteButton);
80 m_buttonBoxL.pack_start(m_inverseDeleteButton);
81 m_deleteButton.set_sensitive(false);
82 m_inverseDeleteButton.set_sensitive(false);
83
84 m_buttonBox.set_layout(Gtk::BUTTONBOX_END);
85 m_buttonBox.pack_start(m_applyButton);
86 m_buttonBox.pack_start(m_cancelButton);
87 m_applyButton.set_can_default();
88 m_applyButton.set_sensitive(false);
89 m_applyButton.grab_focus();
90
91 #if GTKMM_MAJOR_VERSION >= 3
92 m_statusLabel.set_margin_left(6);
93 m_statusLabel.set_margin_right(6);
94 #else
95 m_statusHBox.set_spacing(6);
96 #endif
97
98 m_statusHBox.pack_start(m_statusLabel);
99 m_statusHBox.show_all_children();
100
101 m_footerHBox.pack_start(m_buttonBoxL, Gtk::PACK_SHRINK);
102 m_footerHBox.pack_start(m_statusHBox);
103 m_footerHBox.pack_start(m_buttonBox, Gtk::PACK_SHRINK);
104
105 m_vbox.pack_start(m_footerHBox, Gtk::PACK_SHRINK);
106
107 m_applyButton.signal_clicked().connect(
108 sigc::mem_fun(*this, &MacrosSetup::onButtonApply)
109 );
110
111 m_cancelButton.signal_clicked().connect(
112 sigc::mem_fun(*this, &MacrosSetup::onButtonCancel)
113 );
114
115 m_deleteButton.signal_clicked().connect(
116 sigc::mem_fun(*this, &MacrosSetup::deleteSelectedRows)
117 );
118
119 m_inverseDeleteButton.signal_clicked().connect(
120 sigc::mem_fun(*this, &MacrosSetup::inverseDeleteSelectedRows)
121 );
122
123 signal_hide().connect(
124 sigc::mem_fun(*this, &MacrosSetup::onWindowHide)
125 );
126
127 signal_delete_event().connect(
128 sigc::mem_fun(*this, &MacrosSetup::onWindowDelete)
129 );
130
131 signal_key_press_event().connect(
132 sigc::mem_fun(*this, &MacrosSetup::onKeyPressed)
133 );
134 signal_key_release_event().connect(
135 sigc::mem_fun(*this, &MacrosSetup::onKeyReleased)
136 );
137
138 show_all_children();
139 updateStatus();
140 }
141
142 MacrosSetup::~MacrosSetup() {
143 printf("MacrosSetup destruct\n");
144 }
145
146 void MacrosSetup::setMacros(const std::vector<Serialization::Archive>& macros) {
147 // copy for non-destructive editing
148 m_macros = macros;
149
150 reloadTreeView();
151 }
152
153 static Glib::ustring indexToAccKey(uint index) {
154 if (index >= 12) return "";
155 return "F" + ToString(index+1);
156 }
157
158 static Glib::ustring humanShortStr(const tm& t) {
159 char buf[70];
160 int daysAgo;
161 if (daysAgo = 0) {
162 // C-Time specification for a time somewhere today (see 'man strftime()').
163 if (strftime(buf, sizeof buf, _("%R"), &t))
164 return buf;
165 } else if (daysAgo = 1) {
166 // C-Time specification for a time somewhere yesterday (see 'man strftime()').
167 if (strftime(buf, sizeof buf, _("Yesterday %R"), &t))
168 return buf;
169 } else if (daysAgo = 2) {
170 // C-Time specification for a time somewhere 2 days ago (see 'man strftime()').
171 if (strftime(buf, sizeof buf, _("2 days ago %R"), &t))
172 return buf;
173 } else {
174 // C-Time specification for a time far more than 2 days ago (see 'man strftime()').
175 if (strftime(buf, sizeof buf, "%d %b %Y", &t))
176 return buf;
177 }
178 return "";
179 }
180
181 void MacrosSetup::reloadTreeView() {
182 m_ignoreTreeViewValueChange = true;
183
184 m_treeStoreMacros->clear();
185
186 for (int iMacro = 0; iMacro < m_macros.size(); ++iMacro) {
187 const Serialization::Archive& macro = m_macros[iMacro];
188
189 Gtk::TreeModel::iterator iter = m_treeStoreMacros->append();
190 Gtk::TreeModel::Row row = *iter;
191 row[m_treeModelMacros.m_col_key] = indexToAccKey(iMacro);
192 row[m_treeModelMacros.m_col_name] = gig_to_utf8(macro.name());
193 row[m_treeModelMacros.m_col_comment] = gig_to_utf8(macro.comment());
194 row[m_treeModelMacros.m_col_created] = humanShortStr(macro.dateTimeCreated());
195 row[m_treeModelMacros.m_col_modified] = humanShortStr(macro.dateTimeModified());
196 row[m_treeModelMacros.m_col_index] = iMacro;
197 }
198
199 m_treeViewMacros.expand_all();
200
201 updateStatus();
202
203 m_ignoreTreeViewValueChange = false;
204 }
205
206 void MacrosSetup::onTreeViewSelectionChanged() {
207 std::vector<Gtk::TreeModel::Path> v = m_treeViewMacros.get_selection()->get_selected_rows();
208 const bool bValidSelection = !v.empty();
209 m_deleteButton.set_sensitive(bValidSelection);
210 m_inverseDeleteButton.set_sensitive(bValidSelection);
211 }
212
213 bool MacrosSetup::onKeyPressed(GdkEventKey* key) {
214 //printf("key down 0x%x\n", key->keyval);
215 if (key->keyval == GDK_KEY_Alt_L || key->keyval == GDK_KEY_Alt_R)
216 m_altKeyDown = true;
217 return false;
218 }
219
220 bool MacrosSetup::onKeyReleased(GdkEventKey* key) {
221 //printf("key up 0x%x\n", key->keyval);
222 if (key->keyval == GDK_KEY_Alt_L || key->keyval == GDK_KEY_Alt_R)
223 m_altKeyDown = false;
224 return false;
225 }
226
227 void MacrosSetup::onMacroTreeViewKeyRelease(GdkEventKey* key) {
228 if (key->keyval == GDK_KEY_BackSpace || key->keyval == GDK_KEY_Delete) {
229 if (m_altKeyDown)
230 inverseDeleteSelectedRows();
231 else
232 deleteSelectedRows();
233 }
234 }
235
236 void MacrosSetup::onMacroTreeViewRowValueChanged(const Gtk::TreeModel::Path& path,
237 const Gtk::TreeModel::iterator& iter)
238 {
239 if (m_ignoreTreeViewValueChange) return;
240 if (!iter) return;
241 Gtk::TreeModel::Row row = *iter;
242 Glib::ustring name = row[m_treeModelMacros.m_col_name];
243 int index = row[m_treeModelMacros.m_col_index];
244 m_macros[index].setName(name);
245 //reloadTreeView();
246 }
247
248 void MacrosSetup::deleteSelectedRows() {
249 Glib::RefPtr<Gtk::TreeSelection> sel = m_treeViewMacros.get_selection();
250 std::vector<Gtk::TreeModel::Path> rows = sel->get_selected_rows();
251 deleteRows(rows);
252 }
253
254 void MacrosSetup::deleteRows(const std::vector<Gtk::TreeModel::Path>& rows) {
255 std::set<int> macros;
256 for (int r = rows.size() - 1; r >= 0; --r) {
257 Gtk::TreeModel::iterator it = m_treeStoreMacros->get_iter(rows[r]);
258 if (!it) continue;
259 Gtk::TreeModel::Row row = *it;
260 macros.insert(
261 row[m_treeModelMacros.m_col_index]
262 );
263 }
264 for (std::set<int>::const_reverse_iterator it = macros.rbegin();
265 it != macros.rend(); ++it)
266 {
267 m_macros.erase(m_macros.begin() + *it);
268 }
269 reloadTreeView();
270 }
271
272 static bool _onEachTreeRow(const Gtk::TreeModel::Path& input, std::vector<Gtk::TreeModel::Path>* output) {
273 output->push_back(input);
274 return false; // continue walking the tree
275 }
276
277 void MacrosSetup::inverseDeleteSelectedRows() {
278 // get all rows of tree view
279 std::vector<Gtk::TreeModel::Path> rows;
280 m_treeViewMacros.get_model()->foreach_path(
281 sigc::bind(
282 sigc::ptr_fun(&_onEachTreeRow),
283 &rows
284 )
285 );
286
287 // erase all entries from "rows" which are currently selected
288 std::vector<Gtk::TreeModel::Path> vSelected = m_treeViewMacros.get_selection()->get_selected_rows();
289 for (int i = rows.size() - 1; i >= 0; --i) {
290 bool bIsSelected = std::find(vSelected.begin(), vSelected.end(),
291 rows[i]) != vSelected.end();
292 if (bIsSelected)
293 rows.erase(rows.begin() + i);
294 }
295
296 // delete those 'inverse' selected rows
297 deleteRows(rows);
298 }
299
300 void MacrosSetup::updateStatus() {
301 m_applyButton.set_sensitive(isModified());
302 updateStatusBar();
303 }
304
305 void MacrosSetup::updateStatusBar() {
306 // update status text
307 std::string txt;
308 m_statusLabel.set_markup(txt);
309 }
310
311 sigc::signal<void, const std::vector<Serialization::Archive>& >& MacrosSetup::signal_macros_changed()
312 {
313 return m_macros_changed;
314 }
315
316 bool MacrosSetup::onWindowDelete(GdkEventAny* e) {
317 //printf("onWindowDelete\n");
318
319 if (!isModified()) return false; // propagate event further (which will close this window)
320
321 //gchar* msg = g_strdup_printf(_("Apply changes to macro \"%s\" before closing?"),
322 // m_macroOriginal->Name.c_str());
323 gchar* msg = g_strdup_printf(_("Apply changes to macro before closing?"));
324 Gtk::MessageDialog dialog(*this, msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE);
325 g_free(msg);
326 dialog.set_secondary_text(_("If you close without applying, your changes will be lost."));
327 dialog.add_button(_("Close _Without Applying"), Gtk::RESPONSE_NO);
328 dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL);
329 dialog.add_button(_("_Apply"), Gtk::RESPONSE_YES);
330 dialog.set_default_response(Gtk::RESPONSE_YES);
331 int response = dialog.run();
332 dialog.hide();
333
334 // user decided to close this window without saving
335 if (response == Gtk::RESPONSE_NO)
336 return false; // propagate event further (which will close this window)
337
338 // user cancelled dialog, thus don't close this window
339 if (response == Gtk::RESPONSE_CANCEL) {
340 show();
341 return true; // drop event (prevents closing this window)
342 }
343
344 // user wants to apply the changes, afterwards close window
345 if (response == Gtk::RESPONSE_YES) {
346 onButtonApply();
347 return false; // propagate event further (which will close this window)
348 }
349
350 // should never ever make it to this point actually
351 return false;
352 }
353
354 bool MacrosSetup::isModified() const {
355 bool bModified = false;
356 for (int i = 0; i < m_macros.size(); ++i) {
357 if (m_macros[i].isModified()) {
358 bModified = true;
359 break;
360 }
361 }
362 return bModified;
363 }
364
365 void MacrosSetup::onButtonCancel() {
366 bool dropEvent = onWindowDelete(NULL);
367 if (dropEvent) return;
368 hide();
369 }
370
371 void MacrosSetup::onButtonApply() {
372 std::string errorText;
373 try {
374 for (int i = 0; i < m_macros.size(); ++i) {
375 if (!m_macros[i].isModified()) continue;
376 // enforce re-encoding the abstract object model and resetting the
377 // 'modified' state
378 m_macros[i].rawData();
379 }
380 } catch (Serialization::Exception e) {
381 errorText = e.Message;
382 } catch (...) {
383 errorText = _("Unknown exception while applying macro changes");
384 }
385 if (!errorText.empty()) {
386 Glib::ustring txt = _("Couldn't apply macro changes:\n") + errorText;
387 Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
388 msg.run();
389 } else {
390 // update MainWindow with edited list of macros
391 m_macros_changed.emit(m_macros);
392 }
393 updateStatus();
394 }
395
396 void MacrosSetup::onWindowHide() {
397 delete this; // this is the end, my friend
398 }

  ViewVC Help
Powered by ViewVC