Merge commit 'origin/master' into yaml
[laditools/alessio.git] / ladiconf
blobfd034a66d16c083a78c6b3f26ead5d3583ae8bf7
1 #!/usr/bin/env python
3 # LADITools - Linux Audio Desktop Integration Tools
4 # ladiconf - A configuration GUI for your Linux Audio Desktop
5 # Copyright (C) 2007-2010, Marc-Olivier Barre <marco@marcochapeau.org>, Nedko Arnaudov <nedko@arnaudov.name>
6 # Copyright (C) 2008, Krzysztof Foltman <wdev@foltman.com>
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 import pygtk
22 pygtk.require ('2.0')
23 import gtk
24 import laditools
25 import gobject
26 import sys
28 jack = laditools.jack_configure()
30 def check_ladish():
31 try:
32 proxy = laditools.ladish_proxy()
33 except:
34 print "ladish proxy creation failed"
35 return
37 if not proxy.is_availalbe():
38 print "ladish is not available"
39 return
41 if proxy.studio_is_loaded():
42 if not proxy.studio_is_started():
43 print "ladish studio is loaded and not started"
44 return
45 else:
46 msg = "JACK can only be configured with a stopped studio. Please stop your studio first."
47 title = "Studio is running"
48 else:
49 msg = "JACK can only be configured with a loaded and stopped studio. Please create a new studio or load and stop an existing one."
50 title = "No studio present"
52 # studio is not loaded or loaded and started
53 mdlg = gtk.MessageDialog(type = gtk.MESSAGE_ERROR, buttons = gtk.BUTTONS_CLOSE, message_format = msg)
54 mdlg.set_title(title)
55 mdlg.run()
56 mdlg.hide()
57 sys.exit(0)
59 class parameter:
60 def __init__(self, path):
61 self.path = path
62 self.name = path[-1:]
64 def get_name(self):
65 return self.name
67 def get_type(self):
68 return jack.get_param_type(self.path)
70 def get_value(self):
71 return jack.get_param_value(self.path)
73 def set_value(self, value):
74 jack.set_param_value(self.path, value)
76 def reset_value(self):
77 jack.reset_param_value(self.path)
79 def get_short_description(self):
80 return jack.get_param_short_description(self.path)
82 def get_long_description(self):
83 descr = jack.get_param_long_description(self.path)
84 if not descr:
85 descr = self.get_short_description()
86 return descr
88 def has_range(self):
89 return jack.param_has_range(self.path)
91 def get_range(self):
92 return jack.param_get_range(self.path)
94 def has_enum(self):
95 return jack.param_has_enum(self.path)
97 def is_strict_enum(self):
98 return jack.param_is_strict_enum(self.path)
100 def is_fake_values_enum(self):
101 return jack.param_is_fake_value(self.path)
103 def get_enum_values(self):
104 return jack.param_get_enum_values(self.path)
106 class configure_command:
107 def __init__(self):
108 pass
110 def get_description(self):
111 pass
113 def get_window_title(self):
114 return self.get_description();
116 def run(self, arg):
117 pass
119 class jack_driver_change_command(configure_command):
120 def get_description(self):
121 return 'Select JACK driver'
123 def run(self, arg):
124 dlg = gtk.Dialog()
125 dlg.set_title(self.get_window_title())
126 dlg.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
127 dlg.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
129 driver_list = gtk.TreeView()
130 dlg.vbox.pack_start(driver_list, True)
132 store = gtk.ListStore(str)
134 text_renderer = gtk.CellRendererText()
136 column = gtk.TreeViewColumn("Name", text_renderer, text=0)
137 driver_list.append_column(column)
139 drivers = jack.get_available_driver()
140 for driver in drivers:
141 store.append([driver])
143 driver_list.set_model(store)
145 selection = driver_list.get_selection()
147 current_driver = jack.get_selected_driver()
148 if current_driver:
149 for row in store:
150 if current_driver == row[0]:
151 selection.select_iter(row.iter)
153 driver_list.connect("row-activated", lambda *x: dlg.response(gtk.RESPONSE_OK))
155 driver = None
157 dlg.show_all()
158 ret = dlg.run()
159 if ret == gtk.RESPONSE_OK:
160 jack.select_driver(store.get(selection.get_selected()[1], 0)[0])
161 dlg.hide()
163 class parameter_enum_value(gobject.GObject):
164 def __init__(self, is_fake_value, value, description):
165 gobject.GObject.__init__(self)
166 self.is_fake_value = is_fake_value
167 self.value = value
168 self.description = description
170 def get_description(self):
171 if self.is_fake_value:
172 return self.description
174 return str(self.value) + " - " + self.description
176 gobject.type_register(parameter_enum_value)
178 class parameter_store(gobject.GObject):
179 def __init__(self, param):
180 gobject.GObject.__init__(self)
181 self.param = param
182 self.name = self.param.get_name()
183 self.is_set, self.default_value, self.value = self.param.get_value()
184 self.modified = False
185 self.has_range = self.param.has_range()
186 self.is_strict = self.param.is_strict_enum()
187 self.is_fake_value = self.param.is_fake_values_enum()
189 self.enum_values = []
191 if self.has_range:
192 self.range_min, self.range_max = self.param.get_range()
193 else:
194 for enum_value in self.param.get_enum_values():
195 self.enum_values.append(parameter_enum_value(self.is_fake_value, enum_value[0], enum_value[1]))
197 def get_name(self):
198 return self.name
200 def get_type(self):
201 return self.param.get_type()
203 def get_value(self):
204 return self.value
206 def get_default_value(self):
207 if not self.is_fake_value:
208 return str(self.default_value)
210 for enum_value in self.get_enum_values():
211 if enum_value.value == self.default_value:
212 return enum_value.get_description()
214 return "???"
216 def set_value(self, value):
217 #print "%s -> %s" % (self.name, value)
218 self.value = value
219 self.modified = True
221 def reset_value(self):
222 self.value = self.default_value
224 def get_short_description(self):
225 return self.param.get_short_description()
227 def maybe_save_value(self):
228 if self.modified:
229 self.param.set_value(self.value)
230 self.modified = False
232 def get_range(self):
233 return self.range_min, self.range_max
235 def has_enum(self):
236 return len(self.enum_values) != 0
238 def is_strict_enum(self):
239 return self.is_strict
241 def get_enum_values(self):
242 return self.enum_values
244 gobject.type_register(parameter_store)
246 def combobox_get_active_text(combobox, model_index = 0):
247 model = combobox.get_model()
248 active = combobox.get_active()
249 if active < 0:
250 return None
251 return model[active][model_index]
253 class cell_renderer_param(gtk.GenericCellRenderer):
254 __gproperties__ = { "parameter": (gobject.TYPE_OBJECT, "Parameter", "Parameter", gobject.PARAM_READWRITE) }
256 def __init__(self):
257 self.__gobject_init__()
258 self.parameter = None
259 #self.set_property('mode', gtk.CELL_RENDERER_MODE_EDITABLE)
260 #self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
261 self.renderer_text = gtk.CellRendererText()
262 self.renderer_toggle = gtk.CellRendererToggle()
263 self.renderer_combo = gtk.CellRendererCombo()
264 self.renderer_spinbutton = gtk.CellRendererSpin()
265 for r in (self.renderer_text, self.renderer_combo, self.renderer_spinbutton):
266 r.connect("edited", self.on_edited)
267 self.renderer = None
268 self.edit_widget = None
270 def do_set_property(self, pspec, value):
271 if pspec.name == 'parameter':
272 if value.get_type() == 'b':
273 self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
274 else:
275 self.set_property('mode', gtk.CELL_RENDERER_MODE_EDITABLE)
276 else:
277 print pspec.name
278 setattr(self, pspec.name, value)
280 def do_get_property(self, pspec):
281 return getattr(self, pspec.name)
283 def choose_renderer(self):
284 typechar = self.parameter.get_type()
285 value = self.parameter.get_value()
287 if typechar == "b":
288 self.renderer = self.renderer_toggle
289 self.renderer.set_property('activatable', True)
290 self.renderer.set_property('active', value)
291 self.renderer.set_property("xalign", 0.0)
292 return
294 if self.parameter.has_enum():
295 self.renderer = self.renderer_combo
297 m = gtk.ListStore(str, parameter_enum_value)
299 for value in self.parameter.get_enum_values():
300 m.append([value.get_description(), value])
302 self.renderer.set_property("model",m)
303 self.renderer.set_property('text-column', 0)
304 self.renderer.set_property('editable', True)
305 self.renderer.set_property('has_entry', not self.parameter.is_strict_enum())
307 value = self.parameter.get_value()
308 if self.parameter.is_fake_value:
309 text = "???"
310 for enum_value in self.parameter.get_enum_values():
311 if enum_value.value == value:
312 text = enum_value.get_description()
313 break
314 else:
315 text = str(value)
317 self.renderer.set_property('text', text)
319 return
321 if typechar == 'u' or typechar == 'i':
322 self.renderer = self.renderer_spinbutton
323 self.renderer.set_property('text', str(value))
324 self.renderer.set_property('editable', True)
325 if self.parameter.has_range:
326 range_min, range_max = self.parameter.get_range()
327 self.renderer.set_property('adjustment', gtk.Adjustment(value, range_min, range_max, 1, abs(int((range_max - range_min) / 10))))
328 else:
329 self.renderer.set_property('adjustment', gtk.Adjustment(value, 0, 100000, 1, 1000))
330 return
332 self.renderer = self.renderer_text
333 self.renderer.set_property('editable', True)
334 self.renderer.set_property('text', self.parameter.get_value())
336 def on_render(self, window, widget, background_area, cell_area, expose_area, flags):
337 self.choose_renderer()
338 return self.renderer.render(window, widget, background_area, cell_area, expose_area, flags)
340 def on_get_size(self, widget, cell_area=None):
341 self.choose_renderer()
342 return self.renderer.get_size(widget, cell_area)
344 def on_activate(self, event, widget, path, background_area, cell_area, flags):
345 self.choose_renderer()
346 if self.parameter.get_type() == 'b':
347 self.parameter.set_value(not self.parameter.get_value())
348 widget.get_model()[path][4] = "modified"
349 return True
351 def on_edited(self, renderer, path, value_str):
352 parameter = self.edit_parameter
353 widget = self.edit_widget
354 model = self.edit_tree.get_model()
355 self.edit_widget = None
356 typechar = parameter.get_type()
357 if type(widget) == gtk.ComboBox:
358 value = combobox_get_active_text(widget, 1)
359 if value == None:
360 return
361 value_str = value.value
362 elif type(widget) == gtk.ComboBoxEntry:
363 enum_value = combobox_get_active_text(widget, 1)
364 if enum_value:
365 value_str = enum_value.value
366 else:
367 value_str = widget.get_active_text()
369 if typechar == 'u' or typechar == 'i':
370 try:
371 value = int(value_str)
372 except ValueError, e:
373 # Hide the widget (because it may display something else than what user typed in)
374 widget.hide()
375 # Display the error
376 mdlg = gtk.MessageDialog(buttons = gtk.BUTTONS_OK, message_format = "Invalid value. Please enter an integer number.")
377 mdlg.run()
378 mdlg.hide()
379 # Return the focus back to the tree to prevent buttons from stealing it
380 self.edit_tree.grab_focus()
381 return
382 else:
383 value = value_str
384 parameter.set_value(value)
385 model[path][4] = "modified"
386 self.edit_tree.grab_focus()
388 def on_start_editing(self, event, widget, path, background_area, cell_area, flags):
389 # this happens when edit requested using keyboard
390 if not event:
391 event = gtk.gdk.Event(gtk.gdk.NOTHING)
393 self.choose_renderer()
394 ret = self.renderer.start_editing(event, widget, path, background_area, cell_area, flags)
395 self.edit_widget = ret
396 self.edit_tree = widget
397 self.edit_parameter = self.parameter
398 return ret
400 gobject.type_register(cell_renderer_param)
402 class ladiconf_tooltips(laditools.TreeViewTooltips):
403 def __init__(self, name_column, is_set_column):
404 self.name_column = name_column
405 self.is_set_column = is_set_column
406 laditools.TreeViewTooltips.__init__(self)
408 def location(self, x, y, w, h):
409 return x + 10, y + 10
411 def get_tooltip(self, view, column, path):
412 if column is self.name_column:
413 model = view.get_model()
414 tooltip = model[path][3]
415 return tooltip
417 if column is self.is_set_column:
418 model = view.get_model()
419 param = model[path][1]
420 is_set = model[path][4]
422 if is_set == "default":
423 return None
425 if is_set == "reset":
426 return "Value will be reset to %s" % param.get_default_value()
428 return "Double-click to schedule reset of value to %s" % param.get_default_value()
430 return None
432 class jack_params_configure_command(configure_command):
433 def __init__(self, path):
434 self.path = path
435 self.is_setting = False
437 def reset_value(self, row_path):
438 row = self.liststore[row_path]
439 param = row[1]
440 param.reset_value()
441 row[4] = "reset"
443 def on_row_activated(self, treeview, path, view_column):
444 if view_column == self.tvcolumn_is_set:
445 self.reset_value(path)
447 def on_button_press_event(self, tree, event):
448 if event.type != gtk.gdk._2BUTTON_PRESS:
449 return False
450 # this is needed for proper double-click handling in the list; don't ask me why, I don't know
451 # it's probably because _2BUTTON_PRESS event is still delivered to tree view, automatically deactivating
452 # the newly created edit widget (which gets created on second BUTTON_PRESS but before _2BUTTON_PRESS)
453 # deactivating the widget causes it to be deleted
454 return True
456 def on_key_press_event(self, tree, event):
457 cur = self.treeview.get_cursor()
458 row_path = cur[0][0]
460 # if Delete was pressed, reset the value
461 if event.state == 0 and event.keyval == gtk.keysyms.Delete:
462 self.reset_value(row_path)
463 tree.queue_draw()
464 return True
466 # prevent ESC from activating the editor
467 if event.string < " ":
468 return False
470 # single-key data entry: if the control is a text entry, spin button or combo box/combo box entry,
471 # then edit the current and set the text value to what user has already typed in
472 cur = self.treeview.get_cursor()
473 param = self.liststore[row_path][1]
474 ptype = param.get_type()
476 # we don't care about booleans
477 if ptype == 'b':
478 return False
480 # accept only digits for integer input (or a minus, but only if it's a signed field)
481 if ptype in ('i', 'u'):
482 if not (event.string.isdigit() or (event.string == "-" and ptype == 'i')):
483 return False
485 # Start cell editing
486 # MAYBE: call a specially crafted cell_renderer_param method for this
487 self.treeview.set_cursor_on_cell(cur[0], self.tvcolumn_value, self.renderer_value.renderer, True)
489 # cell_renderer_param::on_start_editing() should set edit_widget.
490 # if edit operation has failed (didn't create a widget), pass the key on
491 # MAYBE: call a specially crafted cell_renderer_param method to do this check
492 if self.renderer_value.edit_widget == None:
493 return
495 widget = self.renderer_value.edit_widget
496 if type(widget) in (gtk.Entry, gtk.ComboBoxEntry):
497 # (combo or plain) text entry - set the content and move the cursor to the end
498 sl = len(event.string)
499 widget.set_text(event.string)
500 widget.select_region(sl, sl)
501 return True
503 if type(self.renderer_value.edit_widget) == gtk.SpinButton:
504 # spin button - set the value and move the cursor to the end
505 if event.string == "-":
506 # special case for minus sign (which can't be converted to float)
507 widget.set_text(event.string)
508 else:
509 widget.set_value(float(event.string))
510 sl = len(widget.get_text())
511 widget.select_region(sl, sl)
512 return True
514 if type(self.renderer_value.edit_widget) == gtk.ComboBox:
515 # combo box - select the first item that starts with typed character
516 model = widget.get_model()
517 item = -1
518 iter = model.get_iter_root()
519 while iter != None:
520 if model.get_value(iter, 0).startswith(event.string):
521 item = model.get_path(iter)[0]
522 break
523 iter = model.iter_next(iter)
524 widget.set_active(item)
525 return True
527 return False
529 def on_cursor_changed(self, tree):
530 cur = self.treeview.get_cursor()
531 if not self.is_setting and cur[1] != None and cur[1].get_title() != self.tvcolumn_value.get_title():
532 self.is_setting = True
533 try:
534 self.treeview.set_cursor_on_cell(cur[0], self.tvcolumn_value)
535 finally:
536 self.is_setting = False
538 def ok_clicked(self, dlg):
539 if self.renderer_value.edit_widget:
540 self.renderer_value.edit_widget.editing_done()
542 def run(self, arg):
543 dlg = gtk.Dialog()
544 dlg.set_title(self.get_window_title())
545 dlg.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
546 dlg.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK).connect("clicked", self.ok_clicked)
548 #dlg.set_transient_for(window)
550 self.liststore = gtk.ListStore(str, parameter_store, str, str, str)
551 self.treeview = gtk.TreeView(self.liststore)
552 self.treeview.set_rules_hint(True)
553 self.treeview.set_enable_search(False)
555 renderer_text = gtk.CellRendererText()
556 renderer_toggle = gtk.CellRendererToggle()
557 renderer_value = cell_renderer_param()
558 self.renderer_value = renderer_value # save for use in event handler methods
560 self.tvcolumn_parameter = gtk.TreeViewColumn('Parameter', renderer_text, text=0)
561 self.tvcolumn_is_set = gtk.TreeViewColumn('Status', renderer_text, text=4)
562 self.tvcolumn_value = gtk.TreeViewColumn('Value', renderer_value, parameter=1)
563 self.tvcolumn_description = gtk.TreeViewColumn('Description', renderer_text, text=2)
565 self.tvcolumn_value.set_resizable(True)
566 self.tvcolumn_value.set_min_width(100)
568 self.treeview.append_column(self.tvcolumn_parameter)
569 self.treeview.append_column(self.tvcolumn_is_set)
570 self.treeview.append_column(self.tvcolumn_value)
571 self.treeview.append_column(self.tvcolumn_description)
573 dlg.vbox.pack_start(self.treeview, True)
575 param_names = jack.get_param_names(self.path)
576 for name in param_names:
577 param = parameter(self.path + [name])
578 store = parameter_store(param)
579 if store.is_set:
580 is_set = "set"
581 else:
582 is_set = "default"
583 self.liststore.append([name, store, param.get_short_description(), param.get_long_description(), is_set])
585 self.treeview.connect("row-activated", self.on_row_activated)
586 self.treeview.connect("cursor-changed", self.on_cursor_changed)
587 self.treeview.connect("key-press-event", self.on_key_press_event)
588 self.treeview.connect("button-press-event", self.on_button_press_event)
590 self.tooltips = ladiconf_tooltips(self.tvcolumn_parameter, self.tvcolumn_is_set)
591 self.tooltips.add_view(self.treeview)
592 if len(param_names):
593 # move cursor to first row and 'value' column
594 self.treeview.set_cursor((0,), self.tvcolumn_value)
596 dlg.show_all()
597 ret = dlg.run()
598 if ret == gtk.RESPONSE_OK:
599 for row in self.liststore:
600 param = row[1]
601 reset = row[4] == "reset"
602 if reset:
603 #print "%s -> reset" % param.get_name()
604 param.param.reset_value()
605 else:
606 if param.modified:
607 #print "%s -> %s" % (param.get_name(), param.get_value())
608 param.maybe_save_value()
609 dlg.hide()
611 class jack_engine_params_configure_command(jack_params_configure_command):
612 def __init__(self):
613 jack_params_configure_command.__init__(self, ['engine'])
615 def get_description(self):
616 return 'JACK engine parameters'
618 class jack_driver_params_configure_command(jack_params_configure_command):
619 def __init__(self):
620 jack_params_configure_command.__init__(self, ['driver'])
622 def get_description(self):
623 return 'JACK driver parameters'
625 def get_window_title(self):
626 return 'JACK "%s" driver parameters' % jack.get_selected_driver()
628 class jack_internal_params_configure_command(jack_params_configure_command):
629 def __init__(self, name):
630 self.name = name
631 jack_params_configure_command.__init__(self, ['internals', name])
633 def get_description(self):
634 return 'JACK "%s" parameters' % self.name
636 check_ladish()
638 commands = [
639 jack_driver_change_command(),
640 jack_engine_params_configure_command(),
641 jack_driver_params_configure_command(),
644 for internal in jack.read_container(['internals']):
645 commands.append(jack_internal_params_configure_command(internal))
647 window = gtk.Window()
649 buttons_widget = gtk.VBox()
651 for command in commands:
652 button = gtk.Button(command.get_description())
653 button.connect('clicked', command.run)
654 buttons_widget.pack_start(button)
656 window.add(buttons_widget)
658 window.show_all()
659 window.connect('destroy', gtk.main_quit)
661 gtk.main()