/[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 3749 - (show annotations) (download)
Sun Feb 16 18:39:53 2020 UTC (18 months, 4 weeks ago) by schoenebeck
File size: 18662 byte(s)
* Script 'patch' variables editor: double click anywhere on a script's
  title row (or hitting <enter> while that row is selected) opens
  script source code editor for that double clicked script.

* Also show a tooltip and a pencil icon on such rows to make user
  visually aware about this feature.

* Bumped version (1.1.1.svn17).

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

  ViewVC Help
Powered by ViewVC