laditools/bin/ladiconf

627 lines
22 KiB
Python
Executable File

#!/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 <marco@marcochapeau.org>
# Copyright (C) 2007-2009, Nedko Arnaudov <nedko@arnaudov.name>
# Copyright (C) 2008, Krzysztof Foltman <wdev@foltman.com>
#
# 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 <http://www.gnu.org/licenses/>.
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()