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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3748 - (show annotations) (download)
Sun Feb 16 17:27:03 2020 UTC (4 years, 2 months ago) by schoenebeck
File size: 18322 byte(s)
* Script 'patch' variables editor: Fixed backspace key event
  conflict while editing some patch variable's value.

* Bumped version (1.1.1.svn16).

1 /*
2 Copyright (c) MMXX 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 "ScriptPatchVars.h"
9
10 #define YELLOW "#c4950c"
11 #define MAGENTA "#790cc4"
12 #define LITE_GRAY "#bababa"
13
14 // Gtk treeview C callback if backspace key is hit while treeview has focus.
15 static gboolean onSelectCursorParent(GtkTreeView* treeview, gpointer userdata) {
16 ScriptPatchVars* self = (ScriptPatchVars*) userdata;
17
18 // This is a hack to prevent treeview from performing implied behaviour on
19 // backspace key event. By default Gtk::TreeView would change current
20 // selection whenever the user hits the backspace key (that is by navigating
21 // to current node's parent). We don't want treeview doing that, since it
22 // would prevent our custom backspace key handling (for reverting overridden
23 // patch variables to their default value) from working correctly, because
24 // our code relies on treeview's 'current selection' which would change
25 // before our handler is triggered. Unfortunately there is currently no
26 // official or cleaner way to prevent treeview from ignoring backspace key
27 // events than the following hack.
28 self->grab_focus();
29
30 // Our key event handler would not be triggered automatically in this case,
31 // so just call our handler directly instead.
32 self->deleteSelectedRows();
33
34 return false;
35 }
36
37 ScriptPatchVars::ScriptPatchVars() :
38 m_ignoreTreeViewValueChange(false), m_instrument(NULL), m_editing(false)
39 {
40 // create treeview (including its data model)
41 m_treeStore = VarsTreeStore::create(m_treeModel);
42 m_treeView.set_model(m_treeStore);
43 m_treeView.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE);
44 {
45 Gtk::CellRendererText* cellrenderer = Gtk::manage(new Gtk::CellRendererText());
46 m_treeView.append_column(_("Name"), *cellrenderer);
47 m_treeView.get_column(0)->add_attribute(cellrenderer->property_markup(), m_treeModel.m_col_name);
48 }
49 m_treeView.append_column(_("Type"), m_treeModel.m_col_type);
50 Gtk::TreeViewColumn* valueColumn = new Gtk::TreeViewColumn(_("Value"));
51 valueColumn->pack_start(m_valueCellRenderer);
52 m_treeView.append_column(*valueColumn);
53 {
54 Gtk::TreeView::Column* column = valueColumn;
55 column->add_attribute(m_valueCellRenderer.property_markup(),
56 m_treeModel.m_col_value);
57 column->add_attribute(m_valueCellRenderer.property_has_entry(),
58 m_treeModel.m_col_allowTextEntry);
59 column->add_attribute(m_valueCellRenderer.property_editable(),
60 m_treeModel.m_col_editable);
61 column->add_attribute(m_valueCellRenderer.property_model(),
62 m_treeModel.m_col_options);
63 }
64 m_valueCellRenderer.property_text_column() = 0;
65 m_valueCellRenderer.signal_edited().connect(
66 sigc::mem_fun(*this, &ScriptPatchVars::onValueCellEdited)
67 );
68 {
69 Gtk::TreeViewColumn* column = m_treeView.get_column(0);
70 Gtk::CellRendererText* cellrenderer =
71 dynamic_cast<Gtk::CellRendererText*>(column->get_first_cell());
72 column->add_attribute(cellrenderer->property_foreground(), m_treeModel.m_col_name_color);
73 column->add_attribute(cellrenderer->property_weight(), m_treeModel.m_col_name_weight);
74 }
75 {
76 Gtk::TreeViewColumn* column = m_treeView.get_column(1);
77 Gtk::CellRendererText* cellrenderer =
78 dynamic_cast<Gtk::CellRendererText*>(column->get_first_cell());
79 cellrenderer->property_foreground().set_value(LITE_GRAY);
80 }
81 {
82 Gtk::TreeViewColumn* column = m_treeView.get_column(2);
83 Gtk::CellRendererText* cellrenderer =
84 dynamic_cast<Gtk::CellRendererText*>(column->get_first_cell());
85 column->add_attribute(cellrenderer->property_foreground(), m_treeModel.m_col_value_color);
86 column->add_attribute(cellrenderer->property_weight(), m_treeModel.m_col_value_weight);
87 cellrenderer->signal_editing_started().connect(
88 [this](Gtk::CellEditable*, const Glib::ustring&) {
89 m_editing = true;
90 }
91 );
92 cellrenderer->signal_editing_canceled().connect([this]{
93 m_editing = false;
94 });
95 }
96 m_treeView.set_headers_visible(true);
97 m_treeView.get_selection()->signal_changed().connect(
98 sigc::mem_fun(*this, &ScriptPatchVars::onTreeViewSelectionChanged)
99 );
100 #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
101 m_treeView.signal_key_release_event().connect
102 #else
103 m_treeView.signal_key_release_event().connect_notify
104 #endif
105 (
106 sigc::mem_fun(*this, &ScriptPatchVars::onTreeViewKeyRelease)
107 );
108 // Gtk "row-activated" signal (Gtkmm's signal_row_activated) is only fired
109 // if this single click property is set to true
110 //m_treeView.set_activate_on_single_click(true);
111 m_treeView.signal_row_activated().connect(
112 [this](const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column){
113 printf("row activated !!!!\n");
114 //m_editing = true; // so we can detect while user is editing some value
115 }
116 );
117 m_treeView.set_has_tooltip(true);
118 m_treeView.signal_query_tooltip().connect(
119 sigc::mem_fun(*this, &ScriptPatchVars::onQueryTreeViewTooltip)
120 );
121 // this signal does not exist in the gtkmm C++ TreeView class, so use C API
122 g_signal_connect(G_OBJECT(m_treeView.gobj()), "select-cursor-parent", G_CALLBACK(onSelectCursorParent), this);
123 m_treeStore->signal_row_changed().connect(
124 sigc::mem_fun(*this, &ScriptPatchVars::onTreeViewRowChanged)
125 );
126
127 /* FIXME: This was an attempt to prevent Gtk's treeview from reacting on
128 backspace key event to prevent undesired navigation change by
129 treeview. Didn't work out for some reason.
130 {
131 GtkWidget* widget = gtk_tree_view_new();
132 GtkTreeViewClass* klass = GTK_TREE_VIEW_GET_CLASS(widget);
133 GtkBindingSet* bindingSet = gtk_binding_set_by_class(klass);
134 gtk_binding_entry_remove(bindingSet, GDK_KEY_BackSpace, (GdkModifierType) 0);
135 gtk_binding_entry_remove(bindingSet, GDK_KEY_BackSpace, GDK_CONTROL_MASK);
136 gtk_widget_destroy(widget);
137 }
138 */
139
140 // scroll view setup
141 add(m_treeView);
142 set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
143
144 #if HAS_GTKMM_SHOW_ALL_CHILDREN
145 show_all_children();
146 #endif
147 }
148
149 static std::map<std::string,std::string> getDefaultValues(gig::Script* script) {
150 std::map<std::string,std::string> vars;
151 LinuxSampler::ScriptVM* vm = LinuxSampler::ScriptVMFactory::Create("gig");
152 LinuxSampler::VMParserContext* ctx = vm->loadScript(
153 script->GetScriptAsText(), std::map<String,String>(), &vars
154 );
155 if (ctx) delete ctx;
156 if (vm) delete vm;
157 return vars;
158 }
159
160 struct PatchVar {
161 LinuxSampler::optional<std::string> defaultValue;
162 LinuxSampler::optional<std::string> overrideValue;
163
164 std::string value() const {
165 if (overrideValue) return *overrideValue;
166 if (defaultValue) return *defaultValue;
167 return "";
168 }
169
170 bool isObsolete() const {
171 return overrideValue && !defaultValue;
172 }
173 };
174
175 static std::map<std::string,PatchVar> getPatchVars(gig::Instrument* instrument, int iScriptSlot) {
176 std::map<std::string,PatchVar> vars;
177 gig::Script* script = instrument->GetScriptOfSlot(iScriptSlot);
178 if (!script) return vars;
179 std::map<std::string,std::string> defaultValues = getDefaultValues(script);
180 for (const auto& var : defaultValues) {
181 vars[var.first].defaultValue = (std::string) trim(var.second);
182 }
183 std::map<std::string,std::string> overriddenValues =
184 instrument->GetScriptPatchVariables(iScriptSlot);
185 for (const auto& var : overriddenValues) {
186 vars[var.first].overrideValue = var.second;
187 }
188 return vars;
189 }
190
191 static std::string varTypeStr(const std::string& varName) {
192 if (varName.length() >= 1) {
193 const char c = varName[0];
194 switch (c) {
195 case '$': return "Integer";
196 case '%': return "Integer Array";
197 case '~': return "Real";
198 case '?': return "Real Array";
199 case '@': return "String";
200 case '!': return "String Array";
201 }
202 }
203 return "";
204 }
205
206 void ScriptPatchVars::buildTreeViewVar(const Gtk::TreeModel::Row& parentRow,
207 int iScriptSlot, gig::Script* script,
208 const std::string name,
209 const struct PatchVar* var)
210 {
211 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION > 24)
212 //HACK: on GTKMM 3.9x append() below requires TreeNodeChildren, parentRow.children() returns TreeNodeConstChildren though, probably going to be fixed before final GTKMM4 release though.
213 const Gtk::TreeNodeConstChildren& children = parentRow.children();
214 Gtk::TreeNodeChildren* const pChildren = (Gtk::TreeNodeChildren* const) &children;
215 Gtk::TreeModel::iterator iterRow = m_treeStore->append(*pChildren);
216 #else
217 Gtk::TreeModel::iterator iterRow = m_treeStore->append(parentRow.children());
218 #endif
219 Gtk::TreeModel::Row row = *iterRow;
220 row[m_treeModel.m_col_name] = name;
221 row[m_treeModel.m_col_name_color] = (var->isObsolete()) ? "red" : MAGENTA;
222 if (var->overrideValue)
223 row[m_treeModel.m_col_name_weight] = PANGO_WEIGHT_BOLD;
224 row[m_treeModel.m_col_type] = varTypeStr(name) + " Variable";
225 row[m_treeModel.m_col_slot] = iScriptSlot;
226 row[m_treeModel.m_col_value] = var->value();
227 row[m_treeModel.m_col_value_color] =
228 (var->isObsolete()) ? "red" : (var->overrideValue) ? YELLOW : "gray";
229 row[m_treeModel.m_col_value_tooltip] =
230 (var->overrideValue && var->defaultValue) ?
231 std::string("Default: ") + *var->defaultValue : "";
232 if (var->overrideValue)
233 row[m_treeModel.m_col_value_weight] = PANGO_WEIGHT_BOLD;
234 row[m_treeModel.m_col_allowTextEntry] = true;
235 row[m_treeModel.m_col_editable] = true;
236 row[m_treeModel.m_col_script] = script;
237
238 //TODO: syntax highlighting for values like e.g.:
239 //row[m_treeModel.m_col_value] = "4<span foreground='#50BC00'>s</span>";
240 //row[m_treeModel.m_col_value] = "0.3<span foreground='#50BC00'>s</span>";
241 //row[m_treeModel.m_col_value] = "-6.3<span foreground='black'>d</span><span foreground='#50BC00'>B</span>";
242
243 //TODO: in future we might add combo boxes for certain value selections
244 // (would probably require some kind of NKSP language extension first)
245 //const char** allKeys = gig::enumKeys(object.type().customTypeName());
246 //if (allKeys) {
247 // Glib::RefPtr<Gtk::ListStore> refOptions = createComboOptions(allKeys);
248 // row[m_treeModelMacro.m_col_options] = refOptions;
249 //}
250 //Glib::RefPtr<Gtk::ListStore> refOptions = createComboOptions(_boolOptions);
251 //row[m_treeModelMacro.m_col_options] = refOptions;
252 }
253
254 void ScriptPatchVars::buildTreeViewSlot(const Gtk::TreeModel::Row& parentRow, int iScriptSlot) {
255 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION > 24)
256 //HACK: on GTKMM 3.9x append() below requires TreeNodeChildren, parentRow.children() returns TreeNodeConstChildren though, probably going to be fixed before final GTKMM4 release though.
257 const Gtk::TreeNodeConstChildren& children = parentRow.children();
258 Gtk::TreeNodeChildren* const pChildren = (Gtk::TreeNodeChildren* const) &children;
259 Gtk::TreeModel::iterator iterRow = m_treeStore->append(*pChildren);
260 #else
261 Gtk::TreeModel::iterator iterRow = m_treeStore->append(parentRow.children());
262 #endif
263 gig::Script* pScript = m_instrument->GetScriptOfSlot(iScriptSlot);
264
265 Gtk::TreeModel::Row row = *iterRow;
266 row[m_treeModel.m_col_name] = pScript->Name + " <sup>Slot " + ToString(iScriptSlot+1) + "</sup>";
267 row[m_treeModel.m_col_name_weight] = PANGO_WEIGHT_BOLD;
268 row[m_treeModel.m_col_type] = "Script";
269 row[m_treeModel.m_col_value] = "";
270 row[m_treeModel.m_col_slot] = -1;
271 row[m_treeModel.m_col_allowTextEntry] = false;
272 row[m_treeModel.m_col_editable] = false;
273 row[m_treeModel.m_col_script] = NULL;
274 row[m_treeModel.m_col_value_tooltip] = "";
275
276 std::map<std::string,PatchVar> vars = getPatchVars(m_instrument, iScriptSlot);
277 for (const auto& var : vars) {
278 buildTreeViewVar(row, iScriptSlot, pScript, var.first, &var.second);
279 }
280 }
281
282 void ScriptPatchVars::reloadTreeView() {
283 m_ignoreTreeViewValueChange = true;
284
285 m_treeStore->clear();
286 if (!m_instrument) return;
287
288 Gtk::TreeModel::iterator iterRoot = m_treeStore->append();
289 Gtk::TreeModel::Row rowRoot = *iterRoot;
290 rowRoot[m_treeModel.m_col_name] = m_instrument->pInfo->Name;
291 rowRoot[m_treeModel.m_col_name_weight] = PANGO_WEIGHT_BOLD;
292 rowRoot[m_treeModel.m_col_type] = "Instrument";
293 rowRoot[m_treeModel.m_col_value] = "";
294 rowRoot[m_treeModel.m_col_slot] = -1;
295 rowRoot[m_treeModel.m_col_allowTextEntry] = false;
296 rowRoot[m_treeModel.m_col_editable] = false;
297 rowRoot[m_treeModel.m_col_script] = NULL;
298 rowRoot[m_treeModel.m_col_value_tooltip] = "";
299
300 for (int i = 0; i < m_instrument->ScriptSlotCount(); ++i)
301 buildTreeViewSlot(rowRoot, i);
302
303 m_treeView.expand_all();
304
305 m_ignoreTreeViewValueChange = false;
306 }
307
308 void ScriptPatchVars::onTreeViewSelectionChanged() {
309 }
310
311 bool ScriptPatchVars::onQueryTreeViewTooltip(int x, int y, bool keyboardTip,
312 const Glib::RefPtr<Gtk::Tooltip>& tooltip)
313 {
314 Gtk::TreeModel::iterator iter;
315 if (!m_treeView.get_tooltip_context_iter(x, y, keyboardTip, iter)) {
316 return false;
317 }
318 Gtk::TreeModel::Path path(iter);
319 Gtk::TreeModel::Row row = *iter;
320 Gtk::TreeViewColumn* pointedColumn = NULL;
321 // resolve the precise table column the mouse points to
322 {
323 Gtk::TreeModel::Path path; // unused
324 int cellX, cellY; // unused
325 m_treeView.get_path_at_pos(x, y, path, pointedColumn, cellX, cellY);
326 }
327 Gtk::TreeViewColumn* valueColumn = m_treeView.get_column(2);
328 if (pointedColumn == valueColumn) { // mouse hovers value column ...
329 const Glib::ustring tip = row[m_treeModel.m_col_value_tooltip];
330 if (tip.empty()) return false; // don't show tooltip
331 // show model / cell's assigned tooltip
332 tooltip->set_markup(tip);
333 m_treeView.set_tooltip_cell(tooltip, &path, valueColumn, NULL);
334 return true; // show tooltip
335 }
336 return false; // don't show tooltip
337 }
338
339 #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
340 bool ScriptPatchVars::onTreeViewKeyRelease(Gdk::EventKey& _key) {
341 GdkEventKey* key = _key.gobj();
342 #else
343 void ScriptPatchVars::onTreeViewKeyRelease(GdkEventKey* key) {
344 #endif
345 if (key->keyval == GDK_KEY_BackSpace || key->keyval == GDK_KEY_Delete) {
346 printf("DELETE on script treeview row\n");
347 deleteSelectedRows();
348 }
349 out:
350 ; // noop
351 #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
352 return false;
353 #endif
354 }
355
356 void ScriptPatchVars::deleteSelectedRows() {
357 Glib::RefPtr<Gtk::TreeSelection> sel = m_treeView.get_selection();
358 std::vector<Gtk::TreeModel::Path> rows = sel->get_selected_rows();
359 deleteRows(rows);
360 }
361
362 void ScriptPatchVars::deleteRows(const std::vector<Gtk::TreeModel::Path>& rows) {
363 // ignore the backspace key here while user is editing some value
364 if (m_ignoreTreeViewValueChange || m_editing) return;
365 if (!m_instrument) return; // just to be sure
366
367 m_ignoreTreeViewValueChange = true;
368
369 signal_vars_to_be_changed.emit(m_instrument);
370
371 for (ssize_t r = rows.size() - 1; r >= 0; --r) {
372 Gtk::TreeModel::iterator it = m_treeStore->get_iter(rows[r]);
373 if (!it) continue;
374 Gtk::TreeModel::Row row = *it;
375 gig::Script* script = row[m_treeModel.m_col_script];
376 int slot = row[m_treeModel.m_col_slot];
377 if (!script || slot == -1) continue; // prohibit deleting meta nodes
378 std::string name = (Glib::ustring) row[m_treeModel.m_col_name];
379 m_instrument->UnsetScriptPatchVariable(slot, name);
380 }
381
382 signal_vars_changed.emit(m_instrument);
383
384 reloadTreeView();
385
386 m_ignoreTreeViewValueChange = false;
387 }
388
389 void ScriptPatchVars::onValueCellEdited(const Glib::ustring& sPath, const Glib::ustring& text) {
390 m_editing = false;
391 Gtk::TreePath path(sPath);
392 Gtk::TreeModel::iterator iter = m_treeStore->get_iter(path);
393 onTreeViewRowValueChanged(path, iter, text);
394 }
395
396 void ScriptPatchVars::onTreeViewRowChanged(const Gtk::TreeModel::Path& path,
397 const Gtk::TreeModel::iterator& iter)
398 {
399 m_editing = false;
400 if (!iter) return;
401 Gtk::TreeModel::Row row = *iter;
402 Glib::ustring value = row[m_treeModel.m_col_value];
403 onTreeViewRowValueChanged(path, iter, value);
404 }
405
406 void ScriptPatchVars::onTreeViewRowValueChanged(const Gtk::TreeModel::Path& path,
407 const Gtk::TreeModel::iterator& iter,
408 const Glib::ustring value)
409 {
410 m_editing = false;
411 if (m_ignoreTreeViewValueChange || !m_instrument) return;
412
413 Gtk::TreeModel::Row row = *iter;
414 gig::Script* script = row[m_treeModel.m_col_script];
415 int slot = row[m_treeModel.m_col_slot];
416 if (!script || slot == -1) return; // prohibit altering meta nodes
417 std::string name = (Glib::ustring) row[m_treeModel.m_col_name];
418
419 signal_vars_to_be_changed.emit(m_instrument);
420
421 m_instrument->SetScriptPatchVariable(slot, name, value);
422
423 signal_vars_changed.emit(m_instrument);
424
425 reloadTreeView();
426 }
427
428 void ScriptPatchVars::setInstrument(gig::Instrument* pInstrument, bool forceUpdate) {
429 if (m_instrument == pInstrument && !forceUpdate)
430 return;
431 m_instrument = pInstrument;
432 reloadTreeView();
433 }

  ViewVC Help
Powered by ViewVC