#! /usr/bin/env python import os from gi.repository import Gtk import gobject import pango import time import playlist import time_fmt scriptdir = os.path.dirname(os.path.abspath(__file__)) class SyncGui: def __init__(self): """construct a SyncGui object""" self.builder = Gtk.Builder() self.builder.add_from_file(scriptdir + "/sync_gui.glade") self.widPlaylistView = self.builder.get_object("PlaylistView") self.widPlaylistStore = self.builder.get_object("PlaylistStore") self.widPosition = self.builder.get_object("Position") self.widPositionScale = self.builder.get_object("PositionScale") self.widPositionAt = self.builder.get_object("PositionAt") self.widPositionRemaining = self.builder.get_object("PositionRemaining") self.widBtnPause = self.builder.get_object("Pause") self.widBtnPlay = self.builder.get_object("Play") self.widStatus = self.builder.get_object("Status") handlers = { "onDestroy": self.onDestroy, "onPlaylistDblClick": self.onPlaylistDblClick, "onNewPosition": self.onNewPosition, "onPrevious": self.onPrevious, "onBackward": self.onBackward, "onStop": self.onStop, "onPause": self.onPause, "onPlay": self.onPlay, "onForward": self.onForward, "onNext": self.onNext, } self.builder.connect_signals(handlers) self.playlist = playlist.Playlist() self.playlist.read("playlist.txt") self.playlist.update(self.widPlaylistStore) self.widStatus.push(0, "TODO...") self.stEntryIdx = -1 # no entry selected self.stName = "" # no current entry name self.stDuration = 0 # current entry has zero size self.stPosition = 0 # at begin of current entry self.stPlaying = False # not playing gobject.timeout_add(10, self.onTimer10ms) self.updateEntry() self.updateButtonVisibility() def showPosition(self): """update the position texts next to the position slider""" # format current time and remaining time posAt = time_fmt.sec2str(self.stPosition) posRemaining = time_fmt.sec2str(self.stDuration - self.stPosition) self.widPositionAt.set_text(posAt) self.widPositionRemaining.set_text(posRemaining) def updatePositionState(self): """update the position in the state, but not the slider""" # calculate (virtual) start time of playing # i.e. the time the playing would have had started to arrive at the # current position now if it had played continuosly self.stPlayStart = time.time() - self.stPosition # update position texts self.showPosition() def updatePosition(self): """update the position including the position slider""" # update GUI slider self.widPositionScale.set_value(self.stPosition) # update position state self.updatePositionState() def updateDuration(self): """update the duration (i.e. range for the slider) based on the current playlist entry""" # get duration of new playlist entry self.stDuration = 0 if self.stEntryIdx >= 0: entry = self.playlist.entries[self.stEntryIdx] if entry["type"] == "normal": self.stDuration = entry["duration"] # set position to begin self.stPosition = 0 # update value range self.widPosition.set_upper(self.stDuration) # update position of slider self.updatePosition() def updateEntry(self): """update current entry of playlist and duration, position, ...""" # sanity check for entry index if self.stEntryIdx < -1 or self.stEntryIdx >= len(self.playlist.entries): self.stEntryIdx = -1 # make current entry bold, all others non-bold def update(model, path, it, user_data): (idx,) = model.get(it, 0) if idx == self.stEntryIdx: weight = pango.WEIGHT_BOLD else: weight = pango.WEIGHT_NORMAL print("DEBUG sync_gui idx=%d weight=%d" % (idx, weight)) model.set(it, 1, weight) self.widPlaylistStore.foreach(update, None) # update duration, position, ... self.updateDuration() def updateButtonVisibility(self): """update the visibility of the buttons based on if playing or not""" self.widBtnPause.set_visible(self.stPlaying) self.widBtnPlay.set_visible(not self.stPlaying) def onDestroy(self, widget): """window will be destroyed""" Gtk.main_quit() def onPlaylistDblClick(self, widget, row, col): """playlist entry has been double-clicked""" # get index of selected entry idx = -1 sel = self.widPlaylistView.get_selection() if sel is not None: (model, it) = sel.get_selected() if it is not None: (idx,) = model.get(it, 0) print("DEBUG sync_gui playlist double-click idx=%d" % (idx)) # update playlist entry self.stEntryIdx = idx # update entry self.updateEntry() # start playing # TODO def onNewPosition(self, widget, scroll, value): """slider has been moved to a new position""" print("DEBUG sync_gui new position " + str(value)); # clamp position to valid range if value < 0: value = 0 if value > self.stDuration: value = self.stDuration # update current position - and play start time if playing self.stPosition = value # update position state (do not touch the slider) self.updatePositionState() def onPrevious(self, widget): """previous button as been pressed""" print("DEBUG sync_gui previous") def onBackward(self, widget): """backward button has been pressed""" print("DEBUG sync_gui backward") def onStop(self, widget): """stop button has been pressed""" print("DEBUG sync_gui stop") self.stPlaying = False self.stPosition = 0 # stop goes back to begin self.updatePosition() self.updateButtonVisibility() def onPause(self, widget): """pause button has been pressed""" print("DEBUG sync_gui pause") self.stPlaying = False self.updateButtonVisibility() def onPlay(self, widget): """play button has been pressed""" print("DEBUG sync_gui play") self.stPlaying = True self.updatePosition() self.updateButtonVisibility() def onForward(self, widget): """forward button has been pressed""" print("DEBUG sync_gui forward") def onNext(self, widget): """next button has been pressed""" print("DEBUG sync_gui next") def onTimer10ms(self): """timer callback, every 10ms""" # update position if playing if self.stPlaying: self.stPosition = time.time() - self.stPlayStart if self.stPosition > self.stDuration: self.stPosition = 0 # FIXME: go to next entry self.updatePosition() return True # main application entry point if __name__ == "__main__": app = SyncGui() Gtk.main()