~ubuntu-branches/debian/wheezy/idjc/wheezy

« back to all changes in this revision

Viewing changes to python/playergui.py

  • Committer: Package Import Robot
  • Author(s): Alessio Treglia
  • Date: 2011-12-03 16:33:59 UTC
  • mfrom: (0.2.6)
  • Revision ID: package-import@ubuntu.com-20111203163359-dq5fy9i756jpoy29
Tags: 0.8.6-1
* New upstream release.
* debian/control:
  - Wrap and sort.
  - Build-depend on autopoint.
  - Drop autotools-dev, unnecessary.
* Drop the whole patch set, none of them is still needed.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#   IDJCmedia.py: GUI code for main media players in IDJC
 
2
#   Copyright (C) 2005-2007 Stephen Fairchild (s-fairchild@users.sourceforge.net)
 
3
#
 
4
#   This program is free software: you can redistribute it and/or modify
 
5
#   it under the terms of the GNU General Public License as published by
 
6
#   the Free Software Foundation, either version 2 of the License, or
 
7
#   (at your option) any later version.
 
8
#
 
9
#   This program is distributed in the hope that it will be useful,
 
10
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
#   GNU General Public License for more details.
 
13
#
 
14
#   You should have received a copy of the GNU General Public License
 
15
#   along with this program in the file entitled COPYING.
 
16
#   If not, see <http://www.gnu.org/licenses/>.
 
17
 
 
18
__all__ = [ 'IDJC_Media_Player', 'make_arrow_button', 'supported' ]
 
19
 
 
20
import os
 
21
import sys
 
22
import time
 
23
import urllib
 
24
import subprocess
 
25
import random
 
26
import signal
 
27
import re
 
28
import xml.dom.minidom as mdom
 
29
import warnings
 
30
import gettext
 
31
from stat import *
 
32
from collections import deque, namedtuple, defaultdict
 
33
from functools import partial
 
34
 
 
35
import glib
 
36
import gobject
 
37
import gtk
 
38
import pango
 
39
import mutagen
 
40
from mutagen.mp3 import MP3
 
41
from mutagen.flac import FLAC
 
42
from mutagen.mp4 import MP4
 
43
from mutagen.easyid3 import EasyID3
 
44
from mutagen.apev2 import APEv2
 
45
from mutagen.asf import ASF
 
46
   
 
47
from idjc import FGlobs, PGlobs
 
48
from . import popupwindow
 
49
from .mutagentagger import *
 
50
from .freefunctions import *
 
51
from .gtkstuff import threadslock
 
52
from .prelims import *
 
53
from .tooltips import main_tips
 
54
 
 
55
 
 
56
t = gettext.translation(FGlobs.package_name, FGlobs.localedir, fallback=True)
 
57
_ = t.gettext
 
58
def N_(text):
 
59
   return text
 
60
 
 
61
pm = ProfileManager()
 
62
set_tip = main_tips.set_tip
 
63
 
 
64
 
 
65
# Suppress warning when None is placed in a ListStore element where some kind of GObject should go. 
 
66
warnings.filterwarnings("ignore", r"g_object_set_qdata: assertion `G_IS_OBJECT \(object\)' failed")
 
67
# Suppress warning when drag 'n dropping tracks to an empty playlist window.
 
68
warnings.filterwarnings("ignore", "IA__gtk_tree_view_scroll_to_cell: assertion `tree_view->priv->tree != NULL' failed.*")
 
69
 
 
70
 
 
71
# Named tuple for a playlist row.
 
72
class PlayerRow(namedtuple("PlayerRow", "rsmeta filename length meta encoding title artist replaygain cuesheet album")):
 
73
   def __nonzero__(self):
 
74
      return self.rsmeta != "<s>valid</s>"
 
75
 
 
76
# Playlist value indicating a file isn't valid.
 
77
NOTVALID = PlayerRow("<s>valid</s>", "", 0, "", "latin1", "", "", 0.0, None, "")
 
78
 
 
79
# Replay Gain value to indicate default.
 
80
RGDEF = 100.0
 
81
 
 
82
# Pathname is an absolute file path or 'missing' or 'pregap'.
 
83
CueSheetTrack = namedtuple("CueSheetTrack", "pathname play tracknum index performer title offset duration replaygain")
 
84
 
 
85
class CueSheetListStore(gtk.ListStore):
 
86
   _columns = (str, int, int, int, str, str, int, int, float)
 
87
   assert len(_columns) == len(CueSheetTrack._fields)
 
88
   def __nonzero__(self):
 
89
      return len(self) != 0
 
90
      
 
91
   def __getitem__(self, i):
 
92
      return CueSheetTrack(*gtk.ListStore.__getitem__(self, i))
 
93
      
 
94
   def __iter__(self):
 
95
      i = 0
 
96
      while 1:
 
97
         try:
 
98
            val = self[i]
 
99
         except IndexError:
 
100
            break
 
101
         yield val
 
102
         i += 1
 
103
      
 
104
   def __init__(self):
 
105
      gtk.ListStore.__init__(self, *self._columns)
 
106
      
 
107
class NumberedLabel(gtk.Label):
 
108
   attrs = pango.AttrList()
 
109
   attrs.insert(pango.AttrFamily("Monospace" , 0, 3))
 
110
   #attrs.insert(pango.AttrWeight(pango.WEIGHT_BOLD, 0, 3))
 
111
   
 
112
   def set_value(self, value):
 
113
      self.set_text("--" if value is None else "%02d" % value)
 
114
      
 
115
   def get_value(self):
 
116
      text = self.get_text()
 
117
      return None if text == "--" else int(self.text)
 
118
     
 
119
   def __init__(self, value=None):
 
120
      gtk.Label.__init__(self)
 
121
      self.set_attributes(self.attrs)
 
122
      self.set_value(value)
 
123
 
 
124
class CellRendererDuration(gtk.CellRendererText):
 
125
   """Render a value in frames as a time mm:ss:hs right justified."""
 
126
   
 
127
   __gproperties__ = { "duration" : (gobject.TYPE_UINT64, "duration",
 
128
      "playback time expressed in CD audio frames",
 
129
      0, long(3e9), 0, gobject.PARAM_WRITABLE) }
 
130
   
 
131
   def __init__(self):
 
132
      gtk.CellRendererText.__init__(self)
 
133
      self.set_property("xalign", 1.0)
 
134
 
 
135
   def do_set_property(self, property, value):
 
136
      if property.name == "duration":
 
137
         s, f = divmod(value, 75)
 
138
         m, s = divmod(s, 60)
 
139
         self.props.text = "%d:%02d.%02d" % (m, s, f // 0.75)
 
140
 
 
141
class CuesheetPlaylist(gtk.Frame):
 
142
   def description_col_func(self, column, cell, model, iter):
 
143
      line = model[model.get_path(iter)[0]]
 
144
      desc = " - ".join(x for x in (line.performer, line.title) if x)
 
145
      desc = desc or os.path.splitext(os.path.split(line.pathname)[1])[0]
 
146
      cell.props.text = desc
 
147
 
 
148
   def play_clicked(self, cellrenderer, path):
 
149
      model = self.treeview.get_model()
 
150
      iter = model.get_iter(path)
 
151
      col = CueSheetTrack._fields.index("play")
 
152
      val = model.get_value(iter, col)
 
153
      model.set_value(iter, col, not val)
 
154
 
 
155
   def __init__(self):
 
156
      gtk.Frame.__init__(self, " %s " % _('Cuesheet Playlist'))
 
157
      self.set_border_width(3)
 
158
      
 
159
      vbox = gtk.VBox()
 
160
      vbox.set_border_width(4)
 
161
      vbox.set_spacing(2)
 
162
      self.add(vbox)
 
163
      vbox.show()
 
164
      hbox = gtk.HBox()
 
165
      hbox.set_spacing(6)
 
166
      vbox.pack_start(hbox, False)
 
167
      
 
168
      def nextprev_unit(label_text):
 
169
         def icon_button(stock_item):
 
170
            button = gtk.Button()
 
171
            image = gtk.image_new_from_stock(stock_item, gtk.ICON_SIZE_MENU)
 
172
            button.set_image(image)
 
173
            image.show()
 
174
            return button
 
175
         
 
176
         box = gtk.HBox()
 
177
         box.set_spacing(6)
 
178
         prev = icon_button(gtk.STOCK_MEDIA_PREVIOUS)
 
179
         box.pack_start(prev)
 
180
         prev.show()
 
181
         
 
182
         lhbox = gtk.HBox()
 
183
         box.pack_start(lhbox, False)
 
184
         lhbox.show()
 
185
         
 
186
         label = gtk.Label(label_text + " ")
 
187
         lhbox.pack_start(label, False)
 
188
         label.show()
 
189
         numbered = NumberedLabel()
 
190
         lhbox.pack_start(numbered, False)
 
191
         numbered.show()
 
192
         
 
193
         next = icon_button(gtk.STOCK_MEDIA_NEXT)
 
194
         box.pack_start(next)
 
195
         next.show()
 
196
         box.show()
 
197
         return box, prev, next
 
198
         
 
199
      # TC: Cuesheet term.
 
200
      box_t, self.prev_track, self.next_track = nextprev_unit(_('Track'))
 
201
      # TC: Cuesheet term.
 
202
      box_i, self.prev_index, self.next_index = nextprev_unit(_('Index'))
 
203
      hbox.pack_start(box_t, fill=False)
 
204
      hbox.pack_start(box_i, fill=False)
 
205
      hbox.show()
 
206
      
 
207
      scrolled = gtk.ScrolledWindow()
 
208
      scrolled.set_size_request(-1, 117)
 
209
      scrolled.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
 
210
      scrolled.set_shadow_type(gtk.SHADOW_ETCHED_IN)
 
211
      vbox.pack_start(scrolled)
 
212
      scrolled.show()
 
213
      self.treeview = gtk.TreeView()
 
214
      #self.treeview.set_headers_visible(True)
 
215
      scrolled.add(self.treeview)
 
216
      self.treeview.show()
 
217
      #self.treeview.set_fixed_height_mode(True)
 
218
      
 
219
      renderer_toggle = gtk.CellRendererToggle()
 
220
      renderer_toggle.connect("toggled", self.play_clicked)
 
221
      renderer_text_desc = gtk.CellRendererText()
 
222
      renderer_text_desc.set_property("ellipsize", pango.ELLIPSIZE_END)
 
223
      renderer_text_rjust = gtk.CellRendererText()
 
224
      renderer_text_rjust.set_property("xalign", 0.9)
 
225
      renderer_duration = CellRendererDuration()
 
226
      
 
227
      # TC: Column heading, whether to play.
 
228
      play = gtk.TreeViewColumn(_('Play'), renderer_toggle, active=1)
 
229
      self.treeview.append_column(play)
 
230
      # TC: Column heading, the track number.
 
231
      track = gtk.TreeViewColumn(_('Trk'), renderer_text_rjust, text=2)
 
232
      self.treeview.append_column(track)
 
233
      # TC: Column heading, the index number.
 
234
      index = gtk.TreeViewColumn(_('Ind'), renderer_text_rjust, text=3)
 
235
      self.treeview.append_column(index)
 
236
      description = gtk.TreeViewColumn(_('Description'), renderer_text_desc)
 
237
      description.set_expand(True)
 
238
      description.set_cell_data_func(renderer_text_desc, self.description_col_func)
 
239
      self.treeview.append_column(description)
 
240
      # TC: Playback time.
 
241
      duration = gtk.TreeViewColumn(_('Duration'), renderer_duration)
 
242
      duration.add_attribute(renderer_duration, "duration", 7)
 
243
      self.treeview.append_column(duration)
 
244
 
 
245
class ButtonFrame(gtk.Frame):
 
246
   def __init__(self, title):
 
247
      gtk.Frame.__init__(self)
 
248
      attrlist = pango.AttrList()
 
249
      attrlist.insert(pango.AttrSize(8000, 0, len(title)))
 
250
      label = gtk.Label(title)
 
251
      label.set_attributes(attrlist)
 
252
      self.set_label_widget(label)
 
253
      label.show()
 
254
      self.hbox = gtk.HBox()
 
255
      self.add(self.hbox)
 
256
      self.hbox.show()
 
257
      self.set_shadow_type(gtk.SHADOW_NONE)
 
258
      self.set_label_align(0.5, 0.5)
 
259
 
 
260
class ExternalPL(gtk.Frame):
 
261
   def get_next(self):
 
262
      next = self._get_next()
 
263
      if next is None:
 
264
         return self._get_next()
 
265
      return next
 
266
   
 
267
   def _get_next(self):
 
268
      if self.active.get_active():
 
269
         try:
 
270
            line = self.gen.next()
 
271
         except StopIteration:
 
272
            self.gen = self.player.get_elements_from([self.pathname])
 
273
            line = None
 
274
         return line
 
275
      return None
 
276
   
 
277
   def cb_active(self, widget):
 
278
      if widget.get_active():
 
279
         self.pathname = (self.filechooser, self.directorychooser)[self.radio_directory.get_active()].get_filename()
 
280
         if self.pathname is not None:
 
281
            self.gen = self.player.get_elements_from([self.pathname])
 
282
            try:
 
283
               line = self.gen.next()
 
284
            except StopIteration:
 
285
               widget.set_active(False)
 
286
            else:
 
287
               self.player.stop.clicked()
 
288
               self.player.liststore.clear()
 
289
               self.player.liststore.append(line)
 
290
               self.player.treeview.get_selection().select_path(0)
 
291
               self.vbox.set_sensitive(False)
 
292
         else:
 
293
            widget.set_active(False)
 
294
      else:
 
295
         self.vbox.set_sensitive(True)
 
296
   
 
297
   def cb_newselection(self, widget, radio):
 
298
      radio.set_active(True)
 
299
   
 
300
   def make_line(self, radio, dialog):
 
301
      button = gtk.FileChooserButton(dialog)
 
302
      dialog.set_current_folder(os.path.expanduser("~"))
 
303
      hbox = gtk.HBox()
 
304
      hbox.pack_start(radio, False, False, 0)
 
305
      hbox.pack_start(button, True, True, 0)
 
306
      radio.show()
 
307
      button.show()
 
308
      return hbox
 
309
   
 
310
   def __init__(self, player):
 
311
      self.player = player
 
312
      gtk.Frame.__init__(self, " %s " % _('External Playlist'))
 
313
      self.set_border_width(4)
 
314
      hbox = gtk.HBox()
 
315
      self.add(hbox)
 
316
      hbox.set_border_width(8)
 
317
      hbox.set_spacing(10)
 
318
      hbox.show()
 
319
      self.vbox = gtk.VBox()
 
320
      hbox.pack_start(self.vbox, True, True, 0)
 
321
      self.vbox.show()
 
322
      # TC: Button text to activate an external playlist.
 
323
      self.active = gtk.ToggleButton("  %s  " % _('Active'))
 
324
      self.active.connect("toggled", self.cb_active)
 
325
      hbox.pack_end(self.active, False, False, 0)
 
326
      self.active.show()
 
327
 
 
328
      filefilter = gtk.FileFilter()
 
329
      filefilter.add_pattern("*.m3u")
 
330
      filefilter.add_pattern("*.pls")
 
331
      filefilter.add_pattern("*.xspf")
 
332
 
 
333
      self.filechooser = gtk.FileChooserDialog(title = _('Choose a playlist file') + pm.title_extra, buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT))
 
334
      self.filechooser.set_filter(filefilter)
 
335
      self.directorychooser = gtk.FileChooserDialog(title = _('Choose a media directory') + pm.title_extra, action = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT))
 
336
      
 
337
      self.radio_file = gtk.RadioButton()
 
338
      self.radio_directory = gtk.RadioButton(self.radio_file)
 
339
      
 
340
      self.filechooser.connect("selection-changed", self.cb_newselection, self.radio_file)
 
341
      self.directorychooser.connect("selection-changed", self.cb_newselection, self.radio_directory)
 
342
      
 
343
      fbox = self.make_line(self.radio_file, self.filechooser)
 
344
      set_tip(fbox, _('Choose a playlist file.'))
 
345
      dbox = self.make_line(self.radio_directory, self.directorychooser)
 
346
      set_tip(dbox, _('Choose a folder/directory of music.'))
 
347
      
 
348
      self.vbox.pack_start(fbox, True, True, 0)
 
349
      self.vbox.pack_start(dbox, True, True, 0)
 
350
      
 
351
      fbox.show()
 
352
      dbox.show()
 
353
 
 
354
class AnnouncementDialog(gtk.Dialog):
 
355
   def write_changes(self, widget):
 
356
      m = "%02d" % int(self.minutes.get_value())
 
357
      s = "%02d" % int(self.seconds.get_value())
 
358
      self.model.set_value(self.iter, 3, "00" + m + s)
 
359
      b = self.tv.get_buffer()
 
360
      text = b.get_text(b.get_start_iter(), b.get_end_iter())
 
361
      self.model.set_value(self.iter, 4, urllib.quote(text))
 
362
      self.player.reselect_please = True
 
363
   def restore_mic_playnext(self, widget):
 
364
      self.player.parent.mic_opener.close_all()
 
365
      if self.model.iter_next(self.iter) is not None:
 
366
         self.player.play.clicked()
 
367
   def delete_announcement(self, widget, event=None):
 
368
      self.model.remove(self.iter)
 
369
      self.player.reselect_please = True
 
370
      gtk.Dialog.destroy(self)
 
371
   def timeout_remove(self, widget):
 
372
      gobject.source_remove(self.timeout)
 
373
   def timer_update(self, lock = True):
 
374
      if lock:
 
375
         gtk.gdk.threads_enter()
 
376
      inttime = int(self.cdt - time.time())
 
377
      if inttime != self.oldinttime:
 
378
         if inttime > 0:
 
379
            stime = "%2d:%02d" % divmod(inttime, 60)
 
380
            self.countdownlabel.set_text(stime)
 
381
            if inttime == 5:
 
382
               self.attrlist.change(self.fontcolour_red)
 
383
            if lock:
 
384
               gtk.gdk.threads_leave()
 
385
            return True
 
386
         else:
 
387
            self.countdownlabel.set_text("--:--")
 
388
            self.attrlist.change(self.fontcolour_black)
 
389
            if lock:
 
390
               gtk.gdk.threads_leave()
 
391
            return False
 
392
      if lock:
 
393
         gtk.gdk.threads_leave()
 
394
      return True
 
395
   def cb_keypress(self, widget, event):
 
396
      self.player.parent.cb_key_capture(widget, event)
 
397
      if event.keyval == 65307:
 
398
         return True
 
399
      if event.keyval == 65288 and self.mode == "active":
 
400
         self.cancel_button.clicked()
 
401
   def __init__(self, player, model, iter, mode):
 
402
      self.player = player
 
403
      self.model = model
 
404
      self.iter = iter
 
405
      self.mode = mode
 
406
      if mode == "initial":
 
407
         model.set_value(iter, 3, "110000")
 
408
         gtk.Dialog.__init__(self, _('Create a new announcement'), player.parent.window, gtk.DIALOG_MODAL)
 
409
      elif mode == "delete_modify":
 
410
         gtk.Dialog.__init__(self, _('Modify or Delete this announcement'), player.parent.window, gtk.DIALOG_MODAL)
 
411
      elif mode == "active":
 
412
         gtk.Dialog.__init__(self, _('Announcement'), player.parent.window, gtk.DIALOG_MODAL)
 
413
      self.connect("key-press-event", self.cb_keypress)
 
414
      ivbox = gtk.VBox()
 
415
      ivbox.set_border_width(10)
 
416
      ivbox.set_spacing(8)
 
417
      self.vbox.add(ivbox)
 
418
      ivbox.show()
 
419
      sw = gtk.ScrolledWindow()
 
420
      sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
 
421
      sw.set_shadow_type(gtk.SHADOW_IN)
 
422
      sw.set_size_request(500, 200)
 
423
      ivbox.pack_start(sw, True, True, 0)
 
424
      sw.show()
 
425
      self.tv = gtk.TextView()
 
426
      if mode == "active":
 
427
         self.tv.unset_flags(gtk.CAN_FOCUS)
 
428
      sw.add(self.tv)
 
429
      self.tv.show()
 
430
      ihbox = gtk.HBox()
 
431
      ivbox.pack_start(ihbox, False, False, 0)
 
432
      ihbox.show()
 
433
      
 
434
      chbox = gtk.HBox()
 
435
      
 
436
      if mode == "initial" or mode == "delete_modify":
 
437
         # TC: The time format as minutes and seconds.
 
438
         countdown_label = gtk.Label('(%s)   ' % _("mm:ss"))
 
439
         chbox.pack_start(countdown_label, False, False, 0)
 
440
         countdown_label.show()
 
441
         minutes_adj = gtk.Adjustment(0.0, 0.0, 59.0, 1.0)
 
442
         seconds_adj = gtk.Adjustment(0.0, 0.0, 59.0, 1.0)
 
443
         self.minutes = gtk.SpinButton(minutes_adj)
 
444
         self.seconds = gtk.SpinButton(seconds_adj)
 
445
         sep = gtk.Label(":")
 
446
         chbox.pack_start(self.minutes, False, False, 0)
 
447
         self.minutes.show()
 
448
         chbox.pack_start(sep, False, False, 0)
 
449
         sep.show()
 
450
         chbox.pack_start(self.seconds, False, False, 0)
 
451
         self.seconds.show()
 
452
         
 
453
      if mode == "active":
 
454
         cdtime = model.get_value(iter, 3)[2:6]
 
455
         if cdtime != "0000":
 
456
            cd = int(cdtime[:2]) * 60 + int(cdtime[2:])
 
457
            self.cdt = time.time() + cd + 1
 
458
            self.countdownlabel = gtk.Label()
 
459
            self.attrlist = pango.AttrList()
 
460
            fontdesc = pango.FontDescription("monospace bold condensed 15")
 
461
            self.attrlist.insert(pango.AttrFontDesc(fontdesc, 0, 5))
 
462
            self.fontcolour_black = pango.AttrForeground(0, 0, 0, 0, 5)
 
463
            self.fontcolour_red = pango.AttrForeground(65535, 0, 0, 0, 5)
 
464
            self.attrlist.insert(self.fontcolour_black)
 
465
            self.countdownlabel.set_attributes(self.attrlist)
 
466
            self.oldinttime = -2
 
467
            self.timer_update(False)
 
468
            self.timeout = gobject.timeout_add(100, self.timer_update)
 
469
            self.connect("destroy", self.timeout_remove)
 
470
            chbox.pack_start(self.countdownlabel, True, False, 0)
 
471
            self.countdownlabel.show()
 
472
         
 
473
      ihbox.pack_start(chbox, True, False, 0)
 
474
      chbox.show()
 
475
      
 
476
      if mode == "delete_modify" or mode == "active":
 
477
         lr = model.get_value(iter, 3)
 
478
         text = model.get_value(iter, 4)
 
479
         self.tv.get_buffer().set_text(urllib.unquote(text))
 
480
      if mode == "delete_modify":
 
481
         self.minutes.set_value((int(lr[2:4])))
 
482
         self.seconds.set_value((int(lr[4:6])))
 
483
      if mode == "active":
 
484
         self.player.parent.mic_opener.open_auto("announcement")
 
485
         
 
486
      thbox = gtk.HBox()
 
487
      thbox.set_spacing(4)
 
488
      ivbox.pack_start(thbox, False, False, 0)
 
489
      thbox.show()
 
490
      # TC: Alongside the name of the next track.
 
491
      label = gtk.Label(_('Next track'))
 
492
      thbox.pack_start(label, False, False, 0)
 
493
      label.show()
 
494
      entry = gtk.Entry()
 
495
      entry.set_editable(False)
 
496
      entry.unset_flags(gtk.CAN_FOCUS)
 
497
      ni = model.iter_next(iter)
 
498
      if ni and model.get_value(ni, 0)[0] != ">" : 
 
499
         entry.set_text(model.get_value(ni, 3))
 
500
      thbox.pack_start(entry, True, True, 0)
 
501
      entry.show()
 
502
      
 
503
      self.ok_button = gtk.Button(gtk.STOCK_OK)
 
504
      if mode == "initial" or mode == "delete_modify":
 
505
         self.ok_button.connect("clicked", self.write_changes)
 
506
      if mode == "active":
 
507
         self.ok_button.connect("clicked", self.restore_mic_playnext)
 
508
      self.ok_button.connect_object("clicked", gtk.Dialog.destroy, self)
 
509
      self.ok_button.set_use_stock(True)
 
510
      self.action_area.add(self.ok_button)
 
511
      self.ok_button.show()
 
512
      if mode == "delete_modify":
 
513
         self.delete_button = gtk.Button(gtk.STOCK_DELETE)
 
514
         self.delete_button.connect("clicked", self.delete_announcement)
 
515
         self.delete_button.set_use_stock(True)
 
516
         self.action_area.add(self.delete_button)
 
517
         self.delete_button.show()
 
518
      self.cancel_button = gtk.Button(gtk.STOCK_CANCEL)
 
519
      if mode == "initial":
 
520
         self.connect("delete-event", self.delete_announcement)
 
521
         self.cancel_button.connect("clicked", self.delete_announcement)
 
522
      else:
 
523
         self.cancel_button.connect_object("clicked", gtk.Dialog.destroy, self)
 
524
      self.cancel_button.set_use_stock(True)
 
525
      self.action_area.add(self.cancel_button)
 
526
      self.cancel_button.show()
 
527
      if mode == "active":
 
528
         self.ok_button.grab_focus()
 
529
      
 
530
class Supported(object):
 
531
   def _check(self, pathname, which):
 
532
      ext = os.path.splitext(pathname)[1].lower()
 
533
      return ext in which and ext or False
 
534
   def playlists_as_text(self):
 
535
      return "(*" + ", *".join(self.playlists) + ")"
 
536
   def media_as_text(self):
 
537
      return "(*" + ", *".join(self.media) + ")"
 
538
   def check_media(self, pathname):
 
539
      return self._check(pathname, self.media)
 
540
   def check_playlists(self, pathname):
 
541
      return self._check(pathname, self.playlists)
 
542
   def __init__(self):
 
543
      self.media = [ ".ogg", ".oga", ".wav", ".aiff", ".au", ".txt", ".cue" ]
 
544
      self.playlists = [ ".m3u", ".xspf", ".pls" ]
 
545
      
 
546
      if FGlobs.avcodec and FGlobs.avformat:
 
547
         self.media.append(".avi")
 
548
         self.media.append(".wma")
 
549
         self.media.append(".ape")
 
550
         self.media.append(".mpc")
 
551
         self.media.append(".mp4")
 
552
         self.media.append(".m4a")
 
553
         self.media.append(".m4b")
 
554
         self.media.append(".m4p")
 
555
      if FGlobs.flacenabled:
 
556
         self.media.append(".flac")
 
557
      if FGlobs.speexenabled:
 
558
         self.media.append(".spx")
 
559
 
 
560
supported = Supported()
 
561
 
 
562
# Arrow button creation helper function
 
563
def make_arrow_button(self, arrow_type, shadow_type, data):
 
564
   button = gtk.Button();
 
565
   arrow = gtk.Arrow(arrow_type, shadow_type);
 
566
   button.add(arrow)
 
567
   button.connect("clicked", self.callback, data)
 
568
   button.show()
 
569
   arrow.show()
 
570
   return button
 
571
 
 
572
def get_number_for(token, string):
 
573
   try:
 
574
      end = string.rindex(token)
 
575
      start = end - 1
 
576
      while start >= 0 and (string[start].isdigit() or string[start] == "."):
 
577
         start = start - 1
 
578
      return int(float(string[start+1:end]))
 
579
   except ValueError:
 
580
      return 0
 
581
   
 
582
class nice_listen_togglebutton(gtk.ToggleButton):
 
583
   def __init__(self, label = None, use_underline = True):
 
584
      try:
 
585
         gtk.ToggleButton.__init__(self, label, use_underline)
 
586
      except RuntimeError:
 
587
         gtk.ToggleButton.__init__(self, label)
 
588
   def __str__(self):
 
589
      return gtk.ToggleButton.__str__(self) + " auto inconsistent when insensitive"
 
590
   def set_sensitive(self, bool):
 
591
      if bool is False:
 
592
         gtk.ToggleButton.set_sensitive(self, False)
 
593
         gtk.ToggleButton.set_inconsistent(self, True)
 
594
      else:
 
595
         gtk.ToggleButton.set_sensitive(self, True)
 
596
         gtk.ToggleButton.set_inconsistent(self, False)
 
597
        
 
598
 
 
599
class CueSheet(object):
 
600
   """A class for parsing cue sheets."""
 
601
 
 
602
   _operands = (("PERFORMER", "SONGWRITER", "TITLE", "PREGAP", "POSTGAP"),
 
603
                ("FILE", "TRACK", "INDEX"))
 
604
   _operands = dict((k, v + 1) for v, o in enumerate(_operands) for k in o)
 
605
 
 
606
   # Try to split a string into three parts with a large quoted section
 
607
   # and parts fore and aft.
 
608
   _quoted = re.compile(r'(.*?)[ \t]"(.*)"[ \t](.*)').match
 
609
 
 
610
   def _time_handler(self, time_str):
 
611
      """Returns the number of frames of audio (75ths of seconds)
 
612
 
 
613
      Minutes can exceed 99 going beyond the cue sheet standard.
 
614
 
 
615
      """
 
616
      try:
 
617
         mm, ss, ff = [int(x) for x in time_str.split(":")]
 
618
      except ValueError:
 
619
         raise ValueError("time must be in (m*)mm:ss:ff format %s" % self.line)
 
620
 
 
621
      if ff < 0 or ff > 74 or ss < 0 or ss > 59 or mm < 0:
 
622
         raise ValueError("a time value is out of range %s" % self.line)
 
623
 
 
624
      return ff + 75 * ss + 75 * 60 * mm
 
625
 
 
626
   def _int_handler(self, int_str):
 
627
      """Attempt to convert to an integer."""
 
628
 
 
629
      try:
 
630
         ret = int(int_str)
 
631
      except:
 
632
         raise ValueError("expected integer value for %s %s", (int_str, self.line))
 
633
      return ret
 
634
 
 
635
   @classmethod
 
636
   def _tokenize(cls, iterable):
 
637
      """Scanner/tokenizer for cue sheets.
 
638
      
 
639
      This routine will iteratively take one line at a time and return
 
640
      the line number, command, and any operands related to the command.
 
641
      
 
642
      Quoted text will have spaces and tabs intact and counts as one,
 
643
      otherwise all consecutive non-whitespace is a token. The first
 
644
      token is the command, the rest are it's operands.
 
645
      
 
646
      """
 
647
      for i, line in enumerate(iterable):
 
648
         line = line.strip() + " "
 
649
         match = cls._quoted(line)
 
650
         if match:
 
651
            left, quoted, right = match.groups()
 
652
            left = left.replace("\t", " ").split()
 
653
            right = right.replace("\t", " ").split()
 
654
         else:
 
655
            left = line.replace("\t", " ").split()
 
656
            right = [""]
 
657
            quoted = ""
 
658
               
 
659
         tokens = filter(lambda x: x, left + [quoted] + right)
 
660
         yield i + 1, tokens[0].upper(), tokens[1:]
 
661
 
 
662
   def _parse_PERFORMER(self):
 
663
      self.segment[self.tracknum][self.command].append(self.operand[0])
 
664
      
 
665
   _parse_SONGWRITER = _parse_TITLE = _parse_PERFORMER
 
666
 
 
667
   def _parse_FILE(self):
 
668
      if not self.operand[1] in ("WAVE", "MP3", "AIFF"):
 
669
         raise ValueError("unsupported file type %s" % self.line)
 
670
         
 
671
      self.filename = self.operand[0]
 
672
      self.prevframes = 0
 
673
      
 
674
   def _parse_TRACK(self):
 
675
      if self.filename is None:
 
676
         raise ValueError("no filename yet specified %s" % self.line)
 
677
 
 
678
      if self.tracknum and self.index < 1:
 
679
         raise ValueError("track %02d lacks a 01 index" % self.tracknum)
 
680
 
 
681
      if self.operand[1] != "AUDIO":
 
682
         raise ValueError("only AUDIO track datatype supported %s" % self.line)
 
683
 
 
684
      num = self._int_handler(self.operand[0])
 
685
      self.tracknum += 1
 
686
      self.index = -1
 
687
      if num != self.tracknum:
 
688
         raise ValueError("unexpected track number %s" % self.line)
 
689
                  
 
690
   def _parse_PREGAP(self):
 
691
      if self.tracknum == 0 or self.index != -1 or "PREGAP" in self.segment[self.tracknum]:
 
692
         raise ValueError("unexpected PREGAP command %s" % self.line)
 
693
         
 
694
      self.segment[self.tracknum]["PREGAP"] = self._time_handler(self.operand[0])
 
695
 
 
696
   def _parse_INDEX(self):
 
697
      if self.tracknum == 0:
 
698
         raise ValueError("no track yet specified %s" % self.line)
 
699
      
 
700
      if "POSTGAP" in self.segment[self.tracknum]:
 
701
         raise ValueError("INDEX command following POSTGAP %s" % self.line)
 
702
      
 
703
      num = self._int_handler(self.operand[0])
 
704
      frames = self._time_handler(self.operand[1])
 
705
      
 
706
      if self.tracknum == 1 and self.index == -1 and frames != 0:
 
707
         raise ValueError("first index must be zero for a file %s" % self.line)
 
708
 
 
709
      if self.index == -1 and num == 1:
 
710
         self.index += 1
 
711
      self.index += 1
 
712
      if num != self.index:
 
713
         raise ValueError("unexpected index number %s" % self.line)
 
714
         
 
715
      if frames < self.prevframes:
 
716
         raise ValueError("index time before the previous index %s" % self.line)
 
717
         
 
718
      if self.prevframes and frames == self.prevframes:
 
719
         raise ValueError("index time no different than previously %s" % self.line)
 
720
         
 
721
      self.segment[self.tracknum][self.index] = (self.filename, frames)
 
722
      
 
723
      self.prevframes = frames
 
724
                  
 
725
   def _parse_POSTGAP(self):
 
726
      if self.tracknum == 0 or self.index < 1 or "POSTGAP" in self.segment[self.tracknum]:
 
727
         raise ValueError("unexpected POSTGAP command %s" % self.line)
 
728
         
 
729
      self.segment[self.tracknum]["POSTGAP"] = self._time_handler(operand[0])
 
730
 
 
731
   def parse(self, iterable):
 
732
      """Return a parsed cuesheet object."""
 
733
      
 
734
      self.filename = None
 
735
      self.tracknum = 0
 
736
      self.segment = defaultdict(partial(defaultdict, list))
 
737
 
 
738
      for self.i, self.command, self.operand in self._tokenize(iterable):
 
739
         if self.command not in self._operands:
 
740
            continue
 
741
 
 
742
         self.line = "on line %d" % self.i
 
743
 
 
744
         if len(self.operand) != self._operands[self.command]:
 
745
            raise ValueError("wrong number of operands got %d required %d %s" %
 
746
                  (len(self.operand), self._operands[self.command], self.line))
 
747
         else:
 
748
            getattr(self, "_parse_" + self.command)()
 
749
 
 
750
      if self.tracknum == 0:
 
751
         raise ValueError("no tracks")
 
752
         
 
753
      if self.index < 1:
 
754
         raise ValueError("track %02d lacks a 01 index" % tracknum)
 
755
       
 
756
      for each in self.segment.itervalues():
 
757
          del each.default_factory
 
758
      del self.segment.default_factory
 
759
                 
 
760
      return self.segment
 
761
 
 
762
       
 
763
class IDJC_Media_Player:
 
764
   playlisttype_extension = tuple(zip(
 
765
      # File format selection items from a list (user can pick only one).
 
766
      (_('By Extension'), _('M3U playlist'), _('XSPF playlist'), _('PLS playlist')),
 
767
      ('', 'm3u', 'xspf', 'pls'),))
 
768
   
 
769
   def make_cuesheet_playlist_entry(self, cue_pathname):
 
770
      cuesheet_liststore = CueSheetListStore()
 
771
      try:
 
772
         with open(cue_pathname) as f:
 
773
            segment_data = CueSheet().parse(f)
 
774
      except (IOError, ValueError), e:
 
775
         print "failed reading cue sheet", cue_pathname
 
776
         print e
 
777
         return NOTVALID
 
778
      
 
779
      basepath = os.path.split(cue_pathname)[0]
 
780
      oldfilename = None
 
781
      totalframes = trackframes = cumulativeframes = 0
 
782
      global_cue_performer = global_cue_title = ""
 
783
 
 
784
      for key, val in sorted(segment_data.iteritems()):
 
785
         track = key
 
786
         cue_performer = ", ".join(val.get("PERFORMER", []))
 
787
         cue_title = ", ".join(val.get("TITLE", []))
 
788
         if key == 0:
 
789
            global_cue_performer = cue_performer
 
790
            global_cue_title = cue_title
 
791
         else:
 
792
            for key2, val2 in sorted(val.iteritems()):
 
793
               if isinstance(key2, int):
 
794
                  index = key2
 
795
                  filename, frames = val2
 
796
                  if filename != oldfilename:
 
797
                     oldfilename = filename
 
798
                     pathname = os.path.join(basepath, filename)
 
799
                     track_data = self.get_media_metadata(pathname)
 
800
                     if track_data:
 
801
                        trackframes = 75 * track_data.length
 
802
                        totalframes += trackframes
 
803
                        replaygain = track_data.replaygain
 
804
                     else:
 
805
                        pathname = ""
 
806
                        trackframes = 0
 
807
                        replaygain = RGDEF
 
808
 
 
809
                  if not cue_performer:
 
810
                     cue_performer = track_data.artist or global_cue_performer
 
811
                  if not cue_title:
 
812
                     cue_title = track_data.title or global_cue_title
 
813
 
 
814
                  try:
 
815
                     nextoffset = val[index + 1][1]
 
816
                  except LookupError:
 
817
                     try:
 
818
                        nextoffset = segment_data[track + 1][0][1]
 
819
                     except LookupError:
 
820
                        try:
 
821
                           nextoffset = segment_data[track + 1][1][1]
 
822
                        except LookupError:
 
823
                           nextoffset = trackframes
 
824
                  
 
825
                  if nextoffset == 0:
 
826
                     nextoffset = trackframes
 
827
                  duration = nextoffset - frames
 
828
                  if not trackframes:
 
829
                     duration = frames = 0
 
830
 
 
831
                  element = CueSheetTrack(pathname, bool(pathname), track, index,
 
832
                                       cue_performer, cue_title, frames, duration, replaygain)
 
833
                  cuesheet_liststore.append(element)
 
834
 
 
835
      if global_cue_performer and global_cue_title:
 
836
         metadata = global_cue_performer + " - " + global_cue_title
 
837
      else:
 
838
         metadata = global_cue_performer or global_cue_title
 
839
      # TC: Missing metadata text.
 
840
      metadata = metadata or _('Unknown')
 
841
 
 
842
      # TC: Cuesheet data element as shown in the playlist.
 
843
      element = PlayerRow('<span foreground="dark green">%s</span>' % _("(Cue sheet)") + rich_safe(metadata),
 
844
            cue_pathname, totalframes // 75 + 1, metadata, "utf-8",
 
845
            global_cue_title, global_cue_performer, RGDEF, cuesheet_liststore, "")
 
846
         
 
847
      return element
 
848
   
 
849
   def get_media_metadata(self, filename):
 
850
      artist = u""
 
851
      title = u""
 
852
      album = u""
 
853
      length = 0
 
854
      artist_retval = u""
 
855
      title_retval = u""
 
856
      album_retval = u""
 
857
      
 
858
      # Strip away any file:// prefix
 
859
      if filename.count("file://", 0, 7):
 
860
         filename = filename[7:]
 
861
      elif filename.count("file:", 0, 5):
 
862
         filename = filename[5:]
 
863
         
 
864
      filext = supported.check_media(filename)
 
865
      if filext == False or os.path.isfile(filename) == False:
 
866
         return NOTVALID._replace(filename=filename)
 
867
 
 
868
      if filext in (".cue", ".txt"):
 
869
         return self.make_cuesheet_playlist_entry(filename)
 
870
      else:
 
871
         cuesheet = None
 
872
 
 
873
      # Use this name for metadata when we can't get anything from tags.
 
874
      # The name will also appear grey to indicate a tagless state.
 
875
      meta_name = os.path.splitext(glib.filename_display_basename(filename))[0].lstrip("0123456789 -")
 
876
      encoding = None  # Obsolete
 
877
      # TC: Playlist text meaning the metadata tag is missing or incomplete.
 
878
      rsmeta_name = '<span foreground="dark red">(%s)</span> %s' % (_('Bad Tag'), rich_safe(meta_name))
 
879
      title_retval = meta_name
 
880
 
 
881
         
 
882
      # Obtain as much metadata from ubiquitous tags as possible.
 
883
      # Files can have ape and id3 tags. ID3 has priority in this case.
 
884
      try:
 
885
         audio = APEv2(filename)
 
886
      except:
 
887
         rg = RGDEF
 
888
         artist = title = ""
 
889
      else:
 
890
         try: 
 
891
            rg = float(audio["REPLAYGAIN_TRACK_GAIN"][0].rstrip(" dB"))
 
892
         except:
 
893
            rg = RGDEF
 
894
         artist = audio.get("ARTIST", [u""])
 
895
         title = audio.get("TITLE", [u""])
 
896
         album = audio.get("ALBUM", [u""])
 
897
      # ID3 is tried second so it can supercede APE tag data.
 
898
      try:
 
899
         audio = EasyID3(filename)
 
900
      except:
 
901
         pass
 
902
      else:
 
903
         try:
 
904
            rg = float(audio["replaygain_track_gain"][0].rstrip(" dB"))
 
905
         except:
 
906
            pass
 
907
         try:
 
908
            artist = audio["artist"]
 
909
         except:
 
910
            pass
 
911
         try:
 
912
            title = audio["title"]
 
913
         except:
 
914
            pass
 
915
         try:
 
916
            album = audio["album"]
 
917
         except:
 
918
            pass
 
919
          
 
920
         
 
921
      # Trying for metadata from native tagging formats.
 
922
      if FGlobs.avcodec and FGlobs.avformat and filext == ".avi":
 
923
         self.parent.mixer_write("AVFP=%s\nACTN=avformatinforequest\nend\n" % filename, True)
 
924
         while 1:
 
925
            line = self.parent.mixer_read()
 
926
            if line.startswith("avformatinfo: artist="):
 
927
               artist = line[21:].strip()
 
928
            if line.startswith("avformatinfo: title="):
 
929
               title = line[20:].strip()
 
930
            if line.startswith("avformatinfo: album="):
 
931
               album = line[20:].strip()
 
932
            if line.startswith("avformatinfo: duration="):
 
933
               length = int(line[23:-1])
 
934
            if line == "avformatinfo: end\n":
 
935
               break
 
936
      
 
937
      elif (filext == ".wav" or filext == ".aiff" or filext == ".au"):
 
938
         self.parent.mixer_write("SNDP=%s\nACTN=sndfileinforequest\nend\n" % filename, True)
 
939
         while 1:
 
940
            line = self.parent.mixer_read()
 
941
            if line == "idjcmixer: sndfileinfo Not Valid\n" or line == "":
 
942
               return NOTVALID._replace(filename=filename)
 
943
            if line.startswith("idjcmixer: sndfileinfo length="):
 
944
               length = int(line[30:-1])
 
945
            if line.startswith("idjcmixer: sndfileinfo artist="):
 
946
               artist = line[30:-1]
 
947
            if line.startswith("idjcmixer: sndfileinfo title="):
 
948
               title = line[29:-1]
 
949
            if line.startswith("idjcmixer: sndfileinfo album="):
 
950
               album = line[29:-1]
 
951
            if line == "idjcmixer: sndfileinfo end\n":
 
952
               break
 
953
         if length == None:
 
954
            return NOTVALID._replace(filename=filename)
 
955
     
 
956
      # This handles chained ogg files as generated by IDJC.
 
957
      elif filext == ".ogg" or filext == ".oga" or filext == ".spx":
 
958
         self.parent.mixer_write("OGGP=%s\nACTN=ogginforequest\nend\n" % filename, True)
 
959
         while 1:
 
960
            line = self.parent.mixer_read()
 
961
            if line == "OIR:NOT VALID\n" or line == "":
 
962
               return NOTVALID._replace(filename=filename)
 
963
            if line.startswith("OIR:ARTIST="):
 
964
               artist = line[11:].strip()
 
965
            if line.startswith("OIR:TITLE="):
 
966
               title = line[10:].strip()
 
967
            if line.startswith("OIR:ALBUM="):
 
968
               album = line[10:].strip()
 
969
            if line.startswith("OIR:LENGTH="):
 
970
               length = int(float(line[11:].strip()))
 
971
            if line.startswith("OIR:REPLAYGAIN_TRACK_GAIN="):
 
972
               try:
 
973
                  rg = float(line[26:].rstrip(" dB\n"))
 
974
               except:
 
975
                  rg = RGDEF
 
976
            if line == "OIR:end\n":
 
977
               break
 
978
      else:
 
979
         # Mutagen used for all remaining formats.
 
980
         try:
 
981
            audio = mutagen.File(filename)
 
982
         except:
 
983
            return NOTVALID._replace(filename=filename)
 
984
         else:
 
985
            length = int(audio.info.length)
 
986
            if isinstance(audio, MP4):
 
987
               try:
 
988
                  artist = audio["\xa9ART"][0]
 
989
               except:
 
990
                  pass
 
991
               try:
 
992
                  title = audio["\xa9nam"][0]
 
993
               except:
 
994
                  pass
 
995
               try:
 
996
                  album = audio["\xa9alb"][0]
 
997
               except:
 
998
                  pass
 
999
            elif isinstance(audio, MP3):
 
1000
               # The LAME tag is the last port of call for Replay Gain info
 
1001
               # due to it frequently being based on the source audio.
 
1002
               if rg == RGDEF:
 
1003
                  try:
 
1004
                     rg = audio.info.track_gain
 
1005
                  except:
 
1006
                     pass
 
1007
                  else:
 
1008
                      if rg is None:
 
1009
                         rg = RGDEF
 
1010
            else:
 
1011
               x = list(audio.get("Artist", []))
 
1012
               x += list(audio.get("Author", []))
 
1013
               if x:
 
1014
                  artist = "/".join((unicode(y) for y in x))
 
1015
               
 
1016
               try:
 
1017
                  x = list(audio["Title"])
 
1018
               except:
 
1019
                  pass
 
1020
               else:
 
1021
                  title = "/".join((unicode(y) for y in x))
 
1022
 
 
1023
               try:
 
1024
                  x = list(audio["Album"])
 
1025
               except:
 
1026
                  pass
 
1027
               else:
 
1028
                  album = "/".join((unicode(y) for y in x))
 
1029
               
 
1030
               try:
 
1031
                  rg = float(unicode(audio["replaygain_track_gain"][-1]).rstrip(" dB"))
 
1032
               except:
 
1033
                  pass 
 
1034
      
 
1035
      if isinstance(artist, list):
 
1036
         artist = u"/".join(artist)
 
1037
         
 
1038
      if isinstance(title, list):
 
1039
         title = u"/".join(title)
 
1040
 
 
1041
      if isinstance(album, list):
 
1042
         album = u"/".join(album)
 
1043
      
 
1044
      if isinstance(artist, str):
 
1045
         try:
 
1046
            artist = artist.decode("utf-8", "strict")
 
1047
         except:
 
1048
            artist = artist.decode("latin1", "replace")
 
1049
            
 
1050
      if isinstance(title, str):
 
1051
         try:
 
1052
            title = title.decode("utf-8", "strict")
 
1053
         except:
 
1054
            title = title.decode("latin1", "replace")
 
1055
 
 
1056
      if isinstance(album, str):
 
1057
         try:
 
1058
            album = album.decode("utf-8", "strict")
 
1059
         except:
 
1060
            album = album.decode("latin1", "replace")
 
1061
            
 
1062
      assert(isinstance(artist, unicode))
 
1063
      assert(isinstance(title, unicode))
 
1064
      assert(isinstance(album, unicode))
 
1065
      
 
1066
      if length == 0:
 
1067
         length = 1
 
1068
      
 
1069
      if artist and title:
 
1070
         meta_name = artist + u" - " + title
 
1071
         return PlayerRow(rich_safe(meta_name), filename, length, meta_name, encoding, title, artist, rg, cuesheet, album)
 
1072
      else:
 
1073
         return PlayerRow(rsmeta_name, filename, length, meta_name, encoding, title_retval, artist, rg, cuesheet, album)
 
1074
 
 
1075
   # Update playlist entries for a given filename e.g. when tag has been edited
 
1076
   def update_playlist(self, newdata):
 
1077
      active = None
 
1078
      for item in self.liststore:
 
1079
         if item[1] == newdata[1]:
 
1080
            if item[0].startswith("<b>"):
 
1081
               item[0] = u"<b>" + newdata[0] + u"</b>"
 
1082
               active = item
 
1083
            else:
 
1084
               item[0] = newdata[0]
 
1085
            for i in range(2, len(item)):
 
1086
               item[i] = newdata[i]
 
1087
      if active is not None:
 
1088
         self.songname = active[3]        # update metadata on server
 
1089
         self.title = active[5].encode("utf-8")
 
1090
         self.artist = active[6].encode("utf-8")
 
1091
         self.album = active[9].encode("utf-8")
 
1092
         self.player_restart()
 
1093
         self.parent.send_new_mixer_stats()
 
1094
      
 
1095
   # Shut down our media players when we exit.
 
1096
   def cleanup(self):
 
1097
      self.exiting = True 
 
1098
      if self.player_is_playing:
 
1099
         self.stop.clicked()
 
1100
      self.save_session()
 
1101
 
 
1102
   def save_session(self):
 
1103
      fh = open(self.session_filename, "w")
 
1104
      extlist = self.external_pl.filechooser.get_filename()
 
1105
      if extlist is not None:
 
1106
         fh.write("extlist=" + extlist + "\n")
 
1107
      extdir = self.external_pl.directorychooser.get_filename()
 
1108
      if extdir is not None:
 
1109
         fh.write("extdir=" + extdir + "\n")
 
1110
      fh.write("digiprogress_type=" + str(int(self.digiprogress_type)) + "\n")
 
1111
      fh.write("stream_button=" + str(int(self.stream.get_active())) + "\n")
 
1112
      fh.write("listen_button=" + str(int(self.listen.get_active())) + "\n")
 
1113
      fh.write("playlist_mode=" + str(self.pl_mode.get_active()) + "\n")
 
1114
      fh.write("plsave_filetype=" + str(self.plsave_filetype) + "\n")
 
1115
      fh.write("plsave_open=" + str(int(self.plsave_open)) + "\n")
 
1116
      fh.write("fade_mode=" + str(self.pl_delay.get_active()) + "\n")
 
1117
      if self.plsave_folder is not None:
 
1118
         fh.write("plsave_folder=" + self.plsave_folder + "\n")
 
1119
      
 
1120
      for entry in self.liststore:
 
1121
         fh.write("pe=")
 
1122
         if entry[0].startswith("<b>"):   # clean off any accidental bold tags
 
1123
            entry[0] = entry[0][3:-4]
 
1124
         for item in entry:
 
1125
            if isinstance(item, int):
 
1126
               item = str(item)
 
1127
               fh.write("i")
 
1128
            elif isinstance(item, float):
 
1129
               item = str(item)
 
1130
               fh.write("f")
 
1131
            elif isinstance(item, str):
 
1132
               fh.write("s")
 
1133
            elif isinstance(item, CueSheetListStore):
 
1134
               fh.write("c")
 
1135
               if item:
 
1136
                  item = "(%s, )" % ", ".join(repr(x) for x in item)
 
1137
               else:
 
1138
                  item = "()"
 
1139
            elif item is None:
 
1140
               fh.write("n")
 
1141
               item = "None"
 
1142
            fh.write(str(len(item)) + ":" + item)
 
1143
         fh.write("\n")
 
1144
      model, iter = self.treeview.get_selection().get_selected()
 
1145
      if iter is not None:
 
1146
         fh.write("select=" + str(model.get_path(iter)[0]) + "\n")
 
1147
      fh.close()
 
1148
 
 
1149
   def restore_session(self):
 
1150
      try:
 
1151
         fh = open(self.session_filename, "r")
 
1152
      except:
 
1153
         return
 
1154
      while 1:
 
1155
         try:
 
1156
            line = fh.readline()
 
1157
            if line == "":
 
1158
               break
 
1159
         except:
 
1160
            break
 
1161
         try:
 
1162
            if line.startswith("extlist="):
 
1163
               self.external_pl.filechooser.set_filename(line[8:-1])
 
1164
            if line.startswith("extdir="):
 
1165
               self.external_pl.directorychooser.set_current_folder(line[7:-1])
 
1166
            if line.startswith("digiprogress_type="):
 
1167
               if int(line[18]) != self.digiprogress_type:
 
1168
                  self.digiprogress_click()
 
1169
            if line.startswith("stream_button="):
 
1170
               self.stream.set_active(int(line[14]))
 
1171
            if line.startswith("listen_button="):
 
1172
               self.listen.set_active(int(line[14]))
 
1173
            if line.startswith("playlist_mode="):
 
1174
               self.pl_mode.set_active(int(line[14]))
 
1175
            if line.startswith("plsave_filetype="):
 
1176
               self.plsave_filetype=int(line[16])
 
1177
            if line.startswith("plsave_open="):
 
1178
               self.plsave_open=bool(int(line[12]))
 
1179
            if line.startswith("plsave_folder="):
 
1180
               self.plsave_folder=line[14:-1]
 
1181
            if line.startswith("fade_mode="):
 
1182
               self.pl_delay.set_active(int(line[10]))
 
1183
            if line.startswith("pe="):
 
1184
               playlist_entry = self.pl_unpack(line[3:])
 
1185
               if not playlist_entry or self.playlist_todo:
 
1186
                  self.playlist_todo.append(playlist_entry.filename)
 
1187
               else:
 
1188
                  self.liststore.append(playlist_entry)
 
1189
            if line.startswith("select="):
 
1190
               path = line[7:-1]
 
1191
               try:
 
1192
                  self.treeview.get_selection().select_path(path)
 
1193
                  self.treeview.scroll_to_cell(path, None, False) 
 
1194
               except:
 
1195
                  pass
 
1196
         except ValueError:
 
1197
            pass
 
1198
      if self.playlist_todo:
 
1199
         print self.playername + " player: the stored playlist data is not compatible with this version\nfiles placed in a queue for rescanning"
 
1200
         gobject.idle_add(self.cb_playlist_todo)
 
1201
         
 
1202
   @threadslock
 
1203
   def cb_playlist_todo(self):
 
1204
      if self.no_more_files:
 
1205
         return False
 
1206
      try:
 
1207
         pathname = self.playlist_todo.popleft()
 
1208
      except:
 
1209
         return False
 
1210
      line = self.get_media_metadata(pathname)
 
1211
      if line:
 
1212
         self.liststore.append(line)
 
1213
      else:
 
1214
         print "file missing or type unsupported %s" % pathname
 
1215
      return True
 
1216
 
 
1217
   def pl_unpack(self, text):           # converts a string encoded list to a python list
 
1218
      start = 0 
 
1219
      item = 0  
 
1220
      reply = []
 
1221
      while text[start] != "\n":
 
1222
         end = start
 
1223
         while text[end] != ":":
 
1224
            end = end + 1                               
 
1225
         nextstart = int(text[start + 1 : end]) + end + 1
 
1226
         
 
1227
         value = text[end + 1 : nextstart]
 
1228
         try:
 
1229
            t = text[start]
 
1230
            if t == "i":
 
1231
               value = int(value)
 
1232
            elif t == "f":
 
1233
               value = float(value)
 
1234
            elif t == "s":
 
1235
               pass
 
1236
            elif t == "c":
 
1237
               csts = eval(value, {"__builtins__":None},{"CueSheetTrack":CueSheetTrack})
 
1238
               value = CueSheetListStore()
 
1239
               for cst in csts:
 
1240
                  value.append(cst)
 
1241
            elif t == "n":
 
1242
               value = None
 
1243
         except Exception, e:
 
1244
            print "pl_unpack: playlist line not valid", e
 
1245
            try:
 
1246
               return NOTVALID._replace(filename=reply[1])
 
1247
            except IndexError:
 
1248
               return NOTVALID
 
1249
            return nv
 
1250
         reply.append(value)
 
1251
         start = nextstart
 
1252
      try:
 
1253
         return PlayerRow._make(reply)
 
1254
      except:
 
1255
         return NOTVALID._replace(filename=reply[1])
 
1256
             
 
1257
   def handle_stop_button(self, widget):
 
1258
      self.restart_cancel = True
 
1259
      if self.is_playing == True:
 
1260
         self.is_playing = False
 
1261
         if self.timeout_source_id:
 
1262
            gobject.source_remove(self.timeout_source_id)
 
1263
         # This will make our play button code branch to its shutdown code.
 
1264
         self.is_stopping = True
 
1265
         # This will emit a signal which will trigger the play button handler code.
 
1266
         self.play.set_active(False)
 
1267
         # Must do pause as well if it is pressed.
 
1268
         if self.pause.get_active() == True:
 
1269
            self.pause.set_active(False)
 
1270
         self.parent.send_new_mixer_stats()
 
1271
         
 
1272
   def handle_pause_button(self, widget, selected):
 
1273
      if self.is_playing == True:
 
1274
         if self.is_paused == False:
 
1275
            # Player pause code goes here
 
1276
            print "Player paused"
 
1277
            self.is_paused = True
 
1278
            self.parent.send_new_mixer_stats()
 
1279
         else:
 
1280
            # Player unpause code goes here
 
1281
            print "Player unpaused"
 
1282
            self.is_paused = False
 
1283
            self.parent.send_new_mixer_stats()
 
1284
      else:
 
1285
         # Prevent the pause button going into its on state when not playing.
 
1286
         if selected:
 
1287
            # We must unselect it.
 
1288
            widget.set_active(False)
 
1289
         else:
 
1290
            self.is_paused = False
 
1291
   
 
1292
   def handle_play_button(self, widget, selected):
 
1293
      if selected == False:
 
1294
         # Prevent the button from being toggled off by clicking on a selected play button.
 
1295
         # This emits a toggled signal so we will handle this eventuality too.
 
1296
         # Note that when flag is_stopping is set we don't want to reactivate this button.
 
1297
         if self.is_stopping == False:
 
1298
            widget.set_active(True)
 
1299
         else:
 
1300
            self.is_stopping = False
 
1301
            if self.player_is_playing == True:
 
1302
               self.player_shutdown()
 
1303
      else:
 
1304
         if self.is_playing == True:
 
1305
            if self.new_title == True:
 
1306
               self.new_title = False
 
1307
               self.player_shutdown()
 
1308
               self.parent.send_new_mixer_stats()
 
1309
               self.player_is_playing = self.player_startup()
 
1310
               if self.player_is_playing == False:
 
1311
                  self.player_is_playing = True
 
1312
               if self.is_paused:
 
1313
                  self.pause.set_active(False)
 
1314
            else:    
 
1315
               print "Someone probably clicked Play when we were already playing"
 
1316
         else:
 
1317
            self.is_playing = True
 
1318
            self.new_title = False
 
1319
            if self.player_startup():
 
1320
               self.player_is_playing = True
 
1321
               print "Player has started"
 
1322
            else:
 
1323
               self.stop.clicked()
 
1324
   
 
1325
   def player_startup(self):
 
1326
      # remember which player started last so we can decide on metadata
 
1327
      print "player_startup %s" % self.playername
 
1328
      self.parent.last_player = self.playername
 
1329
 
 
1330
      if self.player_is_playing == True:
 
1331
         # Use the current song if one is playing.
 
1332
         model = self.model_playing
 
1333
         iter = self.iter_playing
 
1334
      else:
 
1335
         # Get our next playlist item.
 
1336
         treeselection = self.treeview.get_selection()
 
1337
         (model, iter) = treeselection.get_selected()
 
1338
      if iter == None:
 
1339
         print "Nothing selected in the playlist - trying the first entry."
 
1340
         try:
 
1341
            iter = model.get_iter(0)
 
1342
         except:
 
1343
            print "Playlist is empty"
 
1344
            return False
 
1345
         print "We start at the beginning"
 
1346
         treeselection.select_iter(iter)
 
1347
         
 
1348
      self.treeview.scroll_to_cell(model.get_path(iter)[0], None, False)
 
1349
 
 
1350
      self.music_filename = model.get_value(iter, 1)
 
1351
      if self.music_filename != "":
 
1352
         # Songname is used for metadata for mp3         
 
1353
         self.songname = unicode(model.get_value(iter, 3))
 
1354
         # These two are used for ogg metadata
 
1355
         self.title = unicode(model.get_value(iter, 5)).encode("utf-8", "replace")
 
1356
         self.artist = unicode(model.get_value(iter, 6)).encode("utf-8", "replace")
 
1357
         self.album = unicode(model.get_value(iter, 9)).encode("utf-8", "replace")
 
1358
         self.parent.send_new_mixer_stats()      
 
1359
      # rt is the run time in seconds of our song
 
1360
      rt = model.get_value(iter, 2)
 
1361
      if rt < 0:
 
1362
         rt = 0         # playlist controls have negative numbers
 
1363
      # Calculate our seek time scaling from old slider settings.
 
1364
      # Used for when seek is moved before play is pressed.
 
1365
      if os.path.isfile(self.music_filename):
 
1366
         try:
 
1367
            self.start_time = int(self.progressadj.get_value() / self.max_seek * float(rt))
 
1368
         except ZeroDivisionError:
 
1369
            self.start_time = 0
 
1370
      else:
 
1371
         self.start_time = rt   # Seek to the end when file is missing.
 
1372
      print "Seek time is %d seconds" % self.start_time
 
1373
        
 
1374
      if self.parent.prefs_window.rg_adjust.get_active():
 
1375
         self.gain = model.get_value(iter, 7)
 
1376
         if self.gain == RGDEF:
 
1377
            self.gain = self.parent.prefs_window.rg_defaultgain.get_value()
 
1378
         self.gain += self.parent.prefs_window.rg_boost.get_value()
 
1379
         print "final gain value of %f dB" % self.gain      
 
1380
      else:
 
1381
         self.gain = 0.0
 
1382
         print "not using replay gain"
 
1383
           
 
1384
      # Now we recalibrate the progress bar to the current song length
 
1385
      self.digiprogress_f = True
 
1386
      self.progressadj.set_all(float (self.start_time) , 0.0, rt, rt/1000.0, rt/100.0, 0.0)
 
1387
      self.progressadj.emit("changed")
 
1388
      # Set the stop figure used by the progress bar's timeout function
 
1389
      self.progress_stop_figure = model.get_value(iter, 2)
 
1390
      self.progress_current_figure = self.start_time
 
1391
      
 
1392
      self.player_is_playing = True
 
1393
      
 
1394
      # Bold highlight the file we are playing
 
1395
      text = model.get_value(iter, 0)
 
1396
      if not text.startswith("<b>"):
 
1397
         text = "<b>" + text + "</b>"
 
1398
         model.set_value(iter, 0, text)
 
1399
      self.iter_playing = iter
 
1400
      self.model_playing = model
 
1401
      self.max_seek = rt
 
1402
      self.silence_count = 0
 
1403
      
 
1404
      if self.music_filename != "":
 
1405
         self.parent.mixer_write("PLRP=%s\nSEEK=%d\nSIZE=%d\nRGDB=%f\nACTN=playnoflush%s\nend\n" % (
 
1406
                                 self.music_filename, self.start_time, self.max_seek, self.gain, self.playername), True)
 
1407
         while 1:
 
1408
            line = self.parent.mixer_read()
 
1409
            if line.startswith("context_id="):
 
1410
               self.player_cid = int(line[11:-1])
 
1411
               break
 
1412
            if line == "":
 
1413
               self.player_cid = -1
 
1414
               break
 
1415
      else:
 
1416
         print "skipping play for empty filename"
 
1417
         self.player_cid = -1
 
1418
      if self.player_cid == -1:
 
1419
         print "player startup was unsuccessful for file", self.music_filename
 
1420
         # we will treat this as successful and not return false so that end of track processing will take care of it
 
1421
         self.timeout_source_id = gobject.timeout_add(0, self.cb_play_progress_timeout, self.player_cid)
 
1422
      else:
 
1423
         print "player context id is %d\n" % self.player_cid
 
1424
         if self.player_cid & 1:
 
1425
            self.timeout_source_id = gobject.timeout_add(200, self.cb_play_progress_timeout, self.player_cid)
 
1426
         else:
 
1427
            self.invoke_end_of_track_policy()
 
1428
      return True
 
1429
 
 
1430
   def player_shutdown(self):
 
1431
      print "player shutdown code was called"
 
1432
      
 
1433
      if self.iter_playing:
 
1434
         # Unhighlight this track
 
1435
         text = self.model_playing.get_value(self.iter_playing, 0)
 
1436
         if text[:3] == "<b>":
 
1437
            text = text[3:-4]
 
1438
            self.model_playing.set_value(self.iter_playing, 0, text)
 
1439
         self.file_iter_playing = 0
 
1440
      
 
1441
      self.player_is_playing = False
 
1442
      if self.timeout_source_id:
 
1443
         gobject.source_remove(self.timeout_source_id)
 
1444
         
 
1445
      self.progress_current_figure = 0
 
1446
      self.playtime_elapsed.set_value(0)
 
1447
      self.progressadj.set_value(0.0)
 
1448
      self.progressadj.value_changed()
 
1449
      
 
1450
      if self.gapless == False:
 
1451
         self.parent.mixer_write("ACTN=stop%s\nend\n" % self.playername, True)
 
1452
      
 
1453
      self.digiprogress_f = False
 
1454
      self.other_player_initiated = False
 
1455
      self.crossfader_initiated = False
 
1456
   
 
1457
   def set_fade_mode(self, mode):
 
1458
      if self.parent.simplemixer:
 
1459
         mode = 0
 
1460
      self.parent.mixer_write("FADE=%d\nACTN=fademode_%s\nend\n" % (mode, self.playername), True)
 
1461
   
 
1462
   def player_restart(self):
 
1463
      # remember which player started last so we can decide on metadata
 
1464
      print "player_restart %s" % self.playername
 
1465
      self.parent.last_player = self.playername
 
1466
 
 
1467
      gobject.source_remove(self.timeout_source_id)
 
1468
      self.start_time = int (self.progressadj.get_value())
 
1469
      self.silence_count = 0
 
1470
      self.parent.mixer_write("PLRP=%s\nSEEK=%d\nACTN=play%s\nend\n" % (
 
1471
                                self.music_filename, self.start_time, self.playername), True)
 
1472
      while 1:
 
1473
         line = self.parent.mixer_read()
 
1474
         if line.startswith("context_id="):
 
1475
            self.player_cid = int(line[11:-1])
 
1476
            break
 
1477
         if line == "":
 
1478
            self.player_cid = -1
 
1479
            break
 
1480
      if self.player_cid == -1:
 
1481
         print "player startup was unsuccessful for file", self.music_filename
 
1482
         return False
 
1483
      
 
1484
      print "player context id is %d\n" % self.player_cid
 
1485
      
 
1486
      # Restart a callback to update the progressbar.
 
1487
      self.timeout_source_id = gobject.timeout_add(100, self.cb_play_progress_timeout, self.player_cid)
 
1488
      return True
 
1489
 
 
1490
   def next_real_track(self, i):
 
1491
      if i == None:
 
1492
         return None
 
1493
      
 
1494
      m = self.model_playing
 
1495
      while 1:
 
1496
         i = m.iter_next(i)
 
1497
         if i is None:
 
1498
            return None
 
1499
         if m.get_value(i, 0)[0] != ">":
 
1500
            return i
 
1501
 
 
1502
   def first_real_track(self):
 
1503
      m = self.model_playing
 
1504
      i = m.get_iter_first()
 
1505
      
 
1506
      while 1:
 
1507
         if i == None:
 
1508
            return None
 
1509
         if m.get_value(i, 0)[0] != ">":
 
1510
            return i
 
1511
         i = m.get_iter_next(i)
 
1512
 
 
1513
   def invoke_end_of_track_policy(self, mode_text=None):
 
1514
      # This is where we implement the playlist modes for the most part.
 
1515
      if mode_text is None:
 
1516
         mode_text = self.pl_mode.get_active_text()
 
1517
         if self.is_playing == False:
 
1518
            print "Assertion failed in: invoke_end_of_track_policy"
 
1519
            return
 
1520
      
 
1521
      if mode_text == N_('Manual'):
 
1522
         # For Manual mode just stop the player at the end of the track.
 
1523
         print "Stopping in accordance with manual mode"
 
1524
         self.stop.clicked()
 
1525
      elif mode_text == N_('Play All'):
 
1526
         if self.music_filename == "":
 
1527
            self.handle_playlist_control()
 
1528
         else:
 
1529
            self.next.clicked()
 
1530
            treeselection = self.treeview.get_selection()
 
1531
            if self.is_playing == False:
 
1532
               treeselection.select_path(0) # park on the first menu item
 
1533
      elif mode_text == N_('Loop All') or mode_text == N_('Cue Up') or mode_text == N_('Fade Over'):
 
1534
         iter = self.next_real_track(self.iter_playing)
 
1535
         if iter is None:
 
1536
            iter = self.first_real_track()
 
1537
         self.stop.clicked()
 
1538
         if iter is not None:
 
1539
            treeselection = self.treeview.get_selection()
 
1540
            treeselection.select_iter(iter)
 
1541
            if mode_text == N_('Loop All'):
 
1542
               self.play.clicked()
 
1543
         else:
 
1544
            treeselection.select_path(0)
 
1545
      elif mode_text == N_('Random'):
 
1546
         # Not truly random. Effort is made to break the appearance of
 
1547
         # having a set play order to a long term listener without
 
1548
         # re-playing the same track too soon.
 
1549
         
 
1550
         self.stop.clicked()
 
1551
 
 
1552
         poolsize = len(self.liststore) // 10
 
1553
         if poolsize > 50:
 
1554
            poolsize = 50
 
1555
         elif poolsize < 10:
 
1556
            poolsize = 10
 
1557
            if poolsize > len(self.liststore):
 
1558
               poolsize = len(self.liststore)
 
1559
 
 
1560
         if self.parent.server_window.is_streaming or self.parent.server_window.is_recording:
 
1561
            fp = self.parent.files_played
 
1562
         else:
 
1563
            fp = self.parent.files_played_offline
 
1564
         timestamped_pathnames = []
 
1565
         while not timestamped_pathnames:
 
1566
            random_pathnames = [PlayerRow(*x).filename for x in random.sample(self.liststore, poolsize)]
 
1567
            timestamped_pathnames = [(fp.get(pn, 0), pn) for pn in random_pathnames if pn]
 
1568
            timestamped_pathnames.sort()
 
1569
            least_recent_ts = timestamped_pathnames[0][0]
 
1570
            timestamped_pathnames = [x for x in timestamped_pathnames if x[0] == least_recent_ts]
 
1571
            least_recent = random.choice(timestamped_pathnames)[1]
 
1572
         
 
1573
         for path, entry in enumerate(self.liststore):
 
1574
            entry_filename = PlayerRow(*entry).filename
 
1575
            if least_recent == entry_filename:
 
1576
               break
 
1577
            
 
1578
         treeselection = self.treeview.get_selection()        
 
1579
         treeselection.select_path(path)
 
1580
         self.play.clicked()
 
1581
      elif mode_text == N_('External'):
 
1582
         path = self.model_playing.get_path(self.iter_playing)[0]
 
1583
         self.stop.clicked()
 
1584
         next_track = self.external_pl.get_next()
 
1585
         if next_track is None:
 
1586
            print "Cannot obtain anything from external directory/playlist - stopping"
 
1587
         else:
 
1588
            self.model_playing.insert_after(self.iter_playing, next_track)
 
1589
            self.model_playing.remove(self.iter_playing)
 
1590
            treeselection = self.treeview.get_selection()
 
1591
            treeselection.select_path(path)
 
1592
            self.play.clicked()
 
1593
      elif mode_text == N_('Alternate') or mode_text == N_('Random Hop'):
 
1594
         iter = self.next_real_track(self.iter_playing)
 
1595
         if iter is None:
 
1596
            iter = self.first_real_track()
 
1597
         self.stop.clicked()
 
1598
         treeselection = self.treeview.get_selection()
 
1599
         if iter is not None:
 
1600
            treeselection.select_iter(iter)
 
1601
         else:
 
1602
            treeselection.select_path(0)
 
1603
         if self.playername == "left":
 
1604
            self.parent.passright.clicked()
 
1605
            other_player = self.parent.player_right
 
1606
         else:
 
1607
            self.parent.passleft.clicked()
 
1608
            other_player = self.parent.player_left
 
1609
         if mode_text == N_('Alternate'):
 
1610
            other_player.play.clicked()
 
1611
         elif mode_text == N_('Random Hop'):
 
1612
            other_player.invoke_end_of_track_policy(N_('Random'))
 
1613
      else:
 
1614
         print 'The mode "%s" is not currently supported - stopping' % mode_text
 
1615
         self.stop.clicked()
 
1616
 
 
1617
   def handle_playlist_control(self):
 
1618
      treeselection = self.treeview.get_selection()
 
1619
      model = self.model_playing
 
1620
      iter = self.iter_playing
 
1621
      control = model.get_value(iter, 0)
 
1622
      print "control is", control
 
1623
      
 
1624
      if control == "<b>>normalspeed</b>":
 
1625
         self.pbspeedzerobutton.clicked()
 
1626
         self.next.clicked()
 
1627
         if self.is_playing == False:
 
1628
            treeselection.select_path(0)
 
1629
      def x(control_type, open_auto_type):
 
1630
         if control == "<b>>%s</b>" % control_type:
 
1631
            print "player", self.playername, "stopping due to playlist control"
 
1632
            if (self.playername == "left" and self.parent.crossfade.get_value() < 50) or (self.playername == "right" and self.parent.crossfade.get_value() >= 50):
 
1633
               self.parent.mic_opener.open_auto(open_auto_type)
 
1634
            self.stop.clicked()
 
1635
            if model.iter_next(iter):
 
1636
               treeselection.select_iter(model.iter_next(iter))
 
1637
            else:
 
1638
               treeselection.select_iter(model.get_iter_first())
 
1639
      x("stopplayer", "stop_control")
 
1640
      x("stopplayer2", "stop_control2")
 
1641
      if control == "<b>>jumptotop</b>":
 
1642
         self.stop.clicked()
 
1643
         treeselection.select_path(0)
 
1644
         self.play.clicked()
 
1645
      if control == "<b>>announcement</b>":
 
1646
         dia = AnnouncementDialog(self, model, iter, "active")
 
1647
         dia.present()
 
1648
         self.stop.clicked()
 
1649
         if model.iter_next(iter):
 
1650
            treeselection.select_iter(model.iter_next(iter))
 
1651
         else:
 
1652
            treeselection.select_iter(model.get_iter_first())
 
1653
      if control == "<b>>crossfade</b>":
 
1654
         print "player", self.playername, "stopping, crossfade complete"
 
1655
         self.stop.clicked()
 
1656
         if model.iter_next(iter):
 
1657
            treeselection.select_iter(model.iter_next(iter))
 
1658
         else:
 
1659
            treeselection.select_path(0)
 
1660
      if control == "<b>>stopstreaming</b>":
 
1661
         self.next.clicked()
 
1662
         self.parent.server_window.stop_streaming_all()
 
1663
         if self.is_playing == False:
 
1664
            treeselection.select_path(0)
 
1665
      if control == "<b>>stoprecording</b>":
 
1666
         self.next.clicked()
 
1667
         self.parent.server_window.stop_recording_all()
 
1668
         if self.is_playing == False:
 
1669
            treeselection.select_path(0)
 
1670
      if control == "<b>>transfer</b>":
 
1671
         if self.playername == "left":
 
1672
            otherplayer = self.parent.player_right
 
1673
            self.parent.passright.clicked()
 
1674
         else:
 
1675
            otherplayer = self.parent.player_left
 
1676
            self.parent.passleft.clicked()
 
1677
         print "transferring to player", otherplayer.playername
 
1678
         otherplayer.play.clicked()
 
1679
         self.stop.clicked()
 
1680
         if model.iter_next(iter):
 
1681
            treeselection.select_iter(model.iter_next(iter))
 
1682
         else:
 
1683
            treeselection.select_path(0)
 
1684
      if control.startswith("<b>>fade"):
 
1685
         if control.endswith("e5</b>"):
 
1686
            self.set_fade_mode(1)
 
1687
         elif control.endswith("e10</b>"):
 
1688
            self.set_fade_mode(2)
 
1689
         self.next.clicked()
 
1690
         self.set_fade_mode(0)
 
1691
         if self.is_playing == False:
 
1692
            treeselection.select_path(0)
 
1693
      
 
1694
   def get_pl_block_size(self, iter):
 
1695
      size = 0
 
1696
      speedfactor = self.pbspeedfactor
 
1697
      while iter is not None:
 
1698
         length = self.liststore.get_value(iter, 2)
 
1699
         if length == -11:
 
1700
            text = self.liststore.get_value(iter, 0)
 
1701
            if text.startswith("<b>"):
 
1702
               text = text[3:-4]
 
1703
            if text in (">stopplayer", ">stopplayer2", ">transfer", ">crossfade", ">announcement", ">jumptotop"):
 
1704
               break
 
1705
            if text == ">normalspeed":
 
1706
               speedfactor = 1.0
 
1707
         if length >= 0:
 
1708
            size += int(length / speedfactor)
 
1709
         iter = self.liststore.iter_next(iter)
 
1710
      return size
 
1711
 
 
1712
   def update_time_stats(self):
 
1713
      if self.pl_mode.get_active() != 0:                # optimisation -- this function uses a lot of cpu
 
1714
         return
 
1715
      if self.player_is_playing:
 
1716
         tr = int((self.max_seek - self.progressadj.value) / self.pbspeedfactor)
 
1717
         model = self.model_playing
 
1718
         iter = model.iter_next(self.iter_playing)
 
1719
         tr += self.get_pl_block_size(iter)
 
1720
      else:
 
1721
         tr = 0
 
1722
      selection = self.treeview.get_selection()
 
1723
      model, iter = selection.get_selected()
 
1724
      if iter is None:
 
1725
         if self.is_playing:
 
1726
            bs = 0
 
1727
         else:
 
1728
            iter = model.get_iter_first()
 
1729
            bs = self.get_pl_block_size(iter)
 
1730
      else:
 
1731
         try:
 
1732
            if model.get_value(iter, 0)[0:3] == "<b>":
 
1733
               bs = 0
 
1734
            else:
 
1735
               bs = self.get_pl_block_size(iter)
 
1736
         except:
 
1737
            print "Playlist data is fucked up"
 
1738
            bs = 0
 
1739
      bsm, bss = divmod(bs, 60)
 
1740
      if self.is_playing:
 
1741
         trm, trs = divmod(tr, 60)
 
1742
         tm_end = time.localtime(int(time.time()) + tr)
 
1743
         tm_end_h = tm_end[3]
 
1744
         tm_end_m = tm_end[4]
 
1745
         tm_end_s = tm_end[5]
 
1746
         if bs == 0:
 
1747
            self.statusbar_update("%s -%2d:%02d | %s %02d:%02d:%02d" % (
 
1748
                  # TC: The remaining playlist time.
 
1749
                  _('Remaining'), trm, trs,
 
1750
                  # TC: The estimated finish time of the playlist.
 
1751
                  _('Finish'), tm_end_h, tm_end_m, tm_end_s))
 
1752
         else:
 
1753
            self.statusbar_update("%s -%2d:%02d | %s %02d:%02d:%02d | %s %2d:%02d" % (_('Remaining'), trm, trs, _('Finish'), tm_end_h, tm_end_m, tm_end_s, _('Block size'), bsm, bss))
 
1754
      else:
 
1755
         if bs == 0:
 
1756
            self.statusbar_update("")
 
1757
         else:
 
1758
            bft = time.localtime(time.time() + bs)
 
1759
            bf_h = bft[3]
 
1760
            bf_m = bft[4]
 
1761
            bf_s = bft[5]
 
1762
            self.statusbar_update("%s %2d:%02d | %s %02d:%02d:%02d" % (
 
1763
                  # TC: The remaining play time of the block of audio tracks from the highlighted track onwards until the next interruption.
 
1764
                  _('Block size'), bsm, bss, _('Finish'), bf_h, bf_m, bf_s))
 
1765
            
 
1766
   def statusbar_update(self, newtext):         # optimisation -- only update the status bars when the text changes
 
1767
      if newtext != self.oldstatusbartext:
 
1768
         if self.pbspeedfactor < 0.999 or self.pbspeedfactor > 1.001:
 
1769
            newtext = ("%03.1f%% | " % (self.pbspeedfactor * 100)) + newtext
 
1770
         self.pl_statusbar.push(1, newtext)
 
1771
         self.oldstatusbartext = newtext
 
1772
 
 
1773
   def check_mixer_signal(self):
 
1774
      if self.parent.menu_feature_set.get_active() and self.progress_press == False and self.progressadj.upper - self.progress_current_figure < float(self.silence) and self.progressadj.upper > 10.0:
 
1775
         if self.mixer_signal_f.value == 0 and int(self.mixer_cid) == self.player_cid + 1 and self.parent.prefs_window.silence_killer.get_active() and self.eos_inspect() == False:
 
1776
            print "termination by check mixer signal"
 
1777
            self.invoke_end_of_track_policy()
 
1778
 
 
1779
   @threadslock
 
1780
   def cb_play_progress_timeout(self, cid):
 
1781
      if cid % 2 == 0:
 
1782
         # player started at end of track
 
1783
         self.invoke_end_of_track_policy()
 
1784
         return False
 
1785
 
 
1786
      if self.reselect_cursor_please:
 
1787
         treeselection = self.treeview.get_selection()
 
1788
         (model, iter) = treeselection.get_selected()
 
1789
         if iter is not None:
 
1790
            self.treeview.scroll_to_cell(model.get_path(iter)[0], None, False)
 
1791
         else:
 
1792
            self.reselect_please = True
 
1793
         self.reselect_cursor_please = False
 
1794
      if self.reselect_please:
 
1795
         print "Set cursor on track playing"
 
1796
         # This code reselects the playing track after a drag operation.
 
1797
         treeselection = self.treeview.get_selection()
 
1798
         try:
 
1799
            treeselection.select_iter(self.iter_playing)
 
1800
         except:
 
1801
            print "Iter was cancelled probably due to song dragging"
 
1802
         self.reselect_please = False
 
1803
      if self.progress_press == False:
 
1804
         if self.runout.value and self.is_paused == False and self.mixer_cid.value > self.player_cid:
 
1805
            self.gapless = True
 
1806
            print "termination due to end of track"
 
1807
            self.invoke_end_of_track_policy()
 
1808
            self.gapless = False
 
1809
            return False
 
1810
         if self.mixer_signal_f.value == False:
 
1811
            self.silence_count += 1
 
1812
            if self.parent.menu_feature_set.get_active() and self.silence_count >= 120 and self.playtime_elapsed.value > 15 and self.parent.prefs_window.bonus_killer.get_active():
 
1813
               print "termination due to excessive silence"
 
1814
               self.invoke_end_of_track_policy()
 
1815
               return False
 
1816
         else:
 
1817
            self.silence_count = 0
 
1818
 
 
1819
         if self.progress_current_figure != self.playtime_elapsed.value:
 
1820
            # Code runs once a second.
 
1821
            
 
1822
            # Check whether a track is hitting a stream or being recorded.
 
1823
            if self.stream.get_active() and (self.parent.server_window.is_streaming or
 
1824
               self.parent.server_window.is_recording) and (
 
1825
               (self.playername == "left" and self.parent.crossadj.value < 90)
 
1826
               or
 
1827
               (self.playername == "right" and self.parent.crossadj.value > 10)):
 
1828
                  # Log the time the file was last played.
 
1829
               self.parent.files_played[self.music_filename] = time.time()
 
1830
            else:
 
1831
               self.parent.files_played_offline[self.music_filename] = time.time()
 
1832
 
 
1833
         self.progress_current_figure = self.playtime_elapsed.value
 
1834
         self.progressadj.set_value(self.playtime_elapsed.value)
 
1835
         if self.max_seek == 0:
 
1836
            self.progressadj.emit("value_changed")
 
1837
         self.update_time_stats()
 
1838
      else:
 
1839
         # we stop monitoring the play progress during the progress bar drag operation
 
1840
         # by cancelling this timeout
 
1841
         return False
 
1842
      # Calclulate when to sound the DJ alarm (end of music notification)
 
1843
      # Bugs: does not deep scan the playlist controls for >stopplayer so the alarm will not sound if
 
1844
      # preceeded by another playlist control
 
1845
      if self.progress_current_figure == self.progress_stop_figure -10 and self.progressadj.upper > 11 and self.parent.prefs_window.djalarm.get_active():
 
1846
         if ((self.playername == "left" and self.parent.crossadj.get_value() < 50) or (self.playername == "right" and self.parent.crossadj.get_value() >= 50)) and (self.pl_mode.get_active() == 3 or self.pl_mode.get_active() == 4 or (self.pl_mode.get_active() == 0 and (self.model_playing.iter_next(self.iter_playing) is None or (self.pl_mode.get_active() == 0 and self.stop_inspect())))):
 
1847
            if self.alarm_cid != cid:
 
1848
               gobject.timeout_add(1000, self.deferred_alarm)
 
1849
               self.alarm_cid = cid
 
1850
      # Check if the crossfade needs scheduling.      
 
1851
      if self.pl_mode.get_active_text() == N_('Fade Over') or (self.pl_mode.get_active() == 0 and self.fade_inspect()):
 
1852
         eot_crosstime = int(self.progress_stop_figure) - self.parent.passspeed_adj.props.value - int(self.progress_current_figure)
 
1853
         # Start other player
 
1854
         if not self.other_player_initiated and eot_crosstime <= 1:
 
1855
            if self.playername == "left":
 
1856
               self.parent.player_right.play.clicked()
 
1857
            else:
 
1858
               self.parent.player_left.play.clicked()
 
1859
            self.other_player_initiated = True
 
1860
         # Now do the crossfade
 
1861
         if not self.crossfader_initiated and eot_crosstime <= 0:
 
1862
            self.parent.passbutton.clicked()
 
1863
            self.crossfader_initiated = True
 
1864
            desired_direction = (self.playername == "left")
 
1865
            if desired_direction != self.parent.crossdirection:
 
1866
               self.parent.passbutton.clicked()
 
1867
      
 
1868
      rem = self.progress_stop_figure - self.progress_current_figure
 
1869
      if (rem == 5 or rem == 10) and not self.crossfader_initiated and not self.parent.simplemixer:
 
1870
         mode = self.pl_mode.get_active()
 
1871
         next = self.model_playing.iter_next(self.iter_playing)
 
1872
         if next is not None:
 
1873
            nextval = self.model_playing.get_value(next, 0)
 
1874
         else:
 
1875
            nextval = ""
 
1876
         if mode == 0 and nextval.startswith(">"):
 
1877
            if rem == 5 and nextval == ">fade5":
 
1878
               fade = 1
 
1879
            elif rem == 10 and nextval == ">fade10":
 
1880
               fade = 2
 
1881
            else:
 
1882
               fade = 0
 
1883
            if (fade):
 
1884
               self.set_fade_mode(fade)
 
1885
               self.stop.clicked()
 
1886
               treeselection = self.treeview.get_selection()
 
1887
               next = self.model_playing.iter_next(next)
 
1888
               if next is not None:
 
1889
                  path = self.model_playing.get_path(next)
 
1890
                  treeselection.select_path(path)
 
1891
                  self.play.clicked()
 
1892
               else:
 
1893
                  treeselection.select_path(0)
 
1894
               self.set_fade_mode(0)
 
1895
         else:
 
1896
            fade = self.pl_delay.get_active()
 
1897
            if (fade == 1 and rem == 10) or (fade == 2 and rem == 5) or self.pl_mode.get_active() in (3, 4, 6) or (mode == 0 and self.islastinplaylist()):
 
1898
               fade = 0
 
1899
            if fade:
 
1900
               self.set_fade_mode(fade)
 
1901
               self.invoke_end_of_track_policy()
 
1902
               self.set_fade_mode(0)
 
1903
      
 
1904
      return True
 
1905
 
 
1906
   @threadslock
 
1907
   def deferred_alarm(self):
 
1908
      self.parent.alarm = True
 
1909
      self.parent.send_new_mixer_stats()
 
1910
      return False
 
1911
 
 
1912
   def stop_inspect(self):
 
1913
      stoppers = (">stopplayer", ">stopplayer2", ">announcement")
 
1914
      horizon = (">transfer", ">crossfade", ">jumptotop")
 
1915
      i = self.iter_playing
 
1916
      m = self.model_playing
 
1917
      while 1:
 
1918
         i = m.iter_next(i)
 
1919
         if i is None:
 
1920
            return True
 
1921
         v = m.get_value(i, 0)
 
1922
         if v and v[0] != ">":
 
1923
            return False
 
1924
         if v in stoppers:
 
1925
            return True
 
1926
         if v in horizon:
 
1927
            return False
 
1928
 
 
1929
   def fade_inspect(self):
 
1930
      stoppers = (">crossfade")
 
1931
      horizon = (">transfer", ">stopplayer", ">stopplayer2", ">announcement", ">jumptotop")
 
1932
      i = self.iter_playing
 
1933
      m = self.model_playing
 
1934
      while 1:
 
1935
         i = m.iter_next(i)
 
1936
         if i is None:
 
1937
            return False
 
1938
         v = m.get_value(i, 0)
 
1939
         if v and v[0] != ">":
 
1940
            return False
 
1941
         if v in stoppers:
 
1942
            return True
 
1943
         if v in horizon:
 
1944
            return False
 
1945
 
 
1946
   def eos_inspect(self):
 
1947
      # Returns true when playlist ended or stream disconnect is imminent.
 
1948
      if self.pl_mode.get_active():
 
1949
         return False
 
1950
      if self.islastinplaylist():
 
1951
         return True
 
1952
      stoppers = (">stopstreaming", )
 
1953
      horizon = (">transfer", ">crossfade")
 
1954
      i = self.iter_playing
 
1955
      m = self.model_playing
 
1956
      while 1:
 
1957
         i = m.iter_next(i)
 
1958
         if i is None:
 
1959
            return True
 
1960
         v = m.get_value(i, 0)
 
1961
         if v and v[0] != ">":
 
1962
            return False
 
1963
         if v in stoppers:
 
1964
            return True
 
1965
         if v in horizon:
 
1966
            return False
 
1967
 
 
1968
   def islastinplaylist(self):
 
1969
      iter = self.model_playing.iter_next(self.iter_playing)
 
1970
      if iter is None:
 
1971
         return True
 
1972
      else:
 
1973
         return False
 
1974
          
 
1975
   def arrow_up(self):
 
1976
      treeselection = self.treeview.get_selection()
 
1977
      (model, iter) = treeselection.get_selected()
 
1978
      if iter == None:
 
1979
         print "Nothing is selected"
 
1980
      else:
 
1981
         path = model.get_path(iter)
 
1982
         if path[0]:
 
1983
            other_iter = model.get_iter(path[0]-1)
 
1984
            self.liststore.swap(iter, other_iter)
 
1985
            self.treeview.scroll_to_cell(path[0]-1, None, False)
 
1986
            
 
1987
   def arrow_down(self):
 
1988
      treeselection = self.treeview.get_selection()
 
1989
      (model, iter) = treeselection.get_selected()
 
1990
      if iter == None:
 
1991
         print "Nothing is selected"
 
1992
      else:
 
1993
         path = model.get_path(iter)
 
1994
         try:
 
1995
            other_iter = model.get_iter(path[0]+1)
 
1996
            self.liststore.swap(iter, other_iter)
 
1997
            self.treeview.scroll_to_cell(path[0]+1, None, False)
 
1998
         except ValueError:
 
1999
            pass
 
2000
          
 
2001
   def advance(self):
 
2002
      #self.set_fade_mode(self.pl_delay.get_active())
 
2003
      if self.is_playing:
 
2004
         self.parent.mic_opener.open_auto("advance")
 
2005
         path = self.model_playing.get_path(self.iter_playing)[0]+1
 
2006
         self.stop.clicked()
 
2007
         treeselection = self.treeview.get_selection()
 
2008
         treeselection.select_path(path)
 
2009
         self.treeview.scroll_to_cell(path, None, False)
 
2010
      else:
 
2011
         self.parent.mic_opener.close_all()
 
2012
         self.play.clicked()
 
2013
      #self.set_fade_mode(0)
 
2014
          
 
2015
   def callback(self, widget, data):
 
2016
      if data == "pbspeedzero":
 
2017
         self.pbspeedbar.set_value(0.0)
 
2018
 
 
2019
      if data == "Arrow Up":
 
2020
         self.arrow_up()
 
2021
      
 
2022
      if data == "Arrow Dn":
 
2023
         self.arrow_down()
 
2024
               
 
2025
      if data == "Stop":
 
2026
         self.handle_stop_button(widget)
 
2027
         
 
2028
      if data == "Next":
 
2029
         if self.is_playing:
 
2030
            path = self.model_playing.get_path(self.iter_playing)[0]+1
 
2031
            if self.is_paused:
 
2032
               self.stop.clicked()
 
2033
            try:
 
2034
               self.model_playing.get_iter(path)
 
2035
            except:
 
2036
               self.stop.clicked()
 
2037
               return
 
2038
            treeselection = self.treeview.get_selection()           
 
2039
            treeselection.select_path(path)
 
2040
            self.new_title = True
 
2041
            self.play.clicked()        
 
2042
               
 
2043
      if data == "Prev":
 
2044
         if self.is_playing:
 
2045
            treeselection = self.treeview.get_selection()
 
2046
            path = self.model_playing.get_path(self.iter_playing)
 
2047
            if self.is_paused:
 
2048
               self.stop.clicked()
 
2049
            treeselection.select_path(path[0]-1)
 
2050
            self.new_title = True
 
2051
            self.play.clicked()
 
2052
               
 
2053
      # This is for adding files to the playlist using the file requester.
 
2054
      if data == "Add Files":
 
2055
         if self.showing_file_requester == False:
 
2056
            if self.playername == "left":
 
2057
               # TC: File dialog title text.
 
2058
               filerqtext = _('Add music to left playlist')
 
2059
            else:
 
2060
               # TC: File dialog title text.
 
2061
               filerqtext = _('Add music to right playlist')
 
2062
            self.filerq = gtk.FileChooserDialog(filerqtext + pm.title_extra, None, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
 
2063
            self.filerq.set_select_multiple(True)
 
2064
            self.filerq.set_current_folder(str(self.file_requester_start_dir))
 
2065
            self.filerq.add_filter(self.plfilefilter_all)
 
2066
            self.filerq.add_filter(self.plfilefilter_playlists)
 
2067
            self.filerq.add_filter(self.plfilefilter_media)
 
2068
            self.filerq.set_filter(self.plsave_filtertype)
 
2069
            # TC: File filter text.
 
2070
            frame = gtk.Frame(" %s " % _('Supported Media Formats'))
 
2071
            box = gtk.HBox()
 
2072
            box.set_border_width(3)
 
2073
            frame.add(box)
 
2074
            entry = gtk.Entry()
 
2075
            entry.unset_flags(gtk.CAN_FOCUS)
 
2076
            entry.set_has_frame(False)
 
2077
            text = "*" + ", *".join(supported.media)
 
2078
            entry.set_text(text)
 
2079
            entry.show()
 
2080
            box.add(entry)
 
2081
            box.show()
 
2082
            self.filerq.set_extra_widget(frame)
 
2083
            self.filerq.connect("response", self.file_response)
 
2084
            self.filerq.connect("destroy", self.file_destroy)
 
2085
            self.filerq.show()
 
2086
            self.showing_file_requester = True
 
2087
         else:
 
2088
            self.filerq.present()
 
2089
 
 
2090
   def file_response(self, dialog, response_id):
 
2091
      chosenfiles = self.filerq.get_filenames()
 
2092
      if chosenfiles:
 
2093
         self.file_requester_start_dir.set_text(os.path.split(chosenfiles[0])[0])
 
2094
         self.plsave_filtertype = self.filerq.get_filter()
 
2095
      self.filerq.destroy()
 
2096
      if response_id != gtk.RESPONSE_ACCEPT:
 
2097
         return
 
2098
      gen = self.get_elements_from(chosenfiles)
 
2099
      for each in gen:
 
2100
         if self.no_more_files:
 
2101
            self.no_more_files = False
 
2102
            break
 
2103
         self.liststore.append(each)
 
2104
         while gtk.events_pending():
 
2105
            gtk.main_iteration()
 
2106
    
 
2107
   def file_destroy(self, widget):
 
2108
      self.showing_file_requester = False
 
2109
     
 
2110
   def plfile_new_savetype(self, widget):
 
2111
      self.plsave_filetype = self.pltreeview.get_selection().get_selected_rows()[1][0][0]
 
2112
      # TC: Expander text which appears as "Select File Type (.pls)" for the pls file type.
 
2113
      self.expander.set_label(_('Select File Type') + " (" +  self.playlisttype_extension[self.plsave_filetype][0] + ")")
 
2114
 
 
2115
   def plfile_response(self, dialog, response_id):
 
2116
      self.plsave_filtertype = dialog.get_filter()
 
2117
      self.plsave_open = self.expander.get_expanded()
 
2118
      self.plsave_folder = dialog.get_current_folder()
 
2119
      
 
2120
      if response_id == gtk.RESPONSE_ACCEPT:
 
2121
         chosenfile = self.plfilerq.get_filename()
 
2122
      self.plfilerq.destroy()
 
2123
      if response_id != gtk.RESPONSE_ACCEPT:
 
2124
         return
 
2125
      
 
2126
      main, ext = os.path.splitext(chosenfile)
 
2127
      ext = ext.lower()
 
2128
      if self.plsave_filetype == 0:
 
2129
         if not ext in supported.playlists:
 
2130
            main += ext
 
2131
            ext = ".m3u"      # default to m3u playlist format
 
2132
      else:
 
2133
         t = self.plsave_filetype - 1
 
2134
         useext = supported.playlists[t]
 
2135
         others = list(supported.playlists)
 
2136
         del others[t]
 
2137
         if ext != useext:
 
2138
            if not ext in others:
 
2139
               main += ext
 
2140
            ext = useext
 
2141
      chosenfile = main + ext
 
2142
 
 
2143
      validlist = [ x for x in self.liststore if x[0][0] != ">" and x[2] >= 0 ]
 
2144
 
 
2145
      print "Chosenfile is", chosenfile
 
2146
      try:
 
2147
         pl = open(chosenfile, "w")
 
2148
      except IOError:
 
2149
         print "Can't open file for writing.  Permissions problem?"
 
2150
      else:
 
2151
         if (ext == ".m3u"):
 
2152
            try:
 
2153
               pl.write("#EXTM3U\r\n")
 
2154
               for each in validlist:
 
2155
                  pl.write("#EXTINF:%d,%s\r\n" % (each[2], each[3].decode("UTF-8").encode("ISO8859-1", "replace")))
 
2156
                  pl.write(each[1] + "\r\n")
 
2157
            except IndexError:
 
2158
               pl.close()
 
2159
            except IOError:
 
2160
               pl.close()
 
2161
               print "That was odd\n"
 
2162
         
 
2163
         if ext == ".pls":
 
2164
            pl.write("[playlist]\r\nNumberOfEntries=%d\r\n\r\n" % len(validlist))
 
2165
            for i in range(1, len(validlist) + 1):
 
2166
               each = validlist[i - 1]
 
2167
               pl.write("File%d=%s\r\n" % (i, each[1]))
 
2168
               pl.write("Title%d=%s\r\n" % (i, each[3]))
 
2169
               pl.write("Length%d=%d\r\n\r\n" % (i, each[2]))
 
2170
            pl.write("Version=2\r\n")
 
2171
            
 
2172
         if ext == ".xspf":
 
2173
            doc = mdom.getDOMImplementation().createDocument('http://xspf.org/ns/0/', 'playlist', None)
 
2174
            
 
2175
            playlist = doc.documentElement
 
2176
            playlist.setAttribute('version', '1')
 
2177
            playlist.setAttribute('xmlns', 'http://xspf.org/ns/0/')
 
2178
            playlist.setAttribute('xmlns:idjc', 'http://idjc.sourceforge.net/ns/')
 
2179
            
 
2180
            trackList = doc.createElement('trackList')
 
2181
            playlist.appendChild(trackList)
 
2182
            
 
2183
            for each in self.liststore:
 
2184
               row = PlayerRow(*each)
 
2185
               
 
2186
               track = doc.createElement('track')
 
2187
               trackList.appendChild(track)
 
2188
 
 
2189
               if row.rsmeta.startswith(">"):
 
2190
                  extension = doc.createElement('extension')
 
2191
                  track.appendChild(extension)
 
2192
                  extension.setAttribute('application', 'http://idjc.sourceforge.net/ns/')
 
2193
                  
 
2194
                  pld = doc.createElementNS('http://idjc.sourceforge.net/ns/', 'idjc:pld')
 
2195
                  extension.appendChild(pld)
 
2196
                  pld.setAttribute('rsmeta', row.rsmeta)
 
2197
                  pld.setAttribute('length', str(row.length))
 
2198
               else:
 
2199
                  location = doc.createElement('location')
 
2200
                  track.appendChild(location)
 
2201
                  locationText = doc.createTextNode("file://" + urllib.quote(each[1]))
 
2202
                  location.appendChild(locationText)
 
2203
                  
 
2204
                  if each[6]:
 
2205
                     creator = doc.createElement('creator')
 
2206
                     track.appendChild(creator)
 
2207
                     creatorText = doc.createTextNode(each[6])
 
2208
                     creator.appendChild(creatorText)
 
2209
                     
 
2210
                  if each[5]:
 
2211
                     title = doc.createElement('title')
 
2212
                     track.appendChild(title)
 
2213
                     titleText = doc.createTextNode(each[5])
 
2214
                     title.appendChild(titleText)
 
2215
                     
 
2216
                  if each[9]:
 
2217
                     album = doc.createElement('album')
 
2218
                     track.appendChild(album)
 
2219
                     albumText = doc.createTextNode(each[9])
 
2220
                     album.appendChild(albumText)
 
2221
                     
 
2222
                  duration = doc.createElement('duration')
 
2223
                  track.appendChild(duration)
 
2224
                  durationText = doc.createTextNode(str(each[2] * 1000))
 
2225
                  duration.appendChild(durationText)
 
2226
            
 
2227
            xmltext = doc.toxml("UTF-8").replace("><", ">\n<").splitlines()
 
2228
            spc = ""
 
2229
            for i in range(len(xmltext)):
 
2230
               if xmltext[i][1] == "/":
 
2231
                  spc = spc[2:]
 
2232
               if len(xmltext[i]) < 3 or xmltext[i].startswith("<?") or xmltext[i][-2] == "/" or xmltext[i].count("<") == 2:
 
2233
                  xmltext[i] = spc + xmltext[i]
 
2234
               else:
 
2235
                  xmltext[i] = spc + xmltext[i]
 
2236
                  if xmltext[i][len(spc) + 1] != "/":
 
2237
                     spc = spc + "  "
 
2238
            pl.write("\r\n".join(xmltext))
 
2239
            pl.write("\r\n")
 
2240
            doc.unlink()
 
2241
            pl.close()
 
2242
 
 
2243
   def plfile_destroy(self, widget):
 
2244
      self.showing_pl_save_requester = False
 
2245
 
 
2246
   def cb_toggle(self, widget, data):
 
2247
      print "Toggle %s recieved for signal: %s" % (("OFF","ON")[widget.get_active()], data)
 
2248
   
 
2249
      if data == "Play":
 
2250
         self.handle_play_button(widget, widget.get_active())
 
2251
      if data == "Pause":
 
2252
         self.handle_pause_button(widget, widget.get_active())
 
2253
      if data == "Stream":
 
2254
         self.parent.send_new_mixer_stats();
 
2255
      if data == "Listen":
 
2256
         self.parent.send_new_mixer_stats();
 
2257
   
 
2258
   def cb_progress(self, progress):
 
2259
      if self.digiprogress_f:
 
2260
         if self.max_seek > 0:
 
2261
            if self.digiprogress_type == 0 or self.player_is_playing == False:
 
2262
               count = int(progress.value)
 
2263
            else:
 
2264
               count = self.max_seek - int(progress.value)
 
2265
         else:
 
2266
            count = self.progress_current_figure
 
2267
         hours = int(count / 3600)
 
2268
         count = count - (hours * 3600)
 
2269
         minutes = count / 60
 
2270
         seconds = count - (minutes * 60)
 
2271
         if self.digiprogress_type == 0:
 
2272
            self.digiprogress.set_text("%d:%02d:%02d" % (hours, minutes, seconds))
 
2273
         else:
 
2274
            if self.max_seek != 0:
 
2275
               self.digiprogress.set_text(" -%02d:%02d " % (minutes, seconds))
 
2276
            else:
 
2277
               self.digiprogress.set_text(" -00:00 ")
 
2278
      if self.handle_motion_as_drop:
 
2279
         self.handle_motion_as_drop = False
 
2280
         if self.player_restart() == False:
 
2281
            self.next.clicked()
 
2282
         else:
 
2283
            if self.pause.get_active():
 
2284
               self.pause.set_active(False)
 
2285
 
 
2286
   def cb_pbspeed(self, widget, data=None):
 
2287
      self.pbspeedfactor = pow(10.0, widget.get_value() * 0.05)
 
2288
      self.parent.send_new_mixer_stats()
 
2289
 
 
2290
   def digiprogress_click(self):    
 
2291
      self.digiprogress_type = not self.digiprogress_type
 
2292
      if not self.digiprogress_f:
 
2293
         if self.digiprogress_type == 0:
 
2294
            self.digiprogress.set_text("0:00:00")
 
2295
         else:
 
2296
            self.digiprogress.set_text(" -00:00 ")
 
2297
      else:
 
2298
         self.cb_progress(self.progressadj)
 
2299
 
 
2300
   def cb_event(self, widget, event, callback_data):
 
2301
      # Handle click to the play progress indicator
 
2302
      if callback_data == "DigitalProgressPress":
 
2303
         if event.button == 1:
 
2304
            self.digiprogress_click()
 
2305
         if event.button == 3:
 
2306
            self.parent.app_menu.popup(None, None, None, event.button, event.time)
 
2307
         return True                            # Prevent any focus therefore any cursor appearing
 
2308
      if event.button == 1:
 
2309
         # Handle click to the play progress bar
 
2310
         if callback_data == "ProgressPress":
 
2311
            self.progress_press = True
 
2312
            if self.timeout_source_id:
 
2313
               gobject.source_remove(self.timeout_source_id)
 
2314
         elif callback_data == "ProgressRelease":
 
2315
            self.progress_press = False
 
2316
            if self.player_is_playing:
 
2317
               self.progress_current_figure = self.progressadj.get_value()
 
2318
               self.handle_motion_as_drop = True
 
2319
               gobject.idle_add(self.player_progress_value_changed_emitter)
 
2320
      return False
 
2321
      
 
2322
   # This is really a very convoluted workaround to achieve the effect of a connect_after
 
2323
   # method on a button_release_event on the player progress bar to run player_restart
 
2324
   # I tried using connect_after but no such luck hence this retarded idle function.
 
2325
   @threadslock
 
2326
   def player_progress_value_changed_emitter(self):
 
2327
      self.progressadj.emit("value_changed")
 
2328
      return False
 
2329
 
 
2330
   def cb_menu_select(self, widget, data):
 
2331
      print "The %s was chosen from the %s menu" % (data, self.playername)   
 
2332
 
 
2333
   def delete_event(self, widget, event, data=None):
 
2334
      return False
 
2335
 
 
2336
   def get_elements_from(self, pathnames):
 
2337
      self.no_more_files = False
 
2338
      l = len(pathnames)
 
2339
      if l == 1:
 
2340
         ext = os.path.splitext(pathnames[0])[1]
 
2341
         if ext == ".m3u":
 
2342
            return self.get_elements_from_m3u(pathnames[0])
 
2343
         elif ext == ".pls":
 
2344
            return self.get_elements_from_pls(pathnames[0])
 
2345
         elif ext == ".xspf":
 
2346
            return self.get_elements_from_xspf(pathnames[0])
 
2347
         elif os.path.isdir(pathnames[0]):
 
2348
            return self.get_elements_from_directory(pathnames[0], set(), 2)
 
2349
      return self.get_elements_from_chosen(pathnames)
 
2350
 
 
2351
   def get_elements_from_chosen(self, chosenfiles):
 
2352
      for each in chosenfiles:
 
2353
         meta = self.get_media_metadata(each)
 
2354
         if meta:
 
2355
            yield meta
 
2356
   
 
2357
   def get_elements_from_directory_orig(self, chosendir):
 
2358
      files = os.listdir(chosendir)
 
2359
      files.sort()
 
2360
      for each in files:
 
2361
         path = "/".join((chosendir, each))
 
2362
         meta = self.get_media_metadata(path)
 
2363
         if meta:
 
2364
            yield meta
 
2365
 
 
2366
   def get_elements_from_directory(self, chosendir, visited, depth):
 
2367
      depth -= 1
 
2368
      chosendir = os.path.realpath(chosendir)
 
2369
      if chosendir in visited or not os.path.isdir(chosendir):
 
2370
         return
 
2371
      else:
 
2372
         visited.add(chosendir)
 
2373
 
 
2374
      directories = set()
 
2375
 
 
2376
      print chosendir
 
2377
      files = os.listdir(chosendir)
 
2378
      files.sort()
 
2379
      for filename in files:
 
2380
         pathname = "/".join((chosendir, filename))
 
2381
         if os.path.isdir(pathname):
 
2382
            #if os.path.realpath(pathname) == pathname:
 
2383
            if not filename.startswith("."):
 
2384
               directories.add(filename)
 
2385
         else:
 
2386
            meta = self.get_media_metadata(pathname)
 
2387
            if meta:
 
2388
               yield meta
 
2389
               
 
2390
      if depth:
 
2391
         for subdir in directories:
 
2392
            print "examining", "/".join((chosendir, subdir))
 
2393
            gen = self.get_elements_from_directory("/".join((chosendir, subdir)), visited, depth)
 
2394
            for meta in gen:
 
2395
               yield meta
 
2396
 
 
2397
   def get_elements_from_m3u(self, filename):
 
2398
      try:
 
2399
         file = open(filename, "r")
 
2400
         data = file.read().strip()
 
2401
         file.close()
 
2402
      except IOError:
 
2403
         print "Problem reading file", filename
 
2404
         return
 
2405
      basepath = os.path.split(filename)[0] + "/"
 
2406
      data = data.splitlines()
 
2407
      for line, each in enumerate(data):
 
2408
         if each[0] == "#":
 
2409
            continue
 
2410
         if each[0] != "/":
 
2411
            each = basepath + each
 
2412
         # handle special case of a single element referring to a directory
 
2413
         if line == 0 and len(data) == 1 and os.path.isdir(each):
 
2414
            gen = self.get_elements_from_directory(each)
 
2415
            for meta in gen:
 
2416
               yield meta
 
2417
            return
 
2418
         meta = self.get_media_metadata(each)
 
2419
         if meta:
 
2420
            yield meta
 
2421
         line += 1
 
2422
         
 
2423
   def get_elements_from_pls(self, filename):
 
2424
      import ConfigParser
 
2425
      cfg = ConfigParser.RawConfigParser()
 
2426
      try:
 
2427
         cfg.readfp(open(filename))
 
2428
      except IOError:
 
2429
         print "Problem reading file"
 
2430
         return
 
2431
      if cfg.sections() != ['playlist']:
 
2432
         print "wrong number of sections in pls file"
 
2433
         return
 
2434
      if cfg.getint('playlist', 'Version') != 2:
 
2435
         print "can handle version 2 pls playlists only"
 
2436
         return
 
2437
      try:
 
2438
         n = cfg.getint('playlist', 'NumberOfEntries')
 
2439
      except ConfigParser.NoOptionError:
 
2440
         print "NumberOfEntries is missing from playlist"
 
2441
         return
 
2442
      except ValueError:
 
2443
         print "NumberOfEntries is not an int"
 
2444
      for i in range(1, n + 1):
 
2445
         try:
 
2446
            path = cfg.get('playlist', 'File%d' % i)
 
2447
         except:
 
2448
            print "Problem getting file path from playlist"
 
2449
         else:
 
2450
            if os.path.isfile(path):
 
2451
               meta = self.get_media_metadata(path)
 
2452
               if meta:
 
2453
                  yield meta
 
2454
         
 
2455
   def get_elements_from_xspf(self, filename):
 
2456
      class BadXspf(ValueError):
 
2457
         pass
 
2458
      class GotLocation(Exception):
 
2459
         pass
 
2460
      
 
2461
      try:
 
2462
         baseurl = []
 
2463
         
 
2464
         try:
 
2465
            dom = mdom.parse(filename)
 
2466
         except:
 
2467
            raise BadXspf
 
2468
 
 
2469
         if dom.hasChildNodes() and len(dom.childNodes) == 1 and dom.documentElement.nodeName == u'playlist':
 
2470
            playlist = dom.documentElement
 
2471
         else:
 
2472
            raise BadXspf
 
2473
 
 
2474
         if playlist.namespaceURI != u"http://xspf.org/ns/0/":
 
2475
            raise BadXspf
 
2476
            
 
2477
         try:
 
2478
            v = int(playlist.getAttribute('version'))
 
2479
         except:
 
2480
            raise BadXspf
 
2481
         if v < 0 or v > 1:
 
2482
            print "only xspf playlist versions 0 and 1 supported"
 
2483
            raise BadXspf
 
2484
         del v
 
2485
         
 
2486
         # obtain base URLs for relative URLs encountered in trackList
 
2487
         # only one location tag is allowed
 
2488
         locations = [ x for x in playlist.childNodes if x.nodeName == u"location" ]
 
2489
         if len(locations) == 1:
 
2490
            url = locations[0].childNodes[0].wholeText
 
2491
            if url.startswith(u"file:///"):
 
2492
               baseurl.append(url)
 
2493
         elif locations:
 
2494
            raise BadXspf
 
2495
         # lesser priority given to the path of the playlist in the filesystem
 
2496
         baseurl.append(u"file://" + urllib.quote(os.path.split(os.path.realpath(filename))[0].decode("ASCII") + u"/"))
 
2497
         # as before but without realpath
 
2498
         baseurl.append(u"file://" + urllib.quote(os.path.split(filename)[0].decode("ASCII") + u"/"))
 
2499
         if baseurl[-1] == baseurl[-2]:
 
2500
            del baseurl[-1]
 
2501
         
 
2502
         trackLists = playlist.getElementsByTagName('trackList')
 
2503
         if len(trackLists) != 1:
 
2504
            raise BadXspf
 
2505
         trackList = trackLists[0]
 
2506
         if trackList.parentNode != playlist:
 
2507
            raise BadXspf
 
2508
            
 
2509
         tracks = trackList.getElementsByTagName('track')
 
2510
         for track in tracks:
 
2511
            if track.parentNode != trackList:
 
2512
               raise BadXspf
 
2513
            locations = track.getElementsByTagName('location')
 
2514
            try:
 
2515
               for location in locations:
 
2516
                  for base in baseurl:
 
2517
                     url = urllib.unquote(urllib.basejoin(base, location.firstChild.wholeText).encode("ASCII"))
 
2518
                     meta = self.get_media_metadata(url)
 
2519
                     if meta:
 
2520
                        yield meta
 
2521
                        raise GotLocation
 
2522
               # Support namespaced pld tag for literal playlist data.
 
2523
               # This is only used for non playable data such as playlist controls.
 
2524
               extensions = track.getElementsByTagName('extension')
 
2525
               for extension in extensions:
 
2526
                  if extension.getAttribute("application") == "http://idjc.sourceforge.net/ns/":
 
2527
                     customtags = extension.getElementsByTagNameNS("http://idjc.sourceforge.net/ns/", "pld")
 
2528
                     for tag in customtags:
 
2529
                        try:
 
2530
                           literal_entry = NOTVALID._replace(**dict((k, type(getattr(NOTVALID, k))(tag.attributes.get(k).nodeValue)) for k in tag.attributes.keys()))
 
2531
                        except Exception, e:
 
2532
                           print e
 
2533
                           pass
 
2534
                        else:
 
2535
                           yield literal_entry
 
2536
                           raise GotLocation
 
2537
            except GotLocation:
 
2538
               pass
 
2539
         return
 
2540
      except BadXspf:
 
2541
         print "could not parse playlist", filename
 
2542
         return
 
2543
    
 
2544
   def drag_data_delete(self, treeview, context):
 
2545
      if context.action == gtk.gdk.ACTION_MOVE:
 
2546
         treeselection = treeview.get_selection()
 
2547
         model, iter = treeselection.get_selected()
 
2548
         data = model.get_value(iter, 0)
 
2549
         if data[:3] == "<b>":
 
2550
            self.iter_playing = 0
 
2551
            self.stop.clicked()
 
2552
 
 
2553
   def drag_data_get_data(self, treeview, context, selection, target_id, etime):
 
2554
      treeselection = treeview.get_selection()
 
2555
      model, iter = treeselection.get_selected()
 
2556
      if model.get_value(iter, 1) != "":
 
2557
         data = "file://" + model.get_value(iter, 1)
 
2558
      else:
 
2559
         data = "idjcplayercontrol://" + model.get_value(iter, 0) + "+" + str(model.get_value(iter, 2)) + "+" + model.get_value(iter, 3) + "+" + model.get_value(iter, 4)
 
2560
      print "data for drag_get =", data
 
2561
      selection.set(selection.target, 8, data)
 
2562
      self.reselect_please = True
 
2563
      return True
 
2564
 
 
2565
   def drag_data_received_data(self, treeview, context, x, y, dragged, info, etime):
 
2566
      if info != 0:
 
2567
         text = str(dragged.data)
 
2568
         if text[:20] == "idjcplayercontrol://":
 
2569
            ct = text[20:].split("+")
 
2570
            newrow = [ ct[0], "", int(ct[1]), ct[2], ct[3], "", "" ]
 
2571
            drop_info = treeview.get_dest_row_at_pos(x, y)
 
2572
            model = treeview.get_model()
 
2573
            if drop_info == None:
 
2574
               model.append(newrow)
 
2575
            else:
 
2576
               path, position = drop_info
 
2577
               dest_iter = model.get_iter(path)
 
2578
               if(position == gtk.TREE_VIEW_DROP_BEFORE or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
 
2579
                  model.insert_before(dest_iter, newrow)
 
2580
               else:
 
2581
                  model.insert_after(dest_iter, newrow)
 
2582
            if context.action == gtk.gdk.ACTION_MOVE:
 
2583
               context.finish(True, True, etime)
 
2584
         else:
 
2585
            if context.action == gtk.gdk.ACTION_MOVE:
 
2586
               context.finish(True, True, etime)
 
2587
            gobject.idle_add(self.drag_data_received_data_idle, treeview, x, y, text)
 
2588
      else:
 
2589
         treeselection = treeview.get_selection()
 
2590
         model, iter = treeselection.get_selected()
 
2591
         drop_info = treeview.get_dest_row_at_pos(x, y)
 
2592
         if drop_info == None:
 
2593
            self.liststore.move_before(iter, None)
 
2594
         else:
 
2595
            path, position = drop_info
 
2596
            dest_iter = model.get_iter(path)
 
2597
            if(position == gtk.TREE_VIEW_DROP_BEFORE or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
 
2598
               self.liststore.move_before(iter, dest_iter)
 
2599
            else:
 
2600
               self.liststore.move_after(iter, dest_iter)
 
2601
         if context.action == gtk.gdk.ACTION_MOVE:
 
2602
            context.finish(False, False, etime)
 
2603
      return True
 
2604
   
 
2605
   def drag_data_received_data_idle(self, treeview, x, y, dragged):
 
2606
      gtk.gdk.threads_enter()
 
2607
      model = treeview.get_model()
 
2608
      gtk.gdk.threads_leave()
 
2609
        
 
2610
      pathnames = [ urllib.unquote(t[7:]) for t in dragged.strip().splitlines() if t.startswith("file://") ]
 
2611
      gen = self.get_elements_from(pathnames)
 
2612
      
 
2613
      first = True
 
2614
      for media_data in gen:
 
2615
         if self.no_more_files:
 
2616
            self.no_more_files = False
 
2617
            break
 
2618
         if first:
 
2619
            gtk.gdk.threads_enter()
 
2620
            drop_info = treeview.get_dest_row_at_pos(x, y)
 
2621
            gtk.gdk.threads_leave()
 
2622
            if drop_info:
 
2623
               path, position = drop_info
 
2624
               gtk.gdk.threads_enter()
 
2625
               iter = model.get_iter(path)
 
2626
               gtk.gdk.threads_leave()
 
2627
               if(position == gtk.TREE_VIEW_DROP_BEFORE or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
 
2628
                  gtk.gdk.threads_enter()
 
2629
                  iter = model.insert_before(iter, media_data)
 
2630
                  gtk.gdk.threads_leave()
 
2631
               else:
 
2632
                  gtk.gdk.threads_enter()
 
2633
                  iter = model.insert_after(iter, media_data)
 
2634
                  gtk.gdk.threads_leave()
 
2635
            else:
 
2636
               gtk.gdk.threads_enter()
 
2637
               iter = model.append(media_data)
 
2638
               gtk.gdk.threads_leave()
 
2639
            first = False
 
2640
         else:
 
2641
            gtk.gdk.threads_enter()
 
2642
            iter = model.insert_after(iter, media_data)
 
2643
            gtk.gdk.threads_leave()
 
2644
         gtk.gdk.threads_enter()
 
2645
         while gtk.events_pending():
 
2646
            gtk.gdk.threads_leave()
 
2647
            gtk.gdk.threads_enter()
 
2648
            gtk.main_iteration()
 
2649
            gtk.gdk.threads_leave()
 
2650
            gtk.gdk.threads_enter()
 
2651
         gtk.gdk.threads_leave()
 
2652
      self.reselect_please = True
 
2653
      return False
 
2654
   
 
2655
   sourcetargets = [
 
2656
      ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0),
 
2657
      ('text/plain', 0, 1),
 
2658
      ('TEXT', 0, 2),
 
2659
      ('STRING', 0, 3),
 
2660
      ]
 
2661
      
 
2662
   droptargets = [
 
2663
      ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0),
 
2664
      ('text/plain', 0, 1),
 
2665
      ('TEXT', 0, 2),
 
2666
      ('STRING', 0, 3),
 
2667
      ('text/uri-list', 0, 4)                   # Need drop target for prokyon3
 
2668
      ] 
 
2669
      
 
2670
   def cb_doubleclick(self, treeview, path, tvcolumn, user_data):
 
2671
      # Our play button handler will manage to get the song title.
 
2672
      # All we need to do is set a flag and issue a play button started signal.
 
2673
      if self.is_playing:
 
2674
         # The new_title flag allows a new song to be played when the player is going.
 
2675
         self.new_title = True
 
2676
         self.play.clicked()
 
2677
      else:
 
2678
         self.play.clicked()
 
2679
                           
 
2680
   def cb_selection_changed(self, treeselection):
 
2681
      self.cuesheet_playlist.hide()
 
2682
      self.cuesheet_playlist.treeview.set_model(None)
 
2683
      model, iter = treeselection.get_selected()
 
2684
      if iter:
 
2685
         row = PlayerRow._make(self.liststore[model.get_path(iter)[0]])
 
2686
         if row.cuesheet:
 
2687
            self.cuesheet_playlist.treeview.set_model(row.cuesheet)
 
2688
            self.cuesheet_playlist.show()
 
2689
      self.update_time_stats()
 
2690
         
 
2691
   def cb_playlist_changed(self, treemodel, path, iter = None):
 
2692
      self.playlist_changed = True      # used by the request system
 
2693
         
 
2694
   def menu_activate(self, widget, event):
 
2695
      if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
 
2696
         self.menu_model = self.treeview.get_model()
 
2697
         row_info = self.treeview.get_dest_row_at_pos(int(event.x + 0.5), int(event.y + 0.5))
 
2698
         if row_info:
 
2699
            sens = True
 
2700
            path, position = row_info
 
2701
            selection = self.treeview.get_selection()
 
2702
            selection.select_path(path)
 
2703
            self.menu_iter = self.menu_model.get_iter(path)     # store the context of the menu action
 
2704
            pathname = self.menu_model.get_value(self.menu_iter, 1)
 
2705
            self.item_tag.set_sensitive(MutagenGUI.is_supported(pathname) != False)
 
2706
         else:
 
2707
            pathname = ""
 
2708
            self.menu_iter = None
 
2709
            sens = False
 
2710
         self.item_duplicate.set_sensitive(sens)
 
2711
         self.remove_this.set_sensitive(sens)
 
2712
         self.remove_from_here.set_sensitive(sens)
 
2713
         self.remove_to_here.set_sensitive(sens)
 
2714
         self.item_tojingles.set_sensitive(pathname != "")
 
2715
         model = self.treeview.get_model()
 
2716
         if model.get_iter_first() == None:
 
2717
            sens2 = False
 
2718
         else:
 
2719
            sens2 = True
 
2720
         self.pl_menu_item.set_sensitive(sens2)
 
2721
         self.playlist_save.set_sensitive(sens2)
 
2722
         self.playlist_copy.set_sensitive(sens2)
 
2723
         self.playlist_transfer.set_sensitive(sens2)
 
2724
         self.playlist_empty.set_sensitive(sens2)
 
2725
         if self.pl_mode.get_active() != 0:
 
2726
            self.pl_menu_control.set_sensitive(False)
 
2727
         else:
 
2728
            self.pl_menu_control.set_sensitive(True)
 
2729
         
 
2730
         if self.playername == "left":  # determine if anything is selected in the other playlist
 
2731
            tv = self.parent.player_right.treeview.get_selection()
 
2732
         else:
 
2733
            tv = self.parent.player_left.treeview.get_selection()
 
2734
         model, iter = tv.get_selected()
 
2735
         if iter:
 
2736
            sens3 = True
 
2737
         else:
 
2738
            sens3 = False
 
2739
         self.copy_append_cursor.set_sensitive(sens3)
 
2740
         self.copy_prepend_cursor.set_sensitive(sens3)
 
2741
         self.transfer_append_cursor.set_sensitive(sens3)
 
2742
         self.transfer_prepend_cursor.set_sensitive(sens3)   
 
2743
            
 
2744
         widget.popup(None, None, None, event.button, event.time)
 
2745
         return True
 
2746
      return False
 
2747
      
 
2748
   def cb_plexpander(self, widget, param_spec):
 
2749
      if widget.get_expanded():
 
2750
         self.plframe.show()
 
2751
      else:
 
2752
         self.plframe.hide()
 
2753
      
 
2754
   def menuitem_response(self, widget, text):
 
2755
      print "The %s menu option was chosen" % text
 
2756
      model = self.menu_model
 
2757
      iter = self.menu_iter
 
2758
      
 
2759
      if text == "Announcement Control" and iter is not None and model.get_value(iter, 0) == ">announcement":
 
2760
         # modify existing announcement dialog
 
2761
         dia = AnnouncementDialog(self, model, iter, "delete_modify")
 
2762
         dia.show()
 
2763
         return
 
2764
      
 
2765
      dict = {
 
2766
             "Stop Control"              : ">stopplayer",
 
2767
             "Stop Control 2"            : ">stopplayer2",
 
2768
             "Transfer Control"          : ">transfer",
 
2769
             "Crossfade Control"         : ">crossfade",
 
2770
             "Stream Disconnect Control" : ">stopstreaming",
 
2771
             "Stop Recording Control"    : ">stoprecording",
 
2772
             "Normal Speed Control"      : ">normalspeed",
 
2773
             "Announcement Control"      : ">announcement",
 
2774
             "Fade 10"                   : ">fade10",
 
2775
             "Fade 5"                    : ">fade5",
 
2776
             "Fade none"                 : ">fadenone",
 
2777
             "Jump To Top Control"       : ">jumptotop",
 
2778
      }
 
2779
      if dict.has_key(text):
 
2780
         if iter is not None:
 
2781
            iter = model.insert_after(iter)
 
2782
         else:
 
2783
            iter = model.append()
 
2784
         model.set_value(iter, 0, dict[text])
 
2785
         model.set_value(iter, 1, "")
 
2786
         model.set_value(iter, 2, -11)
 
2787
         model.set_value(iter, 3, "")
 
2788
         model.set_value(iter, 4, "")
 
2789
         model.set_value(iter, 5, "")
 
2790
         model.set_value(iter, 6, "")
 
2791
         self.treeview.get_selection().select_iter(iter)
 
2792
         
 
2793
         if text == "Announcement Control":
 
2794
            # brand new announcement dialog
 
2795
            dia = AnnouncementDialog(self, model, iter, "initial")
 
2796
            dia.show()
 
2797
         return
 
2798
      
 
2799
      if text == "MetaTag":
 
2800
         try:
 
2801
            pathname = model.get_value(iter, 1)
 
2802
         except TypeError:
 
2803
            pass
 
2804
         else:
 
2805
            MutagenGUI(pathname, model.get_value(iter, 4) , self.parent)
 
2806
      
 
2807
      if text == "Add File":
 
2808
         self.add.clicked()
 
2809
      
 
2810
      if text == "Playlist Save":
 
2811
         if self.showing_pl_save_requester == False:
 
2812
            if self.playername == "left":
 
2813
               filerqtext = _('Save left playlist')
 
2814
            else:
 
2815
               filerqtext = _('Save right playlist')
 
2816
            vbox = gtk.VBox()
 
2817
            self.expander = gtk.Expander()
 
2818
            self.expander.connect("notify::expanded", self.cb_plexpander)
 
2819
            vbox.add(self.expander)
 
2820
            self.expander.show()
 
2821
            
 
2822
            self.plframe = gtk.Frame()
 
2823
            self.plliststore = gtk.ListStore(str, str)
 
2824
            for row in self.playlisttype_extension:
 
2825
               self.plliststore.append(row)
 
2826
            self.pltreeview = gtk.TreeView(self.plliststore)
 
2827
            self.plframe.add(self.pltreeview)
 
2828
            
 
2829
            self.pltreeview.show()
 
2830
            self.pltreeview.set_rules_hint(True)
 
2831
            cellrenderer1 = gtk.CellRendererText()
 
2832
            self.pltreeviewcol1 = gtk.TreeViewColumn(_('File Type'), cellrenderer1, text = 0)
 
2833
            self.pltreeviewcol1.set_expand(True)
 
2834
            cellrenderer2 = gtk.CellRendererText()
 
2835
            # TC: File extension.
 
2836
            self.pltreeviewcol2 = gtk.TreeViewColumn(_('Extension'), cellrenderer2, text = 1)
 
2837
            self.pltreeview.append_column(self.pltreeviewcol1)
 
2838
            self.pltreeview.append_column(self.pltreeviewcol2)
 
2839
            self.pltreeview.connect("cursor-changed", self.plfile_new_savetype)
 
2840
            self.pltreeview.set_cursor(self.plsave_filetype)
 
2841
            
 
2842
            if (self.plsave_open):
 
2843
               self.expander.set_expanded(True)
 
2844
            vbox.add(self.plframe)
 
2845
               
 
2846
            self.plfilerq = gtk.FileChooserDialog(filerqtext + pm.title_extra, None, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
 
2847
            self.plfilerq.set_current_folder(self.home)
 
2848
            self.plfilerq.add_filter(self.plfilefilter_all)
 
2849
            self.plfilerq.add_filter(self.plfilefilter_playlists)
 
2850
            self.plfilerq.set_filter(self.plsave_filtertype)
 
2851
            self.plfilerq.set_extra_widget(vbox)
 
2852
            if self.plsave_folder is not None:
 
2853
               self.plfilerq.set_current_folder(self.plsave_folder)
 
2854
            if self.plsave_filetype == 0:
 
2855
               self.plfilerq.set_current_name("idjcplaylist.m3u")
 
2856
            else:
 
2857
               self.plfilerq.set_current_name("idjcplaylist")
 
2858
            self.plfilerq.connect("response", self.plfile_response)
 
2859
            self.plfilerq.connect("destroy", self.plfile_destroy)
 
2860
            self.plfilerq.show()
 
2861
            self.showing_pl_save_requester = True
 
2862
         else:
 
2863
            self.plfilerq.present()
 
2864
       
 
2865
      if text == "Remove All":
 
2866
         if self.is_playing:
 
2867
            self.stop.clicked()
 
2868
         self.no_more_files = True
 
2869
         self.liststore.clear()
 
2870
      
 
2871
      if text == "Remove This" and iter != None:
 
2872
         name = model.get_value(iter, 0)
 
2873
         if name[:3] == "<b>":
 
2874
            self.stop.clicked()
 
2875
         self.liststore.remove(iter)
 
2876
         
 
2877
      if text == "Remove From Here" and iter != None:    
 
2878
         path = model.get_path(iter)
 
2879
         try:
 
2880
            while 1:
 
2881
               iter = model.get_iter(path)
 
2882
               if model.get_value(iter, 0)[:3] == "<b>":
 
2883
                  self.stop.clicked()
 
2884
               self.no_more_files = True
 
2885
               self.liststore.remove(iter)
 
2886
         except:
 
2887
            print "Nothing more to delete"
 
2888
         
 
2889
      if text == "Remove To Here" and iter != None:
 
2890
         self.no_more_files = True
 
2891
         path = model.get_path(iter)[0] -1
 
2892
         while path >= 0:
 
2893
            iter = model.get_iter(path)
 
2894
            if model.get_value(iter, 0)[:3] == "<b>":
 
2895
               self.stop.clicked()
 
2896
            self.liststore.remove(iter)
 
2897
            path = path -1
 
2898
 
 
2899
      if text == "Duplicate" and iter != None:
 
2900
         row = list(model[model.get_path(iter)])
 
2901
         if row[0][:3] == "<b>":                # strip off any bold tags
 
2902
            row[0] = row[0][3:-4]
 
2903
         model.insert_after(iter, row)
 
2904
      
 
2905
      if text == "Playlist Exchange":
 
2906
         self.no_more_files = True
 
2907
         if self.playername == "left":
 
2908
            opposite = self.parent.player_right
 
2909
         else:
 
2910
            opposite = self.parent.player_left
 
2911
         self.stop.clicked()
 
2912
         opposite.stop.clicked()
 
2913
         i = 0
 
2914
         try:
 
2915
            while 1:
 
2916
               self.templist.append(self.liststore[i])
 
2917
               i = i + 1
 
2918
         except IndexError:
 
2919
            pass
 
2920
         self.liststore.clear()
 
2921
         i = 0
 
2922
         try:
 
2923
            while 1:
 
2924
               self.liststore.append(opposite.liststore[i])
 
2925
               i = i + 1
 
2926
         except IndexError:
 
2927
            pass
 
2928
         opposite.liststore.clear()
 
2929
         i = 0
 
2930
         try:
 
2931
            while 1:
 
2932
               opposite.liststore.append(self.templist[i])
 
2933
               i = i + 1
 
2934
         except IndexError:
 
2935
            pass
 
2936
         self.templist.clear()
 
2937
         
 
2938
      if text == "Copy Append":
 
2939
         self.copy_playlist("end")
 
2940
         
 
2941
      if text == "Transfer Append":
 
2942
         self.copy_playlist("end")
 
2943
         self.stop.clicked()
 
2944
         self.liststore.clear()
 
2945
                 
 
2946
      if text == "Copy Prepend":
 
2947
         self.copy_playlist("start")
 
2948
         
 
2949
      if text == "Transfer Prepend":
 
2950
         self.copy_playlist("start")
 
2951
         self.stop.clicked()
 
2952
         self.liststore.clear()
 
2953
         
 
2954
      if text == "Copy Append Cursor":
 
2955
         self.copy_playlist("after")
 
2956
         
 
2957
      if text == "Transfer Append Cursor":
 
2958
         self.copy_playlist("after")
 
2959
         self.stop.clicked()
 
2960
         self.liststore.clear()
 
2961
         
 
2962
      if text == "Copy Prepend Cursor":
 
2963
         self.copy_playlist("before")
 
2964
         
 
2965
      if text == "Transfer Prepend Cursor":
 
2966
         self.copy_playlist("before")
 
2967
         self.stop.clicked()
 
2968
         self.liststore.clear()
 
2969
        
 
2970
      if text == "ToJingles":
 
2971
         source = model.get_value(iter, 1)
 
2972
         dest = pm.jinglesdir / os.path.split(source)[1]
 
2973
         try:
 
2974
            source = open(source, "r")
 
2975
            dest = open(dest, "w")
 
2976
            while True:
 
2977
               data = source.read(4096)
 
2978
               dest.write(data)
 
2979
               if len(data) < 4096:
 
2980
                  break;
 
2981
         except IOError:
 
2982
            print "IOError occurred"
 
2983
         source.close
 
2984
         dest.close
 
2985
         self.parent.jingles.refresh.clicked()
 
2986
               
 
2987
      if self.player_is_playing:        # put the cursor on the file playing after a brief pause.
 
2988
         self.reselect_please = True
 
2989
 
 
2990
   def stripbold(self, playlist_item):
 
2991
      copy = list(playlist_item)
 
2992
      if copy[0][:3] == "<b>":
 
2993
         copy[0] = copy[0][3:-4]
 
2994
      return copy
 
2995
                 
 
2996
   def copy_playlist(self, dest):
 
2997
      if self.playername == "left":
 
2998
         other = self.parent.player_right
 
2999
      else:
 
3000
         other = self.parent.player_left
 
3001
      i = 0
 
3002
      try:
 
3003
         if dest == "start":
 
3004
            while 1:
 
3005
               other.liststore.insert(i, self.stripbold(self.liststore[i]))
 
3006
               i = i + 1
 
3007
         if dest == "end":
 
3008
            while 1:
 
3009
               other.liststore.append(self.stripbold(self.liststore[i]))
 
3010
               i = i + 1
 
3011
         
 
3012
         (model, iter) = other.treeview.get_selection().get_selected()         
 
3013
         
 
3014
         if dest == "after":
 
3015
            while 1:
 
3016
               iter = other.liststore.insert_after(iter, self.stripbold(self.liststore[i]))
 
3017
               i = i + 1
 
3018
         if dest == "before":
 
3019
            while 1:
 
3020
               other.liststore.insert_before(iter, self.stripbold(self.liststore[i]))
 
3021
               i = i + 1
 
3022
      except IndexError:
 
3023
         pass
 
3024
            
 
3025
   def cb_keypress(self, widget, event):
 
3026
      # Handle shifted arrow keys for rearranging stuff in the playlist.
 
3027
      if event.state & gtk.gdk.SHIFT_MASK:
 
3028
         if event.keyval == 65362:
 
3029
            self.arrow_up()
 
3030
            return True
 
3031
         if event.keyval == 65364:
 
3032
            self.arrow_down()
 
3033
            return True
 
3034
         if event.keyval == 65361 and self.playername == "right":
 
3035
            treeselection = widget.get_selection()
 
3036
            s_model, s_iter = treeselection.get_selected()
 
3037
            if s_iter is not None:
 
3038
               name = s_model.get_value(s_iter, 0)
 
3039
               if name[:3] == "<b>":
 
3040
                  self.stop.clicked()
 
3041
               otherselection = self.parent.player_left.treeview.get_selection()
 
3042
               d_model, d_iter = otherselection.get_selected()
 
3043
               row = list(s_model[s_model.get_path(s_iter)])
 
3044
               path = s_model.get_path(s_iter)
 
3045
               s_model.remove(s_iter)
 
3046
               treeselection.select_path(path)
 
3047
               if d_iter is None:
 
3048
                  d_iter = d_model.append(row)
 
3049
               else:
 
3050
                  d_iter = d_model.insert_after(d_iter, row)
 
3051
               otherselection.select_iter(d_iter)
 
3052
               self.parent.player_left.treeview.set_cursor(d_model.get_path(d_iter))
 
3053
               self.parent.player_left.treeview.scroll_to_cell(d_model.get_path(d_iter), None, False)
 
3054
            return True
 
3055
         if event.keyval == 65363 and self.playername == "left":
 
3056
            treeselection = widget.get_selection()
 
3057
            s_model, s_iter = treeselection.get_selected()
 
3058
            if s_iter is not None:
 
3059
               name = s_model.get_value(s_iter, 0)
 
3060
               if name[:3] == "<b>":
 
3061
                  self.stop.clicked()
 
3062
               otherselection = self.parent.player_right.treeview.get_selection()
 
3063
               d_model, d_iter = otherselection.get_selected()
 
3064
               row = list(s_model[s_model.get_path(s_iter)])
 
3065
               path = s_model.get_path(s_iter)
 
3066
               s_model.remove(s_iter)
 
3067
               treeselection.select_path(path)
 
3068
               if d_iter is None:
 
3069
                  d_iter = d_model.append(row)
 
3070
               else:
 
3071
                  d_iter = d_model.insert_after(d_iter, row)
 
3072
               otherselection.select_iter(d_iter)
 
3073
               self.parent.player_right.treeview.set_cursor(d_model.get_path(d_iter))
 
3074
               self.parent.player_right.treeview.scroll_to_cell(d_model.get_path(d_iter), None, False)
 
3075
            return True
 
3076
      if event.keyval == 65361 and self.playername == "right":
 
3077
         treeselection = self.parent.player_left.treeview.get_selection()
 
3078
         model, iter = treeselection.get_selected()
 
3079
         if iter is not None:
 
3080
            self.parent.player_left.treeview.set_cursor(model.get_path(iter))
 
3081
         else:
 
3082
            treeselection.select_path(0)
 
3083
         self.parent.player_left.treeview.grab_focus()
 
3084
         return True
 
3085
      if event.keyval == 65363 and self.playername == "left":
 
3086
         treeselection = self.parent.player_right.treeview.get_selection()
 
3087
         model, iter = treeselection.get_selected()
 
3088
         if iter is not None:
 
3089
            self.parent.player_right.treeview.set_cursor(model.get_path(iter))
 
3090
         else:
 
3091
            treeselection.select_path(0)
 
3092
         self.parent.player_right.treeview.grab_focus()
 
3093
         return True
 
3094
      if event.keyval == 65535 or event.keyval == 65439:         # The Main and NK Delete keys
 
3095
         treeselection = widget.get_selection()         # Delete to cause playlist entry removal
 
3096
         model, iter = treeselection.get_selected()
 
3097
         if iter is not None:
 
3098
            path = model.get_path(iter)
 
3099
            if path[0] > 0:
 
3100
               prev = model.get_iter(path[0]-1)
 
3101
            else:
 
3102
               prev = None
 
3103
            try:
 
3104
               next = model.get_iter(path[0]+1)
 
3105
            except:
 
3106
               next = None
 
3107
            name = model.get_value(iter, 0)
 
3108
            if name[:3] == "<b>":
 
3109
               self.stop.clicked()
 
3110
            self.liststore.remove(iter)
 
3111
            if next is not None:
 
3112
               treeselection.select_iter(next)
 
3113
               widget.set_cursor(model.get_path(next))
 
3114
               self.treeview.scroll_to_cell(model.get_path(next), None, False)
 
3115
            elif prev is not None:
 
3116
               treeselection.select_iter(prev)
 
3117
               widget.set_cursor(model.get_path(prev))
 
3118
               self.treeview.scroll_to_cell(model.get_path(prev), None, False)
 
3119
         else:
 
3120
            print "Playlist is empty!"
 
3121
         return True
 
3122
      # Allow certain key presses to work but not allow a text entry box to appear.
 
3123
      if event.string =="\r":
 
3124
         self.stop.clicked()
 
3125
         self.play.clicked()
 
3126
         return True
 
3127
      if event.string == "":
 
3128
         return False
 
3129
      return True
 
3130
            
 
3131
   def rgrowconfig(self, tv_column, cell_renderer, model, iter):
 
3132
      if self.exiting:
 
3133
         return
 
3134
      self.rowconfig(tv_column, cell_renderer, model, iter)
 
3135
      if model.get_value(iter, 0)[0] == ">":
 
3136
         cell_renderer.set_property("text", " ")
 
3137
      else:
 
3138
         if model.get_value(iter, 7) == RGDEF:
 
3139
            # Red triangle.
 
3140
            cell_renderer.set_property("markup", '<span foreground="dark red">&#x25b5;</span>')
 
3141
         else:
 
3142
            # Small green bullet point.
 
3143
            cell_renderer.set_property("markup", '<span foreground="dark green">&#x2022;</span>')
 
3144
        
 
3145
   def playtimerowconfig(self, tv_column, cell_renderer, model, iter):
 
3146
      if self.exiting:
 
3147
         return
 
3148
      playtime = model.get_value(iter, 2)
 
3149
      self.rowconfig(tv_column, cell_renderer, model, iter)
 
3150
      cell_renderer.set_property("xalign", 1.0)
 
3151
      if playtime == -11:
 
3152
         if model.get_value(iter, 0) == ">announcement":
 
3153
            length = model.get_value(iter, 3)[2:6]
 
3154
            if not length:
 
3155
               length = "0000"
 
3156
            if length == "0000":
 
3157
               cell_renderer.set_property("text", "")
 
3158
            else:
 
3159
               if length[0] == "0":
 
3160
                  length = " " + length[1] + ":" + length[2:]
 
3161
               else:
 
3162
                  length = length[:2] + ":" + length[2:]
 
3163
               cell_renderer.set_property("text", length)
 
3164
         else:
 
3165
            cell_renderer.set_property("text", "")
 
3166
      elif playtime == 0:
 
3167
         cell_renderer.set_property("text", "? : ??")
 
3168
      else:   
 
3169
         secs = playtime % 60
 
3170
         playtime -= secs
 
3171
         mins = playtime / 60
 
3172
         text = "%d:%02d" % (mins, secs)
 
3173
         cell_renderer.set_property("text", text)
 
3174
 
 
3175
   def rowconfig(self, tv_column, cell_renderer, model, iter):
 
3176
      if self.exiting:
 
3177
         return
 
3178
      celltext = model.get_value(iter, 0)
 
3179
      if celltext[:4] == "<b>>":
 
3180
         celltext = celltext[3:-4]
 
3181
      if celltext[0] == ">":
 
3182
         cell_renderer.set_property("xalign", 0.45)
 
3183
         cell_renderer.set_property("ypad", 0)
 
3184
         cell_renderer.set_property("scale", 0.75)
 
3185
         cell_renderer.set_property("cell-background-set", True)
 
3186
         cell_renderer.set_property("background-set", True)
 
3187
         cell_renderer.set_property("foreground-set", True)
 
3188
         if self.pl_mode.get_active() == 0:
 
3189
            if celltext == ">fade10":
 
3190
               cell_renderer.set_property("cell-background", "dark red")
 
3191
               cell_renderer.set_property("background", "gray")
 
3192
               cell_renderer.set_property("foreground", "dark red")
 
3193
               # TC: Playlist control.
 
3194
               cell_renderer.set_property("text", _('Fade 10s'))
 
3195
            if celltext == ">fade5":
 
3196
               cell_renderer.set_property("cell-background", "dark red")
 
3197
               cell_renderer.set_property("background", "gray")
 
3198
               cell_renderer.set_property("foreground", "dark red")
 
3199
               # TC: Playlist control.
 
3200
               cell_renderer.set_property("text", _('Fade 5s'))
 
3201
            if celltext == ">fadenone":
 
3202
               cell_renderer.set_property("cell-background", "dark red")
 
3203
               cell_renderer.set_property("background", "gray")
 
3204
               cell_renderer.set_property("foreground", "dark red")
 
3205
               # TC: Playlist control.
 
3206
               cell_renderer.set_property("text", _('No Fade'))
 
3207
            if celltext == ">announcement":
 
3208
               cell_renderer.set_property("cell-background", "dark blue")
 
3209
               cell_renderer.set_property("background", "gray")
 
3210
               cell_renderer.set_property("foreground", "dark blue")
 
3211
               # TC: Playlist control.
 
3212
               cell_renderer.set_property("text", _('Announcement'))
 
3213
            if celltext == ">normalspeed":
 
3214
               cell_renderer.set_property("cell-background", "dark green")
 
3215
               cell_renderer.set_property("background", "gray")
 
3216
               cell_renderer.set_property("foreground", "dark green")
 
3217
               # TC: Playlist control.
 
3218
               cell_renderer.set_property("text", _('>> Normal Speed <<'))
 
3219
            if celltext == ">stopplayer":
 
3220
               cell_renderer.set_property("cell-background", "red")
 
3221
               cell_renderer.set_property("background", "gray")
 
3222
               cell_renderer.set_property("foreground", "red")
 
3223
               # TC: Playlist control.
 
3224
               cell_renderer.set_property("text", _('Player stop'))
 
3225
            if celltext == ">stopplayer2":
 
3226
               cell_renderer.set_property("cell-background", "red")
 
3227
               cell_renderer.set_property("background", "gray")
 
3228
               cell_renderer.set_property("foreground", "red")
 
3229
               # TC: Playlist control.
 
3230
               cell_renderer.set_property("text", _('Player stop 2'))
 
3231
            if celltext == ">jumptotop":
 
3232
               cell_renderer.set_property("cell-background", "dark magenta")
 
3233
               cell_renderer.set_property("background", "gray")
 
3234
               cell_renderer.set_property("foreground", "dark magenta")
 
3235
               # TC: Playlist control.
 
3236
               cell_renderer.set_property("text", _('Jump To Top'))
 
3237
            if celltext == ">stopstreaming":
 
3238
               cell_renderer.set_property("cell-background", "black")
 
3239
               cell_renderer.set_property("background", "gray")
 
3240
               cell_renderer.set_property("foreground", "black")
 
3241
               # TC: Playlist control.
 
3242
               cell_renderer.set_property("text", _('Stop streaming'))
 
3243
            if celltext == ">stoprecording":
 
3244
               cell_renderer.set_property("cell-background", "black")
 
3245
               cell_renderer.set_property("background", "gray")
 
3246
               cell_renderer.set_property("foreground", "black")
 
3247
               # TC: Playlist control.
 
3248
               cell_renderer.set_property("text", _('Stop recording'))
 
3249
            if celltext == ">transfer":
 
3250
               cell_renderer.set_property("cell-background", "magenta")
 
3251
               cell_renderer.set_property("background", "gray")
 
3252
               cell_renderer.set_property("foreground", "magenta")
 
3253
               if self.playername == "left":
 
3254
                  # TC: Playlist control.
 
3255
                  cell_renderer.set_property("text", _('>>> Transfer across >>>'))
 
3256
               else:
 
3257
                  # TC: Playlist control.
 
3258
                  cell_renderer.set_property("text", _('<<< Transfer across <<<'))
 
3259
            if celltext == ">crossfade":
 
3260
               cell_renderer.set_property("cell-background", "blue")
 
3261
               cell_renderer.set_property("background", "gray")
 
3262
               cell_renderer.set_property("foreground", "blue")
 
3263
               if self.playername == "left":
 
3264
                  # TC: Playlist control.
 
3265
                  cell_renderer.set_property("text", _('>>> Fade across >>>'))
 
3266
               else:
 
3267
                  # TC: Playlist control.
 
3268
                  cell_renderer.set_property("text", _('<<< Fade across <<<'))
 
3269
         else:
 
3270
            cell_renderer.set_property("cell-background", "darkgray")
 
3271
            cell_renderer.set_property("background", "darkgray")
 
3272
            cell_renderer.set_property("foreground", "white")
 
3273
            # TC: Playlist control.
 
3274
            cell_renderer.set_property("markup", "<i>%s</i>" % _("Ignored playlist control"))
 
3275
      else:
 
3276
         cell_renderer.set_property("foreground-set", False)
 
3277
         cell_renderer.set_property("cell-background-set", False)
 
3278
         cell_renderer.set_property("background-set", False)
 
3279
         cell_renderer.set_property("scale", 1.0)
 
3280
         cell_renderer.set_property("xalign", 0.0)
 
3281
         cell_renderer.set_property("ypad", 2)
 
3282
            
 
3283
   def cb_playlist_delay(self, widget):
 
3284
      print "inter track fade was changed"
 
3285
            
 
3286
   def cb_playlist_mode(self, widget):
 
3287
      self.pl_delay.set_sensitive(self.pl_mode.get_active() in (0, 1, 2, 5))
 
3288
      if widget.get_active() == 0:
 
3289
         self.pl_statusbar.show()
 
3290
      else:
 
3291
         self.pl_statusbar.hide()
 
3292
      if widget.get_active() == 5:
 
3293
         self.external_pl.show()
 
3294
      else:
 
3295
         self.external_pl.hide()
 
3296
      
 
3297
   def popupwindow_populate(self, window, parentwidget, parent_x, parent_y):
 
3298
      frame = gtk.Frame()
 
3299
      frame.set_shadow_type(gtk.SHADOW_OUT)
 
3300
      window.add(frame)
 
3301
      frame.show()
 
3302
      hbox = gtk.HBox()
 
3303
      hbox.set_border_width(10)
 
3304
      hbox.set_spacing(5)
 
3305
      frame.add(hbox)
 
3306
      image = gtk.Image()
 
3307
      image.set_from_file(FGlobs.pkgdatadir / "icon.png")
 
3308
      hbox.add(image)
 
3309
      image.show()
 
3310
      separator = gtk.VSeparator()
 
3311
      hbox.add(separator)
 
3312
      separator.show()
 
3313
      vbox = gtk.VBox()
 
3314
      vbox.set_spacing(3)
 
3315
      hbox.add(vbox)
 
3316
      vbox.show()
 
3317
      hbox.show()
 
3318
      
 
3319
      trackscount = 0
 
3320
      tracknum = 0
 
3321
      tracktitle = self.songname
 
3322
      duration = 0
 
3323
      for each in self.liststore:
 
3324
         if each[2] > 0:
 
3325
            trackscount += 1
 
3326
            duration += each[2]
 
3327
         if each[0][:3] == "<b>":
 
3328
            tracknum = trackscount
 
3329
         if each[0] == ">announcement":
 
3330
            duration += int(each[3][2:4]) * 60 + int(each[3][4:6])
 
3331
      if trackscount:
 
3332
         duration, seconds = divmod(duration, 60)
 
3333
         hours, minutes = divmod(duration, 60)
 
3334
         hms = hours and "%d:%02d:%02d" % (hours, minutes, seconds) or "%d:%02d" % (minutes, seconds)
 
3335
         if tracknum:
 
3336
            label1 = gtk.Label(_('Playing track {0} of {1}').format(tracknum, trackscount))
 
3337
            vbox.add(label1)
 
3338
            label1.show()
 
3339
            if self.album:
 
3340
               blank = gtk.Label("")
 
3341
               vbox.add(blank)
 
3342
               blank.show()
 
3343
            label2 = gtk.Label(tracktitle)
 
3344
            vbox.add(label2)
 
3345
            label2.show()
 
3346
            if self.album:
 
3347
               # TC: Previous line: Playing track {0} of {1}
 
3348
               label3 = gtk.Label(_('From the album, %s') % self.album)
 
3349
               vbox.add(label3)
 
3350
               label3.show()
 
3351
            blank = gtk.Label("")
 
3352
            vbox.add(blank)
 
3353
            blank.show()
 
3354
         else:
 
3355
            label3 = gtk.Label(_('Total number of tracks %d') % trackscount)
 
3356
            vbox.add(label3)
 
3357
            label3.show()
 
3358
         try:
 
3359
            label4 = gtk.Label(_('Total play duration %s') % hms)
 
3360
         except:
 
3361
            label4 = gtk.Label(_('Total play duration %s'))
 
3362
         vbox.add(label4)
 
3363
         label4.show()
 
3364
      else:
 
3365
         return -1
 
3366
      
 
3367
   def popupwindow_inhibit(self):
 
3368
      return self.pl_menu.flags() & gtk.MAPPED          # we don't want a popup window when there is a popup menu around
 
3369
 
 
3370
   def pl_mode_data_function(self, celllayout, cell, model, iter):
 
3371
      cell.props.text = t.gettext(model.get_value(iter, 0))
 
3372
      
 
3373
   def __init__(self, pbox, name, parent):
 
3374
      self.parent = parent
 
3375
      self.exiting = False                              # used to stop the running of problem code - don't ask
 
3376
      # A box for the Stop/Start/Pause widgets
 
3377
      self.hbox1 = gtk.HBox(True, 0)
 
3378
      self.hbox1.set_border_width(2)
 
3379
      self.hbox1.set_spacing(3)
 
3380
      frame = gtk.Frame()
 
3381
      frame.set_border_width(3)
 
3382
      frame.set_shadow_type(gtk.SHADOW_IN)
 
3383
      frame.add(self.hbox1)
 
3384
      frame.show()
 
3385
      pbox.pack_start(frame, False, False, 0)
 
3386
 
 
3387
      # A box for the progress bar, which may at a later date contain other stuff
 
3388
      # Like an elapsed timer for instance.
 
3389
      self.progressbox = gtk.HBox(False, 0)
 
3390
      self.progressbox.set_border_width(3)
 
3391
      self.progressbox.set_spacing(4)
 
3392
      pbox.pack_start(self.progressbox, False, False, 0)
 
3393
           
 
3394
      # The numerical play progress box
 
3395
      self.digiprogress = gtk.Entry()
 
3396
      self.digiprogress.set_text("0:00:00")
 
3397
      self.digiprogress.set_width_chars(6)
 
3398
      self.digiprogress.set_editable(False)
 
3399
      self.digiprogress.connect("button_press_event", self.cb_event, "DigitalProgressPress")
 
3400
      self.progressbox.pack_start(self.digiprogress, False, False, 1)
 
3401
      self.digiprogress.show()
 
3402
      set_tip(self.digiprogress, _('Left click toggles between showing the amount of time elapsed or remaining on the current track being played.'))
 
3403
      
 
3404
      # The play progress and seek bar
 
3405
      self.progressadj = gtk.Adjustment(0.0, 0.0, 100.0, 0.1, 1.0, 0.0)
 
3406
      self.progressadj.connect("value_changed", self.cb_progress)
 
3407
      self.progressbar = gtk.HScale(self.progressadj)
 
3408
      self.progressbar.set_update_policy(gtk.UPDATE_CONTINUOUS)
 
3409
      self.progressbar.set_digits(1)
 
3410
      self.progressbar.set_value_pos(gtk.POS_TOP)
 
3411
      self.progressbar.set_draw_value(False)
 
3412
      self.progressbar.connect("button_press_event", self.cb_event, "ProgressPress")
 
3413
      self.progressbar.connect("button_release_event", self.cb_event, "ProgressRelease")
 
3414
      self.progressbox.pack_start(self.progressbar, True, True, 0)
 
3415
      self.progressbar.show()
 
3416
      set_tip(self.progressbar, _('This slider acts as both a play progress indicator and as a means for seeking within the currently playing track.'))
 
3417
      
 
3418
      # Finished filling the progress box so lets show it.
 
3419
      self.progressbox.show()
 
3420
      
 
3421
      # A frame for our playlist
 
3422
      if name == "left":
 
3423
         plframe = gtk.Frame(" %s " % _('Playlist 1'))
 
3424
      else:
 
3425
         plframe = gtk.Frame(" %s " % _('Playlist 2'))
 
3426
      plframe.set_border_width(4)
 
3427
      plframe.set_shadow_type(gtk.SHADOW_ETCHED_IN)
 
3428
      plframe.show()
 
3429
      plvbox = gtk.VBox()
 
3430
      plframe.add(plvbox)
 
3431
      plvbox.show()
 
3432
      # The scrollable window box that will contain our playlist.
 
3433
      self.scrolllist = gtk.ScrolledWindow()
 
3434
      self.scrolllist.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
 
3435
      self.scrolllist.set_size_request(-1, 117)
 
3436
      self.scrolllist.set_border_width(4)
 
3437
      self.scrolllist.set_shadow_type(gtk.SHADOW_IN)
 
3438
      # A liststore object for our playlist
 
3439
      # The first one gets rendered and is derived from id3 tags or just is the filename
 
3440
      # when the id3 tag is not sufficient.
 
3441
      # The second one always is the filename and is passed to the player.
 
3442
      self.liststore = gtk.ListStore(str, str, int, str, str, str, str, float, CueSheetListStore, str)
 
3443
      self.templist = gtk.ListStore(str, str, int, str, str, str, str, float, CueSheetListStore, str)
 
3444
      self.treeview = gtk.TreeView(self.liststore)
 
3445
      self.rgcellrender = gtk.CellRendererText()
 
3446
      self.playtimecellrender = gtk.CellRendererText()
 
3447
      self.cellrender = gtk.CellRendererText()
 
3448
      self.cellrender.set_property("ellipsize", pango.ELLIPSIZE_END)
 
3449
      self.rgtvcolumn = gtk.TreeViewColumn("", self.rgcellrender)
 
3450
      self.playtimetvcolumn = gtk.TreeViewColumn("Time", self.playtimecellrender)
 
3451
      self.tvcolumn = gtk.TreeViewColumn("Playlist", self.cellrender, markup=0)
 
3452
      self.rgtvcolumn.set_cell_data_func(self.rgcellrender, self.rgrowconfig)
 
3453
      self.playtimetvcolumn.set_cell_data_func(self.playtimecellrender, self.playtimerowconfig)
 
3454
      self.tvcolumn.set_cell_data_func(self.cellrender, self.rowconfig)
 
3455
      self.playtimetvcolumn.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
 
3456
      self.tvcolumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
 
3457
      self.tvcolumn.set_expand(True)
 
3458
      self.treeview.append_column(self.tvcolumn)
 
3459
      self.treeview.append_column(self.playtimetvcolumn)
 
3460
      self.treeview.set_search_column(0)
 
3461
      self.treeview.set_headers_visible(False)
 
3462
      self.treeview.set_enable_search(False)
 
3463
      self.treeview.enable_model_drag_source( gtk.gdk.BUTTON1_MASK,
 
3464
                                                self.sourcetargets,
 
3465
                                                gtk.gdk.ACTION_DEFAULT |
 
3466
                                                gtk.gdk.ACTION_MOVE)
 
3467
      self.treeview.enable_model_drag_dest( self.droptargets,
 
3468
                                                gtk.gdk.ACTION_DEFAULT)
 
3469
        
 
3470
      self.treeview.connect("drag_data_get", self.drag_data_get_data)
 
3471
      self.treeview.connect("drag_data_received", self.drag_data_received_data)
 
3472
      self.treeview.connect("drag_data_delete", self.drag_data_delete)
 
3473
      
 
3474
      self.treeview.connect("row_activated", self.cb_doubleclick, "Double click")
 
3475
      self.treeview.get_selection().connect("changed", self.cb_selection_changed)
 
3476
      
 
3477
      self.treeview.connect("key_press_event", self.cb_keypress)
 
3478
      
 
3479
      self.liststore.connect("row-inserted", self.cb_playlist_changed)
 
3480
      self.liststore.connect("row-deleted", self.cb_playlist_changed)
 
3481
      
 
3482
      self.scrolllist.add(self.treeview)
 
3483
      self.treeview.show()
 
3484
      
 
3485
      plvbox.pack_start(self.scrolllist, True, True, 0)
 
3486
      self.scrolllist.show()
 
3487
      
 
3488
      # Cue sheet playlist controls.
 
3489
      
 
3490
      self.cuesheet_playlist = CuesheetPlaylist()
 
3491
      plvbox.pack_start(self.cuesheet_playlist)
 
3492
      
 
3493
      # External playlist control unit
 
3494
      self.external_pl = ExternalPL(self)
 
3495
      plvbox.pack_start(self.external_pl, False, False, 0)
 
3496
      
 
3497
      # File filters for file dialogs
 
3498
      self.plfilefilter_all = gtk.FileFilter()
 
3499
      # TC: File filter text.
 
3500
      self.plfilefilter_all.set_name(_('All file types'))
 
3501
      self.plfilefilter_all.add_pattern("*")
 
3502
      self.plfilefilter_playlists = gtk.FileFilter()
 
3503
      # TC: File filter text.
 
3504
      self.plfilefilter_playlists.set_name(_('Playlist types (*.m3u, *.xspf, *.pls)'))
 
3505
      self.plfilefilter_playlists.add_mime_type("audio/x-mpegurl")
 
3506
      self.plfilefilter_playlists.add_mime_type("application/xspf+xml")
 
3507
      self.plfilefilter_playlists.add_mime_type("audio/x-scpls")
 
3508
      self.plfilefilter_media = gtk.FileFilter()
 
3509
      self.plfilefilter_media.set_name(_('Supported media'))
 
3510
      for each in supported.media:
 
3511
         self.plfilefilter_media.add_pattern("*" + each)
 
3512
         self.plfilefilter_media.add_pattern("*" + each.upper())
 
3513
      
 
3514
      # An information display for playlist stats
 
3515
      self.pl_statusbar = gtk.Statusbar()
 
3516
      self.pl_statusbar.set_has_resize_grip(False)
 
3517
      plvbox.pack_start(self.pl_statusbar, False, False, 0)
 
3518
      self.pl_statusbar.show()
 
3519
      set_tip(self.pl_statusbar, _("'Block size' indicates the amount of time that it will take to play from the currently selected track to the next stop.\n'Remaining' is the amount of time until the next stop.\n'Finish' Is the computed time when the tracks will have finished playing."))
 
3520
 
 
3521
      pbox.pack_start(plframe, True, True, 0) 
 
3522
      
 
3523
      # A box for the playback speed controls
 
3524
      self.pbspeedbox = gtk.HBox(False, 0)
 
3525
      self.pbspeedbox.set_border_width(3)
 
3526
      self.pbspeedbox.set_spacing(3)
 
3527
      pbox.pack_start(self.pbspeedbox, False, False, 0)
 
3528
      
 
3529
      # The playback speed control
 
3530
      self.pbspeedadj = gtk.Adjustment(0.0, -12.0, 12.0, 0.0125, 0.0, 0.0)
 
3531
      self.pbspeedadj.connect("value_changed", self.cb_pbspeed)
 
3532
      self.pbspeedbar = gtk.HScale(self.pbspeedadj)
 
3533
      self.pbspeedbar.set_update_policy(gtk.UPDATE_CONTINUOUS)
 
3534
      self.pbspeedbar.set_digits(1)
 
3535
      self.pbspeedbar.set_value_pos(gtk.POS_TOP)
 
3536
      self.pbspeedbar.set_draw_value(False)
 
3537
      self.pbspeedbox.pack_start(self.pbspeedbar, True, True, 0)
 
3538
      self.pbspeedbar.show()
 
3539
      set_tip(self.pbspeedbar, _('This adjusts the playback speed anywhere from 25% to 400%.'))
 
3540
      
 
3541
      self.pbspeedzerobutton = gtk.Button()
 
3542
      self.pbspeedzerobutton.connect("clicked", self.callback, "pbspeedzero")
 
3543
      pixbuf = gtk.gdk.pixbuf_new_from_file(FGlobs.pkgdatadir / "speedicon.png")
 
3544
      pixbuf = pixbuf.scale_simple(55, 14, gtk.gdk.INTERP_BILINEAR)
 
3545
      image = gtk.Image()
 
3546
      image.set_from_pixbuf(pixbuf)
 
3547
      image.show()
 
3548
      self.pbspeedzerobutton.add(image)
 
3549
      self.pbspeedbox.pack_start(self.pbspeedzerobutton, False, False, 1)
 
3550
      self.pbspeedzerobutton.show()
 
3551
      set_tip(self.pbspeedzerobutton, _('This sets the playback speed back to normal.'))
 
3552
      
 
3553
      # The box for the mute widgets.
 
3554
      self.hbox2 = gtk.HBox(False, 0)
 
3555
      self.hbox2.set_border_width(4)
 
3556
      self.hbox2.set_spacing(4)
 
3557
      frame = gtk.Frame()
 
3558
      frame.set_border_width(4)
 
3559
      frame.add(self.hbox2)
 
3560
      pbox.pack_start(frame, False, False, 0)
 
3561
      frame.show()
 
3562
 
 
3563
      # A set of buttons for hbox1 namely Prev/Play/Pause/Stop/Next/Playlist : XMMS order
 
3564
      image = gtk.Image()
 
3565
      image.set_from_file(FGlobs.pkgdatadir / "prev.png")
 
3566
      image.show()
 
3567
      self.prev = gtk.Button()
 
3568
      self.prev.add(image)
 
3569
      self.prev.connect("clicked", self.callback, "Prev")
 
3570
      self.hbox1.add(self.prev)
 
3571
      self.prev.show()
 
3572
      set_tip(self.prev, _('Previous track.'))
 
3573
      
 
3574
      pixbuf = gtk.gdk.pixbuf_new_from_file(FGlobs.pkgdatadir / "play2.png")
 
3575
      pixbuf = pixbuf.scale_simple(14, 14, gtk.gdk.INTERP_BILINEAR)
 
3576
      image=gtk.Image()
 
3577
      image.set_from_pixbuf(pixbuf)
 
3578
      image.show()
 
3579
      self.play = gtk.ToggleButton()
 
3580
      self.play.add(image)
 
3581
      self.play.connect("toggled", self.cb_toggle, "Play")
 
3582
      self.hbox1.add(self.play)
 
3583
      self.play.show()
 
3584
      set_tip(self.play, _('Play.'))
 
3585
      
 
3586
      image=gtk.Image()
 
3587
      image.set_from_file(FGlobs.pkgdatadir / "pause.png")
 
3588
      image.show()
 
3589
      self.pause = gtk.ToggleButton()
 
3590
      self.pause.add(image)
 
3591
      self.pause.connect("toggled", self.cb_toggle, "Pause")
 
3592
      self.hbox1.add(self.pause)
 
3593
      self.pause.show()
 
3594
      set_tip(self.pause, _('Pause.'))
 
3595
      
 
3596
      image=gtk.Image()
 
3597
      image.set_from_file(FGlobs.pkgdatadir / "stop.png")
 
3598
      image.show()
 
3599
      self.stop = gtk.Button()
 
3600
      self.stop.add(image)
 
3601
      self.stop.connect("clicked", self.callback, "Stop")
 
3602
      self.hbox1.add(self.stop)
 
3603
      self.stop.show()
 
3604
      set_tip(self.stop, _('Stop.'))
 
3605
            
 
3606
      image=gtk.Image()
 
3607
      image.set_from_file(FGlobs.pkgdatadir / "next.png")
 
3608
      image.show()
 
3609
      self.next = gtk.Button()
 
3610
      self.next.add(image)
 
3611
      self.next.connect("clicked", self.callback, "Next")
 
3612
      self.hbox1.add(self.next)
 
3613
      self.next.show()
 
3614
      set_tip(self.next, _('Next track.'))
 
3615
 
 
3616
      pixbuf = gtk.gdk.pixbuf_new_from_file(FGlobs.pkgdatadir / "add3.png")
 
3617
      pixbuf = pixbuf.scale_simple(14, 14, gtk.gdk.INTERP_HYPER)
 
3618
      image = gtk.Image()
 
3619
      image.set_from_pixbuf(pixbuf)
 
3620
      image.show()
 
3621
      self.add = gtk.Button()
 
3622
      self.add.add(image)
 
3623
      self.add.connect("clicked", self.callback, "Add Files")
 
3624
      self.hbox1.add(self.add)
 
3625
      self.add.show()
 
3626
      set_tip(self.add, _('Add tracks to the playlist.'))
 
3627
      
 
3628
      # hbox1 is done so it is time to show it
 
3629
      self.hbox1.show()
 
3630
 
 
3631
      # The playlist mode dropdown menu.
 
3632
      
 
3633
      frame = ButtonFrame(_('Playlist Mode'))
 
3634
      self.hbox2.pack_start(frame, True, True, 0)
 
3635
      frame.show()
 
3636
      
 
3637
      self.pl_mode = gtk.combo_box_new_text()
 
3638
      self.pl_mode.set_cell_data_func(self.pl_mode.get_cells()[0], self.pl_mode_data_function)
 
3639
      self.pl_mode.append_text(N_('Play All'))
 
3640
      self.pl_mode.append_text(N_('Loop All'))
 
3641
      self.pl_mode.append_text(N_('Random'))
 
3642
      self.pl_mode.append_text(N_('Manual'))
 
3643
      self.pl_mode.append_text(N_('Cue Up'))
 
3644
      self.pl_mode.append_text(N_('External'))
 
3645
      self.pl_mode.append_text(N_('Alternate'))
 
3646
      self.pl_mode.append_text(N_('Fade Over'))
 
3647
      self.pl_mode.append_text(N_('Random Hop'))
 
3648
      self.pl_mode.set_active(0)
 
3649
      self.pl_mode.connect("changed", self.cb_playlist_mode)
 
3650
      set_tip(self.pl_mode, _("This sets the playlist mode which defines player behaviour after a track has finished playing.\n\n'Play All' is the most versatile mode since it allows the use of embeddable playlist control elements which are accessible using the right click context menu in the playlist. When no playlist controls are present the tracks are played sequentially until the end of the playlist is reached at which point the player will stop.\n\n'Loop All' causes the tracks to be played in sequence, restarting with the first track once the end of the playlist is reached.\n\n'Random' causes the tracks to be played indefinitely with the tracks selected at random.\n\n'Manual' causes the player to stop at the end of each track.\n\n'Cue Up' is similar to manual except that the next track in the playlist will also be highlighted.\n\n'External' draws it's tracks from an external playlist or directory one at a time. Useful for when you want to stream massive playlists.\n\n'Alternate' causes the next track to be cued up before starting the opposite player. The crossfader is moved over.\n\n'Fade Over' will crossfade to the other player at the end of every track.\n\n'Random Hop' will pick a track at random from the other playlist."))
 
3651
      
 
3652
      frame.hbox.pack_start(self.pl_mode, True, True, 0)
 
3653
      self.pl_mode.show()
 
3654
      
 
3655
      # TC: Fade time heading.
 
3656
      frame = ButtonFrame(_('Fade'))
 
3657
      self.hbox2.pack_start(frame, True, True, 0)
 
3658
      frame.show()
 
3659
      
 
3660
      self.pl_delay = gtk.combo_box_new_text()
 
3661
      # TC: Fade time is zero. No fade, none.
 
3662
      self.pl_delay.append_text(_('None'))
 
3663
      self.pl_delay.append_text("5")
 
3664
      self.pl_delay.append_text("10")
 
3665
      self.pl_delay.set_active(0)
 
3666
      self.pl_delay.connect("changed", self.cb_playlist_delay)
 
3667
      set_tip(self.pl_delay, _('This controls the amount of fade between tracks.'))
 
3668
      
 
3669
      frame.hbox.pack_start(self.pl_delay, True, True, 0)
 
3670
      self.pl_delay.show()
 
3671
      
 
3672
      # Mute buttons
 
3673
      
 
3674
      # TC: The audio feed (mix) that the DJ listens to, could be DJ mix or Stream mix.
 
3675
      frame = ButtonFrame(" %s " % _('Audio Feed'))
 
3676
      self.hbox2.pack_start(frame, True, True, 0)
 
3677
      frame.show()
 
3678
      
 
3679
      self.stream = gtk.ToggleButton(" %s " % _('Stream'))
 
3680
      self.stream.set_active(True)
 
3681
      self.stream.connect("toggled", self.cb_toggle, "Stream")
 
3682
      frame.hbox.pack_start(self.stream, True, True, 0)
 
3683
      self.stream.show()
 
3684
      set_tip(self.stream, _('Make output from this player available for streaming.'))
 
3685
            
 
3686
      self.listen = nice_listen_togglebutton(" %s " % _('DJ'))
 
3687
      self.listen.set_active(True)
 
3688
      self.listen.connect("toggled", self.cb_toggle, "Listen")
 
3689
      frame.hbox.pack_start(self.listen, True, True, 0)
 
3690
      self.listen.show()
 
3691
      set_tip(self.listen, _('Make output from this player audible to the DJ.'))
 
3692
      
 
3693
      # hbox2 is now filled so lets show it
 
3694
      self.hbox2.show()
 
3695
      
 
3696
      # Popup menu code here
 
3697
      
 
3698
      # Main popup menu
 
3699
      self.pl_menu = gtk.Menu()
 
3700
 
 
3701
      # TC: Insert playlist control.
 
3702
      self.pl_menu_control = gtk.MenuItem(_('Insert control'))
 
3703
      self.pl_menu.append(self.pl_menu_control)
 
3704
      self.pl_menu_control.show()
 
3705
 
 
3706
      separator = gtk.SeparatorMenuItem()
 
3707
      self.pl_menu.append(separator)
 
3708
      separator.show()
 
3709
      
 
3710
      # TC: The Item submenu.
 
3711
      self.pl_menu_item = gtk.MenuItem(_('Item'))
 
3712
      self.pl_menu.append(self.pl_menu_item)
 
3713
      self.pl_menu_item.show()
 
3714
      
 
3715
      # TC: The Playlist submenu.
 
3716
      self.pl_menu_playlist = gtk.MenuItem(_('Playlist'))
 
3717
      self.pl_menu.append(self.pl_menu_playlist)
 
3718
      self.pl_menu_playlist.show()
 
3719
      
 
3720
      self.pl_menu.show()
 
3721
      
 
3722
      # Control element submenu of main popup menu
 
3723
      
 
3724
      self.control_menu = gtk.Menu()
 
3725
      
 
3726
      # TC: Insert playlist control to set playback speed to normal.
 
3727
      self.control_normal_speed_control = gtk.MenuItem(_('Normal Speed'))
 
3728
      self.control_normal_speed_control.connect("activate", self.menuitem_response, "Normal Speed Control")
 
3729
      self.control_menu.append(self.control_normal_speed_control)
 
3730
      self.control_normal_speed_control.show()
 
3731
      
 
3732
      # TC: Insert playlist control to stop the player.
 
3733
      self.control_menu_stop_control = gtk.MenuItem(_('Player Stop'))
 
3734
      self.control_menu_stop_control.connect("activate", self.menuitem_response, "Stop Control")
 
3735
      self.control_menu.append(self.control_menu_stop_control)
 
3736
      self.control_menu_stop_control.show()
 
3737
 
 
3738
      # TC: Insert playlist control to stop the player.
 
3739
      self.control_menu_stop_control = gtk.MenuItem(_('Player Stop 2'))
 
3740
      self.control_menu_stop_control.connect("activate", self.menuitem_response, "Stop Control 2")
 
3741
      self.control_menu.append(self.control_menu_stop_control)
 
3742
      self.control_menu_stop_control.show()
 
3743
 
 
3744
      # TC: Insert playlist control to jump to the top of the playlist.
 
3745
      self.control_menu_jumptop_control = gtk.MenuItem(_('Jump To Top'))
 
3746
      self.control_menu_jumptop_control.connect("activate", self.menuitem_response, "Jump To Top Control")
 
3747
      self.control_menu.append(self.control_menu_jumptop_control)
 
3748
      self.control_menu_jumptop_control.show()
 
3749
      
 
3750
      # TC: Insert playlist control to transfer to the opposite player.
 
3751
      self.control_menu_transfer_control = gtk.MenuItem(_('Transfer'))
 
3752
      self.control_menu_transfer_control.connect("activate", self.menuitem_response, "Transfer Control")
 
3753
      self.control_menu.append(self.control_menu_transfer_control)
 
3754
      self.control_menu_transfer_control.show()
 
3755
      
 
3756
      # TC: Insert playlist control to crossfade to the opposite player.
 
3757
      self.control_menu_crossfade_control = gtk.MenuItem(_('Crossfade'))
 
3758
      self.control_menu_crossfade_control.connect("activate", self.menuitem_response, "Crossfade Control")
 
3759
      self.control_menu.append(self.control_menu_crossfade_control)
 
3760
      self.control_menu_crossfade_control.show()
 
3761
            
 
3762
      # TC: Embed a DJ announcement text into the playlist.
 
3763
      self.control_menu_announcement_control = gtk.MenuItem(_('Announcement'))
 
3764
      self.control_menu_announcement_control.connect("activate", self.menuitem_response, "Announcement Control")
 
3765
      self.control_menu.append(self.control_menu_announcement_control)
 
3766
      self.control_menu_announcement_control.show()
 
3767
      
 
3768
      separator = gtk.SeparatorMenuItem()
 
3769
      self.control_menu.append(separator)
 
3770
      separator.show()
 
3771
      
 
3772
      # TC: Insert playlist control to do a ten second fade to the next track.
 
3773
      self.control_menu_fade_10_control = gtk.MenuItem(_('Fade 10s'))
 
3774
      self.control_menu_fade_10_control.connect("activate", self.menuitem_response, "Fade 10")
 
3775
      self.control_menu.append(self.control_menu_fade_10_control)
 
3776
      self.control_menu_fade_10_control.show()
 
3777
      
 
3778
      # TC: Insert playlist control to do a five second fade to the next track.
 
3779
      self.control_menu_fade_5_control = gtk.MenuItem(_('Fade 5s'))
 
3780
      self.control_menu_fade_5_control.connect("activate", self.menuitem_response, "Fade 5")
 
3781
      self.control_menu.append(self.control_menu_fade_5_control)
 
3782
      self.control_menu_fade_5_control.show()
 
3783
      
 
3784
      # TC: Insert playlist control to not do a fade to the next track.
 
3785
      self.control_menu_fade_none_control = gtk.MenuItem(_('No Fade'))
 
3786
      self.control_menu_fade_none_control.connect("activate", self.menuitem_response, "Fade none")
 
3787
      self.control_menu.append(self.control_menu_fade_none_control)
 
3788
      self.control_menu_fade_none_control.show()
 
3789
      
 
3790
      separator = gtk.SeparatorMenuItem()
 
3791
      self.control_menu.append(separator)
 
3792
      separator.show()
 
3793
      
 
3794
      # TC: Insert playlist control to stop all the streams.
 
3795
      self.control_menu_stream_disconnect_control = gtk.MenuItem(_('Stop streaming'))
 
3796
      self.control_menu_stream_disconnect_control.connect("activate", self.menuitem_response, "Stream Disconnect Control")
 
3797
      self.control_menu.append(self.control_menu_stream_disconnect_control)
 
3798
      self.control_menu_stream_disconnect_control.show()
 
3799
      
 
3800
      # TC: Insert playlist control to stop all recording.
 
3801
      self.control_menu_stop_recording_control = gtk.MenuItem(_('Stop recording'))
 
3802
      self.control_menu_stop_recording_control.connect("activate", self.menuitem_response, "Stop Recording Control")
 
3803
      self.control_menu.append(self.control_menu_stop_recording_control)
 
3804
      self.control_menu_stop_recording_control.show()
 
3805
      
 
3806
      self.pl_menu_control.set_submenu(self.control_menu)
 
3807
      self.control_menu.show()
 
3808
      
 
3809
      # Item submenu of main popup menu
 
3810
      self.item_menu = gtk.Menu()
 
3811
      
 
3812
      # TC: Menu item. Opens the metadata tagger on the selected track.
 
3813
      self.item_tag = gtk.MenuItem(_('Meta Tag'))
 
3814
      self.item_tag.connect("activate", self.menuitem_response, "MetaTag")
 
3815
      self.item_menu.append(self.item_tag)
 
3816
      self.item_tag.show()
 
3817
      
 
3818
      # TC: Menu Item. Duplicates the selected track in the playlist.
 
3819
      self.item_duplicate = gtk.MenuItem(_('Duplicate'))
 
3820
      self.item_duplicate.connect("activate", self.menuitem_response, "Duplicate")
 
3821
      self.item_menu.append(self.item_duplicate)
 
3822
      self.item_duplicate.show()
 
3823
      
 
3824
      # TC: Menu Item. Remove the selected track.
 
3825
      self.item_remove = gtk.MenuItem(_('Remove'))
 
3826
      self.item_menu.append(self.item_remove)
 
3827
      self.item_remove.show()
 
3828
      
 
3829
      # TC: Menu Item. The selected track is copied to the jingles catalogue.
 
3830
      self.item_tojingles = gtk.MenuItem(_('Add To Jingles'))
 
3831
      self.item_tojingles.connect("activate", self.menuitem_response, "ToJingles")
 
3832
      self.item_menu.append(self.item_tojingles)
 
3833
      self.item_tojingles.show()
 
3834
      
 
3835
      self.pl_menu_item.set_submenu(self.item_menu)
 
3836
      self.item_menu.show()
 
3837
      
 
3838
      # Remove submenu of Item submenu
 
3839
      self.remove_menu = gtk.Menu()
 
3840
 
 
3841
      # TC: Submenu Item. Parent menu item is Remove.
 
3842
      self.remove_this = gtk.MenuItem(_('This'))
 
3843
      self.remove_this.connect("activate", self.menuitem_response, "Remove This")
 
3844
      self.remove_menu.append(self.remove_this)
 
3845
      self.remove_this.show()
 
3846
      
 
3847
      # TC: Submenu Item. Parent menu item is Remove.
 
3848
      self.remove_all = gtk.MenuItem(_('All'))
 
3849
      self.remove_all.connect("activate", self.menuitem_response, "Remove All")
 
3850
      self.remove_menu.append(self.remove_all)
 
3851
      self.remove_all.show()
 
3852
      
 
3853
      # TC: Submenu Item. Parent menu item is Remove.
 
3854
      self.remove_from_here = gtk.MenuItem(_('From Here'))
 
3855
      self.remove_from_here.connect("activate", self.menuitem_response, "Remove From Here")
 
3856
      self.remove_menu.append(self.remove_from_here)
 
3857
      self.remove_from_here.show()
 
3858
      
 
3859
      # TC: Submenu Item. Parent menu item is Remove.
 
3860
      self.remove_to_here = gtk.MenuItem(_('To Here'))
 
3861
      self.remove_to_here.connect("activate", self.menuitem_response, "Remove To Here")
 
3862
      self.remove_menu.append(self.remove_to_here)
 
3863
      self.remove_to_here.show()
 
3864
      
 
3865
      self.item_remove.set_submenu(self.remove_menu)
 
3866
      self.remove_menu.show()
 
3867
      
 
3868
      # Playlist submenu of main popup menu.
 
3869
      self.playlist_menu = gtk.Menu()
 
3870
      
 
3871
      # TC: Open the file dialog for adding music to the chosen playlist.
 
3872
      self.playlist_add_file = gtk.MenuItem(_('Add Music'))
 
3873
      self.playlist_add_file.connect("activate", self.menuitem_response, "Add File")
 
3874
      self.playlist_menu.append(self.playlist_add_file)
 
3875
      self.playlist_add_file.show()
 
3876
      
 
3877
      # TC: Submenu Item. Parent menu is Playlist.
 
3878
      self.playlist_save = gtk.MenuItem(_('Save'))
 
3879
      self.playlist_save.connect("activate", self.menuitem_response, "Playlist Save")
 
3880
      self.playlist_menu.append(self.playlist_save)
 
3881
      self.playlist_save.show()
 
3882
      
 
3883
      separator = gtk.SeparatorMenuItem()
 
3884
      self.playlist_menu.append(separator)
 
3885
      separator.show()
 
3886
      
 
3887
      # TC: Submenu Item. Parent menu is Playlist.
 
3888
      self.playlist_copy = gtk.MenuItem(_('Copy'))
 
3889
      self.playlist_menu.append(self.playlist_copy)
 
3890
      self.playlist_copy.show()
 
3891
      
 
3892
      # TC: Submenu Item. Parent menu is Playlist.
 
3893
      self.playlist_transfer = gtk.MenuItem(_('Transfer'))
 
3894
      self.playlist_menu.append(self.playlist_transfer)
 
3895
      self.playlist_transfer.show()
 
3896
      
 
3897
      # TC: Submenu Item. Parent menu is Playlist.
 
3898
      self.playlist_exchange = gtk.MenuItem(_('Exchange'))
 
3899
      self.playlist_exchange.connect("activate", self.menuitem_response, "Playlist Exchange")
 
3900
      self.playlist_menu.append(self.playlist_exchange)
 
3901
      self.playlist_exchange.show()
 
3902
      
 
3903
      # TC: Submenu Item. Parent menu is Playlist.
 
3904
      self.playlist_empty = gtk.MenuItem(_('Empty'))
 
3905
      self.playlist_empty.connect("activate", self.menuitem_response, "Remove All")
 
3906
      self.playlist_menu.append(self.playlist_empty)
 
3907
      self.playlist_empty.show()
 
3908
      
 
3909
      self.pl_menu_playlist.set_submenu(self.playlist_menu)
 
3910
      self.playlist_menu.show()
 
3911
      
 
3912
      # Position Submenu of Playlist-Copy menu item
 
3913
      
 
3914
      self.copy_menu = gtk.Menu()
 
3915
      
 
3916
      # TC: Submenu Item. Parent menus are Playlist->Copy.
 
3917
      self.copy_append = gtk.MenuItem(_('Append'))
 
3918
      self.copy_append.connect("activate", self.menuitem_response, "Copy Append")
 
3919
      self.copy_menu.append(self.copy_append)
 
3920
      self.copy_append.show()
 
3921
      
 
3922
      # TC: Submenu Item. Parent menus are Playlist->Copy.
 
3923
      self.copy_prepend = gtk.MenuItem(_('Prepend'))
 
3924
      self.copy_prepend.connect("activate", self.menuitem_response, "Copy Prepend")
 
3925
      self.copy_menu.append(self.copy_prepend)
 
3926
      self.copy_prepend.show()
 
3927
      
 
3928
      separator = gtk.SeparatorMenuItem()
 
3929
      self.copy_menu.append(separator)
 
3930
      separator.show()
 
3931
      
 
3932
      # TC: Submenu Item. Parent menus are Playlist->Copy.
 
3933
      self.copy_append_cursor = gtk.MenuItem(_('Append Cursor'))
 
3934
      self.copy_append_cursor.connect("activate", self.menuitem_response, "Copy Append Cursor")
 
3935
      self.copy_menu.append(self.copy_append_cursor)
 
3936
      self.copy_append_cursor.show()
 
3937
      
 
3938
      # TC: Submenu Item. Parent menus are Playlist->Copy.
 
3939
      self.copy_prepend_cursor = gtk.MenuItem(_('Prepend Cursor'))
 
3940
      self.copy_prepend_cursor.connect("activate", self.menuitem_response, "Copy Prepend Cursor")
 
3941
      self.copy_menu.append(self.copy_prepend_cursor)
 
3942
      self.copy_prepend_cursor.show()
 
3943
 
 
3944
      self.playlist_copy.set_submenu(self.copy_menu)
 
3945
      self.copy_menu.show()
 
3946
      
 
3947
      # Position Submenu of Playlist-Transfer menu item
 
3948
      
 
3949
      self.transfer_menu = gtk.Menu()
 
3950
      
 
3951
      # TC: Submenu Item. Parent menus are Playlist->Transfer.
 
3952
      self.transfer_append = gtk.MenuItem(_('Append'))
 
3953
      self.transfer_append.connect("activate", self.menuitem_response, "Transfer Append")
 
3954
      self.transfer_menu.append(self.transfer_append)
 
3955
      self.transfer_append.show()
 
3956
      
 
3957
      # TC: Submenu Item. Parent menus are Playlist->Transfer.
 
3958
      self.transfer_prepend = gtk.MenuItem(_('Prepend'))
 
3959
      self.transfer_prepend.connect("activate", self.menuitem_response, "Transfer Prepend")
 
3960
      self.transfer_menu.append(self.transfer_prepend)
 
3961
      self.transfer_prepend.show()
 
3962
      
 
3963
      separator = gtk.SeparatorMenuItem()
 
3964
      self.transfer_menu.append(separator)
 
3965
      separator.show()
 
3966
      
 
3967
      # TC: Submenu Item. Parent menus are Playlist->Transfer.
 
3968
      self.transfer_append_cursor = gtk.MenuItem(_('Append at Cursor'))
 
3969
      self.transfer_append_cursor.connect("activate", self.menuitem_response, "Transfer Append Cursor")
 
3970
      self.transfer_menu.append(self.transfer_append_cursor)
 
3971
      self.transfer_append_cursor.show()
 
3972
      
 
3973
      # TC: Submenu Item. Parent menus are Playlist->Transfer.
 
3974
      self.transfer_prepend_cursor = gtk.MenuItem(_('Prepend at Cursor'))
 
3975
      self.transfer_prepend_cursor.connect("activate", self.menuitem_response, "Transfer Prepend Cursor")
 
3976
      self.transfer_menu.append(self.transfer_prepend_cursor)
 
3977
      self.transfer_prepend_cursor.show()
 
3978
 
 
3979
      self.playlist_transfer.set_submenu(self.transfer_menu)
 
3980
      self.transfer_menu.show()      
 
3981
 
 
3982
 
 
3983
      self.treeview.connect_object("event", self.menu_activate, self.pl_menu)
 
3984
      popupwindow.PopupWindow(self.treeview, 12, 120, 10, self.popupwindow_populate, self.popupwindow_inhibit)
 
3985
 
 
3986
      # Initialisations
 
3987
      self.playername = name 
 
3988
      self.showing_file_requester = False
 
3989
      self.showing_pl_save_requester = False
 
3990
 
 
3991
      # Get the users home directory and test that the environment variable is okay.
 
3992
      # Actually this is more than bullet proof because when HOME is wrong I cant even run it.
 
3993
      self.home = os.getenv("HOME")
 
3994
      if os.path.exists(self.home):
 
3995
         if os.path.isdir(self.home) == False:
 
3996
            self.home = os.path.basename(self.home)
 
3997
         else:
 
3998
            # Make sure there is a trailing slash or the filerequester will mess up a bit.
 
3999
            if self.home[-1:] != '/':
 
4000
               self.home = self.home + '/'
 
4001
               
 
4002
      self.file_requester_start_dir = slot_object(self.home)
 
4003
      self.plsave_filetype = 0
 
4004
      self.plsave_open = False
 
4005
      self.plsave_filtertype = self.plfilefilter_all
 
4006
      self.plsave_folder = None
 
4007
      
 
4008
      # This is used for mouse-click debounce when deleting files in the playlist.
 
4009
      self.last_time = time.time()
 
4010
      
 
4011
      # This flag symbolises if we are playing music or not.
 
4012
      self.is_playing = False
 
4013
      self.is_paused = False
 
4014
      self.is_stopping = False
 
4015
      self.player_is_playing = False
 
4016
      self.new_title = False
 
4017
      self.timeout_source_id = 0
 
4018
      self.progress_press = False
 
4019
      random.seed()
 
4020
      # The maximum value from the progress bar at startup
 
4021
      self.max_seek = 100.0
 
4022
      self.reselect_please = False
 
4023
      self.reselect_cursor_please = False
 
4024
      self.songname = u""
 
4025
      self.flush = False
 
4026
      self.title = ""
 
4027
      self.artist = ""
 
4028
      self.album = ""
 
4029
      self.gapless = False
 
4030
      self.seek_file_valid = False
 
4031
      self.digiprogress_type = 0
 
4032
      self.digiprogress_f = 0
 
4033
      self.handle_motion_as_drop = False
 
4034
      self.other_player_initiated = False
 
4035
      self.crossfader_initiated = False
 
4036
      self.music_filename = ""
 
4037
      self.session_filename = pm.basedir / (self.playername + "_session")
 
4038
      self.oldstatusbartext = ""
 
4039
      self.pbspeedfactor = 1.0
 
4040
      self.playlist_changed = True
 
4041
      self.alarm_cid = 0
 
4042
      self.playlist_todo = deque()
 
4043
      self.no_more_files = False