#!/usr/bin/python # LADITools - Linux Audio Desktop Integration Tools # ladiconf - A configuration GUI for your Linux Audio Desktop # Copyright (C) 2007-2010, Marc-Olivier Barre # Copyright (C) 2007-2009, Nedko Arnaudov # Copyright (C) 2008, Krzysztof Foltman # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os import sys import pygtk pygtk.require ('2.0') import gtk import gobject try: import imp imp.find_module('laditools') except ImportError: # Running from the build tree? sys.path.insert(0, os.path.join(sys.path[0], os.pardir)) import laditools jack = laditools.jack_configure() def check_ladish(): try: proxy = laditools.ladish_proxy() except: print("ladish proxy creation failed") return if not proxy.is_available(): print("ladish is not available") return if proxy.studio_is_loaded(): if not proxy.studio_is_started(): print("ladish studio is loaded and not started") return else: msg = "JACK can only be configured with a stopped studio. Please stop your studio first." title = "Studio is running" else: msg = "JACK can only be configured with a loaded and stopped studio. Please create a new studio or load and stop an existing one." title = "No studio present" # studio is not loaded or loaded and started mdlg = gtk.MessageDialog(type = gtk.MESSAGE_ERROR, buttons = gtk.BUTTONS_CLOSE, message_format = msg) mdlg.set_title(title) mdlg.run() mdlg.hide() sys.exit(0) class parameter: def __init__(self, path): self.path = path self.name = path[-1:] def get_name(self): return self.name def get_type(self): return jack.get_param_type(self.path) def get_value(self): return jack.get_param_value(self.path) def set_value(self, value): jack.set_param_value(self.path, value) def reset_value(self): jack.reset_param_value(self.path) def get_short_description(self): return jack.get_param_short_description(self.path) def get_long_description(self): descr = jack.get_param_long_description(self.path) if not descr: descr = self.get_short_description() return descr def has_range(self): return jack.param_has_range(self.path) def get_range(self): return jack.param_get_range(self.path) def has_enum(self): return jack.param_has_enum(self.path) def is_strict_enum(self): return jack.param_is_strict_enum(self.path) def is_fake_values_enum(self): return jack.param_is_fake_value(self.path) def get_enum_values(self): return jack.param_get_enum_values(self.path) class configure_command: def __init__(self): pass def get_description(self): pass def get_window_title(self): return self.get_description(); def run(self, arg): pass class parameter_enum_value(gobject.GObject): def __init__(self, is_fake_value, value, description): gobject.GObject.__init__(self) self.is_fake_value = is_fake_value self.value = value self.description = description def get_description(self): if self.is_fake_value: return self.description return str(self.value) + " - " + self.description gobject.type_register(parameter_enum_value) class parameter_store(gobject.GObject): def __init__(self, param): gobject.GObject.__init__(self) self.param = param self.name = self.param.get_name() self.is_set, self.default_value, self.value = self.param.get_value() self.modified = False self.has_range = self.param.has_range() self.is_strict = self.param.is_strict_enum() self.is_fake_value = self.param.is_fake_values_enum() self.enum_values = [] if self.has_range: self.range_min, self.range_max = self.param.get_range() else: for enum_value in self.param.get_enum_values(): self.enum_values.append(parameter_enum_value(self.is_fake_value, enum_value[0], enum_value[1])) def get_name(self): return self.name def get_type(self): return self.param.get_type() def get_value(self): return self.value def get_default_value(self): if not self.is_fake_value: return str(self.default_value) for enum_value in self.get_enum_values(): if enum_value.value == self.default_value: return enum_value.get_description() return "???" def set_value(self, value): #print "%s -> %s" % (self.name, value) self.value = value self.modified = True def reset_value(self): self.value = self.default_value def get_short_description(self): return self.param.get_short_description() def maybe_save_value(self): if self.modified: self.param.set_value(self.value) self.modified = False def get_range(self): return self.range_min, self.range_max def has_enum(self): return len(self.enum_values) != 0 def is_strict_enum(self): return self.is_strict def get_enum_values(self): return self.enum_values gobject.type_register(parameter_store) def combobox_get_active_text(combobox, model_index = 0): model = combobox.get_model() active = combobox.get_active() if active < 0: return None return model[active][model_index] class cell_renderer_param(gtk.GenericCellRenderer): __gproperties__ = { "parameter": (gobject.TYPE_OBJECT, "Parameter", "Parameter", gobject.PARAM_READWRITE) } def __init__(self): self.__gobject_init__() self.parameter = None #self.set_property('mode', gtk.CELL_RENDERER_MODE_EDITABLE) #self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE) self.renderer_text = gtk.CellRendererText() self.renderer_toggle = gtk.CellRendererToggle() self.renderer_combo = gtk.CellRendererCombo() self.renderer_spinbutton = gtk.CellRendererSpin() for r in (self.renderer_text, self.renderer_combo, self.renderer_spinbutton): r.connect("edited", self.on_edited) self.renderer = None self.edit_widget = None def do_set_property(self, pspec, value): if pspec.name == 'parameter': if value.get_type() == 'b': self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE) else: self.set_property('mode', gtk.CELL_RENDERER_MODE_EDITABLE) else: print(pspec.name) setattr(self, pspec.name, value) def do_get_property(self, pspec): return getattr(self, pspec.name) def choose_renderer(self): typechar = self.parameter.get_type() value = self.parameter.get_value() if typechar == "b": self.renderer = self.renderer_toggle self.renderer.set_property('activatable', True) self.renderer.set_property('active', value) self.renderer.set_property("xalign", 0.0) return if self.parameter.has_enum(): self.renderer = self.renderer_combo m = gtk.ListStore(str, parameter_enum_value) for value in self.parameter.get_enum_values(): m.append([value.get_description(), value]) self.renderer.set_property("model",m) self.renderer.set_property('text-column', 0) self.renderer.set_property('editable', True) self.renderer.set_property('has_entry', not self.parameter.is_strict_enum()) value = self.parameter.get_value() if self.parameter.is_fake_value: text = "???" for enum_value in self.parameter.get_enum_values(): if enum_value.value == value: text = enum_value.get_description() break else: text = str(value) self.renderer.set_property('text', text) return if typechar == 'u' or typechar == 'i': self.renderer = self.renderer_spinbutton self.renderer.set_property('text', str(value)) self.renderer.set_property('editable', True) if self.parameter.has_range: range_min, range_max = self.parameter.get_range() self.renderer.set_property('adjustment', gtk.Adjustment(value, range_min, range_max, 1, abs(int((range_max - range_min) / 10)))) else: self.renderer.set_property('adjustment', gtk.Adjustment(value, 0, 100000, 1, 1000)) return self.renderer = self.renderer_text self.renderer.set_property('editable', True) self.renderer.set_property('text', self.parameter.get_value()) def on_render(self, window, widget, background_area, cell_area, expose_area, flags): self.choose_renderer() return self.renderer.render(window, widget, background_area, cell_area, expose_area, flags) def on_get_size(self, widget, cell_area=None): self.choose_renderer() return self.renderer.get_size(widget, cell_area) def on_activate(self, event, widget, path, background_area, cell_area, flags): self.choose_renderer() if self.parameter.get_type() == 'b': self.parameter.set_value(not self.parameter.get_value()) widget.get_model()[path][4] = "modified" return True def on_edited(self, renderer, path, value_str): parameter = self.edit_parameter widget = self.edit_widget model = self.edit_tree.get_model() self.edit_widget = None typechar = parameter.get_type() if type(widget) == gtk.ComboBox: value = combobox_get_active_text(widget, 1) if value == None: return value_str = value.value elif type(widget) == gtk.ComboBoxEntry: enum_value = combobox_get_active_text(widget, 1) if enum_value: value_str = enum_value.value else: value_str = widget.get_active_text() if typechar == 'u' or typechar == 'i': try: value = int(value_str) except ValueError as e: # Hide the widget (because it may display something else than what user typed in) widget.hide() # Display the error mdlg = gtk.MessageDialog(buttons = gtk.BUTTONS_OK, message_format = "Invalid value. Please enter an integer number.") mdlg.run() mdlg.hide() # Return the focus back to the tree to prevent buttons from stealing it self.edit_tree.grab_focus() return else: value = value_str parameter.set_value(value) model[path][4] = "modified" self.edit_tree.grab_focus() def on_start_editing(self, event, widget, path, background_area, cell_area, flags): # this happens when edit requested using keyboard if not event: event = gtk.gdk.Event(gtk.gdk.NOTHING) self.choose_renderer() ret = self.renderer.start_editing(event, widget, path, background_area, cell_area, flags) self.edit_widget = ret self.edit_tree = widget self.edit_parameter = self.parameter return ret gobject.type_register(cell_renderer_param) class ladiconf_tooltips(laditools.TreeViewTooltips): def __init__(self, name_column, is_set_column): self.name_column = name_column self.is_set_column = is_set_column laditools.TreeViewTooltips.__init__(self) def location(self, x, y, w, h): return x + 10, y + 10 def get_tooltip(self, view, column, path): if column is self.name_column: model = view.get_model() tooltip = model[path][3] return tooltip if column is self.is_set_column: model = view.get_model() param = model[path][1] is_set = model[path][4] if is_set == "default": return None if is_set == "reset": return "Value will be reset to %s" % param.get_default_value() return "Double-click to schedule reset of value to %s" % param.get_default_value() return None class jack_params_configure_command(configure_command): def __init__(self, path): self.path = path self.is_setting = False def reset_value(self, row_path): row = self.liststore[row_path] param = row[1] param.reset_value() row[4] = "reset" def on_row_activated(self, treeview, path, view_column): if view_column == self.tvcolumn_is_set: self.reset_value(path) def on_button_press_event(self, tree, event): if event.type != gtk.gdk._2BUTTON_PRESS: return False # this is needed for proper double-click handling in the list; don't ask me why, I don't know # it's probably because _2BUTTON_PRESS event is still delivered to tree view, automatically deactivating # the newly created edit widget (which gets created on second BUTTON_PRESS but before _2BUTTON_PRESS) # deactivating the widget causes it to be deleted return True def on_key_press_event(self, tree, event): cur = self.treeview.get_cursor() row_path = cur[0][0] # if Delete was pressed, reset the value if event.state == 0 and event.keyval == gtk.keysyms.Delete: self.reset_value(row_path) tree.queue_draw() return True # prevent ESC from activating the editor if event.string < " ": return False # single-key data entry: if the control is a text entry, spin button or combo box/combo box entry, # then edit the current and set the text value to what user has already typed in cur = self.treeview.get_cursor() param = self.liststore[row_path][1] ptype = param.get_type() # we don't care about booleans if ptype == 'b': return False # accept only digits for integer input (or a minus, but only if it's a signed field) if ptype in ('i', 'u'): if not (event.string.isdigit() or (event.string == "-" and ptype == 'i')): return False # Start cell editing # MAYBE: call a specially crafted cell_renderer_param method for this self.treeview.set_cursor_on_cell(cur[0], self.tvcolumn_value, self.renderer_value.renderer, True) # cell_renderer_param::on_start_editing() should set edit_widget. # if edit operation has failed (didn't create a widget), pass the key on # MAYBE: call a specially crafted cell_renderer_param method to do this check if self.renderer_value.edit_widget == None: return widget = self.renderer_value.edit_widget if type(widget) in (gtk.Entry, gtk.ComboBoxEntry): # (combo or plain) text entry - set the content and move the cursor to the end sl = len(event.string) widget.set_text(event.string) widget.select_region(sl, sl) return True if type(self.renderer_value.edit_widget) == gtk.SpinButton: # spin button - set the value and move the cursor to the end if event.string == "-": # special case for minus sign (which can't be converted to float) widget.set_text(event.string) else: widget.set_value(float(event.string)) sl = len(widget.get_text()) widget.select_region(sl, sl) return True if type(self.renderer_value.edit_widget) == gtk.ComboBox: # combo box - select the first item that starts with typed character model = widget.get_model() item = -1 iter = model.get_iter_root() while iter != None: if model.get_value(iter, 0).startswith(event.string): item = model.get_path(iter)[0] break iter = model.iter_next(iter) widget.set_active(item) return True return False def on_cursor_changed(self, tree): cur = self.treeview.get_cursor() if not self.is_setting and cur[1] != None and cur[1].get_title() != self.tvcolumn_value.get_title(): self.is_setting = True try: self.treeview.set_cursor_on_cell(cur[0], self.tvcolumn_value) finally: self.is_setting = False def ok_clicked(self, dlg): if self.renderer_value.edit_widget: self.renderer_value.edit_widget.editing_done() def run(self, arg): dlg = gtk.Dialog() dlg.set_title(self.get_window_title()) dlg.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) dlg.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK).connect("clicked", self.ok_clicked) #dlg.set_transient_for(window) self.liststore = gtk.ListStore(str, parameter_store, str, str, str) self.treeview = gtk.TreeView(self.liststore) self.treeview.set_rules_hint(True) self.treeview.set_enable_search(False) renderer_text = gtk.CellRendererText() renderer_toggle = gtk.CellRendererToggle() renderer_value = cell_renderer_param() self.renderer_value = renderer_value # save for use in event handler methods self.tvcolumn_parameter = gtk.TreeViewColumn('Parameter', renderer_text, text=0) self.tvcolumn_is_set = gtk.TreeViewColumn('Status', renderer_text, text=4) self.tvcolumn_value = gtk.TreeViewColumn('Value', renderer_value, parameter=1) self.tvcolumn_description = gtk.TreeViewColumn('Description', renderer_text, text=2) self.tvcolumn_value.set_resizable(True) self.tvcolumn_value.set_min_width(100) self.treeview.append_column(self.tvcolumn_parameter) self.treeview.append_column(self.tvcolumn_is_set) self.treeview.append_column(self.tvcolumn_value) self.treeview.append_column(self.tvcolumn_description) dlg.vbox.pack_start(self.treeview, True) param_names = jack.get_param_names(self.path) for name in param_names: param = parameter(self.path + [name]) store = parameter_store(param) if store.is_set: is_set = "set" else: is_set = "default" self.liststore.append([name, store, param.get_short_description(), param.get_long_description(), is_set]) self.treeview.connect("row-activated", self.on_row_activated) self.treeview.connect("cursor-changed", self.on_cursor_changed) self.treeview.connect("key-press-event", self.on_key_press_event) self.treeview.connect("button-press-event", self.on_button_press_event) self.tooltips = ladiconf_tooltips(self.tvcolumn_parameter, self.tvcolumn_is_set) self.tooltips.add_view(self.treeview) if len(param_names): # move cursor to first row and 'value' column self.treeview.set_cursor((0,), self.tvcolumn_value) dlg.show_all() ret = dlg.run() if ret == gtk.RESPONSE_OK: for row in self.liststore: param = row[1] reset = row[4] == "reset" if reset: #print "%s -> reset" % param.get_name() param.param.reset_value() else: if param.modified: #print "%s -> %s" % (param.get_name(), param.get_value()) param.maybe_save_value() dlg.hide() class jack_engine_params_configure_command(jack_params_configure_command): def __init__(self): jack_params_configure_command.__init__(self, ['engine']) def get_description(self): return 'JACK engine parameters' class jack_driver_params_configure_command(jack_params_configure_command): def __init__(self): jack_params_configure_command.__init__(self, ['driver']) def get_description(self): return 'JACK driver parameters' def get_window_title(self): return 'JACK "%s" driver parameters' % jack.get_selected_driver() class jack_internal_params_configure_command(jack_params_configure_command): def __init__(self, name): self.name = name jack_params_configure_command.__init__(self, ['internals', name]) def get_description(self): return 'JACK "%s" parameters' % self.name check_ladish() commands = [ jack_engine_params_configure_command(), jack_driver_params_configure_command(), ] for internal in jack.read_container(['internals']): commands.append(jack_internal_params_configure_command(internal)) window = gtk.Window() buttons_widget = gtk.VBox() for command in commands: button = gtk.Button(command.get_description()) button.connect('clicked', command.run) buttons_widget.pack_start(button) window.add(buttons_widget) window.show_all() window.connect('destroy', gtk.main_quit) gtk.main()