Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
# -*- coding: utf-8 -*-
#
# This file is part of Panucci.
# Copyright (c) 2008-2011 The Panucci Project
#
# Panucci 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.
#
# Panucci 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 Panucci. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import
import logging
import os.path
import cgi
import gtk
import gobject
import pango
import panucci
from panucci import util
from panucci import platform
from panucci import playlist
from panucci.dbusinterface import interface
from panucci.services import ObservableService
from panucci.gtkui import gtkwidgets as widgets
from panucci.gtkui import gtkplaylist
from panucci.gtkui import gtkutil
try:
import pynotify
pynotify.init('Panucci')
have_pynotify = True
except:
have_pynotify = False
try:
import hildon
except:
if platform.MAEMO:
log = logging.getLogger('panucci.panucci')
log.critical( 'Using GTK widgets, install "python2.5-hildon" '
'for this to work properly.' )
if platform.FREMANTLE:
# Workaround Maemo bug 6694 (Playback in Silent mode)
gobject.set_application_name('FMRadio')
gtk.icon_size_register('panucci-button', 32, 32)
##################################################
# PanucciGUI
##################################################
class PanucciGUI(object):
""" The object that holds the entire panucci gui """
def __init__(self, settings, filename=None):
self.__log = logging.getLogger('panucci.panucci.PanucciGUI')
interface.register_gui(self)
self.config = settings.config
self.playlist = playlist.Playlist(self.config)
# Build the base ui (window and menubar)
if platform.MAEMO:
self.app = hildon.Program()
if platform.FREMANTLE:
window = hildon.StackableWindow()
else:
window = hildon.Window()
self.app.add_window(window)
else:
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.main_window = window
window.set_title('Panucci')
self.window_icon = util.find_data_file('panucci.png')
if self.window_icon is not None:
window.set_icon_from_file( self.window_icon )
window.set_default_size(400, -1)
window.set_border_width(0)
window.connect("destroy", self.destroy)
# Add the tabs (they are private to prevent us from trying to do
# something like gui_root.player_tab.some_function() from inside
# playlist_tab or vice-versa)
self.__player_tab = PlayerTab(self)
self.__playlist_tab = gtkplaylist.PlaylistTab(self, self.playlist)
self.create_actions()
if platform.FREMANTLE:
self.playlist_window = hildon.StackableWindow()
self.playlist_window.set_app_menu(self.create_playlist_app_menu())
else:
self.playlist_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.playlist_window.connect('delete-event', gtk.Widget.hide_on_delete)
self.playlist_window.set_title(_('Playlist'))
self.playlist_window.set_transient_for(self.main_window)
self.playlist_window.add(self.__playlist_tab)
if platform.MAEMO:
if platform.FREMANTLE:
window.set_app_menu(self.create_app_menu())
else:
window.set_menu(self.create_menu())
window.add(self.__player_tab)
else:
menu_vbox = gtk.VBox()
menu_vbox.set_spacing(0)
window.add(menu_vbox)
menu_bar = gtk.MenuBar()
self.create_desktop_menu(menu_bar)
menu_vbox.pack_start(menu_bar, False, False, 0)
menu_bar.show()
menu_vbox.pack_end(self.__player_tab, True, True, 6)
# Tie it all together!
self.__ignore_queue_check = False
self.__window_fullscreen = False
if platform.MAEMO and interface.headset_device:
# Enable play/pause with headset button
import dbus
interface.headset_device.connect_to_signal('Condition', \
self.handle_headset_button)
system_bus = dbus.SystemBus()
PATH = '/org/freedesktop/Hal/devices/computer_logicaldev_input_1'
# Monitor connection state of BT headset
system_bus.add_signal_receiver(self.handle_connection_state, 'DeviceAdded', \
'org.freedesktop.Hal.Manager', None, '/org/freedesktop/Hal/Manager')
# Monitor BT headset buttons
system_bus.add_signal_receiver(self.handle_bt_button, 'Condition', \
'org.freedesktop.Hal.Device', None, PATH)
self.main_window.connect('key-press-event', self.on_key_press)
self.playlist.register( 'file_queued', self.on_file_queued )
self.playlist.register( 'playlist-to-be-overwritten',
self.check_queue )
self.__player_tab.register( 'select-current-item-request',
self.__select_current_item )
self.main_window.show_all()
# this should be done when the gui is ready
self.playlist.init(filepath=filename)
pos_int, dur_int = self.playlist.get_position_duration()
# This prevents bogus values from being set while seeking
if (pos_int > 10**9) and (dur_int > 10**9):
self.__player_tab.set_progress_callback(pos_int, dur_int)
gtk.main()
def create_actions(self):
# File menu
self.action_open = gtk.Action('open_file', _('Add File'), _('Open a file or playlist'), gtk.STOCK_NEW)
self.action_open.connect('activate', self.open_file_callback)
self.action_open_dir = gtk.Action('open_dir', _('Add Folder'), _('Open a directory'), gtk.STOCK_OPEN)
self.action_open_dir.connect('activate', self.open_dir_callback)
self.action_play_one = gtk.Action('play_one', _('Play One'), _('Play one file'), gtk.STOCK_FILE)
self.action_play_one.connect('activate', self.play_one_callback)
self.action_save = gtk.Action('save', _('Save Playlist'), _('Save current playlist to file'), gtk.STOCK_SAVE_AS)
self.action_save.connect('activate', self.save_to_playlist_callback)
self.action_empty_playlist = gtk.Action('empty_playlist', _('Clear Playlist'), _('Clear current playlist'), gtk.STOCK_DELETE)
self.action_empty_playlist.connect('activate', self.empty_playlist_callback)
self.action_delete_bookmarks = gtk.Action('delete_bookmarks', _('Delete All Bookmarks'), _('Deleting all bookmarks'), gtk.STOCK_DELETE)
self.action_delete_bookmarks.connect('activate', self.delete_all_bookmarks_callback)
self.action_quit = gtk.Action('quit', _('Quit'), _('Close Panucci'), gtk.STOCK_QUIT)
self.action_quit.connect('activate', self.destroy)
# Tools menu
self.action_playlist = gtk.Action('playlist', _('Playlist'), _('Open the current playlist'), None)
self.action_playlist.connect('activate', lambda a: self.playlist_window.show())
self.action_settings = gtk.Action('settings', _('Settings'), _('Open the settings dialog'), None)
self.action_settings.connect('activate', self.settings_callback)
self.action_timer = gtk.Action('timer', _('Sleep Timer'), _('Start a timed shutdown'), None)
self.action_timer.connect('activate', self.create_timer_dialog)
self.action_fm = gtk.Action('fm', _('FM Transmitter'), _('Show FM transmitter dialog'), None)
self.action_fm.connect('activate', self.show_fm_transmitter)
# Settings menu
self.action_lock_progress = gtk.ToggleAction('lock_progress', _('Lock Progress Bar'), None, None)
self.action_lock_progress.connect("activate", self.set_boolean_config_callback)
self.action_lock_progress.set_active(self.config.getboolean("options", "lock_progress"))
self.action_dual_action_button = gtk.ToggleAction('dual_action_button', _('Dual Action Button'), None, None)
self.action_dual_action_button.connect("activate", self.set_boolean_config_callback)
self.action_dual_action_button.set_active(self.config.getboolean("options", "dual_action_button"))
self.action_stay_at_end = gtk.ToggleAction('stay_at_end', _('Stay at End'), None, None)
self.action_stay_at_end.connect("activate", self.set_boolean_config_callback)
self.action_stay_at_end.set_active(self.config.getboolean("options", "stay_at_end"))
self.action_seek_back = gtk.ToggleAction('seek_back', _('Seek Back'), None, None)
self.action_seek_back.connect("activate", self.set_boolean_config_callback)
self.action_seek_back.set_active(self.config.getboolean("options", "seek_back"))
self.action_scrolling_labels = gtk.ToggleAction('scrolling_labels', _('Scrolling Labels'), None, None)
self.action_scrolling_labels.connect("activate", self.scrolling_labels_callback)
self.action_scrolling_labels.set_active(self.config.getboolean("options", "scrolling_labels"))
self.action_resume_all = gtk.ToggleAction('resume_all', _('Resume All'), None, None)
self.action_resume_all.connect("activate", self.resume_all_callback)
self.action_resume_all.set_active(self.config.getboolean("options", "resume_all"))
self.action_play_on_headset = gtk.ToggleAction('play_on_headset', _('Play on Headset'), None, None)
self.action_play_on_headset.connect("activate", self.set_boolean_config_callback)
self.action_play_on_headset.set_active(self.config.getboolean("options", "play_on_headset"))
self.action_play_mode = gtk.Action('play_mode', _('Play Mode'), None, None)
self.action_play_mode_all = gtk.RadioAction('all', _('All'), None, None, 0)
self.action_play_mode_all.connect("activate", self.set_play_mode_callback)
self.action_play_mode_single = gtk.RadioAction('single', _('Single'), None, None, 1)
self.action_play_mode_single.connect("activate", self.set_play_mode_callback)
self.action_play_mode_single.set_group(self.action_play_mode_all)
self.action_play_mode_random = gtk.RadioAction('random', _('Random'), None, None, 1)
self.action_play_mode_random.connect("activate", self.set_play_mode_callback)
self.action_play_mode_random.set_group(self.action_play_mode_all)
self.action_play_mode_repeat = gtk.RadioAction('repeat', _('Repeat'), None, None, 1)
self.action_play_mode_repeat.connect("activate", self.set_play_mode_callback)
self.action_play_mode_repeat.set_group(self.action_play_mode_all)
if self.config.get("options", "play_mode") == "single":
self.action_play_mode_single.set_active(True)
elif self.config.get("options", "play_mode") == "random":
self.action_play_mode_random.set_active(True)
elif self.config.get("options", "play_mode") == "repeat":
self.action_play_mode_repeat.set_active(True)
else:
self.action_play_mode_all.set_active(True)
# Help menu
self.action_about = gtk.Action('about', _('About'), _('Show application version'), gtk.STOCK_ABOUT)
self.action_about.connect('activate', self.about_callback)
def create_desktop_menu(self, menu_bar):
file_menu_item = gtk.MenuItem(_('File'))
file_menu = gtk.Menu()
file_menu.append(self.action_open.create_menu_item())
file_menu.append(self.action_open_dir.create_menu_item())
file_menu.append(self.action_play_one.create_menu_item())
file_menu.append(self.action_save.create_menu_item())
file_menu.append(self.action_empty_playlist.create_menu_item())
file_menu.append(self.action_delete_bookmarks.create_menu_item())
file_menu.append(gtk.SeparatorMenuItem())
file_menu.append(self.action_quit.create_menu_item())
file_menu_item.set_submenu(file_menu)
menu_bar.append(file_menu_item)
tools_menu_item = gtk.MenuItem(_('Tools'))
tools_menu = gtk.Menu()
tools_menu.append(self.action_playlist.create_menu_item())
tools_menu.append(self.action_timer.create_menu_item())
#tools_menu.append(self.action_fm.create_menu_item())
tools_menu_item.set_submenu(tools_menu)
menu_bar.append(tools_menu_item)
settings_menu_item = gtk.MenuItem(_('Settings'))
settings_menu = gtk.Menu()
settings_menu.append(self.action_lock_progress.create_menu_item())
settings_menu.append(self.action_dual_action_button.create_menu_item())
settings_menu.append(self.action_stay_at_end.create_menu_item())
settings_menu.append(self.action_seek_back.create_menu_item())
settings_menu.append(self.action_scrolling_labels.create_menu_item())
settings_menu.append(self.action_resume_all.create_menu_item())
play_mode_menu_item = self.action_play_mode.create_menu_item()
settings_menu.append(play_mode_menu_item)
play_mode_menu = gtk.Menu()
play_mode_menu_item.set_submenu(play_mode_menu)
play_mode_menu.append(self.action_play_mode_all.create_menu_item())
play_mode_menu.append(self.action_play_mode_single.create_menu_item())
play_mode_menu.append(self.action_play_mode_random.create_menu_item())
play_mode_menu.append(self.action_play_mode_repeat.create_menu_item())
settings_menu_item.set_submenu(settings_menu)
menu_bar.append(settings_menu_item)
help_menu_item = gtk.MenuItem(_('Help'))
help_menu = gtk.Menu()
help_menu.append(self.action_about.create_menu_item())
help_menu_item.set_submenu(help_menu)
menu_bar.append(help_menu_item)
def create_playlist_app_menu(self):
menu = hildon.AppMenu()
for action in (self.action_save,
self.action_delete_bookmarks):
b = gtk.Button()
action.connect_proxy(b)
menu.append(b)
menu.show_all()
return menu
def create_app_menu(self):
menu = hildon.AppMenu()
for action in (self.action_settings,
self.action_playlist,
self.action_open,
self.action_open_dir,
self.action_play_one,
self.action_empty_playlist,
self.action_timer,
self.action_fm,
self.action_about):
b = gtk.Button()
action.connect_proxy(b)
menu.append(b)
menu.show_all()
return menu
def create_menu(self):
# the main menu
menu = gtk.Menu()
menu_open = gtk.ImageMenuItem(_('Add File'))
menu_open.set_image(
gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU))
menu_open.connect("activate", self.open_file_callback)
menu.append(menu_open)
menu_open = gtk.ImageMenuItem(_('Add Folder'))
menu_open.set_image(
gtk.image_new_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_MENU))
menu_open.connect("activate", self.open_dir_callback)
menu.append(menu_open)
# the recent files menu
self.menu_recent = gtk.MenuItem(_('Open recent playlist'))
menu.append(self.menu_recent)
self.create_recent_files_menu()
menu.append(gtk.SeparatorMenuItem())
menu_save = gtk.ImageMenuItem(_('Save current playlist'))
menu_save.set_image(
gtk.image_new_from_stock(gtk.STOCK_SAVE_AS, gtk.ICON_SIZE_MENU))
menu_save.connect("activate", self.save_to_playlist_callback)
menu.append(menu_save)
menu_save = gtk.ImageMenuItem(_('Delete Playlist'))
menu_save.set_image(
gtk.image_new_from_stock(gtk.STOCK_DELETE, gtk.ICON_SIZE_MENU))
menu_save.connect("activate", self.empty_playlist_callback)
menu.append(menu_save)
menu_save = gtk.ImageMenuItem(_('Delete All Bookmarks'))
menu_save.set_image(
gtk.image_new_from_stock(gtk.STOCK_DELETE, gtk.ICON_SIZE_MENU))
menu_save.connect("activate", self.delete_all_bookmarks_callback)
menu.append(menu_save)
menu.append(gtk.SeparatorMenuItem())
# the settings sub-menu
menu_settings = gtk.MenuItem(_('Settings'))
menu.append(menu_settings)
menu_settings_sub = gtk.Menu()
menu_settings.set_submenu(menu_settings_sub)
menu_settings_enable_dual_action = gtk.CheckMenuItem(_('Enable dual-action buttons') )
menu_settings_enable_dual_action.connect('toggled', self.set_dual_action_button_callback)
menu_settings_enable_dual_action.set_active(self.config.getboolean("options", "dual_action_button"))
menu_settings_sub.append(menu_settings_enable_dual_action)
menu_settings_lock_progress = gtk.CheckMenuItem(_('Lock Progress Bar'))
menu_settings_lock_progress.connect('toggled', self.lock_progress_callback)
menu_settings_lock_progress.set_active(self.config.getboolean("options", "lock_progress"))
menu_settings_sub.append(menu_settings_lock_progress)
menu_about = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
menu_about.connect("activate", self.about_callback)
menu.append(menu_about)
menu.append(gtk.SeparatorMenuItem())
menu_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT)
menu_quit.connect("activate", self.destroy)
menu.append(menu_quit)
return menu
def create_timer_dialog(self,w):
response = widgets.IntDialog.get_int(_("Sleep Timer"), _("Shutdown time in minutes"), 5, 1)
if response[1]:
gobject.timeout_add(60000*response[0], self.timed_shutdown)
def timed_shutdown(self):
self.destroy( None )
return False
def create_recent_files_menu( self ):
max_files = self.config.getint("options", "max_recent_files")
self.recent_files = player.playlist.get_recent_files(max_files)
menu_recent_sub = gtk.Menu()
if len(self.recent_files) > 0:
for f in self.recent_files:
# don't include the temporary playlist in the file list
if f == panucci.PLAYLIST_FILE: continue
# don't include non-existant files
if not os.path.exists( f ): continue
filename, extension = os.path.splitext(os.path.basename(f))
menu_item = gtk.MenuItem( filename.replace('_', ' '))
menu_item.connect('activate', self.on_recent_file_activate, f)
menu_recent_sub.append(menu_item)
else:
menu_item = gtk.MenuItem(_('No recent files available.'))
menu_item.set_sensitive(False)
menu_recent_sub.append(menu_item)
self.menu_recent.set_submenu(menu_recent_sub)
def notify(self, message):
""" Sends a notification using pynotify, returns message """
if platform.DESKTOP and have_pynotify:
icon = util.find_data_file('panucci_64x64.png')
notification = pynotify.Notification(self.main_window.get_title(), message, icon)
notification.show()
elif platform.FREMANTLE:
hildon.hildon_banner_show_information(self.main_window, \
'', message)
elif platform.MAEMO:
# Note: This won't work if we're not in the gtk main loop
markup = '<b>%s</b>\n<small>%s</small>' % (self.main_window.get_title(), message)
hildon.hildon_banner_show_information_with_markup(self.main_window, None, markup)
def destroy(self, widget):
self.main_window.hide()
self.playlist.quit()
util.write_config(self.config)
gtk.main_quit()
def set_progress_indicator(self, loading_title=False):
if platform.FREMANTLE:
if loading_title:
self.main_window.set_title(_('Loading...'))
hildon.hildon_gtk_window_set_progress_indicator(self.main_window, \
True)
while gtk.events_pending():
gtk.main_iteration(False)
def show_main_window(self):
self.main_window.present()
def check_queue(self):
""" Makes sure the queue is saved if it has been modified
True means a new file can be opened
False means the user does not want to continue """
if not self.__ignore_queue_check and self.playlist.queue_modified:
response = gtkutil.dialog(
self.main_window, _('Save current playlist'),
_('Current playlist has been modified'),
_('Opening a new file will replace the current playlist. ') +
_('Do you want to save it before creating a new one?'),
affirmative_button=gtk.STOCK_SAVE,
negative_button=_('Discard changes'))
self.__log.debug('Response to "Save Queue?": %s', response)
if response is None:
return False
elif response:
return self.save_to_playlist_callback()
elif not response:
return True
else:
return False
else:
return True
def open_file_callback(self, widget=None):
# set __ingnore__queue_check because we already did the check
self.__ignore_queue_check = True
filename = gtkutil.get_file_from_filechooser(self)
if filename is not None:
self._play_file(filename)
self.__ignore_queue_check = False
def open_dir_callback(self, widget=None):
filename = gtkutil.get_file_from_filechooser(self, folder=True)
if filename is not None:
self._play_file(filename)
def play_one_callback(self, widget=None):
filename = gtkutil.get_file_from_filechooser(self)
if filename is not None:
self.empty_playlist_callback(None)
self._play_file(filename)
def save_to_playlist_callback(self, widget=None):
filename = gtkutil.get_file_from_filechooser(
self, save_file=True, save_to='playlist.m3u' )
if filename is None:
return False
if os.path.isfile(filename):
response = gtkutil.dialog( self.main_window, _('File already exists'),
_('File already exists'),
_('The file %s already exists. You can choose another name or '
'overwrite the existing file.') % os.path.basename(filename),
affirmative_button=gtk.STOCK_SAVE,
negative_button=_('Rename file'))
if response is None:
return None
elif response:
pass
elif not response:
return self.save_to_playlist_callback()
ext = util.detect_filetype(filename)
if not self.playlist.save_to_new_playlist(filename, ext):
self.notify(_('Error saving playlist...'))
return False
return True
def empty_playlist_callback(self, w):
self.playlist.reset_playlist()
self.__playlist_tab.treeview.get_model().clear()
def delete_all_bookmarks_callback(self, widget=None):
response = gtkutil.dialog(
self.main_window, _('Delete All Bookmarks'),
_('Would you like to delete all bookmarks?'),
_('By accepting all bookmarks in the database will be deleted.'),
negative_button=None)
self.__log.debug('Response to "Delete all bookmarks?": %s', response)
if response:
self.playlist.delete_all_bookmarks()
model = self.__playlist_tab.treeview.get_model()
for row in iter(model):
while model.iter_has_child(row.iter):
bkmk_iter = model.iter_children(row.iter)
model.remove(bkmk_iter)
def set_boolean_config_callback(self, w):
if w.get_active():
self.config.set("options", w.get_name(), "true")
else:
self.config.set("options", w.get_name(), "false")
def scrolling_labels_callback(self, w):
self.set_boolean_config_callback(w)
self.__player_tab.title_label.scrolling = w.get_active()
def resume_all_callback(self, w):
self.set_boolean_config_callback(w)
if not w.get_active():
self.playlist.reset_all_seek_to()
def set_play_mode_callback(self, w):
self.config.set("options", "play_mode", w.get_name())
def __get_fullscreen(self):
return self.__window_fullscreen
def __set_fullscreen(self, value):
if value != self.__window_fullscreen:
if value:
self.main_window.fullscreen()
else:
self.main_window.unfullscreen()
self.__window_fullscreen = value
self.playlist.send_metadata()
fullscreen = property( __get_fullscreen, __set_fullscreen )
def on_key_press(self, widget, event):
if platform.MAEMO:
if event.keyval == gtk.keysyms.F6:
self.fullscreen = not self.fullscreen
def on_recent_file_activate(self, widget, filepath):
self._play_file(filepath)
def on_file_queued(self, filepath, success, notify):
if notify:
filename = os.path.basename(filepath)
if success:
self.__log.info(
self.notify( '%s added successfully.' % filename ))
else:
self.__log.error(
self.notify( 'Error adding %s to the queue.' % filename))
def settings_callback(self, widget):
from panucci.gtkui.gtksettingsdialog import SettingsDialog
SettingsDialog(self)
def about_callback(self, widget):
if platform.FREMANTLE:
from panucci.gtkui.gtkaboutdialog import HeAboutDialog
HeAboutDialog.present(self.main_window, panucci.__version__)
else:
from panucci.gtkui.gtkaboutdialog import AboutDialog
AboutDialog(self.main_window, panucci.__version__)
def show_fm_transmitter(self, ws):
import osso
ctx = osso.Context('Panucci', '1.3.3.7', False)
plugin = osso.Plugin(ctx)
plugin.plugin_execute('libcpfmtx.so', True)
def _play_file(self, filename, pause_on_load=False):
self.playlist.load( os.path.abspath(filename) )
if self.playlist.is_empty:
return False
def handle_headset_button(self, event, button):
if event == 'ButtonPressed' and button == 'phone':
self.playlist.play_pause_toggle()
def handle_connection_state(self, device_path):
PATH = '/org/freedesktop/Hal/devices/computer_logicaldev_input_1'
if device_path == PATH and self.config.getboolean("options", "play_on_headset") and not self.playlist.playing:
self.playlist.play_pause_toggle()
def handle_bt_button(self, signal, button):
# See http://bugs.maemo.org/8283 for details
if signal == 'ButtonPressed':
if button == 'play-cd':
self.playlist.play_pause_toggle()
elif button == 'pause-cd':
self.playlist.play_pause_toggle()
elif button == 'next-song':
self.__player_tab.do_seek(self.config.getint("options", "seek_short"))
elif button == 'previous-song':
self.__player_tab.do_seek(-1*self.config.getint("options", "seek_short"))
def __select_current_item( self ):
# Select the currently playing track in the playlist tab
# and switch to it (so we can edit bookmarks, etc.. there)
self.__playlist_tab.select_current_item()
self.playlist_window.show()
##################################################
# PlayerTab
##################################################
class PlayerTab(ObservableService, gtk.HBox):
""" The tab that holds the player elements """
signals = [ 'select-current-item-request', ]
def __init__(self, gui_root):
self.__log = logging.getLogger('panucci.panucci.PlayerTab')
self.__gui_root = gui_root
self.config = gui_root.config
self.playlist = gui_root.playlist
self.metadata = None
gtk.HBox.__init__(self)
ObservableService.__init__(self, self.signals, self.__log)
self.progress_timer_id = None
self.recent_files = []
self.make_player_tab()
self.has_coverart = False
self.reset_progress()
self.playlist.register('stopped', self.on_player_stopped)
self.playlist.register('playing', self.on_player_playing)
self.playlist.register('paused', self.on_player_paused )
self.playlist.register('end-of-playlist', self.on_player_end_of_playlist)
self.playlist.register('new-track-loaded', self.on_player_new_track)
self.playlist.register('new-metadata-available', self.on_player_new_metadata)
self.playlist.register('reset-playlist', self.on_player_reset_playlist )
def make_player_tab(self):
main_vbox = gtk.VBox()
main_vbox.set_spacing(6)
# add a vbox to self
self.pack_start(main_vbox, True, True)
# a hbox to hold the cover art and metadata vbox
metadata_hbox = gtk.HBox()
metadata_hbox.set_spacing(6)
main_vbox.pack_start(metadata_hbox, True, False)
event_box = gtk.EventBox()
self.cover_art = gtk.Image()
event_box.add(self.cover_art)
metadata_hbox.pack_start(event_box, False, False)
event_box.connect('button-press-event', self.cover_art_callback)
# vbox to hold metadata
metadata_vbox = gtk.VBox()
metadata_vbox.pack_start(gtk.Image(), True, True)
self.artist_label = gtk.Label('')
self.artist_label.set_ellipsize(pango.ELLIPSIZE_END)
metadata_vbox.pack_start(self.artist_label, False, False)
separator = gtk.Label("")
separator.set_size_request(-1, 10)
metadata_vbox.pack_start(separator, False, False)
self.album_label = gtk.Label('')
if platform.FREMANTLE:
hildon.hildon_helper_set_logical_font(self.album_label, 'SmallSystemFont')
hildon.hildon_helper_set_logical_color(self.album_label, gtk.RC_FG, gtk.STATE_NORMAL, 'SecondaryTextColor')
else:
self.album_label.modify_font(pango.FontDescription('normal 8'))
self.album_label.set_ellipsize(pango.ELLIPSIZE_END)
metadata_vbox.pack_start(self.album_label, False, False)
separator = gtk.Label("")
separator.set_size_request(-1, 10)
metadata_vbox.pack_start(separator, False, False)
self.title_label = widgets.ScrollingLabel('',
self.config.get("options", "scrolling_color"),
update_interval=100,
pixel_jump=1,
delay_btwn_scrolls=5000,
delay_halfway=3000)
self.title_label.scrolling = self.config.getboolean("options", "scrolling_labels")
metadata_vbox.pack_start(self.title_label, False, False)
metadata_vbox.pack_start(gtk.Image(), True, True)
metadata_hbox.pack_start( metadata_vbox, True, True )
progress_eventbox = gtk.EventBox()
progress_eventbox.set_events(gtk.gdk.BUTTON_PRESS_MASK)
progress_eventbox.connect(
'button-press-event', self.on_progressbar_changed )
self.progress = gtk.ProgressBar()
self.progress.set_size_request(-1, self.config.getint("options", "progress_height"))
progress_eventbox.add(self.progress)
main_vbox.pack_start( progress_eventbox, False, False )
# make the button box
buttonbox = gtk.HBox()
# A wrapper to help create DualActionButtons with the right settings
def create_da(widget, action, widget2=None, action2=None):
if platform.FREMANTLE:
widget2 = None
action2 = None
return widgets.DualActionButton(widget, action, self.config, widget2, action2)
self.rrewind_button = create_da(
gtkutil.generate_image('media-skip-backward.png'),
lambda: self.do_seek(-1*self.config.getint('options', 'seek_long')),
gtkutil.generate_image("gtk-goto-first-ltr.png"),
self.playlist.prev)
buttonbox.add(self.rrewind_button)
self.rewind_button = create_da(
gtkutil.generate_image('media-seek-backward.png'),
lambda: self.do_seek(-1*self.config.getint('options', 'seek_short')))
buttonbox.add(self.rewind_button)
self.play_pause_button = gtk.Button('')
gtkutil.image(self.play_pause_button, 'media-playback-start.png')
self.play_pause_button.connect( 'clicked',
self.on_btn_play_pause_clicked )
buttonbox.add(self.play_pause_button)
self.forward_button = create_da(
gtkutil.generate_image('media-seek-forward.png'),
lambda: self.do_seek(self.config.getint('options', 'seek_short')))
buttonbox.add(self.forward_button)
self.fforward_button = create_da(
gtkutil.generate_image('media-skip-forward.png'),
lambda: self.do_seek(self.config.getint('options', 'seek_long')),
gtkutil.generate_image("gtk-goto-last-ltr.png"),
self.playlist.next)
buttonbox.add(self.fforward_button)
self.bookmarks_button = create_da(
gtkutil.generate_image('bookmark-new.png'),
self.playlist.add_bookmark_at_current_position,
gtkutil.generate_image(gtk.STOCK_JUMP_TO, True),
lambda *args: self.notify('select-current-item-request'))
buttonbox.add(self.bookmarks_button)
self.set_controls_sensitivity(False)
if platform.FREMANTLE:
for child in buttonbox.get_children():
if isinstance(child, gtk.Button):
child.set_name('HildonButton-thumb')
buttonbox.set_size_request(-1, self.config.getint("options", "button_height"))
main_vbox.pack_start(buttonbox, False, False)
if platform.MAEMO:
self.__gui_root.main_window.connect( 'key-press-event',
self.on_key_press )
# Disable focus for all widgets, so we can use the cursor
# keys + enter to directly control our media player, which
# is handled by "key-press-event"
for w in (
self.rrewind_button, self.rewind_button,
self.play_pause_button, self.forward_button,
self.fforward_button, self.progress,
self.bookmarks_button, ):
w.unset_flags(gtk.CAN_FOCUS)
def set_controls_sensitivity(self, sensitive):
return
for button in self.forward_button, self.rewind_button, \
self.fforward_button, self.rrewind_button:
button.set_sensitive(sensitive)
# the play/pause button should always be available except
# for when the player starts without a file
self.play_pause_button.set_sensitive(True)
def on_dual_action_setting_changed( self, *args ):
for button in self.forward_button, self.rewind_button, \
self.fforward_button, self.rrewind_button, \
self.bookmarks_button:
button.set_longpress_enabled( self.config.getboolean("options", "dual_action_button") )
button.set_duration( self.config.getfloat("options", "dual_action_button_delay") )
def on_key_press(self, widget, event):
if platform.MAEMO:
if event.keyval == gtk.keysyms.Left: # seek back
self.do_seek( -1 * self.config.getint('options', 'seek_long') )
elif event.keyval == gtk.keysyms.Right: # seek forward
self.do_seek( self.config.getint('options', 'seek_long') )
elif event.keyval == gtk.keysyms.Return: # play/pause
self.on_btn_play_pause_clicked()
def on_player_stopped(self):
self.stop_progress_timer()
self.set_controls_sensitivity(False)
gtkutil.image(self.play_pause_button, 'media-playback-start.png')
if self.metadata:
estimated_length = self.metadata.get('length', 0)
self.set_progress_callback( 0, estimated_length )
def on_player_playing(self):
self.progress_timer_callback()
self.start_progress_timer()
gtkutil.image(self.play_pause_button, 'media-playback-pause.png')
self.set_controls_sensitivity(True)
if platform.FREMANTLE:
hildon.hildon_gtk_window_set_progress_indicator(\
self.__gui_root.main_window, False)
def on_player_new_track(self):
for widget in [self.title_label,self.artist_label,self.album_label]:
widget.set_markup('')
widget.hide()
self.cover_art.hide()
self.has_coverart = False
def on_player_new_metadata(self):
self.metadata = self.playlist.get_file_metadata()
self.set_metadata(self.metadata)
position = self.playlist.get_current_position()
estimated_length = self.metadata.get('length', 0)
self.set_progress_callback(position, estimated_length)
def on_player_paused( self, position, duration ):
self.stop_progress_timer() # This should save some power
self.set_progress_callback( position, duration )
gtkutil.image(self.play_pause_button, 'media-playback-start.png')
def on_player_end_of_playlist(self, loop):
if not loop:
estimated_length = self.metadata.get('length', 0)
self.set_progress_callback( 0, estimated_length )
def on_player_reset_playlist(self):
self.on_player_stopped()
self.on_player_new_track()
self.reset_progress()
def reset_progress(self):
self.set_progress_callback(0,0)
self.__gui_root.main_window.set_title("Panucci")
def set_progress_callback(self, time_elapsed, total_time):
""" times must be in nanoseconds """
time_string = "%s / %s" % ( util.convert_ns(time_elapsed),
util.convert_ns(total_time) )
self.progress.set_text( time_string )
fraction = float(time_elapsed) / float(total_time) if total_time else 0
self.progress.set_fraction( fraction )
def on_progressbar_changed(self, widget, event):
if ( not self.config.getboolean("options", "lock_progress") and
event.type == gtk.gdk.BUTTON_PRESS and event.button == 1 ):
new_fraction = event.x/float(widget.get_allocation().width)
resp = self.playlist.do_seek(percent=new_fraction)
if resp:
# Preemptively update the progressbar to make seeking smoother
self.set_progress_callback( *resp )
def on_btn_play_pause_clicked(self, widget=None):
self.playlist.play_pause_toggle()
def cover_art_callback(self, widget, event):
self.on_btn_play_pause_clicked(widget)
def progress_timer_callback( self ):
if self.playlist.playing and not self.playlist.seeking:
pos_int, dur_int = self.playlist.get_position_duration()
# This prevents bogus values from being set while seeking
if pos_int >= 0 and dur_int >= 0:
self.set_progress_callback(pos_int, dur_int)
return True
def start_progress_timer( self ):
if self.progress_timer_id is not None:
self.stop_progress_timer()
self.progress_timer_id = gobject.timeout_add(
1000, self.progress_timer_callback )
def stop_progress_timer( self ):
if self.progress_timer_id is not None:
gobject.source_remove( self.progress_timer_id )
self.progress_timer_id = None
def get_coverart_size( self ):
if platform.MAEMO:
if self.__gui_root.fullscreen:
size = util.coverart_sizes['maemo fullscreen']
else:
size = util.coverart_sizes['maemo']
else:
size = util.coverart_sizes['normal']
return size, size
def set_coverart( self, pixbuf ):
self.cover_art.set_from_pixbuf(pixbuf)
self.cover_art.show()
self.has_coverart = True
def set_metadata( self, tag_message ):
tags = { 'title': self.title_label, 'artist': self.artist_label,
'album': self.album_label }
# set the coverart
if tag_message.has_key('image') and tag_message['image'] is not None:
value = tag_message['image']
pbl = gtk.gdk.PixbufLoader()
try:
pbl.write(value)
pbl.close()
pixbuf = pbl.get_pixbuf()
pixbuf = pixbuf.scale_simple(self.config.getint("options", "cover_height"),
self.config.getint("options", "cover_height"), gtk.gdk.INTERP_BILINEAR )
self.set_coverart(pixbuf)
except Exception, e:
self.__log.exception('Error setting coverart...')
# set the text metadata
for tag,value in tag_message.iteritems():
if tags.has_key(tag) and value is not None and value.strip():
if tag == "artist":
_str = '<big>' + cgi.escape(value) + '</big>'
elif tag == "album":
_str = cgi.escape(value)
elif tag == "title":
_str = '<b><big>' + cgi.escape(value) + '</big></b>'
if not platform.HANDSET:
value += ' - Panucci'
elif len(value) > 25:
value = value[:24] + '...'
self.__gui_root.main_window.set_title( value )
try:
tags[tag].set_markup(_str)
except TypeError, e:
self.__log.exception(str(e))
tags[tag].set_alignment( 0.5*int(not self.has_coverart), 0.5)
tags[tag].show()
def do_seek(self, seek_amount):
resp = self.playlist.do_seek(from_current=seek_amount*10**9)
if resp:
# Preemptively update the progressbar to make seeking smoother
self.set_progress_callback( *resp )