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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3737 - (hide annotations) (download)
Sat Feb 1 20:39:39 2020 UTC (4 years, 3 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 schoenebeck 3737 /*
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