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/>.
28 jack
= laditools
.jack_configure()
32 proxy
= laditools
.ladish_proxy()
34 print "ladish proxy creation failed"
37 if not proxy
.is_availalbe():
38 print "ladish is not available"
41 if proxy
.studio_is_loaded():
42 if not proxy
.studio_is_started():
43 print "ladish studio is loaded and not started"
46 msg
= "JACK can only be configured with a stopped studio. Please stop your studio first."
47 title
= "Studio is running"
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
)
60 def __init__(self
, path
):
68 return jack
.get_param_type(self
.path
)
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
)
85 descr
= self
.get_short_description()
89 return jack
.param_has_range(self
.path
)
92 return jack
.param_get_range(self
.path
)
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
:
110 def get_description(self
):
113 def get_window_title(self
):
114 return self
.get_description();
119 class jack_driver_change_command(configure_command
):
120 def get_description(self
):
121 return 'Select JACK driver'
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()
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
))
159 if ret
== gtk
.RESPONSE_OK
:
160 jack
.select_driver(store
.get(selection
.get_selected()[1], 0)[0])
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
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
)
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
= []
192 self
.range_min
, self
.range_max
= self
.param
.get_range()
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]))
201 return self
.param
.get_type()
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()
216 def set_value(self
, value
):
217 #print "%s -> %s" % (self.name, value)
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
):
229 self
.param
.set_value(self
.value
)
230 self
.modified
= False
233 return self
.range_min
, self
.range_max
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()
251 return model
[active
][model_index
]
253 class cell_renderer_param(gtk
.GenericCellRenderer
):
254 __gproperties__
= { "parameter": (gobject
.TYPE_OBJECT
, "Parameter", "Parameter", gobject
.PARAM_READWRITE
) }
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
)
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
)
275 self
.set_property('mode', gtk
.CELL_RENDERER_MODE_EDITABLE
)
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()
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)
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
:
310 for enum_value
in self
.parameter
.get_enum_values():
311 if enum_value
.value
== value
:
312 text
= enum_value
.get_description()
317 self
.renderer
.set_property('text', text
)
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))))
329 self
.renderer
.set_property('adjustment', gtk
.Adjustment(value
, 0, 100000, 1, 1000))
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"
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)
361 value_str
= value
.value
362 elif type(widget
) == gtk
.ComboBoxEntry
:
363 enum_value
= combobox_get_active_text(widget
, 1)
365 value_str
= enum_value
.value
367 value_str
= widget
.get_active_text()
369 if typechar
== 'u' or typechar
== 'i':
371 value
= int(value_str
)
372 except ValueError, e
:
373 # Hide the widget (because it may display something else than what user typed in)
376 mdlg
= gtk
.MessageDialog(buttons
= gtk
.BUTTONS_OK
, message_format
= "Invalid value. Please enter an integer number.")
379 # Return the focus back to the tree to prevent buttons from stealing it
380 self
.edit_tree
.grab_focus()
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
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
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]
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":
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()
432 class jack_params_configure_command(configure_command
):
433 def __init__(self
, path
):
435 self
.is_setting
= False
437 def reset_value(self
, row_path
):
438 row
= self
.liststore
[row_path
]
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
:
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
456 def on_key_press_event(self
, tree
, event
):
457 cur
= self
.treeview
.get_cursor()
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
)
466 # prevent ESC from activating the editor
467 if event
.string
< " ":
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
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')):
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:
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
)
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
)
509 widget
.set_value(float(event
.string
))
510 sl
= len(widget
.get_text())
511 widget
.select_region(sl
, sl
)
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()
518 iter = model
.get_iter_root()
520 if model
.get_value(iter, 0).startswith(event
.string
):
521 item
= model
.get_path(iter)[0]
523 iter = model
.iter_next(iter)
524 widget
.set_active(item
)
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
534 self
.treeview
.set_cursor_on_cell(cur
[0], self
.tvcolumn_value
)
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()
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
)
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
)
593 # move cursor to first row and 'value' column
594 self
.treeview
.set_cursor((0,), self
.tvcolumn_value
)
598 if ret
== gtk
.RESPONSE_OK
:
599 for row
in self
.liststore
:
601 reset
= row
[4] == "reset"
603 #print "%s -> reset" % param.get_name()
604 param
.param
.reset_value()
607 #print "%s -> %s" % (param.get_name(), param.get_value())
608 param
.maybe_save_value()
611 class jack_engine_params_configure_command(jack_params_configure_command
):
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
):
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
):
631 jack_params_configure_command
.__init
__(self
, ['internals', name
])
633 def get_description(self
):
634 return 'JACK "%s" parameters' % self
.name
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
)
659 window
.connect('destroy', gtk
.main_quit
)