Skip to content

Instantly share code, notes, and snippets.

@shashi
Created July 2, 2009 06:44
Show Gist options
  • Save shashi/139298 to your computer and use it in GitHub Desktop.
Save shashi/139298 to your computer and use it in GitHub Desktop.
__license__ = """
Sonata, an elegant GTK+ client for the Music Player Daemon
Copyright 2006-2008 Scott Horowitz <[email protected]>
This file is part of Sonata.
Sonata 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.
Sonata 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 sys, locale, gettext, os, warnings
import urllib, urllib2, re, gc, shutil
import threading
import mpd
import gobject, gtk, pango
# Prevent deprecation warning for egg:
warnings.simplefilter('ignore', DeprecationWarning)
try:
import egg.trayicon
HAVE_EGG = True
HAVE_STATUS_ICON = False
except ImportError:
HAVE_EGG = False
HAVE_STATUS_ICON = True
# Reset so that we can see any other deprecation warnings
warnings.simplefilter('default', DeprecationWarning)
# Default to no sugar, then test...
HAVE_SUGAR = False
VOLUME_ICON_SIZE = 4
if 'SUGAR_BUNDLE_PATH' in os.environ:
try:
from sugar.activity import activity
HAVE_STATUS_ICON = False
HAVE_SUGAR = True
VOLUME_ICON_SIZE = 3
except:
pass
import mpdhelper as mpdh
import misc, ui, img, tray
from consts import consts
from pluginsystem import pluginsystem
from preferences import Preferences
from config import Config
import tagedit, artwork, about, scrobbler, info, library, streams, playlists, current
import dbus_plugin as dbus
try:
import version
except ImportError:
import svnversion as version
class Base(object):
def __init__(self, args, window=None, sugar=False):
# The following attributes were used but not defined here before:
self.album_current_artist = None
self.allow_art_search = None
self.choose_dialog = None
self.chooseimage_visible = None
self.imagelist = None
self.iterate_handler = None
self.local_dest_filename = None
self.notification_width = None
self.remote_albumentry = None
self.remote_artistentry = None
self.remote_dest_filename = None
self.remotefilelist = None
self.seekidle = None
self.statusicon = None
self.trayeventbox = None
self.trayicon = None
self.trayimage = None
self.artwork = None
self.client = mpd.MPDClient()
self.conn = False
# Constants
self.TAB_CURRENT = _("Current")
self.TAB_LIBRARY = _("Library")
self.TAB_PLAYLISTS = _("Playlists")
self.TAB_STREAMS = _("Streams")
self.TAB_INFO = _("Info")
# If the connection to MPD times out, this will cause the interface to freeze while
# the socket.connect() calls are repeatedly executed. Therefore, if we were not
# able to make a connection, slow down the iteration check to once every 15 seconds.
self.iterate_time_when_connected = 500
self.iterate_time_when_disconnected_or_stopped = 1000 # Slow down polling when disconnected stopped
self.trying_connection = False
self.traytips = tray.TrayIconTips()
# better keep a reference around
try:
self.dbus_service = dbus.SonataDBus(self.dbus_show, self.dbus_toggle, self.dbus_popup)
except Exception:
pass
dbus.start_dbus_interface()
self.gnome_session_management()
misc.create_dir('~/.covers/')
# Initialize vars for GUI
self.current_tab = self.TAB_CURRENT
self.prevconn = []
self.prevstatus = None
self.prevsonginfo = None
self.popuptimes = ['2', '3', '5', '10', '15', '30', _('Entire song')]
self.exit_now = False
self.ignore_toggle_signal = False
self.user_connect = False
self.sonata_loaded = False
self.call_gc_collect = False
self.album_reset_artist()
show_prefs = False
self.merge_id = None
self.actionGroupProfiles = None
self.skip_on_profiles_click = False
self.last_repeat = None
self.last_random = None
self.last_title = None
self.last_progress_frac = None
self.last_progress_text = None
self.last_status_text = ""
self.eggtrayfile = None
self.eggtrayheight = None
self.img_clicked = False
self.mpd_update_queued = False
self.prefs_last_tab = 0
# XXX get rid of all of these:
self.all_tab_names = [self.TAB_CURRENT, self.TAB_LIBRARY, self.TAB_PLAYLISTS, self.TAB_STREAMS, self.TAB_INFO]
all_tab_ids = "current library playlists streams info".split()
self.tabname2id = dict(zip(self.all_tab_names, all_tab_ids))
self.tabid2name = dict(zip(all_tab_ids, self.all_tab_names))
self.tabname2focus = dict()
self.config = Config(_('Default Profile'), _("by") + " %A " + _("from") + " %B", library.library_set_data)
self.preferences = Preferences(self.config)
self.settings_load()
if args.start_visibility is not None:
self.config.withdrawn = not args.start_visibility
if self.config.autoconnect:
self.user_connect = True
args.apply_profile_arg(self)
self.notebook_show_first_tab = not self.config.tabs_expanded or self.config.withdrawn
# Add some icons, assign pixbufs:
self.iconfactory = gtk.IconFactory()
ui.icon(self.iconfactory, 'sonata', self.find_path('sonata.png'))
ui.icon(self.iconfactory, 'artist', self.find_path('sonata-artist.png'))
ui.icon(self.iconfactory, 'album', self.find_path('sonata-album.png'))
icon_theme = gtk.icon_theme_get_default()
if HAVE_SUGAR:
activity_root = activity.get_bundle_path()
icon_theme.append_search_path(os.path.join(activity_root, 'share'))
img_width, _img_height = gtk.icon_size_lookup(VOLUME_ICON_SIZE)
for iconname in ('stock_volume-mute', 'stock_volume-min', 'stock_volume-med', 'stock_volume-max'):
try:
ui.icon(self.iconfactory, iconname, icon_theme.lookup_icon(iconname, img_width, gtk.ICON_LOOKUP_USE_BUILTIN).get_filename())
except:
# Fallback to Sonata-included icons:
ui.icon(self.iconfactory, iconname, self.find_path('sonata-'+iconname+'.png'))
# Main window
if window is None:
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window_owner = True
else:
self.window = window
self.window_owner = False
if self.window_owner:
self.window.set_title('Sonata')
self.window.set_role('mainWindow')
self.window.set_resizable(True)
if self.config.ontop:
self.window.set_keep_above(True)
if self.config.sticky:
self.window.stick()
if not self.config.decorated:
self.window.set_decorated(False)
self.notebook = gtk.Notebook()
# Artwork
self.artwork = artwork.Artwork(self.config, self.find_path, misc.is_lang_rtl(self.window), lambda:self.info_imagebox.get_size_request(), self.schedule_gc_collect, self.target_image_filename, self.imagelist_append, self.remotefilelist_append, self.notebook.get_allocation, self.set_allow_art_search, self.status_is_play_or_pause, self.find_path('sonata-album.png'), self.get_current_song_text)
# Popup menus:
actions = (
('sortmenu', None, _('_Sort List')),
('plmenu', None, _('Sa_ve List to')),
('profilesmenu', None, _('_Connection')),
('playaftermenu', None, _('P_lay after')),
('updatemenu', None, _('_Update')),
('chooseimage_menu', gtk.STOCK_CONVERT, _('Use _Remote Image...'), None, None, self.image_remote),
('localimage_menu', gtk.STOCK_OPEN, _('Use _Local Image...'), None, None, self.image_local),
('fullscreencoverart_menu', gtk.STOCK_FULLSCREEN, _('_Fullscreen Mode'), 'F11', None, self.fullscreen_cover_art),
('resetimage_menu', gtk.STOCK_CLEAR, _('Reset Image'), None, None, self.artwork.on_reset_image),
('playmenu', gtk.STOCK_MEDIA_PLAY, _('_Play'), None, None, self.mpd_pp),
('pausemenu', gtk.STOCK_MEDIA_PAUSE, _('Pa_use'), None, None, self.mpd_pp),
('stopmenu', gtk.STOCK_MEDIA_STOP, _('_Stop'), None, None, self.mpd_stop),
('prevmenu', gtk.STOCK_MEDIA_PREVIOUS, _('Pre_vious'), None, None, self.mpd_prev),
('nextmenu', gtk.STOCK_MEDIA_NEXT, _('_Next'), None, None, self.mpd_next),
('quitmenu', gtk.STOCK_QUIT, _('_Quit'), None, None, self.on_delete_event_yes),
('removemenu', gtk.STOCK_REMOVE, _('_Remove'), None, None, self.on_remove),
('clearmenu', gtk.STOCK_CLEAR, _('_Clear'), '<Ctrl>Delete', None, self.mpd_clear),
('updatefullmenu', None, _('_Entire Library'), '<Ctrl><Shift>u', None, self.on_updatedb),
('updateselectedmenu', None, _('_Selected Items'), '<Ctrl>u', None, self.on_updatedb_shortcut),
('preferencemenu', gtk.STOCK_PREFERENCES, _('_Preferences...'), 'F5', None, self.on_prefs),
('aboutmenu', None, _('_About...'), 'F1', None, self.on_about),
('tagmenu', None, _('_Edit Tags...'), '<Ctrl>t', None, self.on_tags_edit),
('addmenu', gtk.STOCK_ADD, _('_Add'), '<Ctrl>d', None, self.on_add_item),
('replacemenu', gtk.STOCK_REDO, _('_Replace'), '<Ctrl>r', None, self.on_replace_item),
('add2menu', None, _('Add'), '<Shift><Ctrl>d', None, self.on_add_item_play),
('replace2menu', None, _('Replace'), '<Shift><Ctrl>r', None, self.on_replace_item_play),
('rmmenu', None, _('_Delete...'), None, None, self.on_remove),
('sortshuffle', None, _('Shuffle'), '<Alt>r', None, self.mpd_shuffle),
('tab1key', None, 'Tab1 Key', '<Alt>1', None, self.on_switch_to_tab1),
('tab2key', None, 'Tab2 Key', '<Alt>2', None, self.on_switch_to_tab2),
('tab3key', None, 'Tab3 Key', '<Alt>3', None, self.on_switch_to_tab3),
('tab4key', None, 'Tab4 Key', '<Alt>4', None, self.on_switch_to_tab4),
('tab5key', None, 'Tab5 Key', '<Alt>5', None, self.on_switch_to_tab5),
('nexttab', None, 'Next Tab Key', '<Alt>Right', None, self.switch_to_next_tab),
('prevtab', None, 'Prev Tab Key', '<Alt>Left', None, self.switch_to_prev_tab),
('expandkey', None, 'Expand Key', '<Alt>Down', None, self.on_expand),
('collapsekey', None, 'Collapse Key', '<Alt>Up', None, self.on_collapse),
('ppkey', None, 'Play/Pause Key', '<Ctrl>p', None, self.mpd_pp),
('stopkey', None, 'Stop Key', '<Ctrl>s', None, self.mpd_stop),
('prevkey', None, 'Previous Key', '<Ctrl>Left', None, self.mpd_prev),
('nextkey', None, 'Next Key', '<Ctrl>Right', None, self.mpd_next),
('lowerkey', None, 'Lower Volume Key', '<Ctrl>minus', None, self.on_volume_lower),
('raisekey', None, 'Raise Volume Key', '<Ctrl>plus', None, self.on_volume_raise),
('raisekey2', None, 'Raise Volume Key 2', '<Ctrl>equal', None, self.on_volume_raise),
('quitkey', None, 'Quit Key', '<Ctrl>q', None, self.on_delete_event_yes),
('quitkey2', None, 'Quit Key 2', '<Ctrl>w', None, self.on_delete_event_yes),
('connectkey', None, 'Connect Key', '<Alt>c', None, self.on_connectkey_pressed),
('disconnectkey', None, 'Disconnect Key', '<Alt>d', None, self.on_disconnectkey_pressed),
('searchkey', None, 'Search Key', '<Ctrl>h', None, self.on_library_search_shortcut),
)
toggle_actions = (
('showmenu', None, _('S_how Sonata'), None, None, self.on_withdraw_app_toggle, not self.config.withdrawn),
('repeatmenu', None, _('_Repeat'), None, None, self.on_repeat_clicked, False),
('randommenu', None, _('Rando_m'), None, None, self.on_random_clicked, False),
(self.TAB_CURRENT, None, self.TAB_CURRENT, None, None, self.on_tab_toggle, self.config.current_tab_visible),
(self.TAB_LIBRARY, None, self.TAB_LIBRARY, None, None, self.on_tab_toggle, self.config.library_tab_visible),
(self.TAB_PLAYLISTS, None, self.TAB_PLAYLISTS, None, None, self.on_tab_toggle, self.config.playlists_tab_visible),
(self.TAB_STREAMS, None, self.TAB_STREAMS, None, None, self.on_tab_toggle, self.config.streams_tab_visible),
(self.TAB_INFO, None, self.TAB_INFO, None, None, self.on_tab_toggle, self.config.info_tab_visible),
)
uiDescription = """
<ui>
<popup name="imagemenu">
<menuitem action="chooseimage_menu"/>
<menuitem action="localimage_menu"/>
<menuitem action="fullscreencoverart_menu"/>
<separator name="FM1"/>
<menuitem action="resetimage_menu"/>
</popup>
<popup name="traymenu">
<menuitem action="showmenu"/>
<separator name="FM1"/>
<menuitem action="playmenu"/>
<menuitem action="pausemenu"/>
<menuitem action="stopmenu"/>
<menuitem action="prevmenu"/>
<menuitem action="nextmenu"/>
<separator name="FM2"/>
<menuitem action="quitmenu"/>
</popup>
<popup name="mainmenu">
<menuitem action="addmenu"/>
<menuitem action="replacemenu"/>
<menu action="playaftermenu">
<menuitem action="add2menu"/>
<menuitem action="replace2menu"/>
</menu>
<menuitem action="newmenu"/>
<menuitem action="editmenu"/>
<menuitem action="removemenu"/>
<menuitem action="clearmenu"/>
<menuitem action="tagmenu"/>
<menuitem action="renamemenu"/>
<menuitem action="rmmenu"/>
<menu action="sortmenu">
<menuitem action="sortbytitle"/>
<menuitem action="sortbyartist"/>
<menuitem action="sortbyalbum"/>
<menuitem action="sortbyfile"/>
<menuitem action="sortbydirfile"/>
<separator name="FM3"/>
<menuitem action="sortshuffle"/>
<menuitem action="sortreverse"/>
</menu>
<menu action="plmenu">
<menuitem action="savemenu"/>
<separator name="FM4"/>
</menu>
<separator name="FM1"/>
<menuitem action="repeatmenu"/>
<menuitem action="randommenu"/>
<menu action="updatemenu">
<menuitem action="updateselectedmenu"/>
<menuitem action="updatefullmenu"/>
</menu>
<separator name="FM2"/>
<menu action="profilesmenu">
</menu>
<menuitem action="preferencemenu"/>
<menuitem action="aboutmenu"/>
<menuitem action="quitmenu"/>
</popup>
<popup name="librarymenu">
<menuitem action="filesystemview"/>
<menuitem action="artistview"/>
<menuitem action="genreview"/>
<menuitem action="albumview"/>
</popup>
<popup name="hidden">
<menuitem action="quitkey"/>
<menuitem action="quitkey2"/>
<menuitem action="tab1key"/>
<menuitem action="tab2key"/>
<menuitem action="tab3key"/>
<menuitem action="tab4key"/>
<menuitem action="tab5key"/>
<menuitem action="nexttab"/>
<menuitem action="prevtab"/>
<menuitem action="nexttab"/>
<menuitem action="prevtab"/>
<menuitem action="expandkey"/>
<menuitem action="collapsekey"/>
<menuitem action="ppkey"/>
<menuitem action="stopkey"/>
<menuitem action="nextkey"/>
<menuitem action="prevkey"/>
<menuitem action="lowerkey"/>
<menuitem action="raisekey"/>
<menuitem action="raisekey2"/>
<menuitem action="connectkey"/>
<menuitem action="disconnectkey"/>
<menuitem action="centerplaylistkey"/>
<menuitem action="searchkey"/>
</popup>
<popup name="notebookmenu">
"""
for tab in self.all_tab_names:
uiDescription = uiDescription + "<menuitem action=\"" + tab + "\"/>"
uiDescription = uiDescription + "</popup></ui>"
# Try to connect to MPD:
self.mpd_connect(blocking=True)
if self.conn:
self.status = mpdh.status(self.client)
self.iterate_time = self.iterate_time_when_connected
self.songinfo = mpdh.currsong(self.client)
self.artwork.update_songinfo(self.songinfo)
elif self.config.initial_run:
show_prefs = True
# Realizing self.window will allow us to retrieve the theme's
# link-color; we can then apply to it various widgets:
try:
self.window.realize()
linkcolor = self.window.style_get_property("link-color").to_string()
except:
linkcolor = None
# Audioscrobbler
self.scrobbler = scrobbler.Scrobbler(self.config)
self.scrobbler.import_module()
self.scrobbler.init()
# Current tab
self.current = current.Current(self.config, self.client, self.TAB_CURRENT, self.on_current_button_press, self.parse_formatting_colnames, self.parse_formatting, self.connected, lambda:self.sonata_loaded, lambda:self.songinfo, self.update_statusbar, self.iterate_now, lambda:self.library.libsearchfilter_get_style(), self.new_tab)
self.current_treeview = self.current.get_treeview()
self.current_selection = self.current.get_selection()
currentactions = [
('centerplaylistkey', None, 'Center Playlist Key', '<Ctrl>i', None, self.current.center_song_in_list),
('sortbyartist', None, _('By Artist'), None, None, self.current.on_sort_by_artist),
('sortbyalbum', None, _('By Album'), None, None, self.current.on_sort_by_album),
('sortbytitle', None, _('By Song Title'), None, None, self.current.on_sort_by_title),
('sortbyfile', None, _('By File Name'), None, None, self.current.on_sort_by_file),
('sortbydirfile', None, _('By Dir & File Name'), None, None, self.current.on_sort_by_dirfile),
('sortreverse', None, _('Reverse List'), None, None, self.current.on_sort_reverse),
]
# Library tab
self.library = library.Library(self.config, self.client, self.artwork, self.TAB_LIBRARY, self.find_path('sonata-album.png'), self.settings_save, self.current.filtering_entry_make_red, self.current.filtering_entry_revert_color, self.current.filter_key_pressed, self.on_add_item, self.parse_formatting, self.connected, self.on_library_button_press, self.on_library_search_text_click, self.new_tab)
self.library_treeview = self.library.get_treeview()
self.library_selection = self.library.get_selection()
libraryactions = self.library.get_libraryactions()
# Info tab
self.info = info.Info(self.config, self.artwork.get_info_image(), linkcolor, self.on_link_click, self.library.library_return_search_items, self.get_playing_song, self.TAB_INFO, self.on_image_activate, self.on_image_motion_cb, self.on_image_drop_cb, self.album_return_artist_and_tracks, self.new_tab)
self.info_imagebox = self.info.get_info_imagebox()
# Streams tab
self.streams = streams.Streams(self.config, self.window, self.on_streams_button_press, self.on_add_item, self.settings_save, self.iterate_now, self.TAB_STREAMS, self.new_tab)
self.streams_treeview = self.streams.get_treeview()
self.streams_selection = self.streams.get_selection()
streamsactions = [
('newmenu', None, _('_New...'), '<Ctrl>n', None, self.streams.on_streams_new),
('editmenu', None, _('_Edit...'), None, None, self.streams.on_streams_edit),
]
# Playlists tab
self.playlists = playlists.Playlists(self.config, self.window, self.client, lambda:self.UIManager, self.update_menu_visibility, self.iterate_now, self.on_add_item, self.on_playlists_button_press, self.current.get_current_songs, self.connected, self.TAB_PLAYLISTS, self.new_tab)
self.playlists_treeview = self.playlists.get_treeview()
self.playlists_selection = self.playlists.get_selection()
playlistsactions = [
('savemenu', None, _('_New...'), '<Ctrl><Shift>s', None, self.playlists.on_playlist_save),
('renamemenu', None, _('_Rename...'), None, None, self.playlists.on_playlist_rename),
]
# Main app:
self.UIManager = gtk.UIManager()
actionGroup = gtk.ActionGroup('Actions')
actionGroup.add_actions(actions)
actionGroup.add_actions(currentactions)
actionGroup.add_actions(libraryactions)
actionGroup.add_actions(streamsactions)
actionGroup.add_actions(playlistsactions)
actionGroup.add_toggle_actions(toggle_actions)
self.UIManager.insert_action_group(actionGroup, 0)
self.UIManager.add_ui_from_string(uiDescription)
self.populate_profiles_for_menu()
self.window.add_accel_group(self.UIManager.get_accel_group())
self.mainmenu = self.UIManager.get_widget('/mainmenu')
self.randommenu = self.UIManager.get_widget('/mainmenu/randommenu')
self.repeatmenu = self.UIManager.get_widget('/mainmenu/repeatmenu')
self.imagemenu = self.UIManager.get_widget('/imagemenu')
self.traymenu = self.UIManager.get_widget('/traymenu')
self.librarymenu = self.UIManager.get_widget('/librarymenu')
self.library.set_librarymenu(self.librarymenu)
self.notebookmenu = self.UIManager.get_widget('/notebookmenu')
mainhbox = gtk.HBox()
mainvbox = gtk.VBox()
tophbox = gtk.HBox()
self.albumimage = self.artwork.get_albumimage()
self.imageeventbox = ui.eventbox(add=self.albumimage)
self.imageeventbox.drag_dest_set(gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP, [("text/uri-list", 0, 80), ("text/plain", 0, 80)], gtk.gdk.ACTION_DEFAULT)
if not self.config.show_covers:
ui.hide(self.imageeventbox)
tophbox.pack_start(self.imageeventbox, False, False, 5)
topvbox = gtk.VBox()
toptophbox = gtk.HBox()
self.prevbutton = ui.button(stock=gtk.STOCK_MEDIA_PREVIOUS, relief=gtk.RELIEF_NONE, can_focus=False, hidetxt=True)
self.ppbutton = ui.button(stock=gtk.STOCK_MEDIA_PLAY, relief=gtk.RELIEF_NONE, can_focus=False, hidetxt=True)
self.stopbutton = ui.button(stock=gtk.STOCK_MEDIA_STOP, relief=gtk.RELIEF_NONE, can_focus=False, hidetxt=True)
self.nextbutton = ui.button(stock=gtk.STOCK_MEDIA_NEXT, relief=gtk.RELIEF_NONE, can_focus=False, hidetxt=True)
for mediabutton in (self.prevbutton, self.ppbutton, self.stopbutton, self.nextbutton):
toptophbox.pack_start(mediabutton, False, False, 0)
if not self.config.show_playback:
ui.hide(mediabutton)
self.progressbox = gtk.VBox()
self.progresslabel = ui.label(w=-1, h=6)
self.progressbox.pack_start(self.progresslabel)
self.progressbar = ui.progressbar(orient=gtk.PROGRESS_LEFT_TO_RIGHT, frac=0, step=0.05, ellipsize=pango.ELLIPSIZE_END)
self.progresseventbox = ui.eventbox(add=self.progressbar, visible=True)
self.progressbox.pack_start(self.progresseventbox, False, False, 0)
self.progresslabel2 = ui.label(w=-1, h=6)
self.progressbox.pack_start(self.progresslabel2)
toptophbox.pack_start(self.progressbox, True, True, 0)
if not self.config.show_progress:
ui.hide(self.progressbox)
self.volumebutton = ui.togglebutton(relief=gtk.RELIEF_NONE, can_focus=False)
self.volume_set_image("stock_volume-med")
if not self.config.show_playback:
ui.hide(self.volumebutton)
toptophbox.pack_start(self.volumebutton, False, False, 0)
topvbox.pack_start(toptophbox, False, False, 2)
self.expander = ui.expander(text=_("Playlist"), expand=self.config.expanded, can_focus=False)
expanderbox = gtk.VBox()
self.cursonglabel1 = ui.label(y=0)
self.cursonglabel2 = ui.label(y=0)
expanderbox.pack_start(self.cursonglabel1, True, True, 0)
expanderbox.pack_start(self.cursonglabel2, True, True, 0)
self.expander.set_label_widget(expanderbox)
topvbox.pack_start(self.expander, False, False, 2)
tophbox.pack_start(topvbox, True, True, 3)
mainvbox.pack_start(tophbox, False, False, 5)
self.notebook.set_tab_pos(gtk.POS_TOP)
self.notebook.set_scrollable(True)
mainvbox.pack_start(self.notebook, True, True, 5)
self.statusbar = gtk.Statusbar()
self.statusbar.set_has_resize_grip(True)
if not self.config.show_statusbar or not self.config.expanded:
ui.hide(self.statusbar)
mainvbox.pack_start(self.statusbar, False, False, 0)
mainhbox.pack_start(mainvbox, True, True, 3)
if self.window_owner:
self.window.add(mainhbox)
self.window.move(self.config.x, self.config.y)
self.window.set_size_request(270, -1)
elif HAVE_SUGAR:
self.window.set_canvas(mainhbox)
if not self.config.expanded:
ui.hide(self.notebook)
self.cursonglabel1.set_markup('<big><b>' + _('Stopped') + '</b></big>')
self.cursonglabel2.set_markup('<small>' + _('Click to expand') + '</small>')
if self.window_owner:
self.window.set_default_size(self.config.w, 1)
else:
self.cursonglabel1.set_markup('<big><b>' + _('Stopped') + '</b></big>')
self.cursonglabel2.set_markup('<small>' + _('Click to collapse') + '</small>')
if self.window_owner:
self.window.set_default_size(self.config.w, self.config.h)
self.expander.set_tooltip_text(self.cursonglabel1.get_text())
if not self.conn:
self.progressbar.set_text(_('Not Connected'))
elif not self.status:
self.progressbar.set_text(_('No Read Permission'))
# Update tab positions: XXX move to self.new_tab
self.notebook.reorder_child(self.current.get_widgets(), self.config.current_tab_pos)
self.notebook.reorder_child(self.library.get_widgets(), self.config.library_tab_pos)
self.notebook.reorder_child(self.playlists.get_widgets(), self.config.playlists_tab_pos)
self.notebook.reorder_child(self.streams.get_widgets(), self.config.streams_tab_pos)
self.notebook.reorder_child(self.info.get_widgets(), self.config.info_tab_pos)
self.last_tab = self.notebook_get_tab_text(self.notebook, 0)
# Song notification window:
outtertipbox = gtk.VBox()
tipbox = gtk.HBox()
self.trayalbumeventbox, self.trayalbumimage2 = self.artwork.get_trayalbum()
hiddenlbl = ui.label(w=2, h=-1)
tipbox.pack_start(hiddenlbl, False, False, 0)
tipbox.pack_start(self.trayalbumeventbox, False, False, 0)
tipbox.pack_start(self.trayalbumimage2, False, False, 0)
if not self.config.show_covers:
ui.hide(self.trayalbumeventbox)
ui.hide(self.trayalbumimage2)
innerbox = gtk.VBox()
self.traycursonglabel1 = ui.label(markup=_("Playlist"), y=1)
self.traycursonglabel2 = ui.label(markup=_("Playlist"), y=0)
label1 = ui.label(markup='<span size="10"> </span>')
innerbox.pack_start(label1)
innerbox.pack_start(self.traycursonglabel1, True, True, 0)
innerbox.pack_start(self.traycursonglabel2, True, True, 0)
self.trayprogressbar = ui.progressbar(orient=gtk.PROGRESS_LEFT_TO_RIGHT, frac=0, step=0.05, ellipsize=pango.ELLIPSIZE_NONE)
label2 = ui.label(markup='<span size="10"> </span>')
innerbox.pack_start(label2)
innerbox.pack_start(self.trayprogressbar, False, False, 0)
if not self.config.show_progress:
ui.hide(self.trayprogressbar)
label3 = ui.label(markup='<span size="10"> </span>')
innerbox.pack_start(label3)
tipbox.pack_start(innerbox, True, True, 6)
outtertipbox.pack_start(tipbox, False, False, 2)
outtertipbox.show_all()
self.traytips.add_widget(outtertipbox)
self.tooltip_set_window_width()
# Volumescale window
self.volumewindow = gtk.Window(gtk.WINDOW_POPUP)
self.volumewindow.set_skip_taskbar_hint(True)
self.volumewindow.set_skip_pager_hint(True)
self.volumewindow.set_decorated(False)
frame = gtk.Frame()
frame.set_shadow_type(gtk.SHADOW_ETCHED_IN)
self.volumewindow.add(frame)
volbox = gtk.VBox()
volbox.pack_start(ui.label(text="+"), False, False, 0)
self.volumescale = gtk.VScale()
self.volumescale.set_draw_value(True)
self.volumescale.set_value_pos(gtk.POS_TOP)
self.volumescale.set_digits(0)
self.volumescale.set_update_policy(gtk.UPDATE_CONTINUOUS)
self.volumescale.set_inverted(True)
self.volumescale.set_adjustment(gtk.Adjustment(0, 0, 100, 0, 0, 0))
if HAVE_SUGAR:
self.volumescale.set_size_request(-1, 203)
else:
self.volumescale.set_size_request(-1, 103)
volbox.pack_start(self.volumescale, True, True, 0)
volbox.pack_start(ui.label(text="-"), False, False, 0)
frame.add(volbox)
ui.show(frame)
# Fullscreen cover art window
self.fullscreencoverart = gtk.Window()
self.fullscreencoverart.set_title(_("Cover Art"))
self.fullscreencoverart.set_decorated(True)
self.fullscreencoverart.fullscreen()
style = self.fullscreencoverart.get_style().copy()
style.bg[gtk.STATE_NORMAL] = self.fullscreencoverart.get_colormap().alloc_color("black")
style.bg_pixmap[gtk.STATE_NORMAL] = None
self.fullscreencoverart.set_style(style)
self.fullscreencoverart.add_accel_group(self.UIManager.get_accel_group())
fscavbox = gtk.VBox()
fscahbox = gtk.HBox()
self.fullscreenalbumimage = self.artwork.get_fullscreenalbumimage()
fscalbl, fscalbl2 = self.artwork.get_fullscreenalbumlabels()
fscahbox.pack_start(self.fullscreenalbumimage, True, False, 0)
fscavbox.pack_start(ui.label(), True, False, 0)
fscavbox.pack_start(fscahbox, False, False, 0)
fscavbox.pack_start(fscalbl, False, False, 5)
fscavbox.pack_start(fscalbl2, False, False, 5)
fscavbox.pack_start(ui.label(), True, False, 0)
if not self.config.show_covers:
ui.hide(self.fullscreenalbumimage)
self.fullscreencoverart.add(fscavbox)
# Connect to signals
self.window.add_events(gtk.gdk.BUTTON_PRESS_MASK)
self.traytips.add_events(gtk.gdk.BUTTON_PRESS_MASK)
self.traytips.connect('button_press_event', self.on_traytips_press)
self.window.connect('delete_event', self.on_delete_event)
self.window.connect('window_state_event', self.on_window_state_change)
self.window.connect('configure_event', self.on_window_configure)
self.window.connect('key-press-event', self.on_topwindow_keypress)
self.window.connect('focus-out-event', self.on_window_lost_focus)
self.imageeventbox.connect('button_press_event', self.on_image_activate)
self.imageeventbox.connect('drag_motion', self.on_image_motion_cb)
self.imageeventbox.connect('drag_data_received', self.on_image_drop_cb)
self.ppbutton.connect('clicked', self.mpd_pp)
self.stopbutton.connect('clicked', self.mpd_stop)
self.prevbutton.connect('clicked', self.mpd_prev)
self.nextbutton.connect('clicked', self.mpd_next)
self.progresseventbox.connect('button_press_event', self.on_progressbar_press)
self.progresseventbox.connect('scroll_event', self.on_progressbar_scroll)
self.volumebutton.connect('clicked', self.on_volumebutton_clicked)
self.volumebutton.connect('scroll-event', self.on_volumebutton_scroll)
self.expander.connect('activate', self.on_expander_activate)
self.randommenu.connect('toggled', self.on_random_clicked)
self.repeatmenu.connect('toggled', self.on_repeat_clicked)
self.volumescale.connect('change_value', self.on_volumescale_change)
self.volumescale.connect('scroll-event', self.on_volumescale_scroll)
self.cursonglabel1.connect('notify::label', self.on_currsong_notify)
self.progressbar.connect('notify::fraction', self.on_progressbar_notify_fraction)
self.progressbar.connect('notify::text', self.on_progressbar_notify_text)
self.mainwinhandler = self.window.connect('button_press_event', self.on_window_click)
self.notebook.connect('button_press_event', self.on_notebook_click)
self.notebook.connect('size-allocate', self.on_notebook_resize)
self.notebook.connect('switch-page', self.on_notebook_page_change)
self.fullscreencoverart.add_events(gtk.gdk.BUTTON_PRESS_MASK)
self.fullscreencoverart.connect("button-press-event", self.fullscreen_cover_art_close, False)
self.fullscreencoverart.connect("key-press-event", self.fullscreen_cover_art_close, True)
for treeview in [self.current_treeview, self.library_treeview, self.playlists_treeview, self.streams_treeview]:
treeview.connect('popup_menu', self.on_menu_popup)
for treeviewsel in [self.current_selection, self.library_selection, self.playlists_selection, self.streams_selection]:
treeviewsel.connect('changed', self.on_treeview_selection_changed)
for widget in [self.ppbutton, self.prevbutton, self.stopbutton, self.nextbutton, self.progresseventbox, self.expander, self.volumebutton]:
widget.connect('button_press_event', self.menu_popup)
self.systemtray_initialize()
# This will ensure that "Not connected" is shown in the systray tooltip
if not self.conn:
self.update_cursong()
# Ensure that the systemtray icon is added here. This is really only
# important if we're starting in hidden (minimized-to-tray) mode:
if self.window_owner and self.config.withdrawn:
while gtk.events_pending():
gtk.main_iteration()
dbus.init_gnome_mediakeys(self.mpd_pp, self.mpd_stop, self.mpd_prev, self.mpd_next)
# Try to connect to mmkeys signals, if no dbus and gnome 2.18+
if not dbus.using_gnome_mediakeys():
try:
import mmkeys
# this must be an attribute to keep it around:
self.keys = mmkeys.MmKeys()
self.keys.connect("mm_prev", self.mpd_prev)
self.keys.connect("mm_next", self.mpd_next)
self.keys.connect("mm_playpause", self.mpd_pp)
self.keys.connect("mm_stop", self.mpd_stop)
except ImportError:
pass
# Set up current view
self.currentdata = self.current.get_model()
# Initialize playlist data and widget
self.playlistsdata = self.playlists.get_model()
# Initialize streams data and widget
self.streamsdata = self.streams.get_model()
# Initialize library data and widget
self.librarydata = self.library.get_model()
self.artwork.library_artwork_init(self.librarydata, consts.LIB_COVER_SIZE)
if self.window_owner:
icon = self.window.render_icon('sonata', gtk.ICON_SIZE_DIALOG)
self.window.set_icon(icon)
self.streams.populate()
self.iterate_now()
if self.window_owner:
if self.config.withdrawn:
if (HAVE_EGG and self.trayicon.get_property('visible')) or (HAVE_STATUS_ICON and self.statusicon.is_embedded() and self.statusicon.get_visible()):
ui.hide(self.window)
self.window.show_all()
# Ensure that button images are displayed despite GTK+ theme
self.window.get_settings().set_property("gtk-button-images", True)
if self.config.update_on_start:
self.on_updatedb(None)
self.notebook.set_no_show_all(False)
self.window.set_no_show_all(False)
if show_prefs:
self.on_prefs(None)
self.config.initial_run = False
# Ensure that sonata is loaded before we display the notif window
self.sonata_loaded = True
self.on_currsong_notify()
self.current.center_song_in_list()
if HAVE_STATUS_ICON:
gobject.timeout_add(250, self.iterate_status_icon)
gc.disable()
gobject.idle_add(self.header_save_column_widths)
# XXX Plugins temporarily disabled
#for tabs in pluginsystem.get('tabs'):
# self.new_tab(*tabs())
def new_tab(self, page, stock, text, focus):
# create the "ear" of the tab:
hbox = gtk.HBox()
hbox.pack_start(ui.image(stock=stock), False, False, 2)
hbox.pack_start(ui.label(text=text), False, False, 2)
evbox = ui.eventbox(add=hbox)
evbox.show_all()
evbox.connect("button_press_event", self.on_tab_click)
# create the actual tab:
self.notebook.append_page(page, evbox)
if (text in self.tabname2id and
not getattr(self.config,
self.tabname2id[text]+'_tab_visible')):
ui.hide(page)
self.notebook.set_tab_reorderable(page, True)
if self.config.tabs_expanded:
self.notebook.set_tab_label_packing(page, True, True, gtk.PACK_START)
self.tabname2focus[text] = focus
return page
def get_playing_song(self):
if self.status and self.status['state'] in ['play', 'pause'] and self.songinfo:
return self.songinfo
return None
def gnome_session_management(self):
try:
import gnome, gnome.ui
# Code thanks to quodlibet:
# XXX gnome.init sets process name, locale...
gnome.init("sonata", version.VERSION)
misc.setlocale()
client = gnome.ui.master_client()
client.set_restart_style(gnome.ui.RESTART_IF_RUNNING)
command = os.path.normpath(os.path.join(os.getcwd(), sys.argv[0]))
try:
client.set_restart_command([command] + sys.argv[1:])
except TypeError:
# Fedora systems have a broken gnome-python wrapper for this function.
# http://www.sacredchao.net/quodlibet/ticket/591
# http://trac.gajim.org/ticket/929
client.set_restart_command(len(sys.argv), [command] + sys.argv[1:])
client.connect('die', gtk.main_quit)
except:
pass
def profile_menu_name(self, profile_num):
return _("Profile") + ": " + self.config.profile_names[profile_num].replace("&", "")
def populate_profiles_for_menu(self):
host, port, _password = misc.mpd_env_vars()
if self.merge_id:
self.UIManager.remove_ui(self.merge_id)
if self.actionGroupProfiles:
self.UIManager.remove_action_group(self.actionGroupProfiles)
self.actionGroupProfiles = None
self.actionGroupProfiles = gtk.ActionGroup('MPDProfiles')
self.UIManager.ensure_update()
actions = []
if host or port:
action_name = _("Profile") + ": " + _("MPD_HOST/PORT")
actions.append((action_name, None, _("MPD_HOST/PORT").replace("_", "__"), None, None, 0))
actions.append(('disconnect', None, _('Disconnect'), None, None, 1))
active_radio = 0
else:
for i in range(len(self.config.profile_names)):
action_name = self.profile_menu_name(i)
actions.append((action_name, None, "[" + str(i+1) + "] " + self.config.profile_names[i].replace("_", "__"), None, None, i))
actions.append(('disconnect', None, _('Disconnect'), None, None, len(self.config.profile_names)))
active_radio = self.config.profile_num
if not self.conn:
active_radio = len(self.config.profile_names)
self.actionGroupProfiles.add_radio_actions(actions, active_radio, self.on_profiles_click)
uiDescription = """
<ui>
<popup name="mainmenu">
<menu action="profilesmenu">
"""
uiDescription = uiDescription + """<menuitem action=\"""" + 'disconnect' + """\" position="top"/>"""
if host or port:
for i in range(len(self.config.profile_names)):
action_name = _("Profile") + ": " + _("MPD_HOST/PORT")
uiDescription = uiDescription + """<menuitem action=\"""" + action_name + """\" position="top"/>"""
else:
for i in range(len(self.config.profile_names)):
action_name = self.profile_menu_name(len(self.config.profile_names)-i-1)
uiDescription = uiDescription + """<menuitem action=\"""" + action_name + """\" position="top"/>"""
uiDescription = uiDescription + """</menu></popup></ui>"""
self.merge_id = self.UIManager.add_ui_from_string(uiDescription)
self.UIManager.insert_action_group(self.actionGroupProfiles, 0)
self.UIManager.get_widget('/hidden').set_property('visible', False)
def on_profiles_click(self, _radioaction, profile):
if self.skip_on_profiles_click:
return
if profile.get_name() == 'disconnect':
self.on_disconnectkey_pressed(None)
else:
# Clear sonata before we try to connect:
self.mpd_disconnect()
self.iterate_now()
# Now connect to new profile:
self.config.profile_num = profile.get_current_value()
self.on_connectkey_pressed(None)
def mpd_connect(self, blocking=False, force=False):
if blocking:
self._mpd_connect(blocking, force)
else:
thread = threading.Thread(target=self._mpd_connect, args=(blocking, force))
thread.setDaemon(True)
thread.start()
def _mpd_connect(self, _blocking, force):
if self.trying_connection:
return
self.trying_connection = True
if self.user_connect or force:
mpdh.call(self.client, 'disconnect')
host, port, password = misc.mpd_env_vars()
if not host:
host = self.config.host[self.config.profile_num]
if not port:
port = self.config.port[self.config.profile_num]
if not password:
password = self.config.password[self.config.profile_num]
mpdh.call(self.client, 'connect', host, port)
if len(password) > 0:
mpdh.call(self.client, 'password', password)
test = mpdh.status(self.client)
if test:
self.conn = True
else:
self.conn = False
else:
self.conn = False
if not self.conn:
self.status = None
self.songinfo = None
if self.artwork is not None:
self.artwork.update_songinfo(self.songinfo)
self.iterate_time = self.iterate_time_when_disconnected_or_stopped
self.trying_connection = False
def mpd_disconnect(self):
if self.conn:
mpdh.call(self.client, 'close')
mpdh.call(self.client, 'disconnect')
self.conn = False
def on_connectkey_pressed(self, _event):
self.user_connect = True
# Update selected radio button in menu:
self.skip_on_profiles_click = True
host, port, _password = misc.mpd_env_vars()
if host or port:
self.actionGroupProfiles.list_actions()[0].activate()
else:
for gtkAction in self.actionGroupProfiles.list_actions():
if gtkAction.get_name() == self.profile_menu_name(self.config.profile_num):
gtkAction.activate()
break
self.skip_on_profiles_click = False
# Connect:
self.mpd_connect(force=True)
self.iterate_now()
def on_disconnectkey_pressed(self, _event):
self.user_connect = False
# Update selected radio button in menu:
self.skip_on_profiles_click = True
for gtkAction in self.actionGroupProfiles.list_actions():
if gtkAction.get_name() == 'disconnect':
gtkAction.activate()
break
self.skip_on_profiles_click = False
# Disconnect:
self.mpd_disconnect()
def update_status(self):
try:
if not self.conn:
self.mpd_connect()
if self.conn:
self.iterate_time = self.iterate_time_when_connected
self.status = mpdh.status(self.client)
if self.status:
if self.status['state'] == 'stop':
self.iterate_time = self.iterate_time_when_disconnected_or_stopped
self.songinfo = mpdh.currsong(self.client)
self.artwork.update_songinfo(self.songinfo)
if not self.last_repeat or self.last_repeat != self.status['repeat']:
self.repeatmenu.set_active(self.status['repeat'] == '1')
if not self.last_random or self.last_random != self.status['random']:
self.randommenu.set_active(self.status['random'] == '1')
if self.status['xfade'] == '0':
self.config.xfade_enabled = False
else:
self.config.xfade_enabled = True
self.config.xfade = int(self.status['xfade'])
if self.config.xfade > 30:
self.config.xfade = 30
self.last_repeat = self.status['repeat']
self.last_random = self.status['random']
return
except:
pass
self.prevconn = self.client
self.prevstatus = self.status
self.prevsonginfo = self.songinfo
self.conn = False
self.status = None
self.songinfo = None
self.artwork.update_songinfo(self.songinfo)
def iterate(self):
self.update_status()
self.info_update(False)
if self.conn != self.prevconn:
self.handle_change_conn()
if self.status != self.prevstatus:
self.handle_change_status()
if self.config.as_enabled:
# We update this here because self.handle_change_status() won't be
# called while the client is paused.
self.scrobbler.iterate()
if self.songinfo != self.prevsonginfo:
self.handle_change_song()
self.prevconn = self.conn
self.prevstatus = self.status
self.prevsonginfo = self.songinfo
self.iterate_handler = gobject.timeout_add(self.iterate_time, self.iterate) # Repeat ad infitum..
if self.config.show_trayicon:
if HAVE_STATUS_ICON:
if self.statusicon.is_embedded() and not self.statusicon.get_visible():
# Systemtray appears, add icon:
self.systemtray_initialize()
elif not self.statusicon.is_embedded() and self.config.withdrawn:
# Systemtray gone, unwithdraw app:
self.withdraw_app_undo()
elif HAVE_EGG:
if not self.trayicon.get_property('visible'):
# Systemtray appears, add icon:
self.systemtray_initialize()
if self.call_gc_collect:
gc.collect()
self.call_gc_collect = False
def schedule_gc_collect(self):
self.call_gc_collect = True
def iterate_stop(self):
try:
gobject.source_remove(self.iterate_handler)
except:
pass
def iterate_now(self):
# Since self.iterate_time_when_connected has been
# slowed down to 500ms, we'll call self.iterate_now()
# whenever the user performs an action that requires
# updating the client
self.iterate_stop()
self.iterate()
def iterate_status_icon(self):
# Polls for the users' cursor position to display the custom tooltip window when over the
# gtk.StatusIcon. We use this instead of self.iterate() in order to poll more often and
# increase responsiveness.
if self.config.show_trayicon:
if self.statusicon.is_embedded() and self.statusicon.get_visible():
self.tooltip_show_manually()
gobject.timeout_add(250, self.iterate_status_icon)
def on_topwindow_keypress(self, _widget, event):
shortcut = gtk.accelerator_name(event.keyval, event.state)
shortcut = shortcut.replace("<Mod2>", "")
# These shortcuts were moved here so that they don't interfere with searching the library
if shortcut == 'BackSpace' and self.current_tab == self.TAB_LIBRARY:
return self.library.library_browse_parent(None)
elif shortcut == 'Escape':
if self.volumewindow.get_property('visible'):
self.volume_hide()
elif self.current_tab == self.TAB_LIBRARY and self.library.search_visible():
self.library.on_search_end(None)
elif self.current_tab == self.TAB_CURRENT and self.current.filterbox_visible:
self.current.searchfilter_toggle(None)
elif self.config.minimize_to_systray:
if HAVE_STATUS_ICON and self.statusicon.is_embedded() and self.statusicon.get_visible():
self.withdraw_app()
elif HAVE_EGG and self.trayicon.get_property('visible'):
self.withdraw_app()
return
elif shortcut == 'Delete':
self.on_remove(None)
elif self.volumewindow.get_property('visible') and (shortcut == 'Up' or shortcut == 'Down'):
if shortcut == 'Up':
self.on_volume_raise(None)
else:
self.on_volume_lower(None)
return True
if self.current_tab == self.TAB_CURRENT:
if event.state & (gtk.gdk.CONTROL_MASK | gtk.gdk.MOD1_MASK):
return
# XXX this isn't the right thing with GTK input methods:
text = unichr(gtk.gdk.keyval_to_unicode(event.keyval))
# We only want to toggle open the filterbar if the key press is actual text! This
# will ensure that we skip, e.g., F5, Alt, Ctrl, ...
if text != u"\x00" and text.strip():
if not self.current.filterbox_visible:
if text != u"/":
self.current.searchfilter_toggle(None, text)
else:
self.current.searchfilter_toggle(None)
def settings_load(self):
self.config.settings_load_real(library.library_set_data)
def settings_save(self):
self.header_save_column_widths()
self.config.current_tab_pos = self.notebook_get_tab_num(self.notebook, self.TAB_CURRENT)
self.config.library_tab_pos = self.notebook_get_tab_num(self.notebook, self.TAB_LIBRARY)
self.config.playlists_tab_pos = self.notebook_get_tab_num(self.notebook, self.TAB_PLAYLISTS)
self.config.streams_tab_pos = self.notebook_get_tab_num(self.notebook, self.TAB_STREAMS)
self.config.info_tab_pos = self.notebook_get_tab_num(self.notebook, self.TAB_INFO)
self.config.settings_save_real(library.library_get_data)
def handle_change_conn(self):
if not self.conn:
for mediabutton in (self.ppbutton, self.stopbutton, self.prevbutton, self.nextbutton, self.volumebutton):
mediabutton.set_property('sensitive', False)
self.currentdata.clear()
if self.current_treeview.get_model():
self.current_treeview.get_model().clear()
if HAVE_STATUS_ICON:
self.statusicon.set_from_file(self.find_path('sonata_disconnect.png'))
elif HAVE_EGG and self.eggtrayheight:
self.eggtrayfile = self.find_path('sonata_disconnect.png')
self.trayimage.set_from_pixbuf(img.get_pixbuf_of_size(gtk.gdk.pixbuf_new_from_file(self.eggtrayfile), self.eggtrayheight)[0])
self.info_update(True)
if self.current.filterbox_visible:
gobject.idle_add(self.current.searchfilter_toggle, None)
if self.library.search_visible():
self.library.on_search_end(None)
self.handle_change_song()
self.handle_change_status()
else:
for mediabutton in (self.ppbutton, self.stopbutton, self.prevbutton, self.nextbutton, self.volumebutton):
mediabutton.set_property('sensitive', True)
if self.sonata_loaded:
self.library.library_browse(library.library_set_data(path="/"))
self.playlists.populate()
self.streams.populate()
self.on_notebook_page_change(self.notebook, 0, self.notebook.get_current_page())
def _parse_formatting_return_substrings(self, format):
substrings = []
begin_pos = format.find("{")
end_pos = -1
while begin_pos > -1:
if begin_pos > end_pos + 1:
substrings.append(format[end_pos+1:begin_pos])
end_pos = format.find("}", begin_pos)
substrings.append(format[begin_pos:end_pos+1])
begin_pos = format.find("{", end_pos)
substrings.append(format[end_pos+1:])
return substrings
def parse_formatting_colnames(self, format):
text = format.split("|")
for i in range(len(text)):
text[i] = text[i].replace("%A", _("Artist"))
text[i] = text[i].replace("%B", _("Album"))
text[i] = text[i].replace("%T", _("Track"))
text[i] = text[i].replace("%N", _("#"))
text[i] = text[i].replace("%Y", _("Year"))
text[i] = text[i].replace("%G", _("Genre"))
text[i] = text[i].replace("%P", _("Path"))
text[i] = text[i].replace("%F", _("File"))
text[i] = text[i].replace("%S", _("Stream"))
text[i] = text[i].replace("%L", _("Len"))
text[i] = text[i].replace("%D", _("#"))
if text[i].count("{") == text[i].count("}"):
text[i] = text[i].replace("{","").replace("}","")
# If the user wants the format of, e.g., "#%N", we'll
# ensure the # doesn't show up twice in a row.
text[i] = text[i].replace("##", "#")
return text
def _parse_formatting_substrings(self, subformat, item, wintitle):
text = subformat
if subformat.startswith("{") and subformat.endswith("}"):
has_brackets = True
else:
has_brackets = False
flag = "89syufd8sdhf9hsdf"
if "%A" in text:
artist = mpdh.get(item, 'artist', flag)
if artist != flag:
text = text.replace("%A", artist)
else:
if not has_brackets: text = text.replace("%A", _('Unknown'))
else: return ""
if "%B" in text:
album = mpdh.get(item, 'album', flag)
if album != flag:
text = text.replace("%B", album)
else:
if not has_brackets: text = text.replace("%B", _('Unknown'))
else: return ""
if "%T" in text:
title = mpdh.get(item, 'title', flag)
if title != flag:
text = text.replace("%T", title)
else:
if not has_brackets:
if len(item['file'].split('/')[-1]) == 0 or item['file'][:7] == 'http://' or item['file'][:6] == 'ftp://':
# Use path and file name:
text = misc.escape_html(item['file'])
else:
# Use file name only:
text = misc.escape_html(item['file'].split('/')[-1])
if wintitle:
return "[Sonata] " + text
else:
return text
else:
return ""
if "%N" in text:
track = mpdh.get(item, 'track', flag)
if track != flag:
track = mpdh.getnum(item, 'track', flag, False, 2)
text = text.replace("%N", track)
else:
if not has_brackets: text = text.replace("%N", "00")
else: return ""
if "%D" in text:
disc = mpdh.get(item, 'disc', flag)
if disc != flag:
disc = mpdh.getnum(item, 'disc', flag, False, 0)
text = text.replace("%D", disc)
else:
if not has_brackets: text = text.replace("%D", "0")
else: return ""
if "%S" in text:
name = mpdh.get(item, 'name', flag)
if name != flag:
text = text.replace("%S", name)
else:
if not has_brackets: text = text.replace("%S", _('Unknown'))
else: return ""
if "%G" in text:
genre = mpdh.get(item, 'genre', flag)
if genre != flag:
text = text.replace("%G", genre)
else:
if not has_brackets: text = text.replace("%G", _('Unknown'))
else: return ""
if "%Y" in text:
date = mpdh.get(item, 'date', flag)
if date != flag:
text = text.replace("%Y", item['date'])
else:
if not has_brackets: text = text.replace("%Y", "?")
else: return ""
pathname = mpdh.get(item, 'file')
try:
dirname, filename = pathname.rsplit('/', 1)
except ValueError: # XXX is file without '/' ever possible?
dirname, filename = "", pathname
if "%P" in text:
text = text.replace("%P", dirname)
if "%F" in text:
text = text.replace("%F", filename)
if "%L" in text:
time = mpdh.get(item, 'time', flag)
if time != flag:
time = misc.convert_time(int(time))
text = text.replace("%L", time)
else:
if not has_brackets: text = text.replace("%L", "?")
else: return ""
if wintitle:
if "%E" in text:
try:
at, length = [int(c) for c in self.status['time'].split(':')]
at_time = misc.convert_time(at)
text = text.replace("%E", at_time)
except:
if not has_brackets: text = text.replace("%E", "?")
else: return ""
if text.startswith("{") and text.endswith("}"):
return text[1:-1]
else:
return text
def parse_formatting(self, format, item, use_escape_html, wintitle=False):
substrings = self._parse_formatting_return_substrings(format)
text = ""
for sub in substrings:
text = text + str(self._parse_formatting_substrings(sub, item, wintitle))
if use_escape_html:
return misc.escape_html(text)
else:
return text
def info_update(self, update_all, blank_window=False, skip_lyrics=False):
playing_or_paused = self.conn and self.status and self.status['state'] in ['play', 'pause']
try:
newbitrate = self.status['bitrate'] + " kbps"
except:
newbitrate = ''
self.info.info_update(playing_or_paused, newbitrate, self.songinfo, update_all, blank_window, skip_lyrics)
def on_treeview_selection_changed(self, treeselection):
self.update_menu_visibility()
if treeselection == self.current.get_selection():
# User previously clicked inside group of selected rows, re-select
# rows so it doesn't look like anything changed:
if self.current.sel_rows:
for row in self.current.sel_rows:
treeselection.select_path(row)
# Update lib artwork
self.library.on_library_scrolled(None, None)
def on_library_button_press(self, widget, event):
if self.on_button_press(widget, event, False): return True
def on_current_button_press(self, widget, event):
if self.on_button_press(widget, event, True): return True
def on_playlists_button_press(self, widget, event):
if self.on_button_press(widget, event, False): return True
def on_streams_button_press(self, widget, event):
if self.on_button_press(widget, event, False): return True
def on_button_press(self, widget, event, widget_is_current):
ctrl_press = (event.state & gtk.gdk.CONTROL_MASK)
self.volume_hide()
self.current.sel_rows = None
if event.button == 1 and widget_is_current and not ctrl_press:
# If the user clicked inside a group of rows that were already selected,
# we need to retain the selected rows in case the user wants to DND the
# group of rows. If they release the mouse without first moving it,
# then we revert to the single selected row. This is similar to the
# behavior found in thunar.
try:
path, col, x, y = widget.get_path_at_pos(int(event.x), int(event.y))
if widget.get_selection().path_is_selected(path):
self.current.sel_rows = widget.get_selection().get_selected_rows()[1]
except:
pass
elif event.button == 3:
self.update_menu_visibility()
# Calling the popup in idle_add is important. It allows the menu items
# to have been shown/hidden before the menu is popped up. Otherwise, if
# the menu pops up too quickly, it can result in automatically clicking
# menu items for the user!
gobject.idle_add(self.mainmenu.popup, None, None, None, event.button, event.time)
# Don't change the selection for a right-click. This
# will allow the user to select multiple rows and then
# right-click (instead of right-clicking and having
# the current selection change to the current row)
if widget.get_selection().count_selected_rows() > 1:
return True
def on_add_item_play(self, widget):
self.on_add_item(widget, True)
def on_add_item(self, _widget, play_after=False):
if self.conn:
if play_after and self.status:
playid = self.status['playlistlength']
if self.current_tab == self.TAB_LIBRARY:
items = self.library.get_path_child_filenames(True)
mpdh.call(self.client, 'command_list_ok_begin')
for item in items:
mpdh.call(self.client, 'add', item)
mpdh.call(self.client, 'command_list_end')
elif self.current_tab == self.TAB_PLAYLISTS:
model, selected = self.playlists_selection.get_selected_rows()
for path in selected:
mpdh.call(self.client, 'load', misc.unescape_html(model.get_value(model.get_iter(path), 1)))
elif self.current_tab == self.TAB_STREAMS:
model, selected = self.streams_selection.get_selected_rows()
for path in selected:
item = model.get_value(model.get_iter(path), 2)
self.stream_parse_and_add(item)
self.iterate_now()
if play_after:
if self.status['random'] == '1':
# If we are in random mode, we want to play a random song
# instead:
mpdh.call(self.client, 'play')
else:
mpdh.call(self.client, 'play', int(playid))
def stream_parse_and_add(self, item):
# We need to do different things depending on if this is
# a normal stream, pls, m3u, etc..
# Note that we will only download the first 4000 bytes
while gtk.events_pending():
gtk.main_iteration()
f = None
try:
request = urllib2.Request(item)
opener = urllib2.build_opener()
f = opener.open(request).read(4000)
except:
try:
request = urllib2.Request("http://" + item)
opener = urllib2.build_opener()
f = opener.open(request).read(4000)
except:
try:
request = urllib2.Request("file://" + item)
opener = urllib2.build_opener()
f = opener.open(request).read(4000)
except:
pass
while gtk.events_pending():
gtk.main_iteration()
if f:
if misc.is_binary(f):
# Binary file, just add it:
mpdh.call(self.client, 'add', item)
else:
if "[playlist]" in f:
# pls:
self.stream_parse_pls(f)
elif "#EXTM3U" in f:
# extended m3u:
self.stream_parse_m3u(f)
elif "http://" in f:
# m3u or generic list:
self.stream_parse_m3u(f)
else:
# Something else..
mpdh.call(self.client, 'add', item)
else:
# Hopefully just a regular stream, try to add it:
mpdh.call(self.client, 'add', item)
def stream_parse_pls(self, f):
lines = f.split("\n")
for line in lines:
line = line.replace('\r','')
delim = line.find("=")+1
if delim > 0:
line = line[delim:]
if len(line) > 7 and line[0:7] == 'http://':
mpdh.call(self.client, 'add', line)
elif len(line) > 6 and line[0:6] == 'ftp://':
mpdh.call(self.client, 'add', line)
def stream_parse_m3u(self, f):
lines = f.split("\n")
for line in lines:
line = line.replace('\r','')
if len(line) > 7 and line[0:7] == 'http://':
mpdh.call(self.client, 'add', line)
elif len(line) > 6 and line[0:6] == 'ftp://':
mpdh.call(self.client, 'add', line)
def on_replace_item_play(self, widget):
self.on_replace_item(widget, True)
def on_replace_item(self, widget, play_after=False):
if self.status and self.status['state'] == 'play':
play_after = True
# Only clear if an item is selected:
if self.current_tab == self.TAB_LIBRARY:
num_selected = self.library_selection.count_selected_rows()
elif self.current_tab == self.TAB_PLAYLISTS:
num_selected = self.playlists_selection.count_selected_rows()
elif self.current_tab == self.TAB_STREAMS:
num_selected = self.streams_selection.count_selected_rows()
else:
return
if num_selected == 0:
return
self.mpd_clear(None)
self.on_add_item(widget, play_after)
self.iterate_now()
def menu_position(self, _menu):
if self.config.expanded:
x, y, width, height = self.current_treeview.get_allocation()
# Find first selected visible row and popup the menu
# from there
if self.current_tab == self.TAB_CURRENT:
widget = self.current_treeview
column = self.current.columns[0]
elif self.current_tab == self.TAB_LIBRARY:
widget = self.library_treeview
column = self.library.librarycolumn
elif self.current_tab == self.TAB_PLAYLISTS:
widget = self.playlists_treeview
column = self.playlists.playlistscolumn
elif self.current_tab == self.TAB_STREAMS:
widget = self.streams_treeview
column = self.streams.streamscolumn
rows = widget.get_selection().get_selected_rows()[1]
visible_rect = widget.get_visible_rect()
row_y = 0
for row in rows:
row_rect = widget.get_background_area(row, column)
if row_rect.y + row_rect.height <= visible_rect.height and row_rect.y >= 0:
row_y = row_rect.y + 30
break
return (self.config.x + width - 150, self.config.y + y + row_y, True)
else:
return (self.config.x + 250, self.config.y + 80, True)
def handle_change_status(self):
# Called when one of the following items are changed:
# 1. Current playlist (song added, removed, etc)
# 2. Repeat/random/xfade/volume
# 3. Currently selected song in playlist
# 4. Status (playing/paused/stopped)
if self.status == None:
# clean up and bail out
self.update_progressbar()
self.update_cursong()
self.update_wintitle()
self.artwork.artwork_update()
self.update_statusbar()
if not self.conn:
self.librarydata.clear()
self.playlistsdata.clear()
self.streamsdata.clear()
return
# Display current playlist
if self.prevstatus == None or self.prevstatus['playlist'] != self.status['playlist']:
prevstatus_playlist = None
if self.prevstatus:
prevstatus_playlist = self.prevstatus['playlist']
self.current.current_update(prevstatus_playlist, self.status['playlistlength'])
# Update progress frequently if we're playing
if self.status['state'] in ['play', 'pause']:
self.update_progressbar()
# If elapsed time is shown in the window title, we need to update more often:
if "%E" in self.config.titleformat:
self.update_wintitle()
# If state changes
if self.prevstatus == None or self.prevstatus['state'] != self.status['state']:
self.album_get_artist()
# Update progressbar if the state changes too
self.update_progressbar()
self.update_cursong()
self.update_wintitle()
self.info_update(True)
if self.status['state'] == 'stop':
self.ppbutton.set_image(ui.image(stock=gtk.STOCK_MEDIA_PLAY, stocksize=gtk.ICON_SIZE_BUTTON))
self.ppbutton.get_child().get_child().get_children()[1].set_text('')
self.UIManager.get_widget('/traymenu/playmenu').show()
self.UIManager.get_widget('/traymenu/pausemenu').hide()
if HAVE_STATUS_ICON:
self.statusicon.set_from_file(self.find_path('sonata.png'))
elif HAVE_EGG and self.eggtrayheight:
self.eggtrayfile = self.find_path('sonata.png')
self.trayimage.set_from_pixbuf(img.get_pixbuf_of_size(gtk.gdk.pixbuf_new_from_file(self.eggtrayfile), self.eggtrayheight)[0])
elif self.status['state'] == 'pause':
self.ppbutton.set_image(ui.image(stock=gtk.STOCK_MEDIA_PLAY, stocksize=gtk.ICON_SIZE_BUTTON))
self.ppbutton.get_child().get_child().get_children()[1].set_text('')
self.UIManager.get_widget('/traymenu/playmenu').show()
self.UIManager.get_widget('/traymenu/pausemenu').hide()
if HAVE_STATUS_ICON:
self.statusicon.set_from_file(self.find_path('sonata_pause.png'))
elif HAVE_EGG and self.eggtrayheight:
self.eggtrayfile = self.find_path('sonata_pause.png')
self.trayimage.set_from_pixbuf(img.get_pixbuf_of_size(gtk.gdk.pixbuf_new_from_file(self.eggtrayfile), self.eggtrayheight)[0])
elif self.status['state'] == 'play':
self.ppbutton.set_image(ui.image(stock=gtk.STOCK_MEDIA_PAUSE, stocksize=gtk.ICON_SIZE_BUTTON))
self.ppbutton.get_child().get_child().get_children()[1].set_text('')
self.UIManager.get_widget('/traymenu/playmenu').hide()
self.UIManager.get_widget('/traymenu/pausemenu').show()
if self.prevstatus != None:
if self.prevstatus['state'] == 'pause':
# Forces the notification to popup if specified
self.on_currsong_notify()
if HAVE_STATUS_ICON:
self.statusicon.set_from_file(self.find_path('sonata_play.png'))
elif HAVE_EGG and self.eggtrayheight:
self.eggtrayfile = self.find_path('sonata_play.png')
self.trayimage.set_from_pixbuf(img.get_pixbuf_of_size(gtk.gdk.pixbuf_new_from_file(self.eggtrayfile), self.eggtrayheight)[0])
self.artwork.artwork_update()
if self.status['state'] in ['play', 'pause']:
self.current.center_song_in_list()
if self.prevstatus is None or self.status['volume'] != self.prevstatus['volume']:
try:
self.volumescale.get_adjustment().set_value(int(self.status['volume']))
if int(self.status['volume']) == 0:
self.volume_set_image("stock_volume-mute")
elif int(self.status['volume']) < 30:
self.volume_set_image("stock_volume-min")
elif int(self.status['volume']) <= 70:
self.volume_set_image("stock_volume-med")
else:
self.volume_set_image("stock_volume-max")
self.volumebutton.set_tooltip_text(self.status['volume'] + "%")
except:
pass
if self.conn:
if mpdh.mpd_is_updating(self.status):
# MPD library is being updated
self.update_statusbar(True)
elif self.prevstatus == None or mpdh.mpd_is_updating(self.prevstatus) != mpdh.mpd_is_updating(self.status):
if not mpdh.mpd_is_updating(self.status):
# Done updating, refresh interface
self.mpd_updated_db()
elif self.mpd_update_queued:
# If the update happens too quickly, we won't catch it in
# our polling. So let's force an update of the interface:
self.mpd_updated_db()
self.mpd_update_queued = False
if self.config.as_enabled:
playing = self.status and self.status['state'] == 'play'
stopped = self.status and self.status['state'] == 'stop'
if playing:
mpd_time_now = self.status['time']
switched_from_stop_to_play = not self.prevstatus or (self.prevstatus and self.prevstatus['state'] == 'stop')
self.scrobbler.handle_change_status(True, self.prevsonginfo, self.songinfo, switched_from_stop_to_play, mpd_time_now)
elif stopped:
self.scrobbler.handle_change_status(False, self.prevsonginfo)
def mpd_updated_db(self):
self.library.view_caches_reset()
self.update_statusbar(False)
# We need to make sure that we update the artist in case tags have changed:
self.album_reset_artist()
self.album_get_artist()
# Now update the library and playlist tabs
if self.library.search_visible():
self.library.on_library_search_combo_change()
else:
self.library.library_browse(root=self.config.wd)
self.playlists.populate()
# Update info if it's visible:
self.info_update(True)
return False
def album_get_artist(self):
if self.songinfo and 'album' in self.songinfo:
self.album_return_artist_name()
elif self.songinfo and 'artist' in self.songinfo:
self.album_current_artist = [self.songinfo, mpdh.get(self.songinfo, 'artist')]
else:
self.album_current_artist = [self.songinfo, ""]
def volume_set_image(self, stock_icon):
image = ui.image(stock=stock_icon, stocksize=VOLUME_ICON_SIZE)
self.volumebutton.set_image(image)
def handle_change_song(self):
# Called when one of the following items are changed for the current
# mpd song in the playlist:
# 1. Song tags or filename (e.g. if tags are edited)
# 2. Position in playlist (e.g. if playlist is sorted)
# Note that the song does not have to be playing; it can reflect the
# next song that will be played.
self.current.unbold_boldrow(self.current.prev_boldrow)
if self.status and 'song' in self.status:
row = int(self.status['song'])
self.current.boldrow(row)
if self.songinfo:
if not self.prevsonginfo or mpdh.get(self.songinfo, 'file') != mpdh.get(self.prevsonginfo, 'file'):
self.current.center_song_in_list()
self.current.prev_boldrow = row
self.album_get_artist()
self.update_cursong()
self.update_wintitle()
self.artwork.artwork_update()
self.info_update(True)
def update_progressbar(self):
if self.conn and self.status and self.status['state'] in ['play', 'pause']:
at, length = [float(c) for c in self.status['time'].split(':')]
try:
newfrac = at/length
except:
newfrac = 0
else:
newfrac = 0
if not self.last_progress_frac or self.last_progress_frac != newfrac:
if newfrac >= 0 and newfrac <= 1:
self.progressbar.set_fraction(newfrac)
if self.conn:
if self.status and self.status['state'] in ['play', 'pause']:
at, length = [int(c) for c in self.status['time'].split(':')]
at_time = misc.convert_time(at)
try:
time = misc.convert_time(int(mpdh.get(self.songinfo, 'time')))
newtime = at_time + " / " + time
except:
newtime = at_time
elif self.status:
newtime = ' '
else:
newtime = _('No Read Permission')
else:
newtime = _('Not Connected')
if not self.last_progress_text or self.last_progress_text != newtime:
self.progressbar.set_text(newtime)
def update_statusbar(self, updatingdb=False):
if self.config.show_statusbar:
if self.conn and self.status:
try:
days = None
hours = None
mins = None
total_time = misc.convert_time(self.current.total_time)
try:
mins = total_time.split(":")[-2]
hours = total_time.split(":")[-3]
if int(hours) >= 24:
days = str(int(hours)/24)
hours = str(int(hours) - int(days)*24).zfill(2)
except:
pass
if days:
days_text = gettext.ngettext('day', 'days', int(days))
if mins:
if mins.startswith('0') and len(mins) > 1:
mins = mins[1:]
mins_text = gettext.ngettext('minute', 'minutes', int(mins))
if hours:
if hours.startswith('0'):
hours = hours[1:]
hours_text = gettext.ngettext('hour', 'hours', int(hours))
# Show text:
songs_text = gettext.ngettext('song', 'songs', int(self.status['playlistlength']))
if int(self.status['playlistlength']) > 0:
if days:
status_text = str(self.status['playlistlength']) + ' ' + songs_text + ' ' + days + ' ' + days_text + ', ' + hours + ' ' + hours_text + ', ' + _('and') + ' ' + mins + ' ' + mins_text
elif hours:
status_text = str(self.status['playlistlength']) + ' ' + songs_text + ' ' + hours + ' ' + hours_text + ' ' + _('and') + ' ' + mins + ' ' + mins_text
elif mins:
status_text = str(self.status['playlistlength']) + ' ' + songs_text + ' ' + mins + ' ' + mins_text
else:
status_text = ""
else:
status_text = ""
if updatingdb:
status_text = status_text + " " + _("(updating mpd)")
except:
status_text = ""
else:
status_text = ""
if status_text != self.last_status_text:
self.statusbar.push(self.statusbar.get_context_id(""), status_text)
self.last_status_text = status_text
def expander_ellipse_workaround(self):
# Hacky workaround to ellipsize the expander - see
# http://bugzilla.gnome.org/show_bug.cgi?id=406528
cursonglabelwidth = self.expander.get_allocation().width - 15
if cursonglabelwidth > 0:
self.cursonglabel1.set_size_request(cursonglabelwidth, -1)
self.cursonglabel1.set_size_request(cursonglabelwidth, -1)
def get_current_song_text(self):
return self.cursonglabel1.get_text(), self.cursonglabel2.get_text()
def update_cursong(self):
if self.conn and self.status and self.status['state'] in ['play', 'pause']:
# We must show the trayprogressbar and trayalbumeventbox
# before changing self.cursonglabel (and consequently calling
# self.on_currsong_notify()) in order to ensure that the notification
# popup will have the correct height when being displayed for
# the first time after a stopped state.
if self.config.show_progress:
self.trayprogressbar.show()
self.traycursonglabel2.show()
if self.config.show_covers:
self.trayalbumeventbox.show()
self.trayalbumimage2.show()
for label in (self.cursonglabel1, self.cursonglabel2, self.traycursonglabel1, self.traycursonglabel2):
label.set_ellipsize(pango.ELLIPSIZE_END)
self.expander_ellipse_workaround()
if len(self.config.currsongformat1) > 0:
newlabel1 = '<big><b>' + self.parse_formatting(self.config.currsongformat1, self.songinfo, True) + ' </b></big>'
else:
newlabel1 = '<big><b> </b></big>'
if len(self.config.currsongformat2) > 0:
newlabel2 = '<small>' + self.parse_formatting(self.config.currsongformat2, self.songinfo, True) + ' </small>'
else:
newlabel2 = '<small> </small>'
if newlabel1 != self.cursonglabel1.get_label():
self.cursonglabel1.set_markup(newlabel1)
if newlabel2 != self.cursonglabel2.get_label():
self.cursonglabel2.set_markup(newlabel2)
if newlabel1 != self.traycursonglabel1.get_label():
self.traycursonglabel1.set_markup(newlabel1)
if newlabel2 != self.traycursonglabel2.get_label():
self.traycursonglabel2.set_markup(newlabel2)
self.expander.set_tooltip_text(self.cursonglabel1.get_text() + "\n" + self.cursonglabel2.get_text())
else:
for label in (self.cursonglabel1, self.cursonglabel2, self.traycursonglabel1, self.cursonglabel2):
label.set_ellipsize(pango.ELLIPSIZE_NONE)
self.cursonglabel1.set_markup('<big><b>' + _('Stopped') + '</b></big>')
if self.config.expanded:
self.cursonglabel2.set_markup('<small>' + _('Click to collapse') + '</small>')
else:
self.cursonglabel2.set_markup('<small>' + _('Click to expand') + '</small>')
self.expander.set_tooltip_text(self.cursonglabel1.get_text())
if not self.conn:
self.traycursonglabel1.set_label(_('Not Connected'))
elif not self.status:
self.traycursonglabel1.set_label(_('No Read Permission'))
else:
self.traycursonglabel1.set_label(_('Stopped'))
self.trayprogressbar.hide()
self.trayalbumeventbox.hide()
self.trayalbumimage2.hide()
self.traycursonglabel2.hide()
self.update_infofile()
self.update_pidgin()
def update_wintitle(self):
if self.window_owner:
if self.conn and self.status and self.status['state'] in ['play', 'pause']:
newtitle = self.parse_formatting(self.config.titleformat, self.songinfo, False, True)
else:
newtitle = '[Sonata]'
if not self.last_title or self.last_title != newtitle:
self.window.set_property('title', newtitle)
self.last_title = newtitle
def set_allow_art_search(self):
self.allow_art_search = True
def status_is_play_or_pause(self):
return self.conn and self.status and self.status['state'] in ['play', 'pause']
def connected(self):
return self.conn
def tooltip_set_window_width(self):
screen = self.window.get_screen()
pointer_screen, px, py, _ = screen.get_display().get_pointer()
monitor_num = screen.get_monitor_at_point(px, py)
monitor = screen.get_monitor_geometry(monitor_num)
self.notification_width = int(monitor.width * 0.30)
if self.notification_width > consts.NOTIFICATION_WIDTH_MAX:
self.notification_width = consts.NOTIFICATION_WIDTH_MAX
elif self.notification_width < consts.NOTIFICATION_WIDTH_MIN:
self.notification_width = consts.NOTIFICATION_WIDTH_MIN
def on_currsong_notify(self, _foo=None, _bar=None, force_popup=False):
if self.fullscreencoverart.get_property('visible'):
return
if self.sonata_loaded:
if self.conn and self.status and self.status['state'] in ['play', 'pause']:
if self.config.show_covers:
self.traytips.set_size_request(self.notification_width, -1)
else:
self.traytips.set_size_request(self.notification_width-100, -1)
else:
self.traytips.set_size_request(-1, -1)
if self.config.show_notification or force_popup:
try:
gobject.source_remove(self.traytips.notif_handler)
except:
pass
if self.conn and self.status and self.status['state'] in ['play', 'pause']:
try:
self.traytips.notifications_location = self.config.traytips_notifications_location
self.traytips.use_notifications_location = True
if HAVE_STATUS_ICON and self.statusicon.is_embedded() and self.statusicon.get_visible():
self.traytips._real_display(self.statusicon)
elif HAVE_EGG and self.trayicon.get_property('visible'):
self.traytips._real_display(self.trayeventbox)
else:
self.traytips._real_display(None)
if self.config.popup_option != len(self.popuptimes)-1:
if force_popup and not self.config.show_notification:
# Used -p argument and notification is disabled in
# player; default to 3 seconds
timeout = 3000
else:
timeout = int(self.popuptimes[self.config.popup_option])*1000
self.traytips.notif_handler = gobject.timeout_add(timeout, self.traytips.hide)
else:
# -1 indicates that the timeout should be forever.
# We don't want to pass None, because then Sonata
# would think that there is no current notification
self.traytips.notif_handler = -1
except:
pass
else:
self.traytips.hide()
elif self.traytips.get_property('visible'):
try:
self.traytips._real_display(self.trayeventbox)
except:
pass
def on_progressbar_notify_fraction(self, *_args):
self.trayprogressbar.set_fraction(self.progressbar.get_fraction())
def on_progressbar_notify_text(self, *_args):
self.trayprogressbar.set_text(self.progressbar.get_text())
def update_infofile(self):
if self.config.use_infofile is True:
try:
info_file = open(self.config.infofile_path, 'w')
if self.status['state'] in ['play']:
info_file.write('Status: ' + 'Playing' + '\n')
elif self.status['state'] in ['pause']:
info_file.write('Status: ' + 'Paused' + '\n')
elif self.status['state'] in ['stop']:
info_file.write('Status: ' + 'Stopped' + '\n')
try:
info_file.write('Title: ' + mpdh.get(self.songinfo, 'artist') + ' - ' + mpdh.get(self.songinfo, 'title') + '\n')
except:
try:
info_file.write('Title: ' + mpdh.get(self.songinfo, 'title') + '\n') # No Arist in streams
except:
info_file.write('Title: No - ID Tag\n')
info_file.write('Album: ' + mpdh.get(self.songinfo, 'album', 'No Data') + '\n')
info_file.write('Track: ' + mpdh.get(self.songinfo, 'track', '0') + '\n')
info_file.write('File: ' + mpdh.get(self.songinfo, 'file', 'No Data') + '\n')
info_file.write('Time: ' + mpdh.get(self.songinfo, 'time', '0') + '\n')
info_file.write('Volume: ' + self.status['volume'] + '\n')
info_file.write('Repeat: ' + self.status['repeat'] + '\n')
info_file.write('Random: ' + self.status['random'] + '\n')
info_file.close()
except:
pass
def update_pidgin(self):
if True:
try:
status = u'\u266b ' + mpdh.get(self.songinfo, 'title') + ' by ' + mpdh.get(self.songinfo, 'artist') +' (' + mpdh.get(self.songinfo, 'album') + ')'
import dbus
bus = dbus.SessionBus()
obj = bus.get_object("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject")
purple = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface")
old_status = purple.PurpleSavedstatusGetCurrent() # get current status
status_type = purple.PurpleSavedstatusGetType(old_status) # get current status type
new_status = purple.PurpleSavedstatusNew("", status_type) # create new status with old type
purple.PurpleSavedstatusSetMessage(new_status, status) # fill new status with status message
purple.PurpleSavedstatusActivate(new_status) # activate new status
except:
pass
#################
# Gui Callbacks #
#################
def on_delete_event_yes(self, _widget):
self.exit_now = True
self.on_delete_event(None, None)
# This one makes sure the program exits when the window is closed
def on_delete_event(self, _widget, _data=None):
if not self.exit_now and self.config.minimize_to_systray:
if HAVE_STATUS_ICON and self.statusicon.is_embedded() and self.statusicon.get_visible():
self.withdraw_app()
return True
elif HAVE_EGG and self.trayicon.get_property('visible'):
self.withdraw_app()
return True
self.settings_save()
self.artwork.artwork_save_cache()
if self.config.as_enabled:
self.scrobbler.save_cache()
if self.conn and self.config.stop_on_exit:
self.mpd_stop(None)
sys.exit()
def on_window_state_change(self, _widget, _event):
self.volume_hide()
def on_window_lost_focus(self, _widget, _event):
self.volume_hide()
def on_window_configure(self, _widget, _event):
width, height = self.window.get_size()
if self.config.expanded: self.config.w, self.config.h = width, height
else: self.config.w = width
self.config.x, self.config.y = self.window.get_position()
self.expander_ellipse_workaround()
def on_notebook_resize(self, _widget, _event):
if not self.current.resizing_columns :
gobject.idle_add(self.header_save_column_widths)
gobject.idle_add(self.info.resize_elements, self.notebook.allocation)
def on_expand(self, _action):
if not self.config.expanded:
self.expander.set_expanded(False)
self.on_expander_activate(None)
self.expander.set_expanded(True)
def on_collapse(self, _action):
if self.config.expanded:
self.expander.set_expanded(True)
self.on_expander_activate(None)
self.expander.set_expanded(False)
def on_expander_activate(self, _expander):
currheight = self.window.get_size()[1]
self.config.expanded = False
# Note that get_expanded() will return the state of the expander
# before this current click
window_about_to_be_expanded = not self.expander.get_expanded()
if window_about_to_be_expanded:
if self.window.get_size()[1] == self.config.h:
# For WMs like ion3, the app will not actually resize
# when in collapsed mode, so prevent the waiting
# of the player to expand from happening:
skip_size_check = True
else:
skip_size_check = False
if self.config.show_statusbar:
self.statusbar.show()
self.notebook.show_all()
if self.config.show_statusbar:
ui.show(self.statusbar)
else:
ui.hide(self.statusbar)
self.notebook.hide()
if not (self.conn and self.status and self.status['state'] in ['play', 'pause']):
if window_about_to_be_expanded:
self.cursonglabel2.set_markup('<small>' + _('Click to collapse') + '</small>')
else:
self.cursonglabel2.set_markup('<small>' + _('Click to expand') + '</small>')
# Now we wait for the height of the player to increase, so that
# we know the list is visible. This is pretty hacky, but works.
if self.window_owner:
if window_about_to_be_expanded:
if not skip_size_check:
while self.window.get_size()[1] == currheight:
gtk.main_iteration()
# Notebook is visible, now resize:
self.window.resize(self.config.w, self.config.h)
else:
self.window.resize(self.config.w, 1)
if window_about_to_be_expanded:
self.config.expanded = True
if self.status and self.status['state'] in ['play','pause']:
gobject.idle_add(self.current.center_song_in_list)
self.window.set_geometry_hints(self.window)
if self.notebook_show_first_tab:
# Sonata was launched in collapsed state. Ensure we display
# first tab:
self.notebook_show_first_tab = False
self.notebook.set_current_page(0)
# Put focus to the notebook:
self.on_notebook_page_change(self.notebook, 0, self.notebook.get_current_page())
# This callback allows the user to seek to a specific portion of the song
def on_progressbar_press(self, _widget, event):
if event.button == 1:
if self.status and self.status['state'] in ['play', 'pause']:
at, length = [int(c) for c in self.status['time'].split(':')]
try:
pbsize = self.progressbar.allocation
if misc.is_lang_rtl(self.window):
seektime = int(((pbsize.width-event.x)/pbsize.width) * length)
else:
seektime = int((event.x/pbsize.width) * length)
self.seek(int(self.status['song']), seektime)
except:
pass
return True
def on_progressbar_scroll(self, _widget, event):
if self.status and self.status['state'] in ['play', 'pause']:
try:
gobject.source_remove(self.seekidle)
except:
pass
self.seekidle = gobject.idle_add(self._seek_when_idle, event.direction)
return True
def _seek_when_idle(self, direction):
at, length = [int(c) for c in self.status['time'].split(':')]
try:
if direction == gtk.gdk.SCROLL_UP:
seektime = int(self.status['time'].split(":")[0]) + 5
if seektime < 0: seektime = 0
elif direction == gtk.gdk.SCROLL_DOWN:
seektime = int(self.status['time'].split(":")[0]) - 5
if seektime > mpdh.get(self.songinfo, 'time'):
seektime = mpdh.get(self.songinfo, 'time')
self.seek(int(self.status['song']), seektime)
except:
pass
def on_lyrics_search(self, _event):
artist = mpdh.get(self.songinfo, 'artist')
title = mpdh.get(self.songinfo, 'title')
dialog = ui.dialog(title=_('Lyrics Search'), parent=self.window, flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_FIND, gtk.RESPONSE_ACCEPT), role='lyricsSearch', default=gtk.RESPONSE_ACCEPT)
dialog.action_area.get_children()[0].set_label(_("_Search"))
dialog.action_area.get_children()[0].set_image(ui.image(stock=gtk.STOCK_FIND))
artist_hbox = gtk.HBox()
artist_label = ui.label(text=_('Artist Name') + ':')
artist_hbox.pack_start(artist_label, False, False, 5)
artist_entry = ui.entry(text=artist)
artist_hbox.pack_start(artist_entry, True, True, 5)
title_hbox = gtk.HBox()
title_label = ui.label(text=_('Song Title') + ':')
title_hbox.pack_start(title_label, False, False, 5)
title_entry = ui.entry(title)
title_hbox.pack_start(title_entry, True, True, 5)
ui.set_widths_equal([artist_label, title_label])
dialog.vbox.pack_start(artist_hbox)
dialog.vbox.pack_start(title_hbox)
ui.show(dialog.vbox)
response = dialog.run()
if response == gtk.RESPONSE_ACCEPT:
dialog.destroy()
# Delete current lyrics:
filename = self.info.target_lyrics_filename(artist, title, None, consts.LYRICS_LOCATION_HOME)
misc.remove_file(filename)
# Search for new lyrics:
self.info.get_lyrics_start(artist_entry.get_text(), title_entry.get_text(), artist, title, os.path.dirname(mpdh.get(self.songinfo, 'file')))
else:
dialog.destroy()
def mpd_shuffle(self, _action):
if self.conn:
if not self.status or self.status['playlistlength'] == '0':
return
ui.change_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
while gtk.events_pending():
gtk.main_iteration()
mpdh.call(self.client, 'shuffle')
def on_menu_popup(self, _widget):
self.update_menu_visibility()
gobject.idle_add(self.mainmenu.popup, None, None, self.menu_position, 3, 0)
def on_updatedb(self, _action):
if self.conn:
if self.library.search_visible():
self.library.on_search_end(None)
mpdh.update(self.client, '/', self.status)
self.mpd_update_queued = True
def on_updatedb_shortcut(self, _action):
# If no songs selected, update view. Otherwise update
# selected items.
if self.library.not_parent_is_selected():
self.on_updatedb_path(True)
else:
self.on_updatedb_path(False)
def on_updatedb_path(self, selected_only):
if self.conn and self.current_tab == self.TAB_LIBRARY:
if self.library.search_visible():
self.library.on_search_end(None)
filenames = self.library.get_path_child_filenames(True, selected_only)
if len(filenames) > 0:
mpdh.update(self.client, filenames, self.status)
self.mpd_update_queued = True
def on_image_activate(self, widget, event):
self.window.handler_block(self.mainwinhandler)
if event.button == 1 and widget == self.info_imagebox and self.artwork.have_last():
if not self.config.info_art_enlarged:
self.info_imagebox.set_size_request(-1,-1)
self.artwork.artwork_set_image_last()
self.config.info_art_enlarged = True
else:
self.info_imagebox.set_size_request(152, -1)
self.artwork.artwork_set_image_last()
self.config.info_art_enlarged = False
self.volume_hide()
# Force a resize of the info labels, if needed:
gobject.idle_add(self.on_notebook_resize, self.notebook, None)
elif event.button == 1 and widget != self.info_imagebox:
if self.config.expanded:
if self.current_tab != self.TAB_INFO:
self.img_clicked = True
self.switch_to_tab_name(self.TAB_INFO)
self.img_clicked = False
else:
self.switch_to_tab_name(self.last_tab)
elif event.button == 3:
artist = None
album = None
stream = None
if self.conn and self.status and self.status['state'] in ['play', 'pause']:
self.UIManager.get_widget('/imagemenu/chooseimage_menu/').show()
self.UIManager.get_widget('/imagemenu/localimage_menu/').show()
artist = mpdh.get(self.songinfo, 'artist', None)
album = mpdh.get(self.songinfo, 'album', None)
stream = mpdh.get(self.songinfo, 'name', None)
if not (artist or album or stream):
self.UIManager.get_widget('/imagemenu/localimage_menu/').hide()
self.UIManager.get_widget('/imagemenu/resetimage_menu/').hide()
self.UIManager.get_widget('/imagemenu/chooseimage_menu/').hide()
self.imagemenu.popup(None, None, None, event.button, event.time)
gobject.timeout_add(50, self.on_image_activate_after)
return False
def on_image_motion_cb(self, _widget, context, _x, _y, time):
context.drag_status(gtk.gdk.ACTION_COPY, time)
return True
def on_image_drop_cb(self, _widget, _context, _x, _y, selection, _info, _time):
if self.conn and self.status and self.status['state'] in ['play', 'pause']:
uri = selection.data.strip()
path = urllib.url2pathname(uri)
paths = path.rsplit('\n')
thread = threading.Thread(target=self.on_image_drop_cb_thread, args=(paths,))
thread.setDaemon(True)
thread.start()
def on_image_drop_cb_thread(self, paths):
for i, path in enumerate(paths):
remove_after_set = False
paths[i] = path.rstrip('\r')
# Clean up (remove preceding "file://" or "file:")
if paths[i].startswith('file://'):
paths[i] = paths[i][7:]
elif paths[i].startswith('file:'):
paths[i] = paths[i][5:]
elif re.match('^(https?|ftp)://', paths[i]):
try:
# Eliminate query arguments and extract extension & filename
path = urllib.splitquery(paths[i])[0]
extension = os.path.splitext(path)[1][1:]
filename = os.path.split(path)[1]
if img.extension_is_valid(extension):
# Save to temp dir.. we will delete the image afterwards
dest_file = os.path.expanduser('~/.covers/temp/' + filename)
misc.create_dir('~/.covers/temp')
urllib.urlretrieve(paths[i], dest_file)
paths[i] = dest_file
remove_after_set = True
else:
continue
except:
# cleanup undone file
misc.remove_file(paths[i])
raise
paths[i] = os.path.abspath(paths[i])
if img.valid_image(paths[i]):
stream = mpdh.get(self.songinfo, 'name', None)
if stream is not None:
dest_filename = self.artwork.artwork_stream_filename(mpdh.get(self.songinfo, 'name'))
else:
dest_filename = self.target_image_filename()
if dest_filename != paths[i]:
shutil.copyfile(paths[i], dest_filename)
self.artwork.artwork_update(True)
if remove_after_set:
misc.remove_file(paths[i])
def target_image_filename(self, force_location=None, songpath=None, artist=None, album=None):
# Only pass songpath, artist, and album if we are trying to get the
# filename for an album that isn't currently playing
if self.conn:
# If no info passed, you info from currently playing song:
if not album:
album = mpdh.get(self.songinfo, 'album', "")
if not artist:
artist = self.album_current_artist[1]
album = album.replace("/", "")
artist = artist.replace("/", "")
if songpath is None:
songpath = os.path.dirname(mpdh.get(self.songinfo, 'file'))
# Return target filename:
if force_location is not None:
art_loc = force_location
else:
art_loc = self.config.art_location
if art_loc == consts.ART_LOCATION_HOMECOVERS:
targetfile = os.path.expanduser("~/.covers/" + artist + "-" + album + ".jpg")
elif art_loc == consts.ART_LOCATION_COVER:
targetfile = self.config.musicdir[self.config.profile_num] + songpath + "/cover.jpg"
elif art_loc == consts.ART_LOCATION_FOLDER:
targetfile = self.config.musicdir[self.config.profile_num] + songpath + "/folder.jpg"
elif art_loc == consts.ART_LOCATION_ALBUM:
targetfile = self.config.musicdir[self.config.profile_num] + songpath + "/album.jpg"
elif art_loc == consts.ART_LOCATION_CUSTOM:
targetfile = self.config.musicdir[self.config.profile_num] + songpath + "/" + self.config.art_location_custom_filename
targetfile = misc.file_exists_insensitive(targetfile)
return misc.file_from_utf8(targetfile)
def album_return_artist_and_tracks(self):
# Includes logic for Various Artists albums to determine
# the tracks.
datalist = []
album = mpdh.get(self.songinfo, 'album')
songs, playtime, num_songs = self.library.library_return_search_items(album=album)
for song in songs:
year = mpdh.get(song, 'date', '')
artist = mpdh.get(song, 'artist', '')
path = os.path.dirname(mpdh.get(song, 'file'))
data = library.library_set_data(album=album, artist=artist, year=year, path=path)
datalist.append(data)
if len(datalist) > 0:
datalist = misc.remove_list_duplicates(datalist, case=False)
datalist = self.library.list_identify_VA_albums(datalist)
# Find all songs in album:
retsongs = []
for song in songs:
if unicode(mpdh.get(song, 'album')).lower() == unicode(library.library_get_data(datalist[0], 'album')).lower() \
and mpdh.get(song, 'date', '') == library.library_get_data(datalist[0], 'year'):
retsongs.append(song)
artist = library.library_get_data(datalist[0], 'artist')
return artist, retsongs
else:
return None, None
def album_return_artist_name(self):
# Determine if album_name is a various artists album.
if self.album_current_artist[0] == self.songinfo:
return
artist, tracks = self.album_return_artist_and_tracks()
if artist is not None:
self.album_current_artist = [self.songinfo, artist]
else:
self.album_current_artist = [self.songinfo, ""]
def album_reset_artist(self):
self.album_current_artist = [None, ""]
def on_image_activate_after(self):
self.window.handler_unblock(self.mainwinhandler)
def update_preview(self, file_chooser, preview):
filename = file_chooser.get_preview_filename()
pixbuf = None
try:
pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(filename, 128, 128)
except:
pass
if pixbuf == None:
try:
pixbuf = gtk.gdk.PixbufAnimation(filename).get_static_image()
width = pixbuf.get_width()
height = pixbuf.get_height()
if width > height:
pixbuf = pixbuf.scale_simple(128, int(float(height)/width*128), gtk.gdk.INTERP_HYPER)
else:
pixbuf = pixbuf.scale_simple(int(float(width)/height*128), 128, gtk.gdk.INTERP_HYPER)
except:
pass
if pixbuf == None:
pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 1, 8, 128, 128)
pixbuf.fill(0x00000000)
preview.set_from_pixbuf(pixbuf)
have_preview = True
file_chooser.set_preview_widget_active(have_preview)
del pixbuf
self.call_gc_collect = True
def image_local(self, _widget):
dialog = gtk.FileChooserDialog(title=_("Open Image"),action=gtk.FILE_CHOOSER_ACTION_OPEN,buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
filefilter = gtk.FileFilter()
filefilter.set_name(_("Images"))
filefilter.add_pixbuf_formats()
dialog.add_filter(filefilter)
filefilter = gtk.FileFilter()
filefilter.set_name(_("All files"))
filefilter.add_pattern("*")
dialog.add_filter(filefilter)
preview = ui.image()
dialog.set_preview_widget(preview)
dialog.set_use_preview_label(False)
dialog.connect("update-preview", self.update_preview, preview)
stream = mpdh.get(self.songinfo, 'name', None)
album = mpdh.get(self.songinfo, 'album', "").replace("/", "")
artist = self.album_current_artist[1].replace("/", "")
dialog.connect("response", self.image_local_response, artist, album, stream)
dialog.set_default_response(gtk.RESPONSE_OK)
songdir = os.path.dirname(mpdh.get(self.songinfo, 'file'))
currdir = misc.file_from_utf8(self.config.musicdir[self.config.profile_num] + songdir)
if self.config.art_location != consts.ART_LOCATION_HOMECOVERS:
dialog.set_current_folder(currdir)
if stream is not None:
# Allow saving an image file for a stream:
self.local_dest_filename = self.artwork.artwork_stream_filename(stream)
else:
self.local_dest_filename = self.target_image_filename()
dialog.show()
def image_local_response(self, dialog, response, _artist, _album, _stream):
if response == gtk.RESPONSE_OK:
filename = dialog.get_filenames()[0]
# Copy file to covers dir:
if self.local_dest_filename != filename:
shutil.copyfile(filename, self.local_dest_filename)
# And finally, set the image in the interface:
self.artwork.artwork_update(True)
# Force a resize of the info labels, if needed:
gobject.idle_add(self.on_notebook_resize, self.notebook, None)
dialog.destroy()
def imagelist_append(self, elem):
self.imagelist.append(elem)
def remotefilelist_append(self, elem):
self.remotefilelist.append(elem)
def image_remote(self, _widget):
self.choose_dialog = ui.dialog(title=_("Choose Cover Art"), parent=self.window, flags=gtk.DIALOG_MODAL, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT), role='chooseCoverArt', default=gtk.RESPONSE_ACCEPT, separator=False, resizable=False)
choosebutton = self.choose_dialog.add_button(_("C_hoose"), gtk.RESPONSE_ACCEPT)
chooseimage = ui.image(stock=gtk.STOCK_CONVERT, stocksize=gtk.ICON_SIZE_BUTTON)
choosebutton.set_image(chooseimage)
self.imagelist = gtk.ListStore(int, gtk.gdk.Pixbuf)
# Setting col=2 only shows 1 column with gtk 2.16 while col=-1 shows 2
imagewidget = ui.iconview(col=-1, space=0, margin=0, itemw=75, selmode=gtk.SELECTION_SINGLE)
scroll = ui.scrollwindow(policy_x=gtk.POLICY_NEVER, policy_y=gtk.POLICY_ALWAYS, w=360, h=325, add=imagewidget)
self.choose_dialog.vbox.pack_start(scroll, False, False, 0)
hbox = gtk.HBox()
vbox = gtk.VBox()
vbox.pack_start(ui.label(markup='<small> </small>'), False, False, 0)
self.remote_artistentry = ui.entry()
self.remote_albumentry = ui.entry()
entries = [self.remote_artistentry, self.remote_albumentry]
text = [("Artist"), _("Album")]
labels = []
for i in range(len(entries)):
tmphbox = gtk.HBox()
tmplabel = ui.label(text=text[i] + ": ")
labels.append(tmplabel)
tmphbox.pack_start(tmplabel, False, False, 5)
entries[i].connect('activate', self.image_remote_refresh, imagewidget)
tmphbox.pack_start(entries[i], True, True, 5)
vbox.pack_start(tmphbox)
ui.set_widths_equal(labels)
vbox.pack_start(ui.label(markup='<small> </small>'), False, False, 0)
hbox.pack_start(vbox, True, True, 5)
vbox2 = gtk.VBox()
vbox2.pack_start(ui.label(" "))
refreshbutton = ui.button(text=_('_Update'), img=ui.image(stock=gtk.STOCK_REFRESH))
refreshbutton.connect('clicked', self.image_remote_refresh, imagewidget)
vbox2.pack_start(refreshbutton, False, False, 5)
vbox2.pack_start(ui.label(" "))
hbox.pack_start(vbox2, False, False, 15)
searchexpander = ui.expander(text=_("Edit search terms"))
searchexpander.add(hbox)
self.choose_dialog.vbox.pack_start(searchexpander, True, True, 0)
self.choose_dialog.show_all()
self.chooseimage_visible = True
self.remotefilelist = []
stream = mpdh.get(self.songinfo, 'name', None)
if stream is not None:
# Allow saving an image file for a stream:
self.remote_dest_filename = self.artwork.artwork_stream_filename(stream)
else:
self.remote_dest_filename = self.target_image_filename()
album = mpdh.get(self.songinfo, 'album', '')
artist = self.album_current_artist[1]
imagewidget.connect('item-activated', self.image_remote_replace_cover, artist.replace("/", ""), album.replace("/", ""), stream)
self.choose_dialog.connect('response', self.image_remote_response, imagewidget, artist, album, stream)
self.remote_artistentry.set_text(artist)
self.remote_albumentry.set_text(album)
self.allow_art_search = True
self.image_remote_refresh(None, imagewidget)
def image_remote_refresh(self, _entry, imagewidget):
if not self.allow_art_search:
return
self.allow_art_search = False
self.artwork.artwork_stop_update()
while self.artwork.artwork_is_downloading_image():
gtk.main_iteration()
self.imagelist.clear()
imagewidget.set_text_column(-1)
imagewidget.set_model(self.imagelist)
imagewidget.set_pixbuf_column(1)
ui.focus(imagewidget)
ui.change_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
thread = threading.Thread(target=self._image_remote_refresh, args=(imagewidget, None))
thread.setDaemon(True)
thread.start()
def _image_remote_refresh(self, imagewidget, _ignore):
self.artwork.stop_art_update = False
# Retrieve all images from amazon:
artist_search = self.remote_artistentry.get_text()
album_search = self.remote_albumentry.get_text()
if len(artist_search) == 0 and len(album_search) == 0:
gobject.idle_add(self.image_remote_no_tag_found, imagewidget)
return
filename = os.path.expanduser("~/.covers/temp/<imagenum>.jpg")
misc.remove_dir_recursive(os.path.dirname(filename))
misc.create_dir(os.path.dirname(filename))
imgfound = self.artwork.artwork_download_img_to_file(artist_search, album_search, filename, True)
ui.change_cursor(None)
if self.chooseimage_visible:
if not imgfound:
gobject.idle_add(self.image_remote_no_covers_found, imagewidget)
self.call_gc_collect = True
def image_remote_no_tag_found(self, imagewidget):
self.image_remote_warning(imagewidget, _("No artist or album name found."))
def image_remote_no_covers_found(self, imagewidget):
self.image_remote_warning(imagewidget, _("No cover art found."))
def image_remote_warning(self, imagewidget, msgstr):
liststore = gtk.ListStore(int, str)
liststore.append([0, msgstr])
imagewidget.set_pixbuf_column(-1)
imagewidget.set_model(liststore)
imagewidget.set_text_column(1)
ui.change_cursor(None)
self.allow_art_search = True
def image_remote_response(self, dialog, response_id, imagewidget, artist, album, stream):
self.artwork.artwork_stop_update()
if response_id == gtk.RESPONSE_ACCEPT:
try:
self.image_remote_replace_cover(imagewidget, imagewidget.get_selected_items()[0], artist, album, stream)
# Force a resize of the info labels, if needed:
gobject.idle_add(self.on_notebook_resize, self.notebook, None)
except:
dialog.destroy()
pass
else:
dialog.destroy()
ui.change_cursor(None)
self.chooseimage_visible = False
def image_remote_replace_cover(self, _iconview, path, _artist, _album, _stream):
self.artwork.artwork_stop_update()
image_num = int(path[0])
if len(self.remotefilelist) > 0:
filename = self.remotefilelist[image_num]
if os.path.exists(filename):
shutil.move(filename, self.remote_dest_filename)
# And finally, set the image in the interface:
self.artwork.artwork_update(True)
# Clean up..
misc.remove_dir_recursive(os.path.dirname(filename))
self.chooseimage_visible = False
self.choose_dialog.destroy()
while self.artwork.artwork_is_downloading_image():
gtk.main_iteration()
def fullscreen_cover_art(self, _widget):
if self.fullscreencoverart.get_property('visible'):
self.fullscreencoverart.hide()
else:
self.traytips.hide()
self.artwork.fullscreen_cover_art_set_image(force_update=True)
self.fullscreencoverart.show_all()
def fullscreen_cover_art_close(self, _widget, event, key_press):
if key_press:
shortcut = gtk.accelerator_name(event.keyval, event.state)
shortcut = shortcut.replace("<Mod2>", "")
if shortcut != 'Escape':
return
self.fullscreencoverart.hide()
def header_save_column_widths(self):
if not self.config.withdrawn and self.config.expanded:
windowwidth = self.window.allocation.width
if windowwidth <= 10 or self.current.columns[0].get_width() <= 10:
# Make sure we only set self.config.columnwidths if self.current
# has its normal allocated width:
return
notebookwidth = self.notebook.allocation.width
treewidth = 0
for i, column in enumerate(self.current.columns):
colwidth = column.get_width()
treewidth += colwidth
if i == len(self.current.columns)-1 and treewidth <= windowwidth:
self.config.columnwidths[i] = min(colwidth, column.get_fixed_width())
else:
self.config.columnwidths[i] = colwidth
if treewidth > notebookwidth:
self.current.expanderwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
else:
self.current.expanderwindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
self.current.resizing_columns = False
def systemtray_menu(self, status_icon, button, activate_time):
self.traymenu.popup(None, None, gtk.status_icon_position_menu, button, activate_time, status_icon)
def systemtray_activate(self, _status_icon):
# Clicking on a gtk.StatusIcon:
if not self.ignore_toggle_signal:
# This prevents the user clicking twice in a row quickly
# and having the second click not revert to the intial
# state
self.ignore_toggle_signal = True
prev_state = self.UIManager.get_widget('/traymenu/showmenu').get_active()
self.UIManager.get_widget('/traymenu/showmenu').set_active(not prev_state)
if not self.window.window:
# For some reason, self.window.window is not defined if mpd is not running
# and sonata is started with self.config.withdrawn = True
self.withdraw_app_undo()
elif not (self.window.window.get_state() & gtk.gdk.WINDOW_STATE_WITHDRAWN) and self.window.is_active():
# Window is not withdrawn and is active (has toplevel focus):
self.withdraw_app()
else:
self.withdraw_app_undo()
# This prevents the tooltip from popping up again until the user
# leaves and enters the trayicon again
#if self.traytips.notif_handler == None and self.traytips.notif_handler != -1:
#self.traytips._remove_timer()
gobject.timeout_add(100, self.tooltip_set_ignore_toggle_signal_false)
def tooltip_show_manually(self):
# Since there is no signal to connect to when the user puts their
# mouse over the trayicon, we will check the mouse position
# manually and show/hide the window as appropriate. This is called
# every iteration. Note: This should not occur if self.traytips.notif_
# handler has a value, because that means that the tooltip is already
# visible, and we don't want to override that setting simply because
# the user's cursor is not over the tooltip.
if self.traymenu.get_property('visible') and self.traytips.notif_handler != -1:
self.traytips._remove_timer()
elif not self.traytips.notif_handler:
pointer_screen, px, py, _ = self.window.get_screen().get_display().get_pointer()
icon_screen, icon_rect, icon_orient = self.statusicon.get_geometry()
x = icon_rect[0]
y = icon_rect[1]
width = icon_rect[2]
height = icon_rect[3]
if px >= x and px <= x+width and py >= y and py <= y+height:
self.traytips._start_delay(self.statusicon)
else:
self.traytips._remove_timer()
def systemtray_click(self, _widget, event):
# Clicking on an egg system tray icon:
if event.button == 1 and not self.ignore_toggle_signal: # Left button shows/hides window(s)
self.systemtray_activate(None)
elif event.button == 2: # Middle button will play/pause
if self.conn:
self.mpd_pp(self.trayeventbox)
elif event.button == 3: # Right button pops up menu
self.traymenu.popup(None, None, None, event.button, event.time)
return False
def on_traytips_press(self, _widget, _event):
if self.traytips.get_property('visible'):
self.traytips._remove_timer()
def withdraw_app_undo(self):
self.window.move(self.config.x, self.config.y)
if not self.config.expanded:
self.notebook.set_no_show_all(True)
self.statusbar.set_no_show_all(True)
self.window.show_all()
self.notebook.set_no_show_all(False)
self.config.withdrawn = False
self.UIManager.get_widget('/traymenu/showmenu').set_active(True)
if self.notebook_show_first_tab and self.config.expanded:
# Sonata was launched in withdrawn state. Ensure we display
# first tab:
self.notebook_show_first_tab = False
self.notebook.set_current_page(0)
gobject.idle_add(self.withdraw_app_undo_present_and_focus)
def withdraw_app_undo_present_and_focus(self):
self.window.present() # Helps to raise the window (useful against focus stealing prevention)
self.window.grab_focus()
if self.config.sticky:
self.window.stick()
if self.config.ontop:
self.window.set_keep_above(True)
def withdraw_app(self):
if HAVE_EGG or HAVE_STATUS_ICON:
# Save the playlist column widths before withdrawing the app.
# Otherwise we will not be able to correctly save the column
# widths if the user quits sonata while it is withdrawn.
self.header_save_column_widths()
self.window.hide()
self.config.withdrawn = True
self.UIManager.get_widget('/traymenu/showmenu').set_active(False)
def on_withdraw_app_toggle(self, _action):
if self.ignore_toggle_signal:
return
self.ignore_toggle_signal = True
if self.UIManager.get_widget('/traymenu/showmenu').get_active():
self.withdraw_app_undo()
else:
self.withdraw_app()
gobject.timeout_add(500, self.tooltip_set_ignore_toggle_signal_false)
def tooltip_set_ignore_toggle_signal_false(self):
self.ignore_toggle_signal = False
# Change volume on mousewheel over systray icon:
def systemtray_scroll(self, widget, event):
self.on_volumebutton_scroll(widget, event)
def systemtray_size(self, widget, _allocation):
if widget.allocation.height <= 5:
# For vertical panels, height can be 1px, so use width
size = widget.allocation.width
else:
size = widget.allocation.height
if not self.eggtrayheight or self.eggtrayheight != size:
self.eggtrayheight = size
if size > 5 and self.eggtrayfile:
self.trayimage.set_from_pixbuf(img.get_pixbuf_of_size(gtk.gdk.pixbuf_new_from_file(self.eggtrayfile), self.eggtrayheight)[0])
def switch_to_tab_name(self, tab_name):
self.notebook.set_current_page(self.notebook_get_tab_num(self.notebook, tab_name))
def switch_to_tab_num(self, tab_num):
vis_tabnum = self.notebook_get_visible_tab_num(self.notebook, tab_num)
if vis_tabnum != -1:
self.notebook.set_current_page(vis_tabnum)
def on_switch_to_tab1(self, _action):
self.switch_to_tab_num(0)
def on_switch_to_tab2(self, _action):
self.switch_to_tab_num(1)
def on_switch_to_tab3(self, _action):
self.switch_to_tab_num(2)
def on_switch_to_tab4(self, _action):
self.switch_to_tab_num(3)
def on_switch_to_tab5(self, _action):
self.switch_to_tab_num(4)
def switch_to_next_tab(self, _action):
self.notebook.next_page()
def switch_to_prev_tab(self, _action):
self.notebook.prev_page()
def on_volume_lower(self, _action):
new_volume = int(self.volumescale.get_adjustment().get_value()) - 5
if new_volume < 0:
new_volume = 0
self.volumescale.get_adjustment().set_value(new_volume)
self.on_volumescale_change(self.volumescale, 0, 0)
def on_volume_raise(self, _action):
new_volume = int(self.volumescale.get_adjustment().get_value()) + 5
if new_volume > 100:
new_volume = 100
self.volumescale.get_adjustment().set_value(new_volume)
self.on_volumescale_change(self.volumescale, 0, 0)
# Volume control
def on_volumebutton_clicked(self, _widget):
if not self.volumewindow.get_property('visible'):
x_win, y_win = self.volumebutton.window.get_origin()
button_rect = self.volumebutton.get_allocation()
x_coord, y_coord = button_rect.x + x_win, button_rect.y+y_win
width, height = button_rect.width, button_rect.height
self.volumewindow.set_size_request(width, -1)
self.volumewindow.move(x_coord, y_coord+height)
self.volumewindow.present()
else:
self.volume_hide()
def on_volumebutton_scroll(self, _widget, event):
if self.conn:
if event.direction == gtk.gdk.SCROLL_UP:
self.on_volume_raise(None)
elif event.direction == gtk.gdk.SCROLL_DOWN:
self.on_volume_lower(None)
def on_volumescale_scroll(self, _widget, event):
if event.direction == gtk.gdk.SCROLL_UP:
new_volume = int(self.volumescale.get_adjustment().get_value()) + 5
if new_volume > 100:
new_volume = 100
self.volumescale.get_adjustment().set_value(new_volume)
elif event.direction == gtk.gdk.SCROLL_DOWN:
new_volume = int(self.volumescale.get_adjustment().get_value()) - 5
if new_volume < 0:
new_volume = 0
self.volumescale.get_adjustment().set_value(new_volume)
def on_volumescale_change(self, obj, _value, _data):
new_volume = int(obj.get_adjustment().get_value())
mpdh.call(self.client, 'setvol', new_volume)
self.iterate_now()
def volume_hide(self):
self.volumebutton.set_active(False)
if self.volumewindow.get_property('visible'):
self.volumewindow.hide()
def mpd_pp(self, _widget, _key=None):
if self.conn and self.status:
if self.status['state'] in ('stop', 'pause'):
mpdh.call(self.client, 'play')
elif self.status['state'] == 'play':
mpdh.call(self.client, 'pause', '1')
self.iterate_now()
def mpd_stop(self, _widget, _key=None):
if self.conn:
mpdh.call(self.client, 'stop')
self.iterate_now()
def mpd_prev(self, _widget, _key=None):
if self.conn:
mpdh.call(self.client, 'previous')
self.iterate_now()
def mpd_next(self, _widget, _key=None):
if self.conn:
mpdh.call(self.client, 'next')
self.iterate_now()
def on_remove(self, _widget):
if self.conn:
model = None
while gtk.events_pending():
gtk.main_iteration()
if self.current_tab == self.TAB_CURRENT:
self.current.on_remove()
elif self.current_tab == self.TAB_PLAYLISTS:
treeviewsel = self.playlists_selection
model, selected = treeviewsel.get_selected_rows()
if ui.show_msg(self.window, gettext.ngettext("Delete the selected playlist?", "Delete the selected playlists?", int(len(selected))), gettext.ngettext("Delete Playlist", "Delete Playlists", int(len(selected))), 'deletePlaylist', gtk.BUTTONS_YES_NO) == gtk.RESPONSE_YES:
iters = [model.get_iter(path) for path in selected]
for i in iters:
mpdh.call(self.client, 'rm', misc.unescape_html(self.playlistsdata.get_value(i, 1)))
self.playlists.populate()
elif self.current_tab == self.TAB_STREAMS:
treeviewsel = self.streams_selection
model, selected = treeviewsel.get_selected_rows()
if ui.show_msg(self.window, gettext.ngettext("Delete the selected stream?", "Delete the selected streams?", int(len(selected))), gettext.ngettext("Delete Stream", "Delete Streams", int(len(selected))), 'deleteStreams', gtk.BUTTONS_YES_NO) == gtk.RESPONSE_YES:
iters = [model.get_iter(path) for path in selected]
for i in iters:
stream_removed = False
for j in range(len(self.config.stream_names)):
if not stream_removed:
if self.streamsdata.get_value(i, 1) == misc.escape_html(self.config.stream_names[j]):
self.config.stream_names.pop(j)
self.config.stream_uris.pop(j)
stream_removed = True
self.streams.populate()
self.iterate_now()
# Attempt to retain selection in the vicinity..
if model and len(model) > 0:
try:
# Use top row in selection...
selrow = 999999
for row in selected:
if row[0] < selrow:
selrow = row[0]
if selrow >= len(model):
selrow = len(model)-1
treeviewsel.select_path(selrow)
except:
pass
def mpd_clear(self, _widget):
if self.conn:
mpdh.call(self.client, 'clear')
self.iterate_now()
def on_repeat_clicked(self, widget):
if self.conn:
if widget.get_active():
mpdh.call(self.client, 'repeat', 1)
else:
mpdh.call(self.client, 'repeat', 0)
def on_random_clicked(self, widget):
if self.conn:
if widget.get_active():
mpdh.call(self.client, 'random', 1)
else:
mpdh.call(self.client, 'random', 0)
def on_prefs(self, _widget):
trayicon_available = HAVE_EGG or HAVE_STATUS_ICON
trayicon_in_use = ((HAVE_STATUS_ICON and self.statusicon.is_embedded() and
self.statusicon.get_visible())
or (HAVE_EGG and self.trayicon.get_property('visible')))
self.preferences.on_prefs_real(self.window, self.popuptimes, self.scrobbler, trayicon_available, trayicon_in_use, self.on_connectkey_pressed, self.on_currsong_notify, self.update_infofile, self.prefs_notif_toggled, self.prefs_stylized_toggled, self.prefs_art_toggled, self.prefs_playback_toggled, self.prefs_progress_toggled, self.prefs_statusbar_toggled, self.prefs_lyrics_toggled, self.prefs_trayicon_toggled, self.prefs_crossfade_toggled, self.prefs_crossfade_changed, self.prefs_window_response, self.prefs_last_tab)
# XXX move the prefs handling parts of prefs_* to preferences.py
def prefs_window_response(self, window, response, prefsnotebook, direntry, currentoptions, libraryoptions, titleoptions, currsongoptions1, currsongoptions2, infopath_options, using_mpd_env_vars, prev_host, prev_port, prev_password):
if response == gtk.RESPONSE_CLOSE:
self.prefs_last_tab = prefsnotebook.get_current_page()
if self.config.show_lyrics and self.config.lyrics_location != consts.LYRICS_LOCATION_HOME:
if not os.path.isdir(misc.file_from_utf8(self.config.musicdir[self.config.profile_num])):
ui.show_msg(self.window, _("To save lyrics to the music file's directory, you must specify a valid music directory."), _("Music Dir Verification"), 'musicdirVerificationError', gtk.BUTTONS_CLOSE)
# Set music_dir entry focused:
prefsnotebook.set_current_page(0)
direntry.grab_focus()
return
if self.config.show_covers and self.config.art_location != consts.ART_LOCATION_HOMECOVERS:
if not os.path.isdir(misc.file_from_utf8(self.config.musicdir[self.config.profile_num])):
ui.show_msg(self.window, _("To save artwork to the music file's directory, you must specify a valid music directory."), _("Music Dir Verification"), 'musicdirVerificationError', gtk.BUTTONS_CLOSE)
# Set music_dir entry focused:
prefsnotebook.set_current_page(0)
direntry.grab_focus()
return
if self.config.currentformat != currentoptions.get_text():
self.config.currentformat = currentoptions.get_text()
for column in self.current_treeview.get_columns():
self.current_treeview.remove_column(column)
self.current.initialize_columns()
self.current.update_format()
if self.config.libraryformat != libraryoptions.get_text():
self.config.libraryformat = libraryoptions.get_text()
self.library.library_browse(root=self.config.wd)
if self.config.titleformat != titleoptions.get_text():
self.config.titleformat = titleoptions.get_text()
self.update_wintitle()
if (self.config.currsongformat1 != currsongoptions1.get_text()) or (self.config.currsongformat2 != currsongoptions2.get_text()):
self.config.currsongformat1 = currsongoptions1.get_text()
self.config.currsongformat2 = currsongoptions2.get_text()
self.update_cursong()
if self.window_owner:
if self.config.ontop:
self.window.set_keep_above(True)
else:
self.window.set_keep_above(False)
if self.config.sticky:
self.window.stick()
else:
self.window.unstick()
if self.config.decorated != self.window.get_decorated():
self.withdraw_app()
self.window.set_decorated(self.config.decorated)
self.withdraw_app_undo()
if self.config.infofile_path != infopath_options.get_text():
self.config.infofile_path = os.path.expanduser(infopath_options.get_text())
if self.config.use_infofile: self.update_infofile()
if not using_mpd_env_vars:
if prev_host != self.config.host[self.config.profile_num] or prev_port != self.config.port[self.config.profile_num] or prev_password != self.config.password[self.config.profile_num]:
# Try to connect if mpd connection info has been updated:
ui.change_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
self.mpd_connect(force=True)
if self.config.as_enabled:
gobject.idle_add(self.scrobbler.init)
self.settings_save()
self.populate_profiles_for_menu()
ui.change_cursor(None)
window.destroy()
def prefs_crossfade_changed(self, crossfade_spin):
crossfade_value = crossfade_spin.get_value_as_int()
mpdh.call(self.client, 'crossfade', crossfade_value)
def prefs_crossfade_toggled(self, button, crossfade_spin):
crossfade_value = crossfade_spin.get_value_as_int()
if button.get_active():
mpdh.call(self.client, 'crossfade', crossfade_value)
else:
mpdh.call(self.client, 'crossfade', 0)
def prefs_playback_toggled(self, button):
self.config.show_playback = button.get_active()
func = 'show' if self.config.show_playback else 'hide'
for widget in [self.prevbutton, self.ppbutton, self.stopbutton, self.nextbutton, self.volumebutton]:
getattr(ui, func)(widget)
def prefs_progress_toggled(self, button):
self.config.show_progress = button.get_active()
func = 'show' if self.config.show_progress else 'hide'
for widget in [self.progressbox, self.trayprogressbar]:
getattr(ui,func)(widget)
def prefs_art_toggled(self, button, art_hbox1, art_hbox2, art_stylized):
button_active = button.get_active()
art_hbox1.set_sensitive(button_active)
art_hbox2.set_sensitive(button_active)
art_stylized.set_sensitive(button_active)
if button_active:
self.traytips.set_size_request(self.notification_width, -1)
self.artwork.artwork_set_default_icon()
for widget in [self.imageeventbox, self.info_imagebox, self.trayalbumeventbox, self.trayalbumimage2]:
widget.set_no_show_all(False)
if widget in [self.trayalbumeventbox, self.trayalbumimage2]:
if self.conn and self.status and self.status['state'] in ['play', 'pause']:
widget.show_all()
else:
widget.show_all()
self.config.show_covers = True
self.update_cursong()
self.artwork.artwork_update()
else:
self.traytips.set_size_request(self.notification_width-100, -1)
for widget in [self.imageeventbox, self.info_imagebox, self.trayalbumeventbox, self.trayalbumimage2]:
ui.hide(widget)
self.config.show_covers = False
self.update_cursong()
# Force a resize of the info labels, if needed:
gobject.idle_add(self.on_notebook_resize, self.notebook, None)
def prefs_stylized_toggled(self, button):
self.config.covers_type = button.get_active()
self.artwork.artwork_update(True)
def prefs_lyrics_toggled(self, button, lyrics_hbox):
self.config.show_lyrics = button.get_active()
lyrics_hbox.set_sensitive(self.config.show_lyrics)
self.info.show_lyrics_updated()
if self.config.show_lyrics:
self.info_update(True)
def prefs_statusbar_toggled(self, button):
self.config.show_statusbar = button.get_active()
if self.config.show_statusbar:
self.statusbar.set_no_show_all(False)
if self.config.expanded:
self.statusbar.show_all()
else:
ui.hide(self.statusbar)
self.update_statusbar()
def prefs_notif_toggled(self, button, notifhbox):
self.config.show_notification = button.get_active()
notifhbox.set_sensitive(self.config.show_notification)
if self.config.show_notification:
self.on_currsong_notify()
else:
try:
gobject.source_remove(self.traytips.notif_handler)
except:
pass
self.traytips.hide()
def prefs_trayicon_toggled(self, button, minimize):
# Note that we update the sensitivity of the minimize
# CheckButton to reflect if the trayicon is visible.
if button.get_active():
self.config.show_trayicon = True
if HAVE_STATUS_ICON:
self.statusicon.set_visible(True)
if self.statusicon.is_embedded() or self.statusicon.get_visible():
minimize.set_sensitive(True)
elif HAVE_EGG:
self.trayicon.show_all()
if self.trayicon.get_property('visible'):
minimize.set_sensitive(True)
else:
self.config.show_trayicon = False
minimize.set_sensitive(False)
if HAVE_STATUS_ICON:
self.statusicon.set_visible(False)
elif HAVE_EGG:
self.trayicon.hide_all()
def seek(self, song, seektime):
mpdh.call(self.client, 'seek', song, seektime)
self.iterate_now()
def on_link_click(self, type):
browser_not_loaded = False
if type == 'artist':
browser_not_loaded = not misc.browser_load("http://www.wikipedia.org/wiki/Special:Search/" + urllib.quote(mpdh.get(self.songinfo, 'artist')), self.config.url_browser, self.window)
elif type == 'album':
browser_not_loaded = not misc.browser_load("http://www.wikipedia.org/wiki/Special:Search/" + urllib.quote(mpdh.get(self.songinfo, 'album')), self.config.url_browser, self.window)
elif type == 'edit':
if self.songinfo:
self.on_tags_edit(None)
elif type == 'search':
self.on_lyrics_search(None)
elif type == 'editlyrics':
browser_not_loaded = not misc.browser_load(self.info.lyricwiki_editlink(self.songinfo), self.config.url_browser, self.window)
if browser_not_loaded:
ui.show_msg(self.window, _('Unable to launch a suitable browser.'), _('Launch Browser'), 'browserLoadError', gtk.BUTTONS_CLOSE)
def on_tab_click(self, _widget, event):
if event.button == 3:
self.notebookmenu.popup(None, None, None, event.button, event.time)
return True
def on_notebook_click(self, _widget, event):
if event.button == 1:
self.volume_hide()
def notebook_get_tab_num(self, notebook, tabname):
for tab in range(notebook.get_n_pages()):
if self.notebook_get_tab_text(self.notebook, tab) == tabname:
return tab
def notebook_tab_is_visible(self, notebook, tabname):
tab = self.notebook.get_children()[self.notebook_get_tab_num(notebook, tabname)]
if tab.get_property('visible'):
return True
else:
return False
def notebook_get_visible_tab_num(self, notebook, tab_num):
# Get actual tab number for visible tab_num. If there is not
# a visible tab for tab_num, return -1.\
curr_tab = -1
for tab in range(notebook.get_n_pages()):
if notebook.get_children()[tab].get_property('visible'):
curr_tab += 1
if curr_tab == tab_num:
return tab
return -1
def notebook_get_tab_text(self, notebook, tab_num):
tab = notebook.get_children()[tab_num]
return notebook.get_tab_label(tab).get_child().get_children()[1].get_text()
def on_notebook_page_change(self, _notebook, _page, page_num):
self.current_tab = self.notebook_get_tab_text(self.notebook, page_num)
to_focus = self.tabname2focus.get(self.current_tab, None)
if to_focus:
gobject.idle_add(ui.focus, to_focus)
gobject.idle_add(self.update_menu_visibility)
if not self.img_clicked:
self.last_tab = self.current_tab
def on_library_search_text_click(self, _widget, event):
if event.button == 1:
self.volume_hide()
def on_window_click(self, _widget, event):
if event.button == 1:
self.volume_hide()
elif event.button == 3:
self.menu_popup(self.window, event)
def menu_popup(self, widget, event):
if widget == self.window:
if event.get_coords()[1] > self.notebook.get_allocation()[1]:
return
if event.button == 3:
self.update_menu_visibility(True)
gobject.idle_add(self.mainmenu.popup, None, None, None, event.button, event.time)
def on_tab_toggle(self, toggleAction):
name = toggleAction.get_name()
if not toggleAction.get_active():
# Make sure we aren't hiding the last visible tab:
num_tabs_vis = 0
for tab in self.notebook.get_children():
if tab.get_property('visible'):
num_tabs_vis += 1
if num_tabs_vis == 1:
# Keep menu item checking and exit..
toggleAction.set_active(True)
return
# Store value:
if name == self.TAB_CURRENT:
self.config.current_tab_visible = toggleAction.get_active()
elif name == self.TAB_LIBRARY:
self.config.library_tab_visible = toggleAction.get_active()
elif name == self.TAB_PLAYLISTS:
self.config.playlists_tab_visible = toggleAction.get_active()
elif name == self.TAB_STREAMS:
self.config.streams_tab_visible = toggleAction.get_active()
elif name == self.TAB_INFO:
self.config.info_tab_visible = toggleAction.get_active()
# Hide/show:
tabnum = self.notebook_get_tab_num(self.notebook, name)
if toggleAction.get_active():
ui.show(self.notebook.get_children()[tabnum])
else:
ui.hide(self.notebook.get_children()[tabnum])
def on_library_search_shortcut(self, _event):
# Ensure library tab is visible
if not self.notebook_tab_is_visible(self.notebook, self.TAB_LIBRARY):
return
if self.current_tab != self.TAB_LIBRARY:
self.switch_to_tab_name(self.TAB_LIBRARY)
if self.library.search_visible():
self.library.on_search_end(None)
self.library.libsearchfilter_set_focus()
def update_menu_visibility(self, show_songinfo_only=False):
if show_songinfo_only or not self.config.expanded:
for menu in ['add', 'replace', 'playafter', 'rename', 'rm', 'pl', \
'remove', 'clear', 'update', 'new', 'edit', 'sort', 'tag']:
self.UIManager.get_widget('/mainmenu/' + menu + 'menu/').hide()
return
elif self.current_tab == self.TAB_CURRENT:
if len(self.currentdata) > 0:
if self.current_selection.count_selected_rows() > 0:
for menu in ['remove', 'tag']:
self.UIManager.get_widget('/mainmenu/' + menu + 'menu/').show()
else:
for menu in ['remove', 'tag']:
self.UIManager.get_widget('/mainmenu/' + menu + 'menu/').hide()
if not self.current.filterbox_visible:
for menu in ['clear', 'pl', 'sort']:
self.UIManager.get_widget('/mainmenu/' + menu + 'menu/').show()
else:
for menu in ['clear', 'pl', 'sort']:
self.UIManager.get_widget('/mainmenu/' + menu + 'menu/').hide()
else:
for menu in ['clear', 'pl', 'sort', 'remove', 'tag']:
self.UIManager.get_widget('/mainmenu/' + menu + 'menu/').hide()
for menu in ['add', 'replace', 'playafter', 'rename', 'rm', \
'update', 'new', 'edit']:
self.UIManager.get_widget('/mainmenu/' + menu + 'menu/').hide()
elif self.current_tab == self.TAB_LIBRARY:
if len(self.librarydata) > 0:
if self.library_selection.count_selected_rows() > 0:
for menu in ['add', 'replace', 'playafter', 'tag']:
self.UIManager.get_widget('/mainmenu/' + menu + 'menu/').show()
self.UIManager.get_widget('/mainmenu/updatemenu/updateselectedmenu/').show()
else:
for menu in ['add', 'replace', 'playafter', 'tag']:
self.UIManager.get_widget('/mainmenu/' + menu + 'menu/').hide()
self.UIManager.get_widget('/mainmenu/updatemenu/updateselectedmenu/').hide()
else:
for menu in ['add', 'replace', 'playafter', 'tag', 'update']:
self.UIManager.get_widget('/mainmenu/' + menu + 'menu/').hide()
for menu in ['remove', 'clear', 'pl', 'rename', 'rm', 'new', 'edit', 'sort']:
self.UIManager.get_widget('/mainmenu/' + menu + 'menu/').hide()
if self.library.search_visible():
self.UIManager.get_widget('/mainmenu/updatemenu/').hide()
else:
self.UIManager.get_widget('/mainmenu/updatemenu/').show()
self.UIManager.get_widget('/mainmenu/updatemenu/updatefullmenu/').show()
elif self.current_tab == self.TAB_PLAYLISTS:
if self.playlists_selection.count_selected_rows() > 0:
for menu in ['add', 'replace', 'playafter', 'rm']:
self.UIManager.get_widget('/mainmenu/' + menu + 'menu/').show()
if self.playlists_selection.count_selected_rows() == 1 and mpdh.mpd_major_version(self.client) >= 0.13:
self.UIManager.get_widget('/mainmenu/renamemenu/').show()
else:
self.UIManager.get_widget('/mainmenu/renamemenu/').hide()
else:
for menu in ['add', 'replace', 'playafter', 'rm', 'rename']:
self.UIManager.get_widget('/mainmenu/' + menu + 'menu/').hide()
for menu in ['remove', 'clear', 'pl', 'update', 'new', 'edit', 'sort', 'tag']:
self.UIManager.get_widget('/mainmenu/' + menu + 'menu/').hide()
elif self.current_tab == self.TAB_STREAMS:
self.UIManager.get_widget('/mainmenu/newmenu/').show()
if self.streams_selection.count_selected_rows() > 0:
if self.streams_selection.count_selected_rows() == 1:
self.UIManager.get_widget('/mainmenu/editmenu/').show()
else:
self.UIManager.get_widget('/mainmenu/editmenu/').hide()
for menu in ['add', 'replace', 'playafter', 'rm']:
self.UIManager.get_widget('/mainmenu/' + menu + 'menu/').show()
else:
for menu in ['add', 'replace', 'playafter', 'rm']:
self.UIManager.get_widget('/mainmenu/' + menu + 'menu/').hide()
for menu in ['rename', 'remove', 'clear', 'pl', 'update', 'sort', 'tag']:
self.UIManager.get_widget('/mainmenu/' + menu + 'menu/').hide()
def find_path(self, filename):
full_filename = None
if HAVE_SUGAR:
full_filename = os.path.join(activity.get_bundle_path(), 'share', filename)
else:
if os.path.exists(os.path.join(os.path.split(__file__)[0], filename)):
full_filename = os.path.join(os.path.split(__file__)[0], filename)
elif os.path.exists(os.path.join(os.path.split(__file__)[0], 'pixmaps', filename)):
full_filename = os.path.join(os.path.split(__file__)[0], 'pixmaps', filename)
elif os.path.exists(os.path.join(os.path.split(__file__)[0], 'share', filename)):
full_filename = os.path.join(os.path.split(__file__)[0], 'share', filename)
elif os.path.exists(os.path.join(__file__.split('/lib')[0], 'share', 'pixmaps', filename)):
full_filename = os.path.join(__file__.split('/lib')[0], 'share', 'pixmaps', filename)
elif os.path.exists(os.path.join(sys.prefix, 'share', 'pixmaps', filename)):
full_filename = os.path.join(sys.prefix, 'share', 'pixmaps', filename)
if not full_filename:
print filename + " cannot be found. Aborting..."
sys.exit(1)
return full_filename
def on_tags_edit(self, _widget):
ui.change_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
while gtk.events_pending():
gtk.main_iteration()
files = []
temp_mpdpaths = []
if self.current_tab == self.TAB_INFO:
if self.status and self.status['state'] in ['play', 'pause']:
# Use current file in songinfo:
mpdpath = mpdh.get(self.songinfo, 'file')
fullpath = self.config.musicdir[self.config.profile_num] + mpdpath
files.append(fullpath)
temp_mpdpaths.append(mpdpath)
elif self.current_tab == self.TAB_LIBRARY:
# Populates files array with selected library items:
items = self.library.get_path_child_filenames(False)
for item in items:
files.append(self.config.musicdir[self.config.profile_num] + item)
temp_mpdpaths.append(item)
elif self.current_tab == self.TAB_CURRENT:
# Populates files array with selected current playlist items:
temp_mpdpaths = self.current.get_selected_filenames(False)
files = self.current.get_selected_filenames(True)
tageditor = tagedit.TagEditor(self.window, self.tags_mpd_update, self.tags_set_use_mpdpath)
tageditor.set_use_mpdpaths(self.config.tags_use_mpdpath)
tageditor.on_tags_edit(files, temp_mpdpaths, self.config.musicdir[self.config.profile_num])
def tags_set_use_mpdpath(self, use_mpdpath):
self.config.tags_use_mpdpath = use_mpdpath
def tags_mpd_update(self, tag_paths):
mpdh.update(self.client, list(tag_paths), self.status)
self.mpd_update_queued = True
def on_about(self, _action):
about_dialog = about.About(self.window, self.config, version.VERSION, __license__, self.find_path('sonata_large.png'))
statslabel = None
if self.conn:
# Include MPD stats:
stats = mpdh.call(self.client, 'stats')
statslabel = stats['songs'] + ' ' + gettext.ngettext('song', 'songs', int(stats['songs'])) + '.\n'
statslabel = statslabel + stats['albums'] + ' ' + gettext.ngettext('album', 'albums', int(stats['albums'])) + '.\n'
statslabel = statslabel + stats['artists'] + ' ' + gettext.ngettext('artist', 'artists', int(stats['artists'])) + '.\n'
try:
hours_of_playtime = misc.convert_time(float(stats['db_playtime'])).split(':')[-3]
except:
hours_of_playtime = '0'
if int(hours_of_playtime) >= 24:
days_of_playtime = str(int(hours_of_playtime)/24)
statslabel = statslabel + days_of_playtime + ' ' + gettext.ngettext('day of bliss', 'days of bliss', int(days_of_playtime)) + '.'
else:
statslabel = statslabel + hours_of_playtime + ' ' + gettext.ngettext('hour of bliss', 'hours of bliss', int(hours_of_playtime)) + '.'
about_dialog.about_load(statslabel)
def systemtray_initialize(self):
# Make system tray 'icon' to sit in the system tray
if HAVE_STATUS_ICON:
self.statusicon = gtk.StatusIcon()
self.statusicon.set_from_file(self.find_path('sonata.png'))
self.statusicon.set_visible(self.config.show_trayicon)
self.statusicon.connect('popup_menu', self.systemtray_menu)
self.statusicon.connect('activate', self.systemtray_activate)
elif HAVE_EGG:
self.trayimage = ui.image()
self.trayeventbox = ui.eventbox(add=self.trayimage)
self.trayeventbox.connect('button_press_event', self.systemtray_click)
self.trayeventbox.connect('scroll-event', self.systemtray_scroll)
self.trayeventbox.connect('size-allocate', self.systemtray_size)
self.traytips.set_tip(self.trayeventbox)
try:
self.trayicon = egg.trayicon.TrayIcon("TrayIcon")
self.trayicon.add(self.trayeventbox)
if self.config.show_trayicon:
self.trayicon.show_all()
self.eggtrayfile = self.find_path('sonata.png')
self.trayimage.set_from_pixbuf(img.get_pixbuf_of_size(gtk.gdk.pixbuf_new_from_file(self.eggtrayfile), self.eggtrayheight)[0])
else:
self.trayicon.hide_all()
except:
pass
def dbus_show(self):
self.window.hide()
self.withdraw_app_undo()
def dbus_toggle(self):
if self.window.get_property('visible'):
self.withdraw_app()
else:
self.withdraw_app_undo()
def dbus_popup(self):
self.on_currsong_notify(force_popup=True)
def main(self):
gtk.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment