[svn] Update also on double click.
#!/usr/bin/python
# Copyright (C) 2004 by Fabien Ninoles
# IMMSView is aim to be a replacement to XMMS playlist editor
# with better support for IMMS plugin.
# IMMSView 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 2, or (at your option)
# any later version.
# IMMSView 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 GNU Emacs; see the file COPYING. If not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
_immsview_version = "$Id: immsview 1695 2004-02-03 21:50:28Z fabien $"
# $Log$
# Revision 1.14 2004/02/03 21:50:28 fabien
# Update also on double click.
#
# Revision 1.13 2004/02/03 20:55:27 fabien
# Play selected: check if the file exist, elsewhere try to
# update it through the Db.
#
# Revision 1.12 2004/02/03 18:50:03 fabien
# Add double click and remove sorting when refreshing data.
#
# Revision 1.11 2004/02/02 15:45:25 fabien
# Add a comment about the autocommit value in db.connect()
#
# Revision 1.10 2004/02/02 15:42:43 fabien
# OK, reset current after changing song manually... however, I most
# sleep for almost a second to be sure to catch it.
#
# Revision 1.9 2004/02/02 06:54:09 fabien
# Add cvs log since I don't maintain a changelog currently.
#
# Revision 1.8 2004/02/02 06:44:10 fabien
# All functions are now implemented (with a somewhat "cleaner" interface.
#
# Revision 1.7 2004/02/02 04:51:35 fabien
# Switch to GTK2.
#
# Revision 1.6 2004/02/01 17:21:02 fabien
# Add play selected song.
#
# Revision 1.5 2004/02/01 16:22:40 fabien
# Add title.
#
# Revision 1.4 2004/02/01 16:12:45 fabien
# Add current and correct plot function.
#
# Revision 1.3 2004/02/01 15:59:04 fabien
# Adding xmms selection and last display.
#
# Revision 1.2 2004/02/01 04:40:41 fabien
# add versioning.
#
# Revision 1.1 2004/02/01 03:05:25 fabien
# Premiere version de immsview.
# The aim of immsview is to become a better playlist editor than the
# normal
# TODO:
# * IMMS:
# - Add composed rating
# - Rating edition
# * XMMS:
# - getting current playlist
# - editing playlist
# * File support:
# - adding, deleting, suppressing a file (including updating other
# interface).
# - artist, title, genre informations (ID3)
# * Interface:
# - Real application interface (with menu, icons, accelerators, etc.)
import pygtk
pygtk.require('2.0')
import sys
import os
import sqlite
import gobject
import gtk
import gtk.glade
import gettext
import xmms.control
import time
gtk.glade.bindtextdomain('immsview', '/usr/share/immsview/LANG')
gtk.glade.textdomain('immsview')
_ = gettext.gettext
def strtime(seconds):
secs = abs(round(seconds))
minutes = secs / 60;
hours = minutes / 60;
days = hours / 24;
secs = secs % 60;
minutes %= 60;
hours %= 24;
if seconds < 0:
s = "-"
else:
s = ""
if days >= 1:
s += "%dd %dh" % (days, hours)
elif hours >= 1:
s += "%dh%02d" % (hours, minutes)
elif minutes >= 1:
s += "%d'%02d\"" % (minutes, secs)
else:
s += "%d\"" % (secs)
return s
class XMMSControl:
def __getattr__(self, name):
return xmms.control.__dict__[name]
def get_current_file(self):
return self.get_playlist_file(
self.get_playlist_pos())
def find_in_playlist(self, filename):
for idx in range(self.get_playlist_length()):
if filename == self.get_playlist_file(idx):
return idx
return -1
def play_file(self, filename):
idx = self.find_in_playlist(filename)
if idx == -1:
self.enqueue_and_play((filename,))
else:
self.set_playlist_pos(idx)
class IMMSDb:
_dbname = os.environ['HOME'] + '/.imms/imms.db'
# _dbname = os.environ['HOME'] + '/.imms/imms.backup.db'
def __init__(self):
# autocommit = 1 disable autocommit!
self.cx = sqlite.connect(IMMSDb._dbname, autocommit = 1,
timeout = 2, encoding = ('utf-8', 'replace'))
def commit(self):
# self.cx.commit()
pass
def _get_ratings(self, min = 0, max = 250):
cu = self.cx.cursor()
cu.execute('''SELECT Rating.uid, Rating.rating
FROM Rating
WHERE Rating.rating >= %d
AND Rating.rating <= %d
ORDER BY Rating.rating;''' % (min, max))
return cu.fetchall()
def _get_library_uid(self, uid):
cu = self.cx.cursor()
cu.execute('''SELECT Library.path
FROM Library
WHERE Library.uid = %d;''' % (uid,))
return cu.fetchone()
def get_uid_by_path(self, path):
cu = self.cx.cursor()
cu.execute('''SELECT Library.uid
FROM Library
WHERE Library.path = '%s';''' % (path))
return cu.fetchall()
def get_ratings_and_info(self, uids = None):
print time.ctime(time.time()) + ": querying"
cu = self.cx.cursor()
qry = '''SELECT Rating.uid, Rating.rating,
Library.path, Last.last
FROM Rating, Library, Last
WHERE Rating.uid = Library.uid AND
Library.sid = Last.sid '''
if uids:
qry += 'AND (Library.uid = %d' % (uids.pop())
for uid in uids:
qry += ' OR Library.uid = %d' % uid
qry += ') '
qry += 'ORDER BY Rating.rating DESC;'
cu.execute(qry)
# Better to fetch everything since locking can really mess
# things in imms plugin.
print time.ctime(time.time()) + ": mapping"
results = []
tune = cu.fetchone()
while tune:
try:
tmp = {'uid' : tune[0],
'rating' : int(tune[1]),
'path' : tune[2].decode('utf-8', 'replace'),
'last' : int(tune[3])}
results.append(tmp)
except UnicodeDecodeError:
print tune[2]
tune = cu.fetchone()
return results
class IMMSStore(gtk.ListStore):
COL_RATING = 0
COL_PATH = 1
COL_LAST_STR = 2
COL_LAST = 3
COL_SELECT = 4
COL_UID = 5
def __init__(self, db):
gtk.ListStore.__init__(self,
gobject.TYPE_INT,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_INT,
gobject.TYPE_BOOLEAN,
gobject.TYPE_INT)
self.db = db
# self.set_default_sort_func(self.default_sort)
def default_sort(self, a, b, dummy):
return 0
def tune_to_giter(self, tune, giter = None, curtime = 0):
if not curtime:
curtime = time.time()
if not giter:
giter = self.append(None)
self.set(giter,
IMMSStore.COL_UID, tune['uid'],
IMMSStore.COL_RATING, tune['rating'],
IMMSStore.COL_PATH, tune['path'],
IMMSStore.COL_LAST_STR, strtime(curtime-tune['last']),
IMMSStore.COL_LAST, tune['last'],
IMMSStore.COL_SELECT, gtk.FALSE)
return giter
def refresh(self):
curtime = time.time()
col, order = self.get_sort_column_id()
# This create a GTK-Critical in GTKListStore which,
# however, have no consequences AFAIK.
# The doc permit it normally, even without default sort,
# elsewhere
self.set_sort_column_id(-1, 0)
tunes = self.db.get_ratings_and_info()
self.clear()
print time.ctime(time.time()) + ": inserting"
for tune in tunes:
self.tune_to_giter(tune, curtime = curtime)
print time.ctime(time.time()) + ": end insert"
self.set_sort_column_id(col, order)
def find_selected_giter(self):
giter = self.get_iter_first()
while giter:
if self.get_value(giter, IMMSStore.COL_SELECT):
break
giter = self.iter_next(giter)
return giter
def find_giter_from_uid(self, uid):
giter = self.get_iter_first()
while giter:
if self.get_value(giter, IMMSStore.COL_UID) == uid:
break
giter = self.iter_next(giter)
return giter
def find_giter_from_path(self, song):
uids = self.db.get_uid_by_path(song)
if len(uids) == 0:
return None
uid = uids[0]
giter = self.find_giter_from_uid(uid[0])
if not giter:
tunes = self.db.get_ratings_and_info(uids)
if len(tunes) > 0:
giter = self.tune_to_giter(tunes[0])
return giter
def update_iter(self, giter):
uid = self.get_value(giter, IMMSStore.COL_UID)
tunes = self.db.get_ratings_and_info([uid,])
if len(tunes) > 0:
return self.tune_to_giter(tunes[0], giter)
return giter
class IMMSView(gtk.TreeView):
def __init__(self, model, xmms):
gtk.TreeView.__init__(self, model)
self.xmms = xmms
self.create_widgets()
def create_widgets(self):
renderer = gtk.CellRendererText()
renderer.set_property('weight', 700)
column = gtk.TreeViewColumn(_("Rating"), renderer,
weight_set = IMMSStore.COL_SELECT,
text = IMMSStore.COL_RATING)
column.set_sort_column_id(IMMSStore.COL_RATING)
self.append_column(column)
column = gtk.TreeViewColumn(_("Last"), renderer,
weight_set = IMMSStore.COL_SELECT,
text = IMMSStore.COL_LAST_STR)
column.set_sort_column_id(IMMSStore.COL_LAST)
self.append_column(column)
column = gtk.TreeViewColumn(_("File"), renderer,
weight_set = IMMSStore.COL_SELECT,
text = IMMSStore.COL_PATH)
column.set_resizable(gtk.TRUE)
column.set_sort_column_id(IMMSStore.COL_PATH)
self.append_column(column)
self.set_search_column(IMMSStore.COL_PATH)
self.set_headers_clickable(gtk.TRUE)
self.connect('row-activated', self.on_row_activated)
def set_current_song(self, song):
model = self.get_model()
giter = model.find_selected_giter()
if giter:
model.set_value(giter, IMMSStore.COL_SELECT, gtk.FALSE)
giter = model.find_giter_from_path(song)
if giter:
model.set_value(giter, IMMSStore.COL_SELECT, gtk.TRUE)
self.set_cursor(model.get_path(giter))
def get_filename(self, giter):
model = self.get_model()
fn = model.get_value(model.update_iter(giter), IMMSStore.COL_PATH)
try:
os.stat(fn)
except OSError:
return None
return fn
def get_file_selected(self):
model, giter = self.get_selection().get_selected()
if giter:
return self.get_filename(giter)
return None
def on_row_activated(self, tview, path, col):
model = self.get_model()
giter = model.get_iter(path)
fn = self.get_filename(giter)
self.set_current_song(fn)
self.xmms.play_file(fn)
class IMMSToolbar(gtk.Toolbar):
# _IMMSPLOT_COMMAND = 'immsplot &'
_IMMSPLOT_COMMAND = '/home/fabien/bin/immsplot &'
_SLEEP_TIME = 1
def __init__(self, iview, xmms):
gtk.Toolbar.__init__(self)
self.iview = iview
self.xmms = xmms
self.create_widgets()
def create_widgets(self):
self.append_item(_('Refresh'), _('Refresh list'),
None, None, self.do_refresh)
self.append_item(_('Plot'), _('Show graph of rates'),
None, None, self.plot)
self.append_item(_('Current'), _('Get current song'),
None, None, self.do_get_current)
self.append_item(_('Previous'), _('Play previous song'),
None, None, self.do_play_prev)
self.append_item(_('Play'), _('Play selection'),
None, None, self.do_play)
self.append_item(_('Next'), _('Play next song'),
None, None, self.do_play_next)
def plot(self, dummy):
os.system(self._IMMSPLOT_COMMAND)
def do_refresh(self, dummy):
self.iview.get_model().refresh()
def do_get_current(self, dummy):
song = self.xmms.get_current_file()
self.iview.set_current_song(song)
def do_play(self, dummy):
fn = self.iview.get_file_selected()
if fn:
self.xmms.play_file(fn)
time.sleep(self._SLEEP_TIME)
self.do_get_current(dummy)
def do_play_prev(self, dummy):
self.xmms.playlist_prev()
time.sleep(self._SLEEP_TIME)
self.do_get_current(dummy)
def do_play_next(self, dummy):
self.xmms.playlist_next()
time.sleep(self._SLEEP_TIME)
self.do_get_current(dummy)
root = gtk.Window()
root.set_title(_("IMMSView"))
root.connect('destroy', gtk.mainquit)
vbox = gtk.VBox(spacing = 3)
root.add(vbox)
vbox.show()
model = IMMSStore(IMMSDb())
xmms_control = XMMSControl()
iview = IMMSView(model,xmms_control)
scroll = gtk.ScrolledWindow()
scroll.add(iview)
vbox.pack_end(scroll)
iview.show()
scroll.show()
toolbar = IMMSToolbar(iview, xmms_control)
vbox.pack_start(toolbar, expand = gtk.FALSE)
toolbar.show()
root.show()
toolbar.do_refresh(None)
toolbar.do_get_current(None)
gtk.main()