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>
6 # Copyright (C) 2007-2009, Nedko Arnaudov <nedko@arnaudov.name>
7 # Copyright (C) 2008, Krzysztof Foltman <wdev@foltman.com>
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
29 jack
= laditools
.jack_configure()
33 proxy
= laditools
.ladish_proxy()
35 print "ladish proxy creation failed"
38 if not proxy
.is_available():
39 print "ladish is not available"
42 if proxy
.studio_is_loaded():
43 if not proxy
.studio_is_started():
44 print "ladish studio is loaded and not started"
47 msg
= "JACK can only be configured with a stopped studio. Please stop your studio first."
48 title
= "Studio is running"
50 msg
= "JACK can only be configured with a loaded and stopped studio. Please create a new studio or load and stop an existing one."
51 title
= "No studio present"
53 # studio is not loaded or loaded and started
54 mdlg
= gtk
.MessageDialog(type = gtk
.MESSAGE_ERROR
, buttons
= gtk
.BUTTONS_CLOSE
, message_format
= msg
)
61 def __init__(self
, path
):
69 return jack
.get_param_type(self
.path
)
72 return jack
.get_param_value(self
.path
)
74 def set_value(self
, value
):
75 jack
.set_param_value(self
.path
, value
)
77 def reset_value(self
):
78 jack
.reset_param_value(self
.path
)
80 def get_short_description(self
):
81 return jack
.get_param_short_description(self
.path
)
83 def get_long_description(self
):
84 descr
= jack
.get_param_long_description(self
.path
)
86 descr
= self
.get_short_description()
90 return jack
.param_has_range(self
.path
)
93 return jack
.param_get_range(self
.path
)
96 return jack
.param_has_enum(self
.path
)
98 def is_strict_enum(self
):
99 return jack
.param_is_strict_enum(self
.path
)
101 def is_fake_values_enum(self
):
102 return jack
.param_is_fake_value(self
.path
)
104 def get_enum_values(self
):
105 return jack
.param_get_enum_values(self
.path
)
107 class configure_command
:
111 def get_description(self
):
114 def get_window_title(self
):
115 return self
.get_description();
120 class parameter_enum_value(gobject
.GObject
):
121 def __init__(self
, is_fake_value
, value
, description
):
122 gobject
.GObject
.__init
__(self
)
123 self
.is_fake_value
= is_fake_value
125 self
.description
= description
127 def get_description(self
):
128 if self
.is_fake_value
:
129 return self
.description
131 return str(self
.value
) + " - " + self
.description
133 gobject
.type_register(parameter_enum_value
)
135 class parameter_store(gobject
.GObject
):
136 def __init__(self
, param
):
137 gobject
.GObject
.__init
__(self
)
139 self
.name
= self
.param
.get_name()
140 self
.is_set
, self
.default_value
, self
.value
= self
.param
.get_value()
141 self
.modified
= False
142 self
.has_range
= self
.param
.has_range()
143 self
.is_strict
= self
.param
.is_strict_enum()
144 self
.is_fake_value
= self
.param
.is_fake_values_enum()
146 self
.enum_values
= []
149 self
.range_min
, self
.range_max
= self
.param
.get_range()
151 for enum_value
in self
.param
.get_enum_values():
152 self
.enum_values
.append(parameter_enum_value(self
.is_fake_value
, enum_value
[0], enum_value
[1]))
158 return self
.param
.get_type()
163 def get_default_value(self
):
164 if not self
.is_fake_value
:
165 return str(self
.default_value
)
167 for enum_value
in self
.get_enum_values():
168 if enum_value
.value
== self
.default_value
:
169 return enum_value
.get_description()
173 def set_value(self
, value
):
174 #print "%s -> %s" % (self.name, value)
178 def reset_value(self
):
179 self
.value
= self
.default_value
181 def get_short_description(self
):
182 return self
.param
.get_short_description()
184 def maybe_save_value(self
):
186 self
.param
.set_value(self
.value
)
187 self
.modified
= False
190 return self
.range_min
, self
.range_max
193 return len(self
.enum_values
) != 0
195 def is_strict_enum(self
):
196 return self
.is_strict
198 def get_enum_values(self
):
199 return self
.enum_values
201 gobject
.type_register(parameter_store
)
203 def combobox_get_active_text(combobox
, model_index
= 0):
204 model
= combobox
.get_model()
205 active
= combobox
.get_active()
208 return model
[active
][model_index
]
210 class cell_renderer_param(gtk
.GenericCellRenderer
):
211 __gproperties__
= { "parameter": (gobject
.TYPE_OBJECT
, "Parameter", "Parameter", gobject
.PARAM_READWRITE
) }
214 self
.__gobject
_init
__()
215 self
.parameter
= None
216 #self.set_property('mode', gtk.CELL_RENDERER_MODE_EDITABLE)
217 #self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
218 self
.renderer_text
= gtk
.CellRendererText()
219 self
.renderer_toggle
= gtk
.CellRendererToggle()
220 self
.renderer_combo
= gtk
.CellRendererCombo()
221 self
.renderer_spinbutton
= gtk
.CellRendererSpin()
222 for r
in (self
.renderer_text
, self
.renderer_combo
, self
.renderer_spinbutton
):
223 r
.connect("edited", self
.on_edited
)
225 self
.edit_widget
= None
227 def do_set_property(self
, pspec
, value
):
228 if pspec
.name
== 'parameter':
229 if value
.get_type() == 'b':
230 self
.set_property('mode', gtk
.CELL_RENDERER_MODE_ACTIVATABLE
)
232 self
.set_property('mode', gtk
.CELL_RENDERER_MODE_EDITABLE
)
235 setattr(self
, pspec
.name
, value
)
237 def do_get_property(self
, pspec
):
238 return getattr(self
, pspec
.name
)
240 def choose_renderer(self
):
241 typechar
= self
.parameter
.get_type()
242 value
= self
.parameter
.get_value()
245 self
.renderer
= self
.renderer_toggle
246 self
.renderer
.set_property('activatable', True)
247 self
.renderer
.set_property('active', value
)
248 self
.renderer
.set_property("xalign", 0.0)
251 if self
.parameter
.has_enum():
252 self
.renderer
= self
.renderer_combo
254 m
= gtk
.ListStore(str, parameter_enum_value
)
256 for value
in self
.parameter
.get_enum_values():
257 m
.append([value
.get_description(), value
])
259 self
.renderer
.set_property("model",m
)
260 self
.renderer
.set_property('text-column', 0)
261 self
.renderer
.set_property('editable', True)
262 self
.renderer
.set_property('has_entry', not self
.parameter
.is_strict_enum())
264 value
= self
.parameter
.get_value()
265 if self
.parameter
.is_fake_value
:
267 for enum_value
in self
.parameter
.get_enum_values():
268 if enum_value
.value
== value
:
269 text
= enum_value
.get_description()
274 self
.renderer
.set_property('text', text
)
278 if typechar
== 'u' or typechar
== 'i':
279 self
.renderer
= self
.renderer_spinbutton
280 self
.renderer
.set_property('text', str(value
))
281 self
.renderer
.set_property('editable', True)
282 if self
.parameter
.has_range
:
283 range_min
, range_max
= self
.parameter
.get_range()
284 self
.renderer
.set_property('adjustment', gtk
.Adjustment(value
, range_min
, range_max
, 1, abs(int((range_max
- range_min
) / 10))))
286 self
.renderer
.set_property('adjustment', gtk
.Adjustment(value
, 0, 100000, 1, 1000))
289 self
.renderer
= self
.renderer_text
290 self
.renderer
.set_property('editable', True)
291 self
.renderer
.set_property('text', self
.parameter
.get_value())
293 def on_render(self
, window
, widget
, background_area
, cell_area
, expose_area
, flags
):
294 self
.choose_renderer()
295 return self
.renderer
.render(window
, widget
, background_area
, cell_area
, expose_area
, flags
)
297 def on_get_size(self
, widget
, cell_area
=None):
298 self
.choose_renderer()
299 return self
.renderer
.get_size(widget
, cell_area
)
301 def on_activate(self
, event
, widget
, path
, background_area
, cell_area
, flags
):
302 self
.choose_renderer()
303 if self
.parameter
.get_type() == 'b':
304 self
.parameter
.set_value(not self
.parameter
.get_value())
305 widget
.get_model()[path
][4] = "modified"
308 def on_edited(self
, renderer
, path
, value_str
):
309 parameter
= self
.edit_parameter
310 widget
= self
.edit_widget
311 model
= self
.edit_tree
.get_model()
312 self
.edit_widget
= None
313 typechar
= parameter
.get_type()
314 if type(widget
) == gtk
.ComboBox
:
315 value
= combobox_get_active_text(widget
, 1)
318 value_str
= value
.value
319 elif type(widget
) == gtk
.ComboBoxEntry
:
320 enum_value
= combobox_get_active_text(widget
, 1)
322 value_str
= enum_value
.value
324 value_str
= widget
.get_active_text()
326 if typechar
== 'u' or typechar
== 'i':
328 value
= int(value_str
)
329 except ValueError, e
:
330 # Hide the widget (because it may display something else than what user typed in)
333 mdlg
= gtk
.MessageDialog(buttons
= gtk
.BUTTONS_OK
, message_format
= "Invalid value. Please enter an integer number.")
336 # Return the focus back to the tree to prevent buttons from stealing it
337 self
.edit_tree
.grab_focus()
341 parameter
.set_value(value
)
342 model
[path
][4] = "modified"
343 self
.edit_tree
.grab_focus()
345 def on_start_editing(self
, event
, widget
, path
, background_area
, cell_area
, flags
):
346 # this happens when edit requested using keyboard
348 event
= gtk
.gdk
.Event(gtk
.gdk
.NOTHING
)
350 self
.choose_renderer()
351 ret
= self
.renderer
.start_editing(event
, widget
, path
, background_area
, cell_area
, flags
)
352 self
.edit_widget
= ret
353 self
.edit_tree
= widget
354 self
.edit_parameter
= self
.parameter
357 gobject
.type_register(cell_renderer_param
)
359 class ladiconf_tooltips(laditools
.TreeViewTooltips
):
360 def __init__(self
, name_column
, is_set_column
):
361 self
.name_column
= name_column
362 self
.is_set_column
= is_set_column
363 laditools
.TreeViewTooltips
.__init
__(self
)
365 def location(self
, x
, y
, w
, h
):
366 return x
+ 10, y
+ 10
368 def get_tooltip(self
, view
, column
, path
):
369 if column
is self
.name_column
:
370 model
= view
.get_model()
371 tooltip
= model
[path
][3]
374 if column
is self
.is_set_column
:
375 model
= view
.get_model()
376 param
= model
[path
][1]
377 is_set
= model
[path
][4]
379 if is_set
== "default":
382 if is_set
== "reset":
383 return "Value will be reset to %s" % param
.get_default_value()
385 return "Double-click to schedule reset of value to %s" % param
.get_default_value()
389 class jack_params_configure_command(configure_command
):
390 def __init__(self
, path
):
392 self
.is_setting
= False
394 def reset_value(self
, row_path
):
395 row
= self
.liststore
[row_path
]
400 def on_row_activated(self
, treeview
, path
, view_column
):
401 if view_column
== self
.tvcolumn_is_set
:
402 self
.reset_value(path
)
404 def on_button_press_event(self
, tree
, event
):
405 if event
.type != gtk
.gdk
._2BUTTON
_PRESS
:
407 # this is needed for proper double-click handling in the list; don't ask me why, I don't know
408 # it's probably because _2BUTTON_PRESS event is still delivered to tree view, automatically deactivating
409 # the newly created edit widget (which gets created on second BUTTON_PRESS but before _2BUTTON_PRESS)
410 # deactivating the widget causes it to be deleted
413 def on_key_press_event(self
, tree
, event
):
414 cur
= self
.treeview
.get_cursor()
417 # if Delete was pressed, reset the value
418 if event
.state
== 0 and event
.keyval
== gtk
.keysyms
.Delete
:
419 self
.reset_value(row_path
)
423 # prevent ESC from activating the editor
424 if event
.string
< " ":
427 # single-key data entry: if the control is a text entry, spin button or combo box/combo box entry,
428 # then edit the current and set the text value to what user has already typed in
429 cur
= self
.treeview
.get_cursor()
430 param
= self
.liststore
[row_path
][1]
431 ptype
= param
.get_type()
433 # we don't care about booleans
437 # accept only digits for integer input (or a minus, but only if it's a signed field)
438 if ptype
in ('i', 'u'):
439 if not (event
.string
.isdigit() or (event
.string
== "-" and ptype
== 'i')):
443 # MAYBE: call a specially crafted cell_renderer_param method for this
444 self
.treeview
.set_cursor_on_cell(cur
[0], self
.tvcolumn_value
, self
.renderer_value
.renderer
, True)
446 # cell_renderer_param::on_start_editing() should set edit_widget.
447 # if edit operation has failed (didn't create a widget), pass the key on
448 # MAYBE: call a specially crafted cell_renderer_param method to do this check
449 if self
.renderer_value
.edit_widget
== None:
452 widget
= self
.renderer_value
.edit_widget
453 if type(widget
) in (gtk
.Entry
, gtk
.ComboBoxEntry
):
454 # (combo or plain) text entry - set the content and move the cursor to the end
455 sl
= len(event
.string
)
456 widget
.set_text(event
.string
)
457 widget
.select_region(sl
, sl
)
460 if type(self
.renderer_value
.edit_widget
) == gtk
.SpinButton
:
461 # spin button - set the value and move the cursor to the end
462 if event
.string
== "-":
463 # special case for minus sign (which can't be converted to float)
464 widget
.set_text(event
.string
)
466 widget
.set_value(float(event
.string
))
467 sl
= len(widget
.get_text())
468 widget
.select_region(sl
, sl
)
471 if type(self
.renderer_value
.edit_widget
) == gtk
.ComboBox
:
472 # combo box - select the first item that starts with typed character
473 model
= widget
.get_model()
475 iter = model
.get_iter_root()
477 if model
.get_value(iter, 0).startswith(event
.string
):
478 item
= model
.get_path(iter)[0]
480 iter = model
.iter_next(iter)
481 widget
.set_active(item
)
486 def on_cursor_changed(self
, tree
):
487 cur
= self
.treeview
.get_cursor()
488 if not self
.is_setting
and cur
[1] != None and cur
[1].get_title() != self
.tvcolumn_value
.get_title():
489 self
.is_setting
= True
491 self
.treeview
.set_cursor_on_cell(cur
[0], self
.tvcolumn_value
)
493 self
.is_setting
= False
495 def ok_clicked(self
, dlg
):
496 if self
.renderer_value
.edit_widget
:
497 self
.renderer_value
.edit_widget
.editing_done()
501 dlg
.set_title(self
.get_window_title())
502 dlg
.add_button(gtk
.STOCK_CANCEL
, gtk
.RESPONSE_CANCEL
)
503 dlg
.add_button(gtk
.STOCK_OK
, gtk
.RESPONSE_OK
).connect("clicked", self
.ok_clicked
)
505 #dlg.set_transient_for(window)
507 self
.liststore
= gtk
.ListStore(str, parameter_store
, str, str, str)
508 self
.treeview
= gtk
.TreeView(self
.liststore
)
509 self
.treeview
.set_rules_hint(True)
510 self
.treeview
.set_enable_search(False)
512 renderer_text
= gtk
.CellRendererText()
513 renderer_toggle
= gtk
.CellRendererToggle()
514 renderer_value
= cell_renderer_param()
515 self
.renderer_value
= renderer_value
# save for use in event handler methods
517 self
.tvcolumn_parameter
= gtk
.TreeViewColumn('Parameter', renderer_text
, text
=0)
518 self
.tvcolumn_is_set
= gtk
.TreeViewColumn('Status', renderer_text
, text
=4)
519 self
.tvcolumn_value
= gtk
.TreeViewColumn('Value', renderer_value
, parameter
=1)
520 self
.tvcolumn_description
= gtk
.TreeViewColumn('Description', renderer_text
, text
=2)
522 self
.tvcolumn_value
.set_resizable(True)
523 self
.tvcolumn_value
.set_min_width(100)
525 self
.treeview
.append_column(self
.tvcolumn_parameter
)
526 self
.treeview
.append_column(self
.tvcolumn_is_set
)
527 self
.treeview
.append_column(self
.tvcolumn_value
)
528 self
.treeview
.append_column(self
.tvcolumn_description
)
530 dlg
.vbox
.pack_start(self
.treeview
, True)
532 param_names
= jack
.get_param_names(self
.path
)
533 for name
in param_names
:
534 param
= parameter(self
.path
+ [name
])
535 store
= parameter_store(param
)
540 self
.liststore
.append([name
, store
, param
.get_short_description(), param
.get_long_description(), is_set
])
542 self
.treeview
.connect("row-activated", self
.on_row_activated
)
543 self
.treeview
.connect("cursor-changed", self
.on_cursor_changed
)
544 self
.treeview
.connect("key-press-event", self
.on_key_press_event
)
545 self
.treeview
.connect("button-press-event", self
.on_button_press_event
)
547 self
.tooltips
= ladiconf_tooltips(self
.tvcolumn_parameter
, self
.tvcolumn_is_set
)
548 self
.tooltips
.add_view(self
.treeview
)
550 # move cursor to first row and 'value' column
551 self
.treeview
.set_cursor((0,), self
.tvcolumn_value
)
555 if ret
== gtk
.RESPONSE_OK
:
556 for row
in self
.liststore
:
558 reset
= row
[4] == "reset"
560 #print "%s -> reset" % param.get_name()
561 param
.param
.reset_value()
564 #print "%s -> %s" % (param.get_name(), param.get_value())
565 param
.maybe_save_value()
568 class jack_engine_params_configure_command(jack_params_configure_command
):
570 jack_params_configure_command
.__init
__(self
, ['engine'])
572 def get_description(self
):
573 return 'JACK engine parameters'
575 class jack_driver_params_configure_command(jack_params_configure_command
):
577 jack_params_configure_command
.__init
__(self
, ['driver'])
579 def get_description(self
):
580 return 'JACK driver parameters'
582 def get_window_title(self
):
583 return 'JACK "%s" driver parameters' % jack
.get_selected_driver()
585 class jack_internal_params_configure_command(jack_params_configure_command
):
586 def __init__(self
, name
):
588 jack_params_configure_command
.__init
__(self
, ['internals', name
])
590 def get_description(self
):
591 return 'JACK "%s" parameters' % self
.name
596 jack_engine_params_configure_command(),
597 jack_driver_params_configure_command(),
600 for internal
in jack
.read_container(['internals']):
601 commands
.append(jack_internal_params_configure_command(internal
))
603 window
= gtk
.Window()
605 buttons_widget
= gtk
.VBox()
607 for command
in commands
:
608 button
= gtk
.Button(command
.get_description())
609 button
.connect('clicked', command
.run
)
610 buttons_widget
.pack_start(button
)
612 window
.add(buttons_widget
)
615 window
.connect('destroy', gtk
.main_quit
)