From: Richard Kettlewell Date: Sun, 19 Oct 2008 11:07:17 +0000 (+0100) Subject: Remove obsolete tkdisorder X-Git-Tag: 4.3~68 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/commitdiff_plain/b7b9b77dfc9b4d96a7dce1eacdb4a5965f4fd20d Remove obsolete tkdisorder --- diff --git a/doc/Makefile.am b/doc/Makefile.am index 1315273..4989437 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -25,7 +25,6 @@ man_MANS=disorderd.8 disorder.1 disorder.3 disorder_config.5 disorder-dump.8 \ disorder-stats.8 disorder-dbupgrade.8 disorder_templates.5 \ disorder_actions.5 disorder_options.5 disorder.cgi.8 \ disorder_preferences.5 -noinst_MANS=tkdisorder.1 SEDFILES=disorder.1 disorderd.8 disorder_config.5 \ disorder-dump.8 disorder_protocol.5 disorder-deadlock.8 \ @@ -83,7 +82,7 @@ endif EXTRA_DIST=disorderd.8.in disorder.1.in disorder_config.5.in \ disorder.3 disorder-dump.8.in disorder_protocol.5.in \ - tkdisorder.1 disorder-deadlock.8.in disorder-rescan.8.in \ + disorder-deadlock.8.in disorder-rescan.8.in \ disobedience.1.in disorderfm.1.in disorder-speaker.8 \ disorder-playrtp.1.in disorder-decode.8.in disorder-normalize.8 \ disorder-stats.8.in disorder-dbupgrade.8.in \ diff --git a/doc/tkdisorder.1 b/doc/tkdisorder.1 deleted file mode 100644 index 6ac62b0..0000000 --- a/doc/tkdisorder.1 +++ /dev/null @@ -1,62 +0,0 @@ -.\" -.\" Copyright (C) 2005 Richard Kettlewell -.\" -.\" This program is free software: you can redistribute it and/or modify -.\" it under the terms of the GNU General Public License as published by -.\" the Free Software Foundation, either version 3 of the License, or -.\" (at your option) any later version. -.\" -.\" This program is distributed in the hope that it will be useful, -.\" but WITHOUT ANY WARRANTY; without even the implied warranty of -.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -.\" GNU General Public License for more details. -.\" -.\" You should have received a copy of the GNU General Public License -.\" along with this program. If not, see . -.\" -.TH tkdisorder 1 -.SH NAME -tkdisorder \- DisOrder jukebox client -.SH SYNOPSIS -.B tkdisorder -.RI [ OPTIONS ] -.SH DESCRIPTION -.B tkdisorder -is a simple graphical client for DisOrder. -It is not finished and no further -development is planned. -Use \fBdisobedience\fR(1) instead. -.PP -The main window is divided into two. -The top half contains the name -of the current track and a progress bar indicating how far through -playing it is. -It also contains three buttons: -.TP -.B Quit -Terminates tkdisorder. -.TP -.B Scratch -Terminates the current track. -.TP -.B Recent -Pops up a window listing recently played tracks, most recent at the -top. -.PP -The bottom half of the window lists the current queue, with the next -track to be played at the top. -.SH OPTIONS -.TP -.B \-\-help\fR, \fB\-h -Display a usage message. -.TP -.B \-\-version\fR, \fB\-V -Display version number. -.SH "SEE ALSO" -\fBdisorder\fR(1), \fBdisobedience\fR(1), \fBdisorder_config\fR(5) -.PP -"\fBpydoc disorder\fR" for the Python API documentation. -.\" Local Variables: -.\" mode:nroff -.\" fill-column:79 -.\" End: diff --git a/python/Makefile.am b/python/Makefile.am index 8df3a51..2ec62e1 100644 --- a/python/Makefile.am +++ b/python/Makefile.am @@ -16,14 +16,13 @@ # along with this program. If not, see . # -noinst_SCRIPTS=tkdisorder nodist_python_PYTHON=disorder.py SEDFILES=disorder.py include ${top_srcdir}/scripts/sedfiles.make -EXTRA_DIST=disorder.py.in tkdisorder +EXTRA_DIST=disorder.py.in BUILT_SOURCES=disorder.py diff --git a/python/tkdisorder b/python/tkdisorder deleted file mode 100755 index 3a9fde3..0000000 --- a/python/tkdisorder +++ /dev/null @@ -1,478 +0,0 @@ -#! /usr/bin/env python -# -# Copyright (C) 2004, 2005 Richard Kettlewell -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# - -# THIS PROGRAM IS NO LONGER MAINTAINED. -# -# It worked last time I tried running it, but all client maintenance -# effort is now devoted to the web interface and the GTK+ client -# (Disobedience). - -"""Graphical user interface for DisOrder""" - -from Tkinter import * -import tkFont -import Queue -import threading -import disorder -import time -import string -import re -import getopt -import sys - -######################################################################## - -# Architecture: -# -# The main (initial) thread of the program runs all GUI code. The GUI is only -# directly modified from inside this thread. We sometimes call this the -# master thread. -# -# We have a background thread, MonitorStateThread, which waits for changes to -# the server's state which we care about. Whenever such a change occurs it -# notifies all the widgets which care about it (and possibly other widgets; -# the current implementation is unsophisticated.) -# -# Widget poll() methods usually, but NOT ALWAYS, called in the -# MonitorStateThread. Other widget methods are call in the master thread. -# -# We have a separate disorder.client for each thread rather than locking a -# single client. MonitorStateThread also has a private disorder.client that -# it uses to watch for server state changes. - -######################################################################## - -class Intercom: - # communication queue into thread containing Tk event loop - # - # Sets up a callback on the event loop (in this thread) which periodically - # checks the queue for elements; if any are found they are executed. - def __init__(self, master): - self.q = Queue.Queue(); - self.master = master - self.poll() - - def poll(self): - try: - item = self.q.get_nowait() - item() - self.master.after_idle(self.poll) - except Queue.Empty: - self.master.after(100, self.poll) - - def put(self, item): - self.q.put(item) - -######################################################################## - -class ProgressBar(Canvas): - # progress bar widget - def __init__(self, master=None, **kw): - Canvas.__init__(self, master, highlightthickness=0, **kw) - self.outer = self.create_rectangle(0, 0, 0, 0, - outline="#000000", - width=1, - fill="#ffffff") - self.bar = self.create_rectangle(0, 0, 0, 0, - width=1, - fill="#ff0000", - outline='#ff0000') - self.current = None - self.total = None - self.bind("", lambda e: self.redisplay()) - - def update(self, current, total): - self.current = current - if current > total: - current = total - elif current < 0: - current = 0 - self.total = total - self.redisplay() - - def clear(self): - self.current = None - self.total = None - self.redisplay() - - def redisplay(self): - w, h = self.winfo_width(), self.winfo_height() - if w > 0 and h > 0: - self.coords(self.outer, 0, 0, w - 1, h - 1) - if self.total: - bw = int((w - 2) * self.current / self.total) - self.itemconfig(self.bar, - fill="#ff0000", - outline="#ff0000") - self.coords(self.bar, 1, 1, bw, h - 2) - else: - self.itemconfig(self.bar, - fill="#909090", - outline="#909090") - self.coords(self.bar, 1, 1, w - 2, h - 2) - -# look up a track's name part, using client c. Maintains a cache. -part_cache = {} -def part(c, track, context, part): - key = "%s-%s-%s" % (part, context, track) - now = time.time() - if not part_cache.has_key(key) or part_cache[key]['when'] < now - 3600: - part_cache[key] = {'when': now, - 'what': c.part(track, context, part)} - return part_cache[key]['what'] - -class PlayingWidget(Frame): - # widget that always displays information about what's - # playing - def __init__(self, master=None, **kw): - Frame.__init__(self, master, **kw) - # column 0 is descriptions, column 1 is the values - self.columnconfigure(0,weight=0) - self.columnconfigure(1,weight=1) - self.fields = {} - self.field(0, 0, "artist", "Artist") - self.field(1, 0, "album", "Album") - self.field(2, 0, "title", "Title") - # column 1 also has the progress bar in it - self.p = ProgressBar(self, height=20) - self.p.grid(row=3, column=1, sticky=E+W) - # column 2 has operation buttons - b = Button(self, text="Quit", command=self.quit) - b.grid(row=0, column=2, sticky=E+W) - b = Button(self, text="Scratch", command=self.scratch) - b.grid(row=1, column=2, sticky=E+W) - b = Button(self, text="Recent", command=self.recent) - b.grid(row=2, column=2, sticky=E+W) - self.length = 0 - self.update_length() - self.last = None - self.recentw = None - - def field(self, row, column, name, label): - # create a field - Label(self, text=label).grid(row=row, column=column, sticky=E) - self.fields[name] = Text(self, height=1, state=DISABLED) - self.fields[name].grid(row=row, column=column + 1, sticky=W+E); - - def set(self, name, value): - # set a field's value - f = self.fields[name] - f.config(state=NORMAL) - f.delete(1.0, END) - f.insert(END, value) - f.config(state=DISABLED) - - def playing(self, p): - # called with new what's-playing information - values = {} - if p: - for tpart in ['artist', 'album', 'title']: - values[tpart] = part(client, p['track'], 'display', tpart) - try: - self.length = client.length(p['track']) - except disorder.operationError: - self.length = 0 - self.started = int(p['played']) - else: - self.length = 0 - for k in self.fields.keys(): - if k in values: - self.set(k, values[k]) - else: - self.set(k, "") - self.length_bar() - - def length_bar(self): - if self.length and self.length > 0: - self.p.update(time.time() - self.started, self.length) - else: - self.p.clear() - - def update_length(self): - self.length_bar() - self.after(1000, self.update_length) - - def poll(self, c): - p = c.playing() - if p != self.last: - intercom.put(lambda: self.playing(p)) - self.last = p - - def quit(self): - sys.exit(0) - - def scratch(self): - client.scratch() - - def recent_close(self): - self.recentw.destroy() - self.recentw = None - - def recent(self): - if self.recentw: - self.recentw.deiconify() - self.recentw.lift() - else: - w = 80*tracklistFont.measure('A') - h = 40*tracklistFont.metrics("linespace") - self.recentw = Toplevel() - self.recentw.protocol("WM_DELETE_WINDOW", self.recent_close) - self.recentw.title("Recently Played") - # XXX for some reason Toplevel(width=w,height=h) doesn't seem to work - self.recentw.geometry("%dx%d" % (w,h)) - w = RecentWidget(self.recentw) - w.pack(fill=BOTH, expand=1) - mst.add(w); - -class TrackListWidget(Frame): - def __init__(self, master=None, **kw): - Frame.__init__(self, master, **kw) - self.yscrollbar = Scrollbar(self) - self.xscrollbar = Scrollbar(self, orient=HORIZONTAL) - self.canvas = Canvas(self, - xscrollcommand=self.xscrollbar.set, - yscrollcommand=self.yscrollbar.set) - self.xscrollbar.config(command=self.canvas.xview) - self.yscrollbar.config(command=self.canvas.yview) - self.canvas.grid(row=0, column=0, sticky=N+S+E+W) - self.yscrollbar.grid(row=0, column=1, sticky=N+S) - self.xscrollbar.grid(row=1, column=0, sticky=E+W) - self.columnconfigure(0,weight=1) - self.rowconfigure(0,weight=1) - self.last = None - self.default_cursor = self['cursor'] - self.configure(cursor="watch") - - def queue(self, q, w_artists, w_albums, w_titles, artists, albums, titles): - # called with new queue state - # delete old contents - try: - for i in self.canvas.find_all(): - self.canvas.delete(i) - except TclError: - # if the call was queued but not received before the window was deleted - # we might get an error from Tcl/Tk, which no longer knows the window, - # here - return - w = tracklistHFont.measure("Artist") - if w > w_artists: - w_artists = w - w = tracklistHFont.measure("Album") - if w > w_albums: - w_albums = w - w = tracklistHFont.measure("Title") - if w > w_titles: - w_titles = w - hheading = tracklistHFont.metrics("linespace") - h = tracklistFont.metrics('linespace') - x_artist = 8 - x_album = x_artist + w_artists + 16 - x_title = x_album + w_albums + 16 - w = x_title + w_titles + 8 - self.canvas['scrollregion'] = (0, 0, w, h * len(artists) + hheading) - self.canvas.create_text(x_artist, 0, text="Artist", - font=tracklistHFont, - anchor='nw') - self.canvas.create_text(x_album, 0, text="Album", - font=tracklistHFont, - anchor='nw') - self.canvas.create_text(x_title, 0, text="Title", - font=tracklistHFont, - anchor='nw') - y = hheading - for n in range(0,len(artists)): - artist = artists[n] - album = albums[n] - title = titles[n] - if artist != "": - self.canvas.create_text(x_artist, y, text=artist, - font=tracklistFont, - anchor='nw') - if album != "": - self.canvas.create_text(x_album, y, text=album, - font=tracklistFont, - anchor='nw') - if title != "": - self.canvas.create_text(x_title, y, text=title, - font=tracklistFont, - anchor='nw') - y += h - self.last = q - self.configure(cursor=self.default_cursor) - - def poll(self, c): - q = self.getqueue(c) - if q != self.last: - # we do the track name calculation in the background thread so that - # the gui can still be responsive - artists = [] - albums = [] - titles = [] - w_artists = w_albums = w_titles = 16 - for t in q: - artist = part(c, t['track'], 'display', 'artist') - album = part(c, t['track'], 'display', 'album') - title = part(c, t['track'], 'display', 'title') - w = tracklistFont.measure(artist) - if w > w_artists: - w_artists = w - w = tracklistFont.measure(album) - if w > w_albums: - w_albums = w - w = tracklistFont.measure(title) - if w > w_titles: - w_titles = w - artists.append(artist) - albums.append(album) - titles.append(title) - intercom.put(lambda: self.queue(q, w_artists, w_albums, w_titles, - artists, albums, titles)) - self.last = q - -class QueueWidget(TrackListWidget): - def __init__(self, master=None, **kw): - TrackListWidget.__init__(self, master, **kw) - - def getqueue(self, c): - return c.queue() - -class RecentWidget(TrackListWidget): - def __init__(self, master=None, **kw): - TrackListWidget.__init__(self, master, **kw) - - def getqueue(self, c): - l = c.recent() - l.reverse() - return l - -class MonitorStateThread: - # thread to pick up current server state and publish it - # - # Creates a client and monitors it in a daemon thread for state changes. - # Whenever one occurs, call w.poll(c) for every member w of widgets with - # a client owned by the thread in which the call occurs. - def __init__(self, widgets, masterclient=None): - self.logclient = disorder.client() - self.client = disorder.client() - self.clientlock = threading.Lock() - if not masterclient: - masterclient = disorder.client() - self.masterclient = masterclient - self.widgets = widgets - self.lock = threading.Lock() - # the main thread - self.thread = threading.Thread(target=self.run) - self.thread.setDaemon(True) - self.thread.start() - # spare thread for processing additions - self.adderq = Queue.Queue() - self.adder = threading.Thread(target=self.runadder) - self.adder.setDaemon(True) - self.adder.start() - - def notify(self, line): - self.lock.acquire() - widgets = self.widgets - self.lock.release() - for w in widgets: - self.clientlock.acquire() - w.poll(self.client) - self.clientlock.release() - return 1 - - def add(self, w): - self.lock.acquire() - self.widgets.append(w) - self.lock.release() - self.adderq.put(lambda client: w.poll(client)) - - def remove(self, what): - self.lock.acquire() - self.widgets.remove(what) - self.lock.release() - - def run(self): - self.notify("") - self.logclient.log(lambda client, line: self.notify(line)) - - def runadder(self): - while True: - item = self.adderq.get() - self.clientlock.acquire() - item(self.client) - self.clientlock.release() - -######################################################################## - -def usage(s): - # display usage on S - s.write( - """Usage: - - tkdisorder [OPTIONS] - -Options: - - -h, --help Display this message - -V, --version Display version number - -tkdisorder is copyright (c) 2004, 2005 Richard Kettlewell. -""") - -######################################################################## - -try: - opts, rest = getopt.getopt(sys.argv[1:], "Vh", ["version", "help"]) -except getopt.GetoptError, e: - sys.stderr.write("ERROR: %s, try --help for help\n" % e.msg) - sys.exit(1) -for o, v in opts: - if o in ('-V', '--version'): - print "%s" % disorder.version - sys.stdout.close() - sys.exit(0) - if o in ('h', '--help'): - usage(sys.stdout) - sys.stdout.close() - sys.exit(0) - -client = disorder.client() # master thread's client - -root = Tk() -root.title("DisOrder") - -tracklistFont = tkFont.Font(family='Helvetica', size=10) -tracklistHFont = tracklistFont.copy() -tracklistHFont.config(weight="bold") - -p = PlayingWidget(root) -p.pack(fill=BOTH, expand=1) - -q = QueueWidget(root) -q.pack(fill=BOTH, expand=1) - -intercom = Intercom(root) # only need a single intercom -mst = MonitorStateThread([p, q], client) - -root.mainloop() - -# Local Variables: -# py-indent-offset:2 -# comment-column:40 -# fill-column:79 -# End: