/[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 3155 - (show annotations) (download)
Sun May 7 15:32:43 2017 UTC (6 years, 10 months ago) by schoenebeck
File size: 13749 byte(s)
* Macro Editor: implemented reverse selected deletion of rows
* Macro Editor: Fixed "modified" state not being updated after
  clicking on "Apply" button.
* Macro Editor: Show implemented keyboard accelerators as
  symbols on the "Delete" (Ctrl+Del) and "Inverse Delete"
  (Alt+Del) buttons.
* Bumped version (1.0.0.svn37).

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 {
21 add(m_vbox);
22
23 set_default_size(800, 600);
24
25 // create Macro treeview (including its data model)
26 m_treeStoreMacro = MacroTreeStore::create(m_treeModelMacro);
27 m_treeViewMacro.set_model(m_treeStoreMacro);
28 m_treeViewMacro.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE);
29 //m_treeViewMacro.set_tooltip_text(_(""));
30 m_treeViewMacro.append_column(_("Key"), m_treeModelMacro.m_col_name);
31 m_treeViewMacro.append_column(_("Type"), m_treeModelMacro.m_col_type);
32 m_treeViewMacro.append_column_editable(_("Value"), m_treeModelMacro.m_col_value);
33 {
34 Gtk::TreeViewColumn* column = m_treeViewMacro.get_column(1);
35 Gtk::CellRendererText* cellrenderer =
36 dynamic_cast<Gtk::CellRendererText*>(column->get_first_cell());
37 cellrenderer->property_foreground().set_value("#bababa");
38 }
39 /*{
40 Gtk::TreeViewColumn* column = m_treeViewMacro.get_column(0);
41 Gtk::CellRendererText* cellrenderer =
42 dynamic_cast<Gtk::CellRendererText*>(column->get_first_cell());
43 column->add_attribute(
44 cellrenderer->property_foreground(), m_SamplesModel.m_color
45 );
46 }*/
47 /*{
48 Gtk::TreeViewColumn* column = m_treeViewMacro.get_column(1);
49 Gtk::CellRendererText* cellrenderer =
50 dynamic_cast<Gtk::CellRendererText*>(column->get_first_cell());
51 column->add_attribute(
52 cellrenderer->property_foreground(), m_SamplesModel.m_color
53 );
54 }*/
55 m_treeViewMacro.set_headers_visible(true);
56 m_treeViewMacro.get_selection()->signal_changed().connect(
57 sigc::mem_fun(*this, &MacroEditor::onTreeViewSelectionChanged)
58 );
59 m_treeViewMacro.signal_key_release_event().connect_notify(
60 sigc::mem_fun(*this, &MacroEditor::onMacroTreeViewKeyRelease)
61 );
62 m_treeStoreMacro->signal_row_changed().connect(
63 sigc::mem_fun(*this, &MacroEditor::onMacroTreeViewRowValueChanged)
64 );
65 m_ignoreTreeViewValueChange = false;
66
67 m_scrolledWindow.add(m_treeViewMacro);
68 m_scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
69 m_vbox.pack_start(m_scrolledWindow);
70
71 m_buttonBoxL.set_layout(Gtk::BUTTONBOX_START);
72 m_buttonBoxL.pack_start(m_deleteButton);
73 m_buttonBoxL.pack_start(m_inverseDeleteButton);
74 m_deleteButton.set_sensitive(false);
75 m_inverseDeleteButton.set_sensitive(false);
76
77 m_buttonBox.set_layout(Gtk::BUTTONBOX_END);
78 m_buttonBox.pack_start(m_applyButton);
79 m_buttonBox.pack_start(m_cancelButton);
80 m_applyButton.set_can_default();
81 m_applyButton.set_sensitive(false);
82 m_applyButton.grab_focus();
83
84 #if GTKMM_MAJOR_VERSION >= 3
85 m_statusLabel.set_margin_left(6);
86 m_statusLabel.set_margin_right(6);
87 #else
88 m_statusHBox.set_spacing(6);
89 #endif
90
91 m_statusHBox.pack_start(m_statusLabel);
92 m_statusHBox.show_all_children();
93
94 m_footerHBox.pack_start(m_buttonBoxL, Gtk::PACK_SHRINK);
95 m_footerHBox.pack_start(m_statusHBox);
96 m_footerHBox.pack_start(m_buttonBox, Gtk::PACK_SHRINK);
97
98 m_vbox.pack_start(m_footerHBox, Gtk::PACK_SHRINK);
99
100 m_applyButton.signal_clicked().connect(
101 sigc::mem_fun(*this, &MacroEditor::onButtonApply)
102 );
103
104 m_cancelButton.signal_clicked().connect(
105 sigc::mem_fun(*this, &MacroEditor::onButtonCancel)
106 );
107
108 m_deleteButton.signal_clicked().connect(
109 sigc::mem_fun(*this, &MacroEditor::deleteSelectedRows)
110 );
111
112 m_inverseDeleteButton.signal_clicked().connect(
113 sigc::mem_fun(*this, &MacroEditor::inverseDeleteSelectedRows)
114 );
115
116 signal_hide().connect(
117 sigc::mem_fun(*this, &MacroEditor::onWindowHide)
118 );
119
120 signal_delete_event().connect(
121 sigc::mem_fun(*this, &MacroEditor::onWindowDelete)
122 );
123
124 signal_key_press_event().connect(
125 sigc::mem_fun(*this, &MacroEditor::onKeyPressed)
126 );
127 signal_key_release_event().connect(
128 sigc::mem_fun(*this, &MacroEditor::onKeyReleased)
129 );
130
131 show_all_children();
132 updateStatus();
133 }
134
135 MacroEditor::~MacroEditor() {
136 printf("MacroEditor destruct\n");
137 }
138
139 void MacroEditor::setMacro(Serialization::Archive* macro) {
140 m_macroOriginal = macro;
141 if (!macro) {
142 set_title(_("No Macro"));
143 return;
144 }
145
146 //set_title(std::string(_("Macro Editor:")) + " \"" + macro->name() + "\"");
147 set_title(std::string(_("Macro Editor:")));
148
149 // copy for non-destructive editing
150 m_macro = *macro;
151
152 reloadTreeView();
153 }
154
155 void MacroEditor::buildTreeView(const Gtk::TreeModel::Row& parentRow, const Serialization::Object& parentObject) {
156 for (int iMember = 0; iMember < parentObject.members().size(); ++iMember) {
157 const Serialization::Member& member = parentObject.members()[iMember];
158 const Serialization::Object& object = m_macro.objectByUID(member.uid());
159 Gtk::TreeModel::iterator iterRow = m_treeStoreMacro->append(parentRow.children());
160 Gtk::TreeModel::Row row = *iterRow;
161 row[m_treeModelMacro.m_col_name] = gig_to_utf8(member.name());
162 row[m_treeModelMacro.m_col_type] = gig_to_utf8(member.type().asLongDescr());
163 row[m_treeModelMacro.m_col_uid] = object.uid();
164 if (object.type().isClass()) {
165 row[m_treeModelMacro.m_col_value] = "(class)";
166 buildTreeView(row, object);
167 } else {
168 row[m_treeModelMacro.m_col_value] = m_macro.valueAsString(object);
169 }
170 }
171 }
172
173 void MacroEditor::reloadTreeView() {
174 m_ignoreTreeViewValueChange = true;
175
176 m_treeStoreMacro->clear();
177
178 const Serialization::Object& rootObject = m_macro.rootObject();
179
180 Gtk::TreeModel::iterator iterRoot = m_treeStoreMacro->append();
181 Gtk::TreeModel::Row rowRoot = *iterRoot;
182 rowRoot[m_treeModelMacro.m_col_name] = "(Root)";
183 rowRoot[m_treeModelMacro.m_col_type] = gig_to_utf8(rootObject.type().asLongDescr());
184 rowRoot[m_treeModelMacro.m_col_value] = "";
185 rowRoot[m_treeModelMacro.m_col_uid] = rootObject.uid();
186
187 buildTreeView(rowRoot, rootObject);
188
189 m_treeViewMacro.expand_all();
190
191 updateStatus();
192
193 m_ignoreTreeViewValueChange = false;
194 }
195
196 void MacroEditor::onTreeViewSelectionChanged() {
197 std::vector<Gtk::TreeModel::Path> v = m_treeViewMacro.get_selection()->get_selected_rows();
198 const bool bValidSelection = !v.empty();
199 m_deleteButton.set_sensitive(bValidSelection);
200 m_inverseDeleteButton.set_sensitive(bValidSelection);
201 }
202
203 bool MacroEditor::onKeyPressed(GdkEventKey* key) {
204 //printf("key down 0x%x\n", key->keyval);
205 if (key->keyval == GDK_KEY_Alt_L || key->keyval == GDK_KEY_Alt_R)
206 m_altKeyDown = true;
207 return false;
208 }
209
210 bool MacroEditor::onKeyReleased(GdkEventKey* key) {
211 //printf("key up 0x%x\n", key->keyval);
212 if (key->keyval == GDK_KEY_Alt_L || key->keyval == GDK_KEY_Alt_R)
213 m_altKeyDown = false;
214 return false;
215 }
216
217 void MacroEditor::onMacroTreeViewKeyRelease(GdkEventKey* key) {
218 if (key->keyval == GDK_KEY_BackSpace || key->keyval == GDK_KEY_Delete) {
219 if (m_altKeyDown)
220 inverseDeleteSelectedRows();
221 else
222 deleteSelectedRows();
223 }
224 }
225
226 void MacroEditor::onMacroTreeViewRowValueChanged(const Gtk::TreeModel::Path& path,
227 const Gtk::TreeModel::iterator& iter)
228 {
229 if (m_ignoreTreeViewValueChange) return;
230 if (!iter) return;
231 Gtk::TreeModel::Row row = *iter;
232 Glib::ustring value = row[m_treeModelMacro.m_col_value];
233 Serialization::UID uid = row[m_treeModelMacro.m_col_uid];
234 Serialization::String gigvalue(gig_from_utf8(value));
235 Serialization::Object& object = m_macro.objectByUID(uid);
236 std::string errorText;
237 try {
238 m_macro.setAutoValue(object, gigvalue);
239 } catch (Serialization::Exception e) {
240 errorText = e.Message;
241 } catch (...) {
242 errorText = _("Unknown exception during object value change");
243 }
244 if (!errorText.empty()) {
245 Glib::ustring txt = _("Couldn't change value:\n") + errorText;
246 Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
247 msg.run();
248 }
249 }
250
251 void MacroEditor::deleteSelectedRows() {
252 Glib::RefPtr<Gtk::TreeSelection> sel = m_treeViewMacro.get_selection();
253 std::vector<Gtk::TreeModel::Path> rows = sel->get_selected_rows();
254 deleteRows(rows);
255 }
256
257 void MacroEditor::deleteRows(const std::vector<Gtk::TreeModel::Path>& rows) {
258 for (int r = rows.size() - 1; r >= 0; --r) {
259 Gtk::TreeModel::iterator it = m_treeStoreMacro->get_iter(rows[r]);
260 if (!it) continue;
261 Gtk::TreeModel::Row row = *it;
262 Serialization::UID uid = row[m_treeModelMacro.m_col_uid];
263 if (uid == m_macro.rootObject().uid()) continue; // prohibit deleting root object
264 Gtk::TreeModel::iterator itParent = row.parent();
265 if (!itParent) continue;
266 Gtk::TreeModel::Row rowParent = *itParent;
267 Serialization::UID uidParent = rowParent[m_treeModelMacro.m_col_uid];
268 //Serialization::Object& object = m_macro.objectByUID(uid);
269 Serialization::Object& parentObject = m_macro.objectByUID(uidParent);
270 const Serialization::Member& member = parentObject.memberByUID(uid);
271 m_macro.removeMember(parentObject, member);
272 //m_macro.remove(object);
273 }
274 reloadTreeView();
275 }
276
277 static bool _onEachTreeRow(const Gtk::TreeModel::Path& input, std::vector<Gtk::TreeModel::Path>* output) {
278 output->push_back(input);
279 return false; // continue walking the tree
280 }
281
282 void MacroEditor::inverseDeleteSelectedRows() {
283 // get all rows of tree view
284 std::vector<Gtk::TreeModel::Path> rows;
285 m_treeViewMacro.get_model()->foreach_path(
286 sigc::bind(
287 sigc::ptr_fun(&_onEachTreeRow),
288 &rows
289 )
290 );
291
292 // erase all entries from "rows" which are currently selected
293 std::vector<Gtk::TreeModel::Path> vSelected = m_treeViewMacro.get_selection()->get_selected_rows();
294 for (int i = rows.size() - 1; i >= 0; --i) {
295 bool bIsSelected = std::find(vSelected.begin(), vSelected.end(),
296 rows[i]) != vSelected.end();
297 if (bIsSelected)
298 rows.erase(rows.begin() + i);
299 }
300
301 // delete those 'inverse' selected rows
302 deleteRows(rows);
303 }
304
305 void MacroEditor::updateStatus() {
306 m_applyButton.set_sensitive(isModified());
307 updateStatusBar();
308 }
309
310 void MacroEditor::updateStatusBar() {
311 // update status text
312 std::string txt;
313 m_statusLabel.set_markup(txt);
314 }
315
316 bool MacroEditor::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 macro editor 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 macro editor
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 MacroEditor::isModified() const {
355 return m_macro.isModified();
356 }
357
358 void MacroEditor::onButtonCancel() {
359 bool dropEvent = onWindowDelete(NULL);
360 if (dropEvent) return;
361 hide();
362 }
363
364 void MacroEditor::onButtonApply() {
365 std::string errorText;
366 try {
367 // enforce re-encoding the abstract object model and resetting the
368 // 'modified' state
369 m_macro.rawData();
370 // replace actual effective Archive object which is effectively used
371 // for macro apply operations
372 *m_macroOriginal = m_macro;
373 } catch (Serialization::Exception e) {
374 errorText = e.Message;
375 } catch (...) {
376 errorText = _("Unknown exception while applying macro changes");
377 }
378 if (!errorText.empty()) {
379 Glib::ustring txt = _("Couldn't apply macro changes:\n") + errorText;
380 Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
381 msg.run();
382 }
383 updateStatus();
384 }
385
386 void MacroEditor::onWindowHide() {
387 delete this; // this is the end, my friend
388 }

  ViewVC Help
Powered by ViewVC