# Copyright (C) 2006 Adam Olsen
#
# 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 1, 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, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import thread, os, os.path, string
import tracks, xlmisc, media, audioscrobbler
from gettext import gettext as _
import pygtk, common
pygtk.require('2.0')
import gtk, gtk.glade, pango, subprocess
try:
import gamin
GAMIN_AVAIL = True
except ImportError:
GAMIN_AVAIL = False
settings = None
xml = None
TEXT_VIEW_DEFAULT = """{title}
{artist}
on {album} - [{length}]"""
class PrefsItem(object):
"""
Representing a gtk.Entry preferences item
"""
def __init__(self, name, default, change=None, done=None):
"""
Initializes the preferences item
expects the name of the widget in the .glade file, the default for
this setting, an optional function to be called when the value is
changed, and an optional function to be called when this setting
is applied
"""
self.widget = xml.get_widget(('prefs_%s' % name).replace("/", "_"))
self.name = name
self.default = default
self.change = change
self.done = done
self.set_pref()
if change:
self.setup_change()
def setup_change(self):
"""
Sets up the function to be called when this preference is changed
"""
self.widget.connect('focus-out-event',
self.change, self.name, self.widget.get_text())
self.widget.connect('activate',
lambda *e: self.change(self.widget, None, self.name,
self.widget.get_text()))
def set_pref(self):
"""
Sets the GUI widget up for this preference
"""
self.widget.set_text(str(settings.get_str(self.name, default=self.default)))
def do_done(self):
"""
Calls the done function
"""
return self.done(self.widget)
def apply(self):
"""
applies this setting
"""
if self.done and not self.do_done(): return False
settings.set_str(self.name, self.widget.get_text())
return True
class CryptedPrefsItem(PrefsItem):
"""
An encrypted preferences item
"""
def __init__(self, name, default, change=None, done=None):
PrefsItem.__init__(self, name, default, change, done)
def set_pref(self):
self.widget.set_text(settings.get_crypted(self.name,
default=self.default))
def apply(self):
if self.done and not self.do_done(): return False
settings.set_crypted(self.name, self.widget.get_text())
return True
class PrefsTextViewItem(PrefsItem):
"""
Represents a gtk.TextView
"""
def __init__(self, name, default, change=None, done=None):
"""
Initializes the object
"""
PrefsItem.__init__(self, name, default, change, done)
def setup_change(self):
"""
Detects changes in this widget
"""
self.widget.connect('focus-out-event',
self.change, self.name, self.get_all_text())
def get_all_text(self):
"""
Returns the value of the text buffer
"""
buf = self.widget.get_buffer()
start = buf.get_start_iter()
end = buf.get_end_iter()
return buf.get_text(start, end)
def set_pref(self):
"""
Sets the value of this widget
"""
self.widget.get_buffer().set_text(str(settings.get_str(self.name,
default=self.default)))
def do_done(self):
"""
Calls the done function
"""
return self.done(self.widget)
def apply(self):
"""
Applies the setting
"""
if self.done and not self.do_done(): return False
settings.set_str(self.name, self.get_all_text())
return True
class CheckPrefsItem(PrefsItem):
"""
A class to represent check boxes in the preferences window
"""
def __init__(self, name, default, change=None, done=None):
PrefsItem.__init__(self, name, default, change, done)
def setup_change(self):
self.widget.connect('toggled',
self.change)
def set_pref(self):
self.widget.set_active(settings.get_boolean(self.name,
self.default))
def apply(self):
if self.done and not self.do_done(): return False
settings.set_boolean(self.name, self.widget.get_active())
return True
class ListPrefsItem(PrefsItem):
"""
A class to represent a space separated list in the preferences window
"""
def __init__(self, name, default, change=None, done=None):
PrefsItem.__init__(self, name, default, change, done)
def set_pref(self):
items = settings.get_list(self.name, default=self.default)
self.widget.set_text(" ".join(items))
def apply(self):
if self.done and not self.do_done(): return False
text = self.widget.get_text()
settings.set_list(self.name, text.split(' '))
return True
class FloatPrefsItem(PrefsItem):
"""
A class to represent a floating point number in the preferences window
"""
def __init__(self, name, default, change=None, done=None):
PrefsItem.__init__(self, name, default, change, done)
def set_pref(self):
self.widget.set_text(str(settings.get_float(self.name, default=self.default)))
def apply(self):
if self.done and not self.do_done(): return False
settings.set_float(self.name, float(self.widget.get_text()))
return True
class ColorButtonPrefsItem(PrefsItem):
"""
A class to represent the color button in the prefs window
"""
def __init__(self, name, default, change=None, done=None):
PrefsItem.__init__(self, name, default, change, done)
def setup_change(self):
self.widget.connect('color-set',
self.change, self.name)
def set_pref(self):
self.widget.set_color(gtk.gdk.color_parse(
settings.get_str(self.name, self.default)))
def apply(self):
if self.done and not self.do_done(): return False
color = self.widget.get_color()
string = "#%.2x%.2x%.2x" % (color.red / 257, color.green / 257,
color.blue / 257)
settings[self.name] = string
return True
class FontButtonPrefsItem(ColorButtonPrefsItem):
"""
Font button
"""
def __init__(self, name, default, change=None, done=None):
ColorButtonPrefsItem.__init__(self, name, default, change, done)
def setup_change(self):
self.widget.connect('font-set', self.change, self.name)
def set_pref(self):
font = settings.get_str(self.name, self.default)
self.widget.set_font_name(font)
def apply(self):
if self.done and not self.do_don(): return False
font = self.widget.get_font_name()
settings[self.name] = font
return True
class DirPrefsItem(PrefsItem):
"""
Directory chooser button
"""
def __init__(self, name, default, change=None, done=None):
PrefsItem.__init__(self, name, default, change, done)
def setup_change(self):
pass
def set_pref(self):
"""
Sets the current directory
"""
directory = settings.get_str(self.name, self.default)
self.widget.set_filename(directory)
def apply(self):
if self.done and not self.do_done(): return False
directory = self.widget.get_filename()
settings[self.name] = directory
return True
class ComboPrefsItem(PrefsItem):
"""
combo box
"""
def __init__(self, name, default, change=None, done=None,
use_index=False):
self.use_index = use_index
PrefsItem.__init__(self, name, default, change, done)
def setup_change(self):
self.widget.connect('changed',
self.change)
def set_pref(self):
item = settings.get_str(self.name, self.default)
if self.use_index:
index = settings.get_int(self.name, self.default)
self.widget.set_active(index)
return
model = self.widget.get_model()
iter = model.get_iter_first()
count = 0
while True:
value = model.get_value(iter, 0)
if value == item:
self.widget.set_active(count)
break
count += 1
iter = model.iter_next(iter)
if not iter: break
def apply(self):
if self.done and not self.do_done(): return False
if self.use_index:
settings[self.name] = self.widget.get_active()
else:
settings[self.name] = self.widget.get_active_text()
return True
class Preferences(object):
"""
Preferences Dialog
"""
order = (_("General"), _("Advanced"))
items = ({_("General"):
(_("Library"),
_('Notification'),
_('Last.fm'),
_('Radio')),
_("Advanced"):
'',
})
def __init__(self, parent):
"""
Initilizes the preferences dialog
"""
global settings, xml
self.exaile = parent
self.fields = []
self.warning_shown = False
self.popup = None
self.osd_settings = xlmisc.get_osd_settings(self.exaile.settings)
settings = self.exaile.settings
self.xml = gtk.glade.XML('exaile.glade', 'PreferencesDialog', 'exaile')
xml = self.xml
self.window = self.xml.get_widget('PreferencesDialog')
self.window.set_transient_for(parent.window)
self.window.connect('delete-event', lambda *e: self.cancel())
self.nb = self.xml.get_widget('prefs_nb')
self.nb.set_show_tabs(False)
self.xml.get_widget('prefs_cancel_button').connect('clicked',
lambda *e: self.cancel())
self.xml.get_widget('prefs_apply_button').connect('clicked',
self.apply)
self.xml.get_widget('prefs_ok_button').connect('clicked',
self.ok)
self.label = self.xml.get_widget('prefs_frame_label')
self.tree = self.xml.get_widget('prefs_tree')
text = gtk.CellRendererText()
col = gtk.TreeViewColumn(_('Preferences'), text, text=0)
self.tree.append_column(col)
self.model = gtk.TreeStore(str, int)
self.tree.set_model(self.model)
count = 0
for header in self.order:
items = self.items[header]
node = self.model.append(None, [_(header), count]) # header is a string that must be translated by gettext, therefore it is necessary to wrap it in _()
count += 1
for item in items:
self.model.append(node, [_(item), _(count)]) # item and count are string that must be translated by gettext, therefore it is necessary to wrap them in _()
count += 1
self.tree.expand_row(self.model.get_path(node), False)
selection = self.tree.get_selection()
selection.connect('changed', self.switch_pane)
selection.select_path((0,))
xml.get_widget('prefs_lastfm_pass').set_invisible_char('*')
xml.get_widget('prefs_audio_sink').set_active(0)
self.text_display = PrefsTextViewItem('osd/display_text',
TEXT_VIEW_DEFAULT, self.display_popup)
self.fields.append(self.text_display)
self.fields.append(ComboPrefsItem('ui/tab_placement',
0, None, self.setup_tabs, use_index=True))
simple_settings = ({
'ui/use_splash': (CheckPrefsItem, True),
# 'watch_directories': (CheckPrefsItem, False, self.check_gamin,
# self.setup_gamin),
# 'watch_exclude_dirs': (ListPrefsItem, []),
'fetch_covers': (CheckPrefsItem, True),
'save_queue': (CheckPrefsItem, True),
'ui/ensure_visible': (CheckPrefsItem, True),
'art_filenames': (ListPrefsItem,
['cover.jpg', 'folder.jpg', '.folder.jpg', 'album.jpg', 'art.jpg']),
'open_last': (CheckPrefsItem, True),
'osd/enabled': (CheckPrefsItem, True),
'osd/w': (PrefsItem, '400', self.osd_adjust_size),
'osd/h': (PrefsItem, '95', self.osd_adjust_size),
'lastfm/user': (PrefsItem, ''),
'lastfm/pass': (CryptedPrefsItem, '', None, self.setup_lastfm),
'cd_device': (PrefsItem, '/dev/cdrom'),
'audio_sink': (ComboPrefsItem, 'Use GConf Settings'),
'osd/tray': (CheckPrefsItem, True),
'osd/bgcolor': (ColorButtonPrefsItem, '#567ea2',
self.osd_colorpicker),
'osd/text_color': (ColorButtonPrefsItem, '#ffffff',
self.osd_colorpicker),
'osd/text_font': (FontButtonPrefsItem, 'Sans 10',
self.osd_fontpicker),
'ui/use_tray': (CheckPrefsItem, False, None, self.setup_tray),
'amazon_locale': (PrefsItem, 'us'),
'wikipedia_locale': (PrefsItem, 'en'),
'scan_interval': (FloatPrefsItem, 25, None,
self.setup_scan_interval),
'download_feeds': (CheckPrefsItem, True),
})
for setting, value in simple_settings.iteritems():
c = value[0]
default = value[1]
if len(value) == 3: change = value[2]
else: change = None
if len(value) == 4: done = value[3]
else: done = None
item = c(setting, default, change, done)
self.fields.append(item)
def setup_scan_interval(self, widget):
"""
Makes sure the scan interval is valid, and starts it over with the
new time value
"""
value = widget.get_text()
try:
value = float(value)
if value < 1 and value != 0:
common.error(self.exaile.window, "Library scan interval must "
"be at least 1 minute.")
return False
except ValueError:
common.error(self.exaile.window, "%s is an invalid value "
"for the library rescan interval" % value)
return False
self.exaile.start_scan_interval(value)
return True
def check_gamin(self, widget):
"""
Make sure gamin is availabe
"""
if widget.get_active():
if not GAMIN_AVAIL:
common.error(self.exaile.window,
_("Cannot watch directories for changes. "
"Install python2.4-gamin to use this feature."))
widget.set_active(False)
return False
def setup_lastfm(self, widget):
"""
Connects to last.fm if the password field isn't empty
"""
if not widget.get_text(): return True
user = xml.get_widget('prefs_lastfm_user').get_text()
password = widget.get_text()
thread.start_new_thread(audioscrobbler.get_scrobbler_session,
(self.exaile, user, password, True))
return True
def setup_gamin(self, widget):
"""
Sets up gamin if needs be
"""
if widget.get_active() and not self.exaile.mon:
self.exaile.setup_gamin(True)
return True
def setup_tabs(self, widget, *p):
"""
Sets up tab placement for the playlists tab
"""
index = widget.get_active()
self.exaile.set_tab_placement(index)
return True
def ok(self, widget):
"""
Called when the user clicks 'ok'
"""
if self.apply(None):
self.cancel()
self.window.hide()
self.window.destroy()
def apply(self, widget):
"""
Applies settings
"""
for item in self.fields:
if not item.apply():
print item.name
return False
xlmisc.POPUP = None
return True
def setup_tray(self, widget, event=None):
"""
Sets up the tray icon
"""
val = widget.get_active()
if val: self.exaile.setup_tray()
else: self.exaile.remove_tray()
return True
def osd_adjust_size(self, widget, event, name, previous):
"""
Called when the user requests to adjust the size of the osd
"""
try:
val = int(widget.get_text())
except ValueError:
widget.set_text(previous)
return
self.osd_settings[name] = val
self.display_popup()
def osd_fontpicker(self, widget, name):
"""
Gets the font from the font picker, and re-sets up the OSD window
"""
self.osd_settings[name] = widget.get_font_name()
self.display_popup()
def osd_colorpicker(self, widget, name):
"""
Shows a colorpicker
"""
color = widget.get_color()
string = "#%.2x%.2x%.2x" % (color.red / 257, color.green / 257,
color.blue / 257)
self.osd_settings[name] = string
self.display_popup()
def cancel(self):
"""
Closes the preferences dialog, ensuring that the osd popup isn't
still showing
"""
if self.popup:
self.popup.window.hide()
self.popup.window.destroy()
self.window.hide()
self.window.destroy()
def switch_pane(self, selection):
"""
Switches a pane
"""
(model, iter) = selection.get_selected()
if not iter: return
index = self.model.get_value(iter, 1)
self.nb.set_current_page(index)
page = self.nb.get_nth_page(index)
title = self.nb.get_tab_label(page)
self.label.set_markup("%s" %
common.escape_xml(title.get_label()))
if index == 2:
self.osd_settings = xlmisc.get_osd_settings(self.exaile.settings)
self.display_popup()
else:
if self.popup:
self.popup.window.destroy()
self.popup = None
def display_popup(self, *e):
"""
Shows the OSD window
"""
if self.popup:
(x, y) = self.popup.window.get_position()
self.osd_settings['osd/x'] = x
self.osd_settings['osd/y'] = y
self.popup.window.destroy()
if not self.warning_shown:
common.info(self.window,
_("Move the OSD window to the location you want it to "
"appear when listening"))
self.warning_shown = True
self.popup = xlmisc.OSDWindow(self.exaile, self.osd_settings,
False, True)
track = BlankClass()
for item in ('title', 'artist', 'album', 'length', 'track', 'bitrate',
'genre', 'year', 'rating'):
setattr(track, item, item)
display_text = self.text_display.get_all_text()
display_text = display_text.replace("{volume}", "\\{volume\\}")
self.popup.show_track_osd(track, display_text,
'images%snocover.png' % os.sep)
return False
def run(self):
"""
Runs the dialog
"""
self.window.show_all()
class BlankClass(object):
pass