Handle remaining samples < 4 correctly(?).
[calfbox.git] / py / gui_tools.py
blob96110060d2e11f54135037b70a31b2140e5a80e3
1 import cbox
2 import math
3 import re
4 from gi.repository import GObject, Gdk, Gtk, GLib as glib
6 # Compatibility stuff
7 Gtk.TreeView.insert_column_with_attributes = lambda self, col, title, renderer, xarg = None, **kwargs: self.insert_column(Gtk.TreeViewColumn(title = title, cell_renderer = renderer, **kwargs), col)
9 def callback(cmd, cb, args):
10 print ("%s %s %s" % (cmd, cb, args))
12 def bold_label(text, halign = 1):
13 l = Gtk.Label()
14 l.set_markup("<b>%s</b>" % text)
15 l.set_alignment(halign, 0.5)
16 return l
18 def left_label(text):
19 l = Gtk.Label(text)
20 l.set_alignment(0, 0.5)
21 return l
23 def standard_hslider(adj):
24 sc = Gtk.HScale(adjustment = adj)
25 sc.set_size_request(160, -1)
26 sc.set_value_pos(Gtk.PositionType.RIGHT)
27 return sc
29 notenames = {
30 'c' : 0, 'c#' : 1, 'db' : 1,
31 'd' : 2, 'd#' : 3, 'eb' : 3,
32 'e' : 4, 'e#' : 5, 'fb' : 5,
33 'f' : 5, 'f#' : 6, 'gb' : 6,
34 'g' : 7, 'g#' : 8, 'ab' : 8,
35 'a' : 9, 'a#' : 10, 'bb' : 10,
36 'b' : 11, 'b#' : 0, 'cb' : 11,
39 def sfznote2value(note):
40 if re.match("[0-9]+$", note):
41 return int(rdata['key'])
42 else:
43 g = re.match("([cdefgab](#|b)?)(-?[0-9])", note)
44 if g is None:
45 raise ValueError(note)
46 return 12 + int(g.group(3)) * 12 + notenames[g.group(1).lower()]
48 class LogMapper:
49 def __init__(self, min, max, format = "%f"):
50 self.min = min
51 self.max = max
52 self.format_string = format
53 def map(self, value):
54 return float(self.min) * ((float(self.max) / self.min) ** (value / 100.0))
55 def unmap(self, value):
56 return math.log(value / float(self.min)) * 100 / math.log(float(self.max) / self.min)
57 def format_value(self, value):
58 return self.format_string % self.map(value)
61 freq_format = "%0.1f"
62 ms_format = "%0.1f ms"
63 lfo_freq_mapper = LogMapper(0.01, 20, "%0.2f")
64 env_mapper = LogMapper(0.002, 20, "%f")
65 filter_freq_mapper = LogMapper(20, 20000, "%0.1f Hz")
67 def standard_mapped_hslider(adj, mapper):
68 sc = Gtk.HScale(adjustment = adj)
69 sc.set_size_request(160, -1)
70 sc.set_value_pos(Gtk.PositionType.RIGHT)
71 sc.connect('format-value', lambda scale, value, mapper: mapper.format_value(value), mapper)
72 return sc
74 def standard_align(w, xo, yo, xs, ys):
75 a = Gtk.Alignment(xalign = xo, yalign = yo, xscale = xs, yscale = ys)
76 a.add(w)
77 return a
79 def standard_vscroll_window(width, height, content = None):
80 scroller = Gtk.ScrolledWindow()
81 scroller.set_size_request(width, height)
82 scroller.set_shadow_type(Gtk.ShadowType.NONE);
83 scroller.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
84 if content is not None:
85 scroller.add_with_viewport(content)
86 return scroller
88 def standard_combo(model, active_item = None, column = 0, active_item_lookup = None, lookup_column = None, width = None):
89 cb = Gtk.ComboBox(model = model)
90 if active_item_lookup is not None:
91 if lookup_column is None:
92 lookup_column = column
93 active_item = ls_index(model, active_item_lookup, lookup_column)
94 if active_item is not None:
95 cb.set_active(active_item)
96 cell = Gtk.CellRendererText()
97 if width is not None:
98 cb.set_size_request(width, -1)
99 cb.pack_start(cell, True)
100 cb.add_attribute(cell, 'text', column)
101 return cb
103 def ls_index(list_store, value, column):
104 for i in range(len(list_store)):
105 if list_store[i][column] == value:
106 return i
107 return None
109 def standard_filter(patterns, name):
110 f = Gtk.FileFilter()
111 for p in patterns:
112 f.add_pattern(p)
113 f.set_name(name)
114 return f
116 def checkbox_changed_bool(checkbox, vpath):
117 vpath.set(1 if checkbox.get_active() else 0)
119 def adjustment_changed_int(adjustment, vpath):
120 vpath.set(int(adjustment.get_value()))
122 def adjustment_changed_float(adjustment, vpath):
123 vpath.set(float(adjustment.get_value()))
125 def adjustment_changed_float_mapped(adjustment, vpath, mapper):
126 vpath.set(mapper.map(adjustment.get_value()))
128 def combo_value_changed(combo, vpath, value_offset = 0):
129 if combo.get_active() != -1:
130 vpath.set(value_offset + combo.get_active())
132 def combo_value_changed_use_column(combo, vpath, column):
133 if combo.get_active() != -1:
134 vpath.set(combo.get_model()[combo.get_active()][column])
136 def tree_toggle_changed_bool(renderer, tree_path, model, opath, column):
137 model[int(tree_path)][column] = not model[int(tree_path)][column]
138 cbox.do_cmd(model.make_row_item(opath, tree_path), None, [1 if model[int(tree_path)][column] else 0])
140 def tree_combo_changed(renderer, tree_path, new_value, model, opath, column):
141 new_value = renderer.get_property('model')[new_value][0]
142 model[int(tree_path)][column] = new_value
143 cbox.do_cmd(model.make_row_item(opath, tree_path), None, [new_value])
145 def standard_toggle_renderer(list_model, path, column):
146 toggle = Gtk.CellRendererToggle()
147 toggle.connect('toggled', tree_toggle_changed_bool, list_model, path, column)
148 return toggle
150 def standard_combo_renderer(list_model, model, path, column):
151 combo = Gtk.CellRendererCombo()
152 combo.set_property('model', model)
153 combo.set_property('editable', True)
154 combo.set_property('has-entry', False)
155 combo.set_property('text-column', 1)
156 combo.set_property('mode', Gtk.CellRendererMode.EDITABLE)
157 combo.connect('changed', tree_combo_changed, list_model, path, column)
158 return combo
160 def add_display_row(t, row, label, path, values, item):
161 t.attach(bold_label(label), 0, 1, row, row+1, Gtk.AttachOptions.SHRINK | Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK)
162 w = left_label(getattr(values, item))
163 t.attach(w, 1, 2, row, row+1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK)
164 return w
166 def set_timer(widget, time, func, *args):
167 refresh_id = glib.timeout_add(time, func, *args)
168 widget.connect('destroy', lambda obj, id: glib.source_remove(id), refresh_id)
170 def create_menu(title, items):
171 menuitem = Gtk.MenuItem.new_with_mnemonic(title)
172 if items is not None:
173 menu = Gtk.Menu()
174 menuitem.set_submenu(menu)
175 for label, meth in items:
176 mit = Gtk.MenuItem.new_with_mnemonic(label)
177 if meth is None:
178 mit.set_sensitive(False)
179 else:
180 mit.connect('activate', meth)
181 menu.append(mit)
182 return menuitem
184 def note_to_name(note):
185 if note < 0:
186 return "N/A"
187 n = note % 12
188 return ("C C#D D#E F F#G G#A A#B "[n * 2 : n * 2 + 2]) + str((note // 12) - 2)
190 #################################################################################################################################
192 class TableRowWidget:
193 def __init__(self, label, name, **kwargs):
194 self.label = label
195 self.name = name
196 self.kwargs = kwargs
197 def get_with_default(self, name, def_value):
198 return self.kwargs[name] if name in self.kwargs else def_value
199 def create_label(self):
200 return bold_label(self.label)
201 def has_attr(self, values):
202 if type(values) is dict:
203 return self.name in values
204 return hasattr(values, self.name)
205 def get_attr(self, values):
206 if type(values) is dict:
207 return values[self.name]
208 return getattr(values, self.name)
209 def get_value(self, values, vpath):
210 if len(vpath.args) == 1:
211 return self.get_attr(values)[vpath.args[0]]
212 return self.get_attr(values)
213 def add_row(self, table, row, vpath, values):
214 table.attach(self.create_label(), 0, 1, row, row + 1, Gtk.AttachOptions.SHRINK | Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK)
215 widget, refresher = self.create_widget(vpath)
216 table.attach(widget, 1, 2, row, row + 1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK)
217 if values is not None:
218 refresher(values)
219 return refresher
220 def update_sensitive(self, widget, values):
221 sensitive = (values is not None) and (self.get_with_default('setter', 0) is not None) and self.has_attr(values)
222 widget.set_sensitive(sensitive)
223 return sensitive
225 class SliderRow(TableRowWidget):
226 def __init__(self, label, name, minv, maxv, **kwargs):
227 TableRowWidget.__init__(self, label, name, **kwargs)
228 self.minv = minv
229 self.maxv = maxv
230 def create_widget(self, vpath):
231 setter = self.get_with_default('setter', adjustment_changed_float)
232 step = self.kwargs['step'] if 'step' in self.kwargs else 1
233 adj = Gtk.Adjustment(self.minv, self.minv, self.maxv, step, 6, 0)
234 slider = standard_hslider(adj)
235 if 'digits' in self.kwargs:
236 slider.set_digits(self.kwargs['digits'])
237 if setter is not None:
238 adj.connect("value_changed", setter, vpath.plus(self.name))
239 else:
240 slider.set_sensitive(False)
241 def refresher(values):
242 if self.update_sensitive(slider, values):
243 adj.set_value(self.get_value(values, vpath))
244 return (slider, refresher)
246 class IntSliderRow(SliderRow):
247 def __init__(self, label, name, minv, maxv, **kwargs):
248 SliderRow.__init__(self, label, name, minv, maxv, setter = adjustment_changed_int, **kwargs)
249 def create_widget(self, vpath):
250 (slider, refresher) = SliderRow.create_widget(self, vpath)
251 slider.connect('change-value', self.on_change_value)
252 slider.set_digits(0)
253 return slider, refresher
254 def on_change_value(self, range, scroll, value):
255 range.set_value(int(value))
256 return True
259 class MappedSliderRow(TableRowWidget):
260 def __init__(self, label, name, mapper, **kwargs):
261 TableRowWidget.__init__(self, label, name, **kwargs)
262 self.mapper = mapper
263 def create_widget(self, vpath):
264 setter = self.get_with_default('setter', adjustment_changed_float_mapped)
265 adj = Gtk.Adjustment(0, 0, 100, 1, 6, 0)
266 slider = standard_mapped_hslider(adj, self.mapper)
267 if setter is not None:
268 adj.connect("value_changed", setter, vpath.plus(self.name), self.mapper)
269 else:
270 slider.set_sensitive(False)
271 def refresher(values):
272 if self.update_sensitive(slider, values):
273 adj.set_value(self.mapper.unmap(self.get_value(values, vpath)))
274 return (slider, refresher)
276 class CheckBoxRow(TableRowWidget):
277 def create_widget(self, vpath):
278 widget = Gtk.CheckButton(self.label)
279 widget.connect("clicked", checkbox_changed_bool, vpath.plus(self.name))
280 def refresher(values):
281 if self.update_sensitive(widget, values):
282 widget.set_active(self.get_value(values, vpath) > 0)
283 return (widget, refresher)
285 #################################################################################################################################
287 class SelectObjectDialog(Gtk.Dialog):
288 def __init__(self, parent):
289 Gtk.Dialog.__init__(self, self.title, parent, Gtk.DialogFlags.MODAL,
290 (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK))
291 self.set_default_response(Gtk.ResponseType.OK)
292 model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING)
294 self.update_model(model)
296 scroll = Gtk.ScrolledWindow()
297 scenes = Gtk.TreeView(model)
298 scenes.insert_column_with_attributes(0, "Name", Gtk.CellRendererText(), text=0)
299 scenes.insert_column_with_attributes(1, "Title", Gtk.CellRendererText(), text=3)
300 scenes.insert_column_with_attributes(2, "Type", Gtk.CellRendererText(), text=1)
301 scenes.get_column(0).set_property('min_width', 150)
302 scenes.get_column(1).set_property('min_width', 300)
303 scenes.get_column(2).set_property('min_width', 150)
304 scroll.add(scenes)
305 self.vbox.pack_start(scroll, False, False, 0)
306 scenes.show()
307 scroll.set_size_request(640, 500)
308 scroll.show()
309 self.scenes = scenes
310 self.scenes.connect('cursor-changed', lambda w: self.update_default_button())
311 self.scenes.connect('row-activated', lambda w, path, column: self.response(Gtk.ResponseType.OK))
312 self.scenes.set_cursor((0,), scenes.get_column(0))
313 self.scenes.grab_focus()
314 self.update_default_button()
316 def get_selected_object(self):
317 return self.scenes.get_model()[self.scenes.get_cursor()[0].get_indices()[0]]
318 def update_default_button(self):
319 self.set_response_sensitive(Gtk.ResponseType.OK, self.scenes.get_cursor()[1] is not None)
321 #################################################################################################################################
323 class SaveConfigObjectDialog(Gtk.Dialog):
324 def __init__(self, parent, title):
325 Gtk.Dialog.__init__(self, title, parent, Gtk.DialogFlags.MODAL,
326 (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK))
327 self.set_default_response(Gtk.ResponseType.OK)
329 l = Gtk.Label()
330 l.set_text_with_mnemonic("_Name")
331 e = Gtk.Entry()
332 self.entry = e
333 e.set_activates_default(True)
334 e.connect('changed', self.on_entry_changed)
335 row = Gtk.HBox()
336 row.pack_start(l, False, False, 5)
337 row.pack_start(e, True, True, 5)
338 row.show_all()
339 self.vbox.pack_start(row, True, True, 0)
340 e.grab_focus()
341 self.on_entry_changed(e)
342 def get_name(self):
343 return self.entry.get_text()
344 def on_entry_changed(self, w):
345 self.set_response_sensitive(Gtk.ResponseType.OK, w.get_text() != '')