/[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 3737 - (show annotations) (download)
Sat Feb 1 20:39:39 2020 UTC (4 years, 2 months ago) by schoenebeck
File size: 18051 byte(s)
* NKSP: Added support for managing script 'patch' variables for each
  instrument; added a dedicated "Script" tab on right-hand side of Gigedit's
  main window with a list view to manage these variables.

* Bumped version (1.1.1.svn14).

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 }
88 m_treeView.set_headers_visible(true);
89 m_treeView.get_selection()->signal_changed().connect(
90 sigc::mem_fun(*this, &ScriptPatchVars::onTreeViewSelectionChanged)
91 );
92 #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
93 m_treeView.signal_key_release_event().connect
94 #else
95 m_treeView.signal_key_release_event().connect_notify
96 #endif
97 (
98 sigc::mem_fun(*this, &ScriptPatchVars::onTreeViewKeyRelease)
99 );
100 // Gtk "row-activated" signal (Gtkmm's signal_row_activated) is only fired
101 // if this single click property is set to true
102 //m_treeView.set_activate_on_single_click(true);
103 m_treeView.signal_row_activated().connect(
104 [this](const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column){
105 printf("row activated !!!!\n");
106 //m_editing = true; // so we can detect while user is editing some value
107 }
108 );
109 m_treeView.set_has_tooltip(true);
110 m_treeView.signal_query_tooltip().connect(
111 sigc::mem_fun(*this, &ScriptPatchVars::onQueryTreeViewTooltip)
112 );
113 // this signal does not exist in the gtkmm C++ TreeView class, so use C API
114 g_signal_connect(G_OBJECT(m_treeView.gobj()), "select-cursor-parent", G_CALLBACK(onSelectCursorParent), this);
115 m_treeStore->signal_row_changed().connect(
116 sigc::mem_fun(*this, &ScriptPatchVars::onTreeViewRowChanged)
117 );
118
119 /* FIXME: This was an attempt to prevent Gtk's treeview from reacting on
120 backspace key event to prevent undesired navigation change by
121 treeview. Didn't work out for some reason.
122 {
123 GtkWidget* widget = gtk_tree_view_new();
124 GtkTreeViewClass* klass = GTK_TREE_VIEW_GET_CLASS(widget);
125 GtkBindingSet* bindingSet = gtk_binding_set_by_class(klass);
126 gtk_binding_entry_remove(bindingSet, GDK_KEY_BackSpace, (GdkModifierType) 0);
127 gtk_binding_entry_remove(bindingSet, GDK_KEY_BackSpace, GDK_CONTROL_MASK);
128 gtk_widget_destroy(widget);
129 }
130 */
131
132 // scroll view setup
133 add(m_treeView);
134 set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
135
136 #if HAS_GTKMM_SHOW_ALL_CHILDREN
137 show_all_children();
138 #endif
139 }
140
141 static std::map<std::string,std::string> getDefaultValues(gig::Script* script) {
142 std::map<std::string,std::string> vars;
143 LinuxSampler::ScriptVM* vm = LinuxSampler::ScriptVMFactory::Create("gig");
144 LinuxSampler::VMParserContext* ctx = vm->loadScript(
145 script->GetScriptAsText(), std::map<String,String>(), &vars
146 );
147 if (ctx) delete ctx;
148 if (vm) delete vm;
149 return vars;
150 }
151
152 struct PatchVar {
153 LinuxSampler::optional<std::string> defaultValue;
154 LinuxSampler::optional<std::string> overrideValue;
155
156 std::string value() const {
157 if (overrideValue) return *overrideValue;
158 if (defaultValue) return *defaultValue;
159 return "";
160 }
161
162 bool isObsolete() const {
163 return overrideValue && !defaultValue;
164 }
165 };
166
167 static std::map<std::string,PatchVar> getPatchVars(gig::Instrument* instrument, int iScriptSlot) {
168 std::map<std::string,PatchVar> vars;
169 gig::Script* script = instrument->GetScriptOfSlot(iScriptSlot);
170 if (!script) return vars;
171 std::map<std::string,std::string> defaultValues = getDefaultValues(script);
172 for (const auto& var : defaultValues) {
173 vars[var.first].defaultValue = (std::string) trim(var.second);
174 }
175 std::map<std::string,std::string> overriddenValues =
176 instrument->GetScriptPatchVariables(iScriptSlot);
177 for (const auto& var : overriddenValues) {
178 vars[var.first].overrideValue = var.second;
179 }
180 return vars;
181 }
182
183 static std::string varTypeStr(const std::string& varName) {
184 if (varName.length() >= 1) {
185 const char c = varName[0];
186 switch (c) {
187 case '$': return "Integer";
188 case '%': return "Integer Array";
189 case '~': return "Real";
190 case '?': return "Real Array";
191 case '@': return "String";
192 case '!': return "String Array";
193 }
194 }
195 return "";
196 }
197
198 void ScriptPatchVars::buildTreeViewVar(const Gtk::TreeModel::Row& parentRow,
199 int iScriptSlot, gig::Script* script,
200 const std::string name,
201 const struct PatchVar* var)
202 {
203 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION > 24)
204 //HACK: on GTKMM 3.9x append() below requires TreeNodeChildren, parentRow.children() returns TreeNodeConstChildren though, probably going to be fixed before final GTKMM4 release though.
205 const Gtk::TreeNodeConstChildren& children = parentRow.children();
206 Gtk::TreeNodeChildren* const pChildren = (Gtk::TreeNodeChildren* const) &children;
207 Gtk::TreeModel::iterator iterRow = m_treeStore->append(*pChildren);
208 #else
209 Gtk::TreeModel::iterator iterRow = m_treeStore->append(parentRow.children());
210 #endif
211 Gtk::TreeModel::Row row = *iterRow;
212 row[m_treeModel.m_col_name] = name;
213 row[m_treeModel.m_col_name_color] = (var->isObsolete()) ? "red" : MAGENTA;
214 if (var->overrideValue)
215 row[m_treeModel.m_col_name_weight] = PANGO_WEIGHT_BOLD;
216 row[m_treeModel.m_col_type] = varTypeStr(name) + " Variable";
217 row[m_treeModel.m_col_slot] = iScriptSlot;
218 row[m_treeModel.m_col_value] = var->value();
219 row[m_treeModel.m_col_value_color] =
220 (var->isObsolete()) ? "red" : (var->overrideValue) ? YELLOW : "gray";
221 row[m_treeModel.m_col_value_tooltip] =
222 (var->overrideValue && var->defaultValue) ?
223 std::string("Default: ") + *var->defaultValue : "";
224 if (var->overrideValue)
225 row[m_treeModel.m_col_value_weight] = PANGO_WEIGHT_BOLD;
226 row[m_treeModel.m_col_allowTextEntry] = true;
227 row[m_treeModel.m_col_editable] = true;
228 row[m_treeModel.m_col_script] = script;
229
230 //TODO: syntax highlighting for values like e.g.:
231 //row[m_treeModel.m_col_value] = "4<span foreground='#50BC00'>s</span>";
232 //row[m_treeModel.m_col_value] = "0.3<span foreground='#50BC00'>s</span>";
233 //row[m_treeModel.m_col_value] = "-6.3<span foreground='black'>d</span><span foreground='#50BC00'>B</span>";
234
235 //TODO: in future we might add combo boxes for certain value selections
236 // (would probably require some kind of NKSP language extension first)
237 //const char** allKeys = gig::enumKeys(object.type().customTypeName());
238 //if (allKeys) {
239 // Glib::RefPtr<Gtk::ListStore> refOptions = createComboOptions(allKeys);
240 // row[m_treeModelMacro.m_col_options] = refOptions;
241 //}
242 //Glib::RefPtr<Gtk::ListStore> refOptions = createComboOptions(_boolOptions);
243 //row[m_treeModelMacro.m_col_options] = refOptions;
244 }
245
246 void ScriptPatchVars::buildTreeViewSlot(const Gtk::TreeModel::Row& parentRow, int iScriptSlot) {
247 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION > 24)
248 //HACK: on GTKMM 3.9x append() below requires TreeNodeChildren, parentRow.children() returns TreeNodeConstChildren though, probably going to be fixed before final GTKMM4 release though.
249 const Gtk::TreeNodeConstChildren& children = parentRow.children();
250 Gtk::TreeNodeChildren* const pChildren = (Gtk::TreeNodeChildren* const) &children;
251 Gtk::TreeModel::iterator iterRow = m_treeStore->append(*pChildren);
252 #else
253 Gtk::TreeModel::iterator iterRow = m_treeStore->append(parentRow.children());
254 #endif
255 gig::Script* pScript = m_instrument->GetScriptOfSlot(iScriptSlot);
256
257 Gtk::TreeModel::Row row = *iterRow;
258 row[m_treeModel.m_col_name] = pScript->Name + " <sup>Slot " + ToString(iScriptSlot+1) + "</sup>";
259 row[m_treeModel.m_col_name_weight] = PANGO_WEIGHT_BOLD;
260 row[m_treeModel.m_col_type] = "Script";
261 row[m_treeModel.m_col_value] = "";
262 row[m_treeModel.m_col_slot] = -1;
263 row[m_treeModel.m_col_allowTextEntry] = false;
264 row[m_treeModel.m_col_editable] = false;
265 row[m_treeModel.m_col_script] = NULL;
266 row[m_treeModel.m_col_value_tooltip] = "";
267
268 std::map<std::string,PatchVar> vars = getPatchVars(m_instrument, iScriptSlot);
269 for (const auto& var : vars) {
270 buildTreeViewVar(row, iScriptSlot, pScript, var.first, &var.second);
271 }
272 }
273
274 void ScriptPatchVars::reloadTreeView() {
275 m_ignoreTreeViewValueChange = true;
276
277 m_treeStore->clear();
278 if (!m_instrument) return;
279
280 Gtk::TreeModel::iterator iterRoot = m_treeStore->append();
281 Gtk::TreeModel::Row rowRoot = *iterRoot;
282 rowRoot[m_treeModel.m_col_name] = m_instrument->pInfo->Name;
283 rowRoot[m_treeModel.m_col_name_weight] = PANGO_WEIGHT_BOLD;
284 rowRoot[m_treeModel.m_col_type] = "Instrument";
285 rowRoot[m_treeModel.m_col_value] = "";
286 rowRoot[m_treeModel.m_col_slot] = -1;
287 rowRoot[m_treeModel.m_col_allowTextEntry] = false;
288 rowRoot[m_treeModel.m_col_editable] = false;
289 rowRoot[m_treeModel.m_col_script] = NULL;
290 rowRoot[m_treeModel.m_col_value_tooltip] = "";
291
292 for (int i = 0; i < m_instrument->ScriptSlotCount(); ++i)
293 buildTreeViewSlot(rowRoot, i);
294
295 m_treeView.expand_all();
296
297 m_ignoreTreeViewValueChange = false;
298 }
299
300 void ScriptPatchVars::onTreeViewSelectionChanged() {
301 }
302
303 bool ScriptPatchVars::onQueryTreeViewTooltip(int x, int y, bool keyboardTip,
304 const Glib::RefPtr<Gtk::Tooltip>& tooltip)
305 {
306 Gtk::TreeModel::iterator iter;
307 if (!m_treeView.get_tooltip_context_iter(x, y, keyboardTip, iter)) {
308 return false;
309 }
310 Gtk::TreeModel::Path path(iter);
311 Gtk::TreeModel::Row row = *iter;
312 Gtk::TreeViewColumn* pointedColumn = NULL;
313 // resolve the precise table column the mouse points to
314 {
315 Gtk::TreeModel::Path path; // unused
316 int cellX, cellY; // unused
317 m_treeView.get_path_at_pos(x, y, path, pointedColumn, cellX, cellY);
318 }
319 Gtk::TreeViewColumn* valueColumn = m_treeView.get_column(2);
320 if (pointedColumn == valueColumn) { // mouse hovers value column ...
321 const Glib::ustring tip = row[m_treeModel.m_col_value_tooltip];
322 if (tip.empty()) return false; // don't show tooltip
323 // show model / cell's assigned tooltip
324 tooltip->set_markup(tip);
325 m_treeView.set_tooltip_cell(tooltip, &path, valueColumn, NULL);
326 return true; // show tooltip
327 }
328 return false; // don't show tooltip
329 }
330
331 #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
332 bool ScriptPatchVars::onTreeViewKeyRelease(Gdk::EventKey& _key) {
333 GdkEventKey* key = _key.gobj();
334 #else
335 void ScriptPatchVars::onTreeViewKeyRelease(GdkEventKey* key) {
336 #endif
337 if (key->keyval == GDK_KEY_BackSpace || key->keyval == GDK_KEY_Delete) {
338 printf("DELETE on script treeview row\n");
339 deleteSelectedRows();
340 }
341 out:
342 ; // noop
343 #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
344 return false;
345 #endif
346 }
347
348 void ScriptPatchVars::deleteSelectedRows() {
349 Glib::RefPtr<Gtk::TreeSelection> sel = m_treeView.get_selection();
350 std::vector<Gtk::TreeModel::Path> rows = sel->get_selected_rows();
351 deleteRows(rows);
352 }
353
354 void ScriptPatchVars::deleteRows(const std::vector<Gtk::TreeModel::Path>& rows) {
355 // ignore the backspace key here while user is editing some value
356 if (m_ignoreTreeViewValueChange /*|| m_editing*/) return;
357 if (!m_instrument) return; // just to be sure
358
359 m_ignoreTreeViewValueChange = true;
360
361 signal_vars_to_be_changed.emit(m_instrument);
362
363 for (ssize_t r = rows.size() - 1; r >= 0; --r) {
364 Gtk::TreeModel::iterator it = m_treeStore->get_iter(rows[r]);
365 if (!it) continue;
366 Gtk::TreeModel::Row row = *it;
367 gig::Script* script = row[m_treeModel.m_col_script];
368 int slot = row[m_treeModel.m_col_slot];
369 if (!script || slot == -1) continue; // prohibit deleting meta nodes
370 std::string name = (Glib::ustring) row[m_treeModel.m_col_name];
371 m_instrument->UnsetScriptPatchVariable(slot, name);
372 }
373
374 signal_vars_changed.emit(m_instrument);
375
376 reloadTreeView();
377
378 m_ignoreTreeViewValueChange = false;
379 }
380
381 void ScriptPatchVars::onValueCellEdited(const Glib::ustring& sPath, const Glib::ustring& text) {
382 //m_editing = false;
383 Gtk::TreePath path(sPath);
384 Gtk::TreeModel::iterator iter = m_treeStore->get_iter(path);
385 onTreeViewRowValueChanged(path, iter, text);
386 }
387
388 void ScriptPatchVars::onTreeViewRowChanged(const Gtk::TreeModel::Path& path,
389 const Gtk::TreeModel::iterator& iter)
390 {
391 //m_editing = false;
392 if (!iter) return;
393 Gtk::TreeModel::Row row = *iter;
394 Glib::ustring value = row[m_treeModel.m_col_value];
395 onTreeViewRowValueChanged(path, iter, value);
396 }
397
398 void ScriptPatchVars::onTreeViewRowValueChanged(const Gtk::TreeModel::Path& path,
399 const Gtk::TreeModel::iterator& iter,
400 const Glib::ustring value)
401 {
402 //m_editing = false;
403 if (m_ignoreTreeViewValueChange || !m_instrument) return;
404
405 Gtk::TreeModel::Row row = *iter;
406 gig::Script* script = row[m_treeModel.m_col_script];
407 int slot = row[m_treeModel.m_col_slot];
408 if (!script || slot == -1) return; // prohibit altering meta nodes
409 std::string name = (Glib::ustring) row[m_treeModel.m_col_name];
410
411 signal_vars_to_be_changed.emit(m_instrument);
412
413 m_instrument->SetScriptPatchVariable(slot, name, value);
414
415 signal_vars_changed.emit(m_instrument);
416
417 reloadTreeView();
418 }
419
420 void ScriptPatchVars::setInstrument(gig::Instrument* pInstrument, bool forceUpdate) {
421 if (m_instrument == pInstrument && !forceUpdate)
422 return;
423 m_instrument = pInstrument;
424 reloadTreeView();
425 }

  ViewVC Help
Powered by ViewVC