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

« back to all changes in this revision

Viewing changes to python/sourceclientgui.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
#   sourceclientgui.py: new for version 0.7 this provides the graphical
 
2
#   user interface for the new improved streaming module
 
3
#   Copyright (C) 2007 Stephen Fairchild (s-fairchild@users.sourceforge.net)
 
4
#
 
5
#   This program is free software: you can redistribute it and/or modify
 
6
#   it under the terms of the GNU General Public License as published by
 
7
#   the Free Software Foundation, either version 2 of the License, or
 
8
#   (at your option) any later version.
 
9
#
 
10
#   This program is distributed in the hope that it will be useful,
 
11
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
#   GNU General Public License for more details.
 
14
#
 
15
#   You should have received a copy of the GNU General Public License
 
16
#   along with this program in the file entitled COPYING.
 
17
#   If not, see <http://www.gnu.org/licenses/>.
 
18
 
 
19
__all__ = ['SourceClientGui']
 
20
 
 
21
 
 
22
import os
 
23
import time
 
24
import fcntl
 
25
import subprocess
 
26
import urllib
 
27
import urllib2
 
28
import base64
 
29
import gettext
 
30
import traceback
 
31
import xml.dom.minidom as mdom
 
32
import xml.etree.ElementTree
 
33
from collections import namedtuple
 
34
from threading import Thread
 
35
 
 
36
import pango
 
37
import gtk
 
38
import gobject
 
39
 
 
40
from idjc import FGlobs, PGlobs
 
41
from .freefunctions import string_multireplace
 
42
from .gtkstuff import DefaultEntry, threadslock, HistoryEntry, WindowSizeTracker
 
43
from .dialogs import *
 
44
from .irc import IRCPane
 
45
from .tooltips import main_tips
 
46
from .prelims import ProfileManager
 
47
 
 
48
 
 
49
t = gettext.translation(FGlobs.package_name, FGlobs.localedir, fallback=True)
 
50
_ = t.gettext
 
51
 
 
52
 
 
53
pm = ProfileManager()
 
54
set_tip = main_tips.set_tip
 
55
 
 
56
 
 
57
ENCODER_START=1; ENCODER_STOP=0                                 # start_stop_encoder constants
 
58
 
 
59
LISTFORMAT = (("check_stats", bool), ("server_type", int), ("host", str), ("port", int), ("mount", str), ("listeners", int), ("login", str), ("password", str))
 
60
ListLine = namedtuple("ListLine", " ".join([x[0] for x in LISTFORMAT]))
 
61
BLANK_LISTLINE = ListLine(1, 0, "", 8000, "", -1, "", "")
 
62
 
 
63
 
 
64
class SmallLabel(gtk.Label):
 
65
   def __init__(self, text=None):
 
66
      gtk.Label.__init__(self, text)
 
67
      attrlist = pango.AttrList()
 
68
      attrlist.insert(pango.AttrSize(8000, 0, 1000000))
 
69
      self.set_attributes(attrlist)
 
70
      
 
71
 
 
72
class HistoryEntryWithMenu(HistoryEntry):
 
73
   def __init__(self):
 
74
      HistoryEntry.__init__(self, initial_text=("", "%s", "%r - %t"))
 
75
      self.child.connect("populate-popup", self._on_populate_popup)
 
76
      
 
77
      
 
78
   def _on_populate_popup(self, entry, menu):
 
79
      # TC: gtk.Entry popup menu item. Attribute may be one of Artist, Title, Album, and so on.
 
80
      attr_menu_item = gtk.MenuItem(_('Insert Attribute'))
 
81
      submenu = gtk.Menu()
 
82
      attr_menu_item.set_submenu(submenu)
 
83
      for label, subst in zip((_('Artist'), _('Title'), _('Album'), _('Song name')),
 
84
                                             (u"%r", u"%t", u"%l", u"%s")):
 
85
         mi = gtk.MenuItem(label)
 
86
         mi.connect("activate", self._on_menu_activate, entry, subst)
 
87
         submenu.append(mi)
 
88
      
 
89
      menu.append(attr_menu_item)
 
90
      attr_menu_item.show_all()
 
91
 
 
92
 
 
93
   def _on_menu_activate(self, mi, entry, subst):
 
94
      p = entry.get_position()
 
95
      entry.insert_text(subst, p)
 
96
      entry.set_position(p + len(subst))
 
97
 
 
98
 
 
99
class ModuleFrame(gtk.Frame):
 
100
   def __init__(self, frametext = None):
 
101
      gtk.Frame.__init__(self, frametext)
 
102
      gtk.Frame.set_shadow_type(self, gtk.SHADOW_ETCHED_OUT)
 
103
      self.vbox = gtk.VBox()
 
104
      self.add(self.vbox)
 
105
      self.vbox.show()
 
106
 
 
107
class CategoryFrame(gtk.Frame):
 
108
   def __init__(self, frametext = None):
 
109
      gtk.Frame.__init__(self, frametext)
 
110
      gtk.Frame.set_shadow_type(self, gtk.SHADOW_IN)
 
111
 
 
112
class SubcategoryFrame(gtk.Frame):
 
113
   def __init__(self, frametext = None):
 
114
      gtk.Frame.__init__(self, frametext)
 
115
      gtk.Frame.set_shadow_type(self, gtk.SHADOW_ETCHED_IN)
 
116
 
 
117
class ConnectionDialog(gtk.Dialog):
 
118
   """Create new data for or edit an item in the connection table.
 
119
   
 
120
   When an item is selected in the TreeView, will edit, else add.
 
121
   """
 
122
   server_types = (_('Icecast 2 Master'), _('Shoutcast Master'),
 
123
                  _('Icecast 2 Stats/Relay'), _('Shoutcast Stats/Relay'))
 
124
 
 
125
   def __init__(self, parent_window, tree_selection):
 
126
      gtk.Dialog.__init__(self, _('Enter new server connection details'), 
 
127
         parent_window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
 
128
         (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
 
129
      model, iter = tree_selection.get_selected()
 
130
         
 
131
      # Configuration from existing server data.
 
132
      #
 
133
      cap_master = True
 
134
      preselect = 0
 
135
      data = BLANK_LISTLINE
 
136
      try:
 
137
         first = ListLine._make(model[0])
 
138
      except IndexError:
 
139
         pass  # Defaults are fine. Server table currently empty.
 
140
      else:
 
141
         if iter:
 
142
            # In editing mode.
 
143
            self.set_title(_('Edit existing server connection details') + pm.title_extra)
 
144
            index = model.get_path(iter)[0]
 
145
            data = ListLine._make(model[index])
 
146
            preselect = data.server_type
 
147
            if index and first.server_type < 2:
 
148
               # Editing non first line where a master server is configured.
 
149
               cap_master = False
 
150
         else:
 
151
            # In adding additional server mode.
 
152
            if first.server_type < 2:
 
153
               cap_master = False
 
154
               preselect = first.server_type + 2
 
155
 
 
156
      # Widgets
 
157
      #
 
158
      liststore = gtk.ListStore(int, str, int)
 
159
      for i, (l, t) in enumerate(zip(self.server_types, (cap_master, cap_master, True, True))):
 
160
         liststore.append((i, l, t))
 
161
      self.servertype = gtk.ComboBox(liststore)
 
162
      icon_renderer = CellRendererXCast()
 
163
      text_renderer = gtk.CellRendererText()
 
164
      self.servertype.pack_start(icon_renderer, False)
 
165
      self.servertype.pack_start(text_renderer, True)
 
166
      self.servertype.set_attributes(icon_renderer, servertype=0, sensitive=2)
 
167
      self.servertype.set_attributes(text_renderer, text=1, sensitive=2)
 
168
      self.servertype.set_model(liststore)
 
169
      
 
170
      self.hostname = DefaultEntry("localhost")
 
171
      adj = gtk.Adjustment(8000.0, 0.0, 65535.0, 1.0, 10.0)
 
172
      self.portnumber = gtk.SpinButton(adj, 1.0, 0)
 
173
      self.mountpoint = DefaultEntry("/listen")
 
174
      self.loginname = DefaultEntry("source")
 
175
      self.password = DefaultEntry("changeme")
 
176
      self.password.set_visibility(False)
 
177
      self.stats = gtk.CheckButton(_('This server is to be scanned for audience figures'))
 
178
      
 
179
      # Layout
 
180
      #
 
181
      self.set_border_width(5)
 
182
      hbox = gtk.HBox(spacing = 20)
 
183
      hbox.set_border_width(15)
 
184
      icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_DIALOG)
 
185
      hbox.pack_start(icon)
 
186
      col = gtk.VBox(homogeneous = True, spacing = 4)
 
187
      hbox.pack_start(col)
 
188
      sg = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
 
189
      for text, widget in zip(
 
190
            (_('Server type'), _('Hostname'), _('Port number'), 
 
191
            _('Mount point'), _('Login name'), _('Password')), 
 
192
            (self.servertype, self.hostname, self.portnumber, 
 
193
            self.mountpoint, self.loginname, self.password)):
 
194
         row = gtk.HBox()
 
195
         row.set_spacing(3)
 
196
         label = gtk.Label(text)
 
197
         label.set_alignment(1.0, 0.5)
 
198
         row.pack_start(label, False)
 
199
         row.pack_start(widget)
 
200
         sg.add_widget(label)
 
201
         col.pack_start(row)
 
202
      col.pack_start(self.stats, False)
 
203
      self.get_content_area().pack_start(hbox)
 
204
      self.hostname.set_width_chars(30)
 
205
      hbox.show_all()
 
206
 
 
207
      # Signals
 
208
      #
 
209
      self.connect("response", self._on_response, tree_selection, model, iter)
 
210
      self.servertype.connect("changed", self._on_servertype_changed)
 
211
 
 
212
      # Data fill
 
213
      #
 
214
      self.servertype.set_active(preselect)
 
215
      self.hostname.set_text(data.host)
 
216
      self.portnumber.set_value(data.port)
 
217
      self.mountpoint.set_text(data.mount)
 
218
      self.loginname.set_text(data.login)
 
219
      self.password.set_text(data.password)
 
220
      self.stats.set_active(data.check_stats)
 
221
      
 
222
   @staticmethod
 
223
   def _on_response(self, response_id, tree_selection, model, iter):
 
224
      if response_id == gtk.RESPONSE_ACCEPT:
 
225
         for entry in (self.hostname, self.mountpoint, self.loginname, self.password):
 
226
            entry.set_text(entry.get_text().strip())
 
227
         self.hostname.set_text(self.hostname.get_text().split("://")[-1].strip())
 
228
         self.mountpoint.set_text("/" + self.mountpoint.get_text().lstrip("/"))
 
229
 
 
230
         data = ListLine(check_stats=self.stats.get_active(),
 
231
                         server_type=self.servertype.get_active(),
 
232
                         host=self.hostname.get_text(),
 
233
                         port=int(self.portnumber.get_value()),
 
234
                         mount=self.mountpoint.get_text(),
 
235
                         listeners=-1,
 
236
                         login=self.loginname.get_text(),
 
237
                         password=self.password.get_text())
 
238
 
 
239
         if self.servertype.get_active() < 2:
 
240
            if iter:
 
241
               model.remove(iter)
 
242
            new_iter = model.insert(0, data)
 
243
         else:
 
244
            if iter:
 
245
               new_iter = model.insert_after(iter, data)
 
246
               model.remove(iter)
 
247
            else:
 
248
               new_iter = model.append(data)
 
249
         tree_selection.select_path(model.get_path(new_iter))
 
250
         tree_selection.get_tree_view().scroll_to_cell(model.get_path(new_iter))
 
251
         tree_selection.get_tree_view().get_model().row_changed(model.get_path(new_iter), new_iter)
 
252
      self.destroy()
 
253
      
 
254
   def _on_servertype_changed(self, servertype):
 
255
      sens = not (servertype.get_active() & 1)
 
256
      self.mountpoint.set_sensitive(sens)
 
257
      self.loginname.set_sensitive(sens)
 
258
 
 
259
class StatsThread(Thread):
 
260
   def __init__(self, d):
 
261
      Thread.__init__(self)
 
262
      self.is_shoutcast = d["server_type"] % 2
 
263
      self.host = d["host"]
 
264
      self.port = d["port"]
 
265
      self.mount = d["mount"]
 
266
      if self.is_shoutcast:
 
267
         self.login = "admin"
 
268
      else:
 
269
         self.login = d["login"]
 
270
      self.passwd = d["password"]
 
271
      self.listeners = -2         # preset error code for failed/timeout
 
272
      self.url = "http://%s:%d%s" % (self.host, self.port, self.mount)
 
273
   def run(self):
 
274
      class BadXML(ValueError):
 
275
         pass
 
276
      
 
277
      hostport = "%s:%d" % (self.host, self.port)
 
278
      if self.is_shoutcast:
 
279
         stats_url = "http://%s/admin.cgi?mode=viewxml" % hostport
 
280
         realm = "Shoutcast Server"
 
281
      else:
 
282
         stats_url = "http://%s/admin/listclients?mount=%s" % (hostport, self.mount)
 
283
         realm = "Icecast2 Server"
 
284
      auth_handler = urllib2.HTTPBasicAuthHandler()
 
285
      auth_handler.add_password(realm, hostport, self.login, self.passwd)
 
286
      opener = urllib2.build_opener(auth_handler)
 
287
      opener.addheaders = [('User-agent', 'Mozilla/5.0')]
 
288
      
 
289
      try:
 
290
         f = opener.open(stats_url)
 
291
         xmlfeed = f.read()
 
292
      except:
 
293
         print "failed to get server stats for", self.url
 
294
         return
 
295
      f.close()
 
296
      try:
 
297
         dom = mdom.parseString(xmlfeed)
 
298
      except:
 
299
         print "failed to parse server stats for", self.url
 
300
         return
 
301
      
 
302
      try:
 
303
         if self.is_shoutcast:
 
304
            if dom.documentElement.tagName == u'SHOUTCASTSERVER':
 
305
               shoutcastserver = dom.documentElement
 
306
            else:
 
307
               raise BadXML
 
308
            currentlisteners = shoutcastserver.getElementsByTagName('CURRENTLISTENERS')
 
309
            try:
 
310
               self.listeners = int(currentlisteners[0].firstChild.wholeText.strip())
 
311
            except:
 
312
               raise BadXML
 
313
         else:
 
314
            if dom.documentElement.tagName == u'icestats':
 
315
               icestats = dom.documentElement
 
316
            else:
 
317
               raise BadXML
 
318
            sources = icestats.getElementsByTagName('source')
 
319
            for source in sources:
 
320
               mount = source.getAttribute('mount')
 
321
               if stats_url.endswith(mount):
 
322
                  listeners = source.getElementsByTagName('Listeners')
 
323
                  try:
 
324
                     self.listeners = int(listeners[0].firstChild.wholeText.strip())
 
325
                     break
 
326
                  except:
 
327
                     raise BadXML
 
328
            else:
 
329
               raise BadXML
 
330
      except BadXML:
 
331
         print "Unexpected data in server stats XML file"
 
332
      dom.unlink()
 
333
      print "server", self.url, "has", self.listeners, "listeners"
 
334
 
 
335
class ActionTimer(object):
 
336
   def run(self):
 
337
      if self.n == 0:
 
338
         self.first()
 
339
      self.n += 1
 
340
      if self.n == self.ticks:
 
341
         self.n = 0
 
342
         self.last()
 
343
   def __init__(self, ticks, first, last):
 
344
      assert(ticks)
 
345
      self.ticks = ticks
 
346
      self.n = 0
 
347
      self.first = first
 
348
      self.last = last
 
349
 
 
350
class CellRendererXCast(gtk.CellRendererText):
 
351
   icons = ("<span foreground='#0077FF'>&#x25A0;</span>",
 
352
            "<span foreground='orange'>&#x25A0;</span>",
 
353
            "<span foreground='#0077FF'>&#x25B4;</span>",
 
354
            "<span foreground='orange'>&#x25B4;</span>")
 
355
            
 
356
   ins_icons = ("<span foreground='#CCCCCC'>&#x25A0;</span>",
 
357
            "<span foreground='#CCCCCC'>&#x25A0;</span>",
 
358
            "<span foreground='#CCCCCC'>&#x25B4;</span>",
 
359
            "<span foreground='#CCCCCC'>&#x25B4;</span>")
 
360
 
 
361
   __gproperties__ = {
 
362
      'servertype' : (gobject.TYPE_INT,
 
363
                      'kind of server',
 
364
                      'indication by number of the server in use',
 
365
                      0, 3, 0, gobject.PARAM_READWRITE),
 
366
      'sensitive' : (gobject.TYPE_BOOLEAN,
 
367
                     'sensitivity flag',
 
368
                     'indication of selectability',
 
369
                      1, gobject.PARAM_READWRITE)
 
370
      }
 
371
   
 
372
   def __init__(self):
 
373
      gtk.CellRendererText.__init__(self)
 
374
      self._servertype = 0
 
375
      self._sensitive = 1
 
376
      self.props.xalign = 0.5
 
377
      self.props.family = "monospace"
 
378
 
 
379
   def do_get_property(self, property):
 
380
      if property.name == 'servertype':
 
381
         return self._servertype
 
382
      elif property.name == 'sensitive':
 
383
         return self._sensitive
 
384
      else:
 
385
         raise AttributeError
 
386
         
 
387
   def do_set_property(self, property, value):
 
388
      if property.name == 'servertype':
 
389
         self._servertype = value
 
390
      elif property.name == 'sensitive':
 
391
         self._sensitive = value
 
392
      else:
 
393
         raise AttributeError
 
394
 
 
395
      if self._sensitive:
 
396
         self.props.markup = self.icons[self._servertype]
 
397
      else:
 
398
         self.props.markup = self.ins_icons[self._servertype]
 
399
 
 
400
 
 
401
class ConnectionPane(gtk.VBox):
 
402
   def get_master_server_type(self):
 
403
      try:
 
404
         s_type = ListLine(*self.liststore[0]).server_type
 
405
      except IndexError:
 
406
         return 0
 
407
      return 0 if s_type >= 2 else s_type + 1
 
408
 
 
409
   def set_button(self, tab):
 
410
      if self.get_master_server_type():
 
411
         config = ListLine(*self.liststore[0])
 
412
         text = "{0.host}:{0.port}{0.mount}".format(config)
 
413
         tab.server_connect_label.set_text(text)
 
414
      else:
 
415
         # TC: The connect button text when no connection details have been entered.
 
416
         tab.server_connect_label.set_text(_('No Master Server Configured'))
 
417
   
 
418
   def individual_listeners_toggle_cb(self, cell, path):
 
419
      self.liststore[path][0] = not self.liststore[path][0]
 
420
 
 
421
   def listeners_renderer_cb(self, column, cell, model, iter):
 
422
      listeners = model.get_value(iter, 5)
 
423
      if listeners == -1:
 
424
         cell.set_property("text", "")
 
425
         cell.set_property("xalign", 0.5)
 
426
      elif listeners == -2:
 
427
         cell.set_property("text", u"\u2049")
 
428
         cell.set_property("xalign", 0.5)
 
429
      else:
 
430
         cell.set_property("text", listeners)
 
431
         cell.set_property("xalign", 1.0)
 
432
 
 
433
   def master_is_set(self):
 
434
      return bool(self.get_master_server_type())
 
435
 
 
436
   def streaming_set(self, val):
 
437
      self._streaming_set = val
 
438
 
 
439
   def streaming_is_set(self):
 
440
      return self._streaming_set
 
441
 
 
442
   def row_to_dict(self, rownum):
 
443
      """ obtain a dictionary of server data for a specified row """
 
444
            
 
445
      return ListLine._make(self.liststore[rownum])._asdict()
 
446
   
 
447
   def dict_to_row(self, _dict):
 
448
      """ append a row of server data from a dictionary """
 
449
      
 
450
      _dict["listeners"] = -1
 
451
      row = ListLine(**_dict)
 
452
      t = row.server_type
 
453
      if t < 2:                           # check if first line contains master server info
 
454
         self.liststore.insert(0, row)
 
455
      else:
 
456
         self.liststore.append(row)
 
457
      return
 
458
 
 
459
   def saver(self):
 
460
      server = []
 
461
      template = ("<%s dtype=\"int\">%d</%s>", "<%s dtype=\"str\">%s</%s>")
 
462
      for i in range(len(self.liststore)):
 
463
         s = self.row_to_dict(i)
 
464
         del s["listeners"]
 
465
         s["password"] = base64.encodestring(s["password"])
 
466
         d = []
 
467
         for key, value in s.iteritems():
 
468
            if type(value) == str:
 
469
               t = template[1]
 
470
               value = urllib.quote(value)
 
471
            else:
 
472
               t = template[0]
 
473
            d.append(t % (key, value, key))
 
474
         server.append("".join(("<server>", "".join(d), "</server>")))
 
475
      return "<connections>%s</connections>" % "".join(server)
 
476
   
 
477
   def loader(self, xmldata):
 
478
      def get_child_text(nodelist):
 
479
         t = []
 
480
         for node in nodelist:
 
481
            if node.nodeType == node.TEXT_NODE:
 
482
               t.append(node.data)
 
483
         return "".join(t)
 
484
      if not xmldata:
 
485
         return
 
486
      try:
 
487
         try:
 
488
            dom = mdom.parseString(xmldata)
 
489
         except:
 
490
            print "ConnectionPane.loader: failed to parse xml data...\n", xmldata
 
491
            raise
 
492
         assert(dom.documentElement.tagName == "connections")
 
493
         for server in dom.getElementsByTagName("server"):
 
494
            d = {}
 
495
            for node in server.childNodes:
 
496
               key = str(node.tagName)
 
497
               dtype = node.getAttribute("dtype")
 
498
               raw = get_child_text(node.childNodes)
 
499
               if dtype == "str":
 
500
                  value = urllib.unquote(raw)
 
501
               elif dtype == "int":
 
502
                  value = int(raw)
 
503
               else:
 
504
                  raise ValueError("ConnectionPane.loader: dtype (%s) is unhandled" % dtype)
 
505
               d[key] = value
 
506
            try:
 
507
               d["password"] = base64.decodestring(d["password"])
 
508
            except KeyError:
 
509
               pass
 
510
            self.dict_to_row(d)
 
511
      except Exception, e:
 
512
         print e
 
513
      self.treeview.get_selection().select_path(0)
 
514
         
 
515
   def stats_commence(self):
 
516
      self.stats_rows = []
 
517
      getstats = self.stats_always.get_active() or (self.stats_ifconnected.get_active() and self.streaming_is_set())
 
518
      for i, row in enumerate(self.liststore):
 
519
         if row[0] and getstats:
 
520
            d = self.row_to_dict(i)
 
521
            if d["server_type"] == 1:
 
522
               ap = self.tab.admin_password_entry.get_text().strip()
 
523
               if ap:
 
524
                  d["password"] = ap
 
525
            stats_thread = StatsThread(d)
 
526
            stats_thread.start()
 
527
            ref = gtk.TreeRowReference(self.liststore, i)
 
528
            self.stats_rows.append((ref, stats_thread))
 
529
         else:
 
530
            row[5] = -1       # sets listeners text to 'unknown'
 
531
 
 
532
   def stats_collate(self):
 
533
      count = 0
 
534
      for ref, thread in self.stats_rows:
 
535
         if ref.valid() == False:
 
536
            print "stats_collate:", thread.url, "invalidated by its removal from the stats list"
 
537
            continue
 
538
         row = ref.get_model()[ref.get_path()[0]]
 
539
         row[5] = thread.listeners
 
540
         if thread.listeners > 0:
 
541
            count += thread.listeners
 
542
      self.listeners_display.set_text(str(count))
 
543
      self.listeners = count
 
544
 
 
545
   def on_dialog_destroy(self, dialog, tree_selection, old_iter):
 
546
      model, iter = tree_selection.get_selected()
 
547
      if iter is None and old_iter is not None:
 
548
         tree_selection.select_iter(old_iter)
 
549
 
 
550
   def on_new_clicked(self, button, tree_selection):
 
551
      old_iter = tree_selection.get_selected()[1]
 
552
      tree_selection.unselect_all()
 
553
      self.connection_dialog = ConnectionDialog(self.tab.scg.window, tree_selection)
 
554
      self.connection_dialog.connect("destroy", self.on_dialog_destroy, tree_selection, old_iter)
 
555
      self.connection_dialog.show()
 
556
      
 
557
   def on_edit_clicked(self, button, tree_selection):
 
558
      model, iter = tree_selection.get_selected()
 
559
      if iter:
 
560
         self.connection_dialog = ConnectionDialog(self.tab.scg.window, tree_selection)
 
561
         self.connection_dialog.show()
 
562
      else:
 
563
         print "nothing selected for edit"
 
564
   
 
565
   def on_remove_clicked(self, button, tree_selection):
 
566
      model, iter = tree_selection.get_selected()
 
567
      if iter:
 
568
         if model.remove(iter):
 
569
            tree_selection.select_iter(iter)
 
570
      else:
 
571
         print "nothing selected for removal"
 
572
 
 
573
   def on_keypress(self, widget, event):
 
574
      if gtk.gdk.keyval_name(event.keyval) == "Delete":
 
575
         self.remove.clicked()
 
576
 
 
577
   def on_selection_changed(self, tree_selection):
 
578
      sens = tree_selection.get_selected()[1] is not None
 
579
      for button in self.require_selection:
 
580
         button.set_sensitive(sens)
 
581
 
 
582
   def __init__(self, set_tip, tab):
 
583
      self.tab = tab
 
584
      gtk.VBox.__init__(self)
 
585
      self.streaming_set(False)
 
586
      vbox = gtk.VBox()
 
587
      vbox.set_border_width(6)
 
588
      vbox.set_spacing(6)
 
589
      self.add(vbox)
 
590
      vbox.show()
 
591
      scrolled = gtk.ScrolledWindow()
 
592
      scrolled.set_shadow_type(gtk.SHADOW_ETCHED_IN)
 
593
      scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
 
594
      vbox.pack_start(scrolled, True)
 
595
      scrolled.show()
 
596
      self.liststore = gtk.ListStore(*[x[1] for x in LISTFORMAT])
 
597
      self.liststore.connect("row-deleted", lambda x, y: self.set_button(tab))
 
598
      self.liststore.connect("row-changed", lambda x, y, z: self.set_button(tab))
 
599
      self.set_button(tab)
 
600
      self.treeview = gtk.TreeView(self.liststore)
 
601
      set_tip(self.treeview, _('A table of servers with which to connect. Only one master server can be added for the purpose of streaming. All other servers will appear below the master server in the list for the purpose of stats collection which can be toggled on a per server basis.'))
 
602
      self.treeview.set_enable_search(False)
 
603
      self.treeview.connect("key-press-event", self.on_keypress)
 
604
 
 
605
      rend_type = CellRendererXCast()
 
606
      rend_type.set_property("xalign", 0.5) 
 
607
      col_type = gtk.TreeViewColumn("", rend_type, servertype = 1)
 
608
      col_type.set_sizing = gtk.TREE_VIEW_COLUMN_AUTOSIZE
 
609
      col_type.set_alignment(0.5)
 
610
      self.treeview.append_column(col_type)
 
611
      text_cell_rend = gtk.CellRendererText()
 
612
      text_cell_rend.set_property("ellipsize", pango.ELLIPSIZE_END)
 
613
      col_host = gtk.TreeViewColumn(_('Hostname/IP address'), text_cell_rend, text=2)
 
614
      col_host.set_sizing = gtk.TREE_VIEW_COLUMN_FIXED
 
615
      col_host.set_expand(True)
 
616
      self.treeview.append_column(col_host)
 
617
      rend_port = gtk.CellRendererText()
 
618
      rend_port.set_property("xalign", 1.0)
 
619
      # TC: TCP port number.
 
620
      col_port = gtk.TreeViewColumn(_('Port'), rend_port, text = 3)
 
621
      col_port.set_sizing = gtk.TREE_VIEW_COLUMN_AUTOSIZE
 
622
      col_port.set_alignment(0.5)
 
623
      self.treeview.append_column(col_port)
 
624
      # TC: Mount point is a technical term in relation to icecast servers.
 
625
      col_mount = gtk.TreeViewColumn(_('Mount point      '), text_cell_rend, text=4)
 
626
      col_mount.set_sizing = gtk.TREE_VIEW_COLUMN_AUTOSIZE
 
627
      self.treeview.append_column(col_mount)
 
628
      
 
629
      rend_enabled = gtk.CellRendererToggle()
 
630
      rend_enabled.connect("toggled", self.individual_listeners_toggle_cb)
 
631
      rend_listeners = gtk.CellRendererText()
 
632
      # TC: This is the listener count heading.
 
633
      col_listeners = gtk.TreeViewColumn(_('Listeners'))
 
634
      col_listeners.set_sizing = gtk.TREE_VIEW_COLUMN_AUTOSIZE
 
635
      col_listeners.pack_start(rend_enabled, False)
 
636
      col_listeners.pack_start(rend_listeners)
 
637
      col_listeners.add_attribute(rend_enabled, "active", 0)
 
638
      col_listeners.set_cell_data_func(rend_listeners, self.listeners_renderer_cb)
 
639
      self.treeview.append_column(col_listeners)
 
640
      scrolled.add(self.treeview)
 
641
      self.treeview.show()
 
642
 
 
643
      hbox = gtk.HBox()
 
644
      
 
645
      self.listener_count_button = gtk.Button()
 
646
      ihbox = gtk.HBox()
 
647
      set_tip(ihbox, _('The sum total of listeners in this server tab.'))
 
648
      pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(FGlobs.pkgdatadir / "listenerphones.png", 20, 16)
 
649
      image = gtk.image_new_from_pixbuf(pixbuf)
 
650
      ihbox.pack_start(image, False, False, 0)
 
651
      image.show()
 
652
      frame = gtk.Frame()
 
653
      frame.set_border_width(0)
 
654
      ihbox.pack_start(frame, True, True, 0)
 
655
      frame.show()
 
656
      ihbox.show()
 
657
      self.listeners_display = gtk.Label("0")
 
658
      self.listeners_display.set_alignment(1.0, 0.5)
 
659
      self.listeners_display.set_width_chars(6)
 
660
      self.listeners_display.set_padding(3, 0)
 
661
      frame.add(self.listeners_display)
 
662
      self.listeners_display.show()
 
663
      self.listener_count_button.add(ihbox)
 
664
      hbox.pack_start(self.listener_count_button, False)
 
665
      
 
666
      lcmenu = gtk.Menu()
 
667
      self.listener_count_button.connect("button-press-event",
 
668
         lambda w, e: lcmenu.popup(None, None, None, e.button, e.time))
 
669
      lc_stats = gtk.MenuItem("Update")
 
670
      lcmenu.append(lc_stats)
 
671
      lcsubmenu = gtk.Menu()
 
672
      lc_stats.set_submenu(lcsubmenu)
 
673
      self.stats_never = gtk.RadioMenuItem(None, _('Never'))
 
674
      self.stats_never.connect("toggled", lambda w: ihbox.set_sensitive(not w.get_active()))
 
675
      self.stats_always  = gtk.RadioMenuItem(self.stats_never, _('Always'))
 
676
      self.stats_ifconnected = gtk.RadioMenuItem(self.stats_never, _('If connected'))
 
677
      self.stats_ifconnected.set_active(True)
 
678
      lcsubmenu.append(self.stats_never)
 
679
      lcsubmenu.append(self.stats_always)
 
680
      lcsubmenu.append(self.stats_ifconnected)
 
681
      lcmenu.show_all()
 
682
      
 
683
      bbox = gtk.HButtonBox()
 
684
      bbox.set_spacing(6)
 
685
      bbox.set_layout(gtk.BUTTONBOX_END)
 
686
      new = gtk.Button("New")
 
687
      self.remove = gtk.Button("Remove")
 
688
      edit = gtk.Button("Edit")
 
689
      bbox.add(new)
 
690
      bbox.add(self.remove)
 
691
      bbox.add(edit)
 
692
      self.require_selection = (edit, self.remove)
 
693
      selection = self.treeview.get_selection()
 
694
      selection.connect("changed", self.on_selection_changed)
 
695
      selection.emit("changed")
 
696
      new.connect("clicked", self.on_new_clicked, selection)
 
697
      edit.connect("clicked", self.on_edit_clicked, selection)
 
698
      self.remove.connect("clicked", self.on_remove_clicked, selection)
 
699
      self.require_selection = (self.remove, edit)
 
700
      hbox.pack_start(bbox)
 
701
      vbox.pack_start(hbox, False)
 
702
      hbox.show_all()
 
703
      self.timer = ActionTimer(40, self.stats_commence, self.stats_collate)
 
704
 
 
705
class TimeEntry(gtk.HBox):              # A 24-hour-time entry widget with a checkbutton
 
706
   def time_valid(self):
 
707
      return self.seconds_past_midnight >= 0
 
708
   def get_seconds_past_midnight(self):
 
709
      return self.seconds_past_midnight
 
710
   def set_active(self, boolean):
 
711
      self.check.set_active(boolean and True or False)
 
712
   def get_active(self):
 
713
      return self.check.get_active() and self.time_valid
 
714
   def __entry_activate(self, widget):
 
715
      boolean = widget.get_active()
 
716
      self.entry.set_sensitive(boolean)
 
717
      if boolean:
 
718
         self.entry.grab_focus()
 
719
   def __key_validator(self, widget, event):
 
720
      if event.keyval < 128:
 
721
         if event.string == ":":
 
722
            return False
 
723
         if event.string < "0" or event.string > "9":
 
724
            return True
 
725
   def __time_updater(self, widget):
 
726
      text = widget.get_text()
 
727
      if len(text) == 5 and text[2] == ":":
 
728
         try:
 
729
            hours = int(text[:2])
 
730
            minutes = int(text[3:])
 
731
         except:
 
732
            self.seconds_past_midnight = -1
 
733
         else:
 
734
            if hours >= 0 and hours <=23 and minutes >= 0 and minutes <= 59:
 
735
               self.seconds_past_midnight = hours * 3600 + minutes * 60
 
736
            else:
 
737
               self.seconds_past_midnight = -1
 
738
      else:
 
739
         self.seconds_past_midnight = -1
 
740
   def __init__(self, labeltext):
 
741
      gtk.HBox.__init__(self)
 
742
      self.set_spacing(3)
 
743
      self.check = gtk.CheckButton(labeltext)
 
744
      self.check.connect("toggled", self.__entry_activate)
 
745
      self.pack_start(self.check, False)
 
746
      self.check.show()
 
747
      self.entry = gtk.Entry(5)
 
748
      self.entry.set_sensitive(False)
 
749
      self.entry.set_width_chars(5)
 
750
      self.entry.set_text("00:00")
 
751
      self.entry.connect("key-press-event", self.__key_validator)
 
752
      self.entry.connect("changed", self.__time_updater)
 
753
      self.pack_start(self.entry, False)
 
754
      self.entry.show()
 
755
      self.seconds_past_midnight = -1
 
756
 
 
757
class AutoAction(gtk.HBox):                     # widget consiting of a check button and several radio buttons
 
758
   def activate(self):                          # radio buttons linked to actions to be performed on the activate method
 
759
      if self.get_active():                     # all radio buttons exist in the same radio group so only one action is
 
760
         for radio, action in self.action_lookup:       # performed
 
761
            if radio.get_active():
 
762
               action()
 
763
   def get_active(self):
 
764
      return self.check_button.get_active()
 
765
   def set_active(self, boolean):
 
766
      self.check_button.set_active(boolean)
 
767
   def get_radio_index(self):
 
768
      return self.radio_active
 
769
   def set_radio_index(self, value):
 
770
      try:
 
771
         self.action_lookup[value][0].clicked()
 
772
      except:
 
773
         try:
 
774
            self.action_lookup[0][0].clicked()
 
775
         except:
 
776
            pass
 
777
   def __set_sensitive(self, widget):
 
778
      boolean = widget.get_active()
 
779
      for radio, action in self.action_lookup:
 
780
         radio.set_sensitive(boolean)
 
781
   def __handle_radioclick(self, widget, which):
 
782
      if widget.get_active():
 
783
         self.radio_active = which
 
784
   def __init__(self, labeltext, names_actions):
 
785
      gtk.HBox.__init__(self)
 
786
      self.radio_active = 0
 
787
      self.check_button = gtk.CheckButton(labeltext)
 
788
      self.set_spacing(4)
 
789
      self.pack_start(self.check_button, False, False, 0)
 
790
      self.check_button.show()
 
791
      lastradio = None
 
792
      self.action_lookup = []
 
793
      for index, (name, action) in enumerate(names_actions):
 
794
         radio = gtk.RadioButton(lastradio, name)
 
795
         radio.connect("clicked", self.__handle_radioclick, index)
 
796
         lastradio = radio
 
797
         radio.set_sensitive(False)
 
798
         self.check_button.connect("toggled", self.__set_sensitive)
 
799
         self.pack_start(radio, False, False, 0)
 
800
         radio.show()
 
801
         self.action_lookup.append((radio, action))
 
802
 
 
803
class FramedSpin(gtk.Frame):
 
804
   """A framed spin button that can be disabled"""
 
805
   def get_value(self):
 
806
      if self.check.get_active():
 
807
         return self.spin.get_value()
 
808
      else:
 
809
         return -1
 
810
   def get_cooked_value(self):
 
811
      if self.check.get_active():
 
812
         return self.spin.get_value() * self.adj_basis.get_value() / 100
 
813
      else:
 
814
         return -1
 
815
   def set_value(self, new_value):
 
816
      self.spin.set_value(new_value)
 
817
   def cb_toggled(self, widget):
 
818
      self.spin.set_sensitive(widget.get_active())
 
819
   def __init__(self, text, adj, adj_basis):
 
820
      self.adj_basis = adj_basis
 
821
      gtk.Frame.__init__(self)
 
822
      self.check = gtk.CheckButton(text)
 
823
      hbox = gtk.HBox()
 
824
      hbox.pack_start(self.check, False, False, 2)
 
825
      self.check.show()
 
826
      self.set_label_widget(hbox)
 
827
      hbox.show()
 
828
      vbox = gtk.VBox()
 
829
      vbox.set_border_width(2)
 
830
      self.spin = gtk.SpinButton(adj)
 
831
      vbox.add(self.spin)
 
832
      self.spin.show()
 
833
      self.spin.set_sensitive(False)
 
834
      self.add(vbox)
 
835
      vbox.show()
 
836
      self.check.connect("toggled", self.cb_toggled)
 
837
 
 
838
class SimpleFramedSpin(gtk.Frame):
 
839
   """A framed spin button"""
 
840
   def get_value(self):
 
841
      if self.check.get_active():
 
842
         return self.spin.get_value()
 
843
      else:
 
844
         return -1
 
845
   def set_value(self, new_value):
 
846
      self.spin.set_value(new_value)
 
847
   def __init__(self, text, adj):
 
848
      gtk.Frame.__init__(self)
 
849
      label = gtk.Label(text)
 
850
      hbox = gtk.HBox()
 
851
      hbox.pack_start(label, False, False, 3)
 
852
      label.show()
 
853
      self.set_label_widget(hbox)
 
854
      hbox.show()
 
855
      vbox = gtk.VBox()
 
856
      vbox.set_border_width(2)
 
857
      self.spin = gtk.SpinButton(adj)
 
858
      vbox.add(self.spin)
 
859
      self.spin.show()
 
860
      self.add(vbox)
 
861
      vbox.show()
 
862
 
 
863
class Tab(gtk.VBox):
 
864
   def show_indicator(self, colour):
 
865
      thematch = self.indicator_lookup[colour]
 
866
      thematch.show()
 
867
      for colour, indicator in self.indicator_lookup.iteritems():
 
868
         if indicator is not thematch:
 
869
            indicator.hide()
 
870
   def send(self, stringtosend):
 
871
      self.source_client_gui.send("tab_id=%d\n%s" % (self.numeric_id, stringtosend))
 
872
   def receive(self):
 
873
      return self.source_client_gui.receive()
 
874
   def __init__(self, scg, numeric_id, indicator_lookup):
 
875
      self.indicator_lookup = indicator_lookup
 
876
      self.numeric_id = numeric_id
 
877
      self.source_client_gui = scg
 
878
      gtk.VBox.__init__(self)
 
879
      gtk.VBox.set_border_width(self, 8)
 
880
      gtk.VBox.show(self)
 
881
 
 
882
 
 
883
 
 
884
class Troubleshooting(gtk.VBox):
 
885
   def __init__(self):
 
886
      gtk.VBox.__init__(self)
 
887
      self.set_border_width(6)
 
888
      self.set_spacing(8)
 
889
      
 
890
      hbox = gtk.HBox()
 
891
      hbox.set_spacing(4)
 
892
      # TC: user agents are strings that internet clients use to identify themselves to a server.
 
893
      # TC: typically application name, version, maybe a capabilities list.
 
894
      self.custom_user_agent = gtk.CheckButton(_("Custom user agent string"))
 
895
      self.custom_user_agent.connect("toggled", self._on_custom_user_agent)
 
896
      hbox.pack_start(self.custom_user_agent, False)
 
897
      self.user_agent_entry = HistoryEntry()
 
898
      self.user_agent_entry.set_sensitive(False)
 
899
      hbox.pack_start(self.user_agent_entry)
 
900
      self.pack_start(hbox, False)
 
901
      set_tip(hbox, _("Set this on the occasion that the server or its firewall specifically refuses to allow libshout based clients."))
 
902
      
 
903
      frame = gtk.Frame()
 
904
      self.automatic_reconnection = gtk.CheckButton(_("If the connection breaks reconnect to the server"))
 
905
      self.automatic_reconnection.set_active(True)
 
906
      frame.set_label_widget(self.automatic_reconnection)
 
907
      self.pack_start(frame, False)
 
908
      
 
909
      reconbox = gtk.HBox()
 
910
      reconbox.set_border_width(6)
 
911
      reconbox.set_spacing(4)
 
912
      frame.add(reconbox)
 
913
      # TC: Label for a comma separated list of delay times.
 
914
      reconlabel = gtk.Label(_("Delay times"))
 
915
      reconbox.pack_start(reconlabel, False)
 
916
      self.reconnection_times = HistoryEntry(initial_text=("10,10,60", "5"), store_blank=False)
 
917
      set_tip(self.reconnection_times, _("A comma separated list of delays in seconds between reconnection attempts. Note that bad values or values less than 5 will be interpreted as 5."))
 
918
      reconbox.pack_start(self.reconnection_times, True)
 
919
      # TC: A user specifed sequence is to be allowed to repeat itself indefinitely.
 
920
      self.reconnection_repeat = gtk.CheckButton(_("Repeat"))
 
921
      set_tip(self.reconnection_repeat, _("Repeat the sequence of delays indefinitely."))
 
922
      reconbox.pack_start(self.reconnection_repeat, False)
 
923
      # TC: User specifies no dialog box to be shown.
 
924
      self.reconnection_quiet = gtk.CheckButton(_("Quiet"))
 
925
      set_tip(self.reconnection_quiet, _("Keep the reconnection dialogue box hidden at all times."))
 
926
      reconbox.pack_start(self.reconnection_quiet, False)
 
927
      self.automatic_reconnection.connect("toggled", self._on_automatic_reconnection, reconbox)
 
928
      
 
929
      frame = gtk.Frame(" %s " % _("The contingency plan upon the stream buffer becoming full is..."))
 
930
      sbfbox = gtk.VBox()
 
931
      sbfbox.set_border_width(6)
 
932
      sbfbox.set_spacing(1)
 
933
      frame.add(sbfbox)
 
934
      self.pack_start(frame, False)
 
935
      
 
936
      self.sbf_discard_audio = gtk.RadioButton(None, _("Discard audio data for as long as needed."))
 
937
      self.sbf_reconnect = gtk.RadioButton(self.sbf_discard_audio, _("Assume the connection is beyond saving and reconnect."))
 
938
      for each in (self.sbf_discard_audio, self.sbf_reconnect):
 
939
         sbfbox.pack_start(each, True, False)
 
940
      
 
941
      self.show_all()
 
942
      
 
943
      self.objects = {"custom_user_agent": (self.custom_user_agent, "active"),
 
944
                     "user_agent_entry": (self.user_agent_entry, "history"),
 
945
                     "automatic_reconnection": (self.automatic_reconnection, "active"),
 
946
                     "reconnection_times": (self.reconnection_times, "history"),
 
947
                     "reconnection_repeat": (self.reconnection_repeat, "active"),
 
948
                     "reconnection_quiet": (self.reconnection_quiet, "active"),
 
949
                     "sbf_reconnect": (self.sbf_reconnect, "active"),
 
950
      }
 
951
      
 
952
      
 
953
   def _on_custom_user_agent(self, widget):
 
954
      self.user_agent_entry.set_sensitive(widget.get_active())
 
955
      
 
956
      
 
957
   def _on_automatic_reconnection(self, widget, reconbox):
 
958
      reconbox.set_sensitive(widget.get_active())
 
959
 
 
960
 
 
961
 
 
962
class StreamTab(Tab):
 
963
   class ResampleFrame(SubcategoryFrame):
 
964
      def cb_eval(self, widget, data = None):
 
965
         if data is not None:
 
966
            if widget.get_active():
 
967
               self.extraction_method = data
 
968
            else:
 
969
               return
 
970
         if self.extraction_method == "no_resample":
 
971
            self.resample_rate = self.jack_sample_rate
 
972
         elif self.extraction_method == "standard":
 
973
            self.resample_rate = int(self.resample_rate_combo_box.get_active_text())
 
974
         else:
 
975
            self.resample_rate = int(self.resample_rate_spin_adj.get_value())
 
976
         self.resample_quality = ("highest", "high", "fast", "fastest")[self.resample_quality_combo_box.get_active()]
 
977
         self.mp3_compatible = self.resample_rate in self.mp3_samplerates
 
978
         self.parentobject.mp3_dummy_object.clicked()           # update mp3 pane
 
979
         self.parentobject.vorbis_dummy_object.clicked()
 
980
      def __init__(self, parent, sizegroup):
 
981
         self.parentobject = parent
 
982
         self.jack_sample_rate = parent.source_client_gui.jack_sample_rate
 
983
         self.resample_rate = self.jack_sample_rate
 
984
         self.extraction_method = "no_resample"
 
985
         self.mp3_compatible = True
 
986
         SubcategoryFrame.__init__(self, " %s " % _('Sample rate'))
 
987
         self.resample_no_resample, self.resample_standard, self.resample_custom = self.parentobject.make_radio(3)
 
988
         self.resample_no_resample.connect("clicked", self.cb_eval, "no_resample")
 
989
         self.resample_standard.connect("clicked", self.cb_eval, "standard")
 
990
         self.resample_custom.connect("clicked", self.cb_eval, "custom")
 
991
         no_resample_label = gtk.Label(_('Use JACK sample rate'))
 
992
         self.mp3_samplerates = (48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000)
 
993
         self.resample_rate_combo_box = self.parentobject.make_combo_box(map(str, self.mp3_samplerates))
 
994
         self.resample_rate_combo_box.set_active(1)
 
995
         self.resample_rate_combo_box.connect("changed", self.cb_eval)
 
996
         self.resample_rate_spin_adj = gtk.Adjustment(44100, 4000, 190000, 10, 100, 0)
 
997
         self.resample_rate_spin_control = gtk.SpinButton(self.resample_rate_spin_adj, 0, 0)
 
998
         self.resample_rate_spin_control.connect("value-changed", self.cb_eval)
 
999
         resample_quality_label = gtk.Label(_('Quality'))
 
1000
         self.resample_quality_combo_box = self.parentobject.make_combo_box((_('Highest'),
 
1001
                        _('Good'), _('Fast'), _('Fastest')))
 
1002
         self.resample_quality_combo_box.set_active(2)
 
1003
         self.resample_quality_combo_box.connect("changed", self.cb_eval)
 
1004
         self.resample_dummy_object = gtk.Button()
 
1005
         self.resample_dummy_object.connect("clicked", self.cb_eval)
 
1006
         sample_rate_pane = self.parentobject.item_item_layout(((self.resample_no_resample, no_resample_label),
 
1007
                              (self.resample_standard, self.resample_rate_combo_box),
 
1008
                              (self.resample_custom, self.resample_rate_spin_control),
 
1009
                              (resample_quality_label, self.resample_quality_combo_box)), sizegroup)
 
1010
         sample_rate_pane.set_border_width(10)
 
1011
         self.add(sample_rate_pane)
 
1012
         sample_rate_pane.show()
 
1013
         set_tip(self.resample_no_resample.get_parent(), _('No additional resampling will occur. The stream sample rate will be that of the JACK sound server.'))
 
1014
         set_tip(self.resample_standard.get_parent(), _('Use one of the standard mp3 sample rates for the stream.'))
 
1015
         set_tip(self.resample_custom.get_parent(), _('Complete sample rate freedom. Note that only sample rates that appear in the drop down box can be used with an mp3 stream.'))
 
1016
         set_tip(self.resample_quality_combo_box.get_parent(), _('This selects the audio resampling method to be used, efficiency versus quality. Highest mode offers the best sound quality but also uses the most CPU (not recommended for systems built before 2006). Fastest mode while it uses by far the least amount of CPU should be avoided if at all possible.'))
 
1017
   def make_combo_box(self, items):
 
1018
      combobox = gtk.combo_box_new_text()
 
1019
      for each in items:
 
1020
         combobox.append_text(each)
 
1021
      return combobox
 
1022
   def make_radio(self, qty):
 
1023
      listofradiobuttons = []
 
1024
      for iteration in range(qty):
 
1025
         listofradiobuttons.append(gtk.RadioButton())
 
1026
         if iteration > 0:
 
1027
            listofradiobuttons[iteration].set_group(listofradiobuttons[0])
 
1028
      return listofradiobuttons
 
1029
   def make_radio_with_text(self, labels):
 
1030
      listofradiobuttons = []
 
1031
      for count, label in enumerate(labels):
 
1032
         listofradiobuttons.append(gtk.RadioButton(None, label))
 
1033
         if count > 0:
 
1034
            listofradiobuttons[count].set_group(listofradiobuttons[0])
 
1035
      return listofradiobuttons
 
1036
   def make_notebook_tab(self, notebook, labeltext, tooltip = None):
 
1037
      label = gtk.Label(labeltext)
 
1038
      if tooltip is not None:
 
1039
         set_tip(label, tooltip)
 
1040
      vbox = gtk.VBox()
 
1041
      notebook.append_page(vbox, label)
 
1042
      label.show()
 
1043
      vbox.show()
 
1044
      return vbox
 
1045
   def item_item_layout(self, item_item_pairs, sizegroup):
 
1046
      vbox = gtk.VBox()
 
1047
      vbox.set_spacing(2)
 
1048
      for left, right in item_item_pairs:
 
1049
         hbox = gtk.HBox()
 
1050
         sizegroup.add_widget(hbox)
 
1051
         hbox.set_spacing(5)
 
1052
         if left is not None:
 
1053
            hbox.pack_start(left, False, False, 0)
 
1054
            left.show()
 
1055
         if right is not None:
 
1056
            hbox.pack_start(right, True, True, 0)
 
1057
            right.show()
 
1058
         vbox.pack_start(hbox, False, False, 0)
 
1059
         hbox.show()
 
1060
      return vbox
 
1061
   def item_item_layout2(self, item_item_pairs, sizegroup):
 
1062
      rhs_size = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
 
1063
      vbox = gtk.VBox()
 
1064
      vbox.set_spacing(2)
 
1065
      for left, right in item_item_pairs:
 
1066
         hbox = gtk.HBox()
 
1067
         rhs_size.add_widget(left)
 
1068
         sizegroup.add_widget(hbox)
 
1069
         hbox.set_spacing(5)
 
1070
         hbox.pack_start(left, False, False, 0)
 
1071
         left.show()
 
1072
         if right is not None:
 
1073
            rhs_size.add_widget(right)
 
1074
            hbox.pack_end(right, False, False, 0)
 
1075
            right.show()
 
1076
         vbox.pack_start(hbox, False, False, 0)
 
1077
         hbox.show()
 
1078
      return vbox
 
1079
   def item_item_layout3(self, leftitems, rightitems):
 
1080
      outer = gtk.HBox()
 
1081
      wedge = gtk.HBox()
 
1082
      outer.pack_start(wedge, False, False, 2)
 
1083
      wedge = gtk.HBox()
 
1084
      outer.pack_end(wedge, False, False, 2)
 
1085
      lh = gtk.HBox()
 
1086
      rh = gtk.HBox()
 
1087
      outer.pack_start(lh, True, False, 0)
 
1088
      outer.pack_start(rh, True, False, 0)
 
1089
      lv = gtk.VBox()
 
1090
      rv = gtk.VBox()
 
1091
      lh.pack_start(lv, False, False, 0)
 
1092
      rh.pack_start(rv, False, False, 0)
 
1093
      lframe = gtk.Frame()
 
1094
      lframe.set_shadow_type(gtk.SHADOW_OUT)
 
1095
      rframe = gtk.Frame()
 
1096
      rframe.set_shadow_type(gtk.SHADOW_OUT)
 
1097
      lv.pack_start(lframe, True, False, 0)
 
1098
      rv.pack_start(rframe, True, False, 0)
 
1099
      lvi = gtk.VBox()
 
1100
      lvi.set_border_width(5)
 
1101
      lvi.set_spacing(7)
 
1102
      rvi = gtk.VBox()
 
1103
      rvi.set_border_width(5)
 
1104
      rvi.set_spacing(7)
 
1105
      lframe.add(lvi)
 
1106
      rframe.add(rvi)
 
1107
      for item in leftitems:
 
1108
         lvi.pack_start(item, True, False, 0)
 
1109
      for item in rightitems:
 
1110
         rvi.pack_start(item, True, False, 0)
 
1111
      return outer
 
1112
   def label_item_layout(self, label_item_pairs, sizegroup):    # align vertical colums of label : item
 
1113
      hbox = gtk.HBox()                                         # label is right justified and narrow as possible
 
1114
      vbox_left = gtk.VBox()                                    # the item widget is free to expand
 
1115
      vbox_left.set_spacing(1)
 
1116
      vbox_right = gtk.VBox()
 
1117
      vbox_right.set_spacing(1)
 
1118
      hbox.pack_start(vbox_left, False, False, 0)
 
1119
      hbox.pack_start(vbox_right, True, True, 0)
 
1120
      hbox.set_spacing(3)
 
1121
      for text, item in label_item_pairs:
 
1122
         if text is not None:
 
1123
            labelbox = gtk.HBox()
 
1124
            if type(text) == str:
 
1125
               label = gtk.Label(text)
 
1126
            else:
 
1127
               label = text
 
1128
            sizegroup.add_widget(label)
 
1129
            labelbox.pack_end(label, False, False)
 
1130
            label.show()
 
1131
            vbox_left.pack_start(labelbox, False, False, 0)
 
1132
            labelbox.show()
 
1133
         itembox = gtk.HBox()
 
1134
         sizegroup.add_widget(itembox)
 
1135
         itembox.add(item)
 
1136
         item.show()
 
1137
         vbox_right.pack_start(itembox, False, False, 0)
 
1138
         itembox.show()
 
1139
      vbox_left.show()
 
1140
      vbox_right.show()
 
1141
      return hbox
 
1142
   def send(self, string_to_send):
 
1143
      Tab.send(self, "dev_type=streamer\n" + string_to_send)
 
1144
   def receive(self):
 
1145
      return Tab.receive(self)
 
1146
   def cb_servertype(self, widget):
 
1147
      sens = bool(widget.get_active())
 
1148
      for each in (self.mount_entry, self.login_entry):
 
1149
         each.set_sensitive(sens)
 
1150
      self.update_sensitives()
 
1151
   def update_sensitives(self, *params):
 
1152
      if self.encoder == "off":
 
1153
         self.update_button.set_sensitive(False)
 
1154
      mode = self.connection_pane.get_master_server_type() # 0 = none, 1 = icecast2, 2 = shoutcast
 
1155
      self.recorder_valid_override = False
 
1156
      
 
1157
      if self.encoder == "ogg":
 
1158
         self.server_connect.set_sensitive(mode == 1 or self.server_connect.get_active())
 
1159
         if self.format_page == 0:
 
1160
            self.update_button.set_sensitive(False)
 
1161
         elif self.format_page == 1:
 
1162
           self.update_button.set_sensitive(self.vorbis_settings_valid)
 
1163
         elif self.format_page == 2:
 
1164
            try:
 
1165
               self.update_button.set_sensitive(self.file_dialog.get_filename().lower().endswith(".ogg"))
 
1166
            except AttributeError:
 
1167
               self.update_button.set_sensitive(False)
 
1168
         else:
 
1169
            print "update_sensitives: unhandled format page"
 
1170
      elif self.encoder == "mp3":
 
1171
         self.server_connect.set_sensitive(mode != 0 or self.server_connect.get_active())
 
1172
         if self.format_page == 0:
 
1173
            self.update_button.set_sensitive(self.mp3_compatibility != "s-rate!")
 
1174
         elif self.format_page == 1:
 
1175
            self.update_button.set_sensitive(False)
 
1176
         elif self.format_page == 2:
 
1177
            try:
 
1178
               self.update_button.set_sensitive(self.file_dialog.get_filename().lower().endswith(".mp3"))
 
1179
            except AttributeError:
 
1180
               self.update_button.set_sensitive(False)
 
1181
         else:
 
1182
            print "update_sensitives: unhandled format page"
 
1183
      elif self.encoder == "off":
 
1184
         self.test_monitor.set_sensitive(True)
 
1185
         if self.format_page == 0:
 
1186
            self.recorder_valid_override = sens = bool(self.mp3_compatibility != "s-rate!" and lameenabled)
 
1187
            sens = sens and mode
 
1188
            self.server_connect.set_sensitive(sens)
 
1189
            self.test_monitor.set_sensitive(sens)
 
1190
         elif self.format_page == 1:
 
1191
            if self.subformat_page == 0:
 
1192
               self.recorder_valid_override = sens = self.vorbis_settings_valid
 
1193
            elif self.subformat_page == 1:   # OggFLAC
 
1194
               sr = self.stream_resample_frame.resample_rate
 
1195
               self.recorder_valid_override = sens = sr <= 65535 or sr % 10 == 0
 
1196
            elif self.subformat_page == 2:   # Speex
 
1197
               self.recorder_valid_override = sens = True                   # True always for now
 
1198
            self.server_connect.set_sensitive(sens and mode == 1)
 
1199
            self.test_monitor.set_sensitive(sens)
 
1200
         try:
 
1201
            record_tabs = self.source_client_gui.recordtabframe.tabs
 
1202
         except:
 
1203
            pass        # this will be called inevitably before recordtabframe has been created yet
 
1204
         else:
 
1205
            for rectab in record_tabs:
 
1206
               rectab.source_dest.source_combo.emit("changed")  # update sensitivity on record buttons
 
1207
      if self.encoder != "off":
 
1208
         if self.format_page == 0:
 
1209
            if self.encoder == "ogg" or self.mp3_compatibility == "s-rate!":
 
1210
               self.update_button.set_sensitive(False)
 
1211
         if self.format_page == 1 and self.encoder == "mp3":
 
1212
               self.update_button.set_sensitive(False)
 
1213
   
 
1214
   def cb_file_dialog_response(self, widget, response_id):
 
1215
      self.update_sensitives()
 
1216
   def cb_format_notebook(self, widget, page, page_num):
 
1217
      if self.format_page != page_num:
 
1218
         self.format_page = page_num
 
1219
         self.update_sensitives()
 
1220
   def cb_subformat_notebook(self, widget, page, page_num):
 
1221
      if self.subformat_page != page_num:
 
1222
         self.subformat_page = page_num
 
1223
         self.update_sensitives()
 
1224
   def cb_mp3tab(self, widget, data = None):
 
1225
      if data == "standard" or data == "custom":
 
1226
         if widget.get_active():
 
1227
            self.mp3_bitrate_widget = data
 
1228
         else:
 
1229
            return
 
1230
      self.mp3_stereo_type = ("stereo", "mono", "jstereo")[self.mp3_stereo_combo_box.get_active()]
 
1231
      self.mp3_encode_quality = self.mp3_encoding_quality_combo_box.get_active_text()
 
1232
      if self.mp3_bitrate_widget == "standard":
 
1233
         self.mp3_bitrate = int(self.mp3_bitrate_combo_box.get_active_text())
 
1234
      elif self.mp3_bitrate_widget == "custom":
 
1235
         self.mp3_bitrate = int(self.mp3_bitrate_spin_adj.get_value())
 
1236
      self.mp3_standard_bitrate = self.mp3_bitrate in self.mp3_standard_bitrates
 
1237
      self.mp3_samplerate = self.stream_resample_frame.resample_rate
 
1238
      self.mp3_resample_compatible = self.stream_resample_frame.mp3_compatible
 
1239
      self.mp3_compatibility = "freeformat"
 
1240
      if not self.mp3_resample_compatible:
 
1241
         self.mp3_compatibility = "s-rate!"
 
1242
      else:
 
1243
         if self.mpeg_std_search(self.mp3_bitrate, self.mp3_samplerate, self.mp3_mpeg2_5_bitrates_samplerates):
 
1244
            self.mp3_compatibility = "mpeg 2.5"
 
1245
         if self.mpeg_std_search(self.mp3_bitrate, self.mp3_samplerate, self.mp3_mpeg2_bitrates_samplerates):
 
1246
            self.mp3_compatibility = "mpeg 2"
 
1247
         if self.mpeg_std_search(self.mp3_bitrate, self.mp3_samplerate, self.mp3_mpeg1_bitrates_samplerates):
 
1248
            self.mp3_compatibility = "mpeg 1"
 
1249
      self.mp3_compatibility_status.push(1, self.mp3_compatibility)
 
1250
      self.mp3_freeformat = ("0", "1")[self.mp3_compatibility == "freeformat"]
 
1251
      self.update_sensitives()
 
1252
   
 
1253
   def cb_oggtab(self, widget, data = None):
 
1254
      ogg_bitrate = self.ogg_encoding_nominal_spin_adj.get_value()
 
1255
      minactive = self.ogg_min_checkbutton.get_active()
 
1256
      maxactive = self.ogg_max_checkbutton.get_active()
 
1257
      self.ogg_encoding_relmin_spin_control.set_sensitive(minactive)
 
1258
      self.ogg_encoding_relmax_spin_control.set_sensitive(maxactive)
 
1259
      if minactive:
 
1260
         ogg_min = self.ogg_encoding_relmin_spin_adj.get_value() + ogg_bitrate
 
1261
         if ogg_min <= 0:
 
1262
            ogg_min = -1
 
1263
      else:
 
1264
         ogg_min = -1
 
1265
      if maxactive:
 
1266
         ogg_max = self.ogg_encoding_relmax_spin_adj.get_value() + ogg_bitrate
 
1267
      else:
 
1268
         ogg_max = -1
 
1269
      self.send("sample_rate=%d\nbit_rate=%d\nbit_rate_min=%d\nbit_rate_max=%d\nstereo=%s\ncommand=test_ogg_values\n" % (self.stream_resample_frame.resample_rate, 
 
1270
                ogg_bitrate, ogg_min, ogg_max,
 
1271
                ("mono","stereo")[self.ogg_encoding_stereo_checkbutton.get_active()]))
 
1272
      self.vorbis_settings_valid = self.receive() == "succeeded"
 
1273
      self.update_sensitives()
 
1274
      
 
1275
   def cb_vorbistab(self, widget, data = None):
 
1276
      vorbis_bitrate = self.vorbis_encoding_nominal_spin_adj.get_value()
 
1277
      vorbis_min = self.vorbis_encoding_lower_spin_control.get_cooked_value()
 
1278
      vorbis_max = self.vorbis_encoding_upper_spin_control.get_cooked_value()
 
1279
      self.send("sample_rate=%d\nbit_rate=%d\nbit_rate_min=%d\nbit_rate_max=%d\nstereo=%s\ncommand=test_ogg_values\n" % (self.stream_resample_frame.resample_rate, 
 
1280
                vorbis_bitrate, vorbis_min, vorbis_max,
 
1281
                ("mono","stereo")[self.vorbis_stereo_rb.get_active()]))
 
1282
      self.vorbis_settings_valid = self.receive() == "succeeded"
 
1283
      self.update_sensitives()
 
1284
      
 
1285
   def mpeg_std_search(self, bitrate, samplerate, brsr):
 
1286
      return bitrate in brsr[0] and samplerate in brsr[1]
 
1287
   def server_reconnect(self):
 
1288
      if self.connection_string:
 
1289
         self.send("command=server_disconnect\n")
 
1290
         self.receive()
 
1291
         time.sleep(0.25)
 
1292
         self.send(self.connection_string)
 
1293
         self.receive()
 
1294
   def cb_server_connect(self, widget):
 
1295
      if widget.get_active():
 
1296
         self.start_stop_encoder(ENCODER_START)
 
1297
         d = self.connection_pane.row_to_dict(0)
 
1298
 
 
1299
         self.connection_string = "\n".join((
 
1300
               "stream_source=" + str(self.numeric_id),
 
1301
               "server_type=" + ("Icecast 2", "Shoutcast")[d["server_type"]],
 
1302
               "host=" + d["host"],
 
1303
               "port=%d" % d["port"],
 
1304
               "mount=" + d["mount"],
 
1305
               "login=" + d["login"],
 
1306
               "password=" + d["password"],
 
1307
               "useragent=" + (self.troubleshooting.user_agent_entry.get_text().strip() if self.troubleshooting.custom_user_agent.get_active() else ""),
 
1308
               "dj_name=" + self.dj_name_entry.get_text().strip(),
 
1309
               "listen_url=" + self.listen_url_entry.get_text().strip(),
 
1310
               "description=" + self.description_entry.get_text().strip(),
 
1311
               "genre=" + self.genre_entry.get_text().strip(),
 
1312
               "irc=" + self.irc_entry.get_text().strip(),
 
1313
               "aim=" + self.aim_entry.get_text().strip(),
 
1314
               "icq=" + self.icq_entry.get_text().strip(),
 
1315
               "make_public=" + str(bool(self.make_public.get_active())),
 
1316
               "command=server_connect\n"))
 
1317
         self.send(self.connection_string)
 
1318
         self.is_shoutcast = d["server_type"] == 1
 
1319
         if self.receive() == "failed":
 
1320
            self.server_connect.set_active(False)
 
1321
            self.connection_string = None
 
1322
         else:
 
1323
            self.connection_pane.streaming_set(True)
 
1324
      else:
 
1325
         self.send("command=server_disconnect\n")
 
1326
         self.receive()
 
1327
         self.start_stop_encoder(ENCODER_STOP)
 
1328
         self.connection_string = None
 
1329
         self.connection_pane.streaming_set(False)
 
1330
   def cb_test_monitor(self, widget):
 
1331
      if widget.get_active():
 
1332
         self.start_stop_encoder(ENCODER_START)
 
1333
         self.send("command=monitor_start\n")
 
1334
      else:
 
1335
         self.send("command=monitor_stop\n")
 
1336
         self.start_stop_encoder(ENCODER_STOP)
 
1337
   def cb_update_button(self, widget):
 
1338
      self.start_encoder("encoder_update")
 
1339
      if self.server_connect.get_active() and self.is_shoutcast:
 
1340
         self.server_reconnect()
 
1341
   def start_stop_encoder(self, command):               # provides for nested starts, stops of the encoder
 
1342
      if command == ENCODER_START:
 
1343
         self.encoder_on_count += 1
 
1344
         if self.encoder_on_count == 1:
 
1345
            self.start_encoder()
 
1346
      else:
 
1347
         self.encoder_on_count -= 1
 
1348
         if self.encoder_on_count == 0:
 
1349
            self.stop_encoder()
 
1350
      self.update_sensitives()
 
1351
   def start_encoder(self, command = "encoder_start"):
 
1352
      self.metadata_update.clicked()
 
1353
      if self.format_page == 0:
 
1354
         self.encoder = "mp3"
 
1355
         self.send("format=mp3\nencode_source=jack\nsample_rate=%d\nresample_quality=%s\nbit_rate=%d\nstereo=%s\nencode_quality=%s\nfreeformat_mp3=%s\ncommand=%s\n" %   (self.stream_resample_frame.resample_rate,
 
1356
                                         self.stream_resample_frame.resample_quality,
 
1357
                                         self.mp3_bitrate, 
 
1358
                                         self.mp3_stereo_type,
 
1359
                                         self.mp3_encode_quality,
 
1360
                                         self.mp3_freeformat,
 
1361
                                         command))
 
1362
         if self.receive() == "succeeded":
 
1363
            self.format_info_bar.push(1, "mp3   %dHz   %dkbps   %s" % (self.stream_resample_frame.resample_rate, self.mp3_bitrate, self.mp3_stereo_type))
 
1364
         else:
 
1365
            self.format_info_bar.push(1, "")
 
1366
      elif self.format_page == 1:
 
1367
         self.encoder = "ogg"
 
1368
         if self.subformat_page == 0:  # vorbis
 
1369
            vorbis_bitrate = self.vorbis_encoding_nominal_spin_adj.get_value()
 
1370
            vorbis_min = self.vorbis_encoding_lower_spin_control.get_cooked_value()
 
1371
            vorbis_max = self.vorbis_encoding_upper_spin_control.get_cooked_value()
 
1372
            vorbis_channels = ("mono", "stereo")[self.vorbis_stereo_rb.get_active()]
 
1373
            self.send("format=ogg\nsubformat=vorbis\nencode_source=jack\nsample_rate=%d\nresample_quality=%s\nbit_rate=%d\nbit_rate_min=%d\nbit_rate_max=%d\nstereo=%s\ncommand=%s\n" % (self.stream_resample_frame.resample_rate, 
 
1374
                                             self.stream_resample_frame.resample_quality,
 
1375
                                             vorbis_bitrate, vorbis_min, vorbis_max,
 
1376
                                             vorbis_channels, command))
 
1377
            if self.receive() == "succeeded":
 
1378
               if vorbis_min == vorbis_max == -1:
 
1379
                  managed = ""
 
1380
               else:
 
1381
                  managed = "   managed"
 
1382
               self.format_info_bar.push(1, "Ogg Vorbis   %dHz   %dkbps   %s%s" % (self.stream_resample_frame.resample_rate, vorbis_bitrate, vorbis_channels, managed))
 
1383
            else:
 
1384
               self.format_info_bar.push(1, "")
 
1385
         elif self.subformat_page == 1: # OggFLAC
 
1386
            flac_channels = ("mono", "stereo")[self.flacstereo.get_active()]
 
1387
            flac_bitwidth = ("16", "20", "24")[(self.flac20bit.get_active() and 1) + (self.flac24bit.get_active() and 2)]
 
1388
            self.send("format=ogg\nsubformat=flac\nencode_source=jack\nsample_rate=%d\nresample_quality=%s\nbit_width=%s\nstereo=%s\nuse_metadata=%d\ncommand=%s\n" % (self.stream_resample_frame.resample_rate, self.stream_resample_frame.resample_quality, flac_bitwidth, flac_channels, self.flacmetadata.get_active(), command))
 
1389
            if self.receive() == "succeeded":
 
1390
               self.format_info_bar.push(1, "Ogg FLAC   %s bit   %s" % (flac_bitwidth, flac_channels))
 
1391
            else:
 
1392
               self.format_info_bar.push(1, "")
 
1393
         elif self.subformat_page == 2: # speex
 
1394
            speex_srate = (32000, 16000, 8000)[self.speex_mode.get_active()]
 
1395
            speex_channels = ("mono", "stereo")[self.speex_stereo.get_active()]
 
1396
            self.send("format=ogg\nsubformat=speex\nencode_source=jack\nsample_rate=%d\nresample_quality=%s\nspeex_mode=%d\nstereo=%s\nuse_metadata=%d\nspeex_quality=%s\nspeex_complexity=%s\ncommand=%s\n" % (speex_srate, self.stream_resample_frame.resample_quality, self.speex_mode.get_active(), speex_channels, self.speex_metadata.get_active(), self.speex_quality.get_active_text(), self.speex_complexity.get_active_text(), command))
 
1397
            metatext = ("-Meta", "+Meta")[self.speex_metadata.get_active()]
 
1398
            if self.receive() == "succeeded":
 
1399
               self.format_info_bar.push(1, "Speex   %s   %s   Q%s   C%s   %s" % (self.speex_mode.get_active_text(), speex_channels.capitalize(), self.speex_quality.get_active_text(), self.speex_complexity.get_active_text(), metatext))
 
1400
            else:
 
1401
               self.format_info_bar.push(1, "")
 
1402
      else:
 
1403
         if self.file_dialog.get_filename().endswith(".mp3"):
 
1404
            self.encoder = "mp3"
 
1405
         else:
 
1406
            self.encoder = "ogg"
 
1407
         self.send("format=%s\nencode_source=file\nfilename=%s\noffset=%d\ncommand=%s\n" %   (self.encoder, self.file_dialog.get_filename(), self.file_offset_adj.get_value(), command))
 
1408
         if self.receive() == "succeeded":
 
1409
            self.format_info_bar.push(1, self.file_dialog.get_filename())
 
1410
         else:
 
1411
            self.format_info_bar.push(1, "")
 
1412
   def stop_encoder(self):
 
1413
      self.encoder = "off"
 
1414
      self.send("command=encoder_stop\n")
 
1415
      if self.receive() == "failed":
 
1416
         print "stop_encoder: encoder was already stopped"
 
1417
      self.format_info_bar.push(1, "")
 
1418
   
 
1419
   def server_type_cell_data_func(self, celllayout, cell, model, iter):
 
1420
      text = model.get_value(iter, 0)
 
1421
      if text == _('Shoutcast') and lameenabled == 0:
 
1422
         cell.set_property("sensitive", False)
 
1423
      else:
 
1424
         cell.set_property("sensitive", True)
 
1425
   
 
1426
   def cb_metadata(self, widget):
 
1427
      fallback = self.metadata_fallback.get_text()
 
1428
      songname = self.scg.parent.songname.encode("utf-8") or fallback
 
1429
      table = [("%%", "%")] + zip(("%r", "%t", "%l"), ((getattr(self.scg.parent, x) or fallback) for x in ("artist", "title", "album")))
 
1430
      table.append(("%s", songname))
 
1431
      raw_cm = self.metadata.get_text().encode("utf-8", "replace").strip()
 
1432
      cm = string_multireplace(raw_cm, table)
 
1433
      
 
1434
      if self.scg.parent.prefs_window.mp3_utf8.get_active():
 
1435
         cm_lat1 = cm
 
1436
      else:
 
1437
         cm_lat1 = cm.decode("utf-8").encode("iso8859-1", "replace").strip()
 
1438
 
 
1439
      if cm:
 
1440
         disp = cm
 
1441
      else:
 
1442
         tab = ("mp3", "ogg")[self.format_page] if self.encoder == "off" else self.encoder
 
1443
         if tab == "mp3":
 
1444
            disp = songname
 
1445
         elif tab == "ogg":
 
1446
            disp = "[{0[%r]}], [{0[%t]}], [{0[%l]}]".format(dict(table))
 
1447
         else:
 
1448
            disp = "no metadata string defined for this stream format"
 
1449
 
 
1450
            
 
1451
      self.metadata_display.push(0, disp)
 
1452
      self.metadata_update.set_relief(gtk.RELIEF_HALF)
 
1453
      self.scg.send("tab_id=%d\ndev_type=encoder\ncustom_meta=%s\ncustom_meta_lat1=%s\ncommand=new_custom_metadata\n" % (self.numeric_id, cm, cm_lat1))
 
1454
 
 
1455
   def cb_new_metadata_format(self, widget):
 
1456
      self.metadata_update.set_relief(gtk.RELIEF_NORMAL)  
 
1457
   
 
1458
   @threadslock
 
1459
   def deferred_connect(self):
 
1460
      """Intended to be called from a thread."""
 
1461
      
 
1462
      self.server_connect.set_active(True)
 
1463
  
 
1464
   def cb_kick_incumbent(self, widget, post_action=lambda : None):
 
1465
      """Try to remove whoever is using the server so that we can connect."""
 
1466
      
 
1467
      mode = self.connection_pane.get_master_server_type()
 
1468
      if mode == 0:
 
1469
         return
 
1470
        
 
1471
      srv = ListLine(*self.connection_pane.liststore[0])
 
1472
      auth_handler = urllib2.HTTPBasicAuthHandler()
 
1473
 
 
1474
      if mode == 1:
 
1475
         url = "http://" + urllib.quote(srv.host) + ":" + str(srv.port) + "/admin/killsource?mount=" + urllib.quote(srv.mount)
 
1476
         auth_handler.add_password("Icecast2 Server", srv.host + ":" + str(srv.port), srv.login, srv.password)
 
1477
         def check_reply(reply):
 
1478
            try:
 
1479
               elem = xml.etree.ElementTree.fromstring(reply)
 
1480
            except xml.etree.ElementTree.ParseError:
 
1481
               return False
 
1482
            else:
 
1483
               rslt = "succeeded" if elem.findtext("return") == "1" else "failed"
 
1484
               print "kick %s: %s" % (rslt, elem.findtext("message"))
 
1485
               return rslt == "succeeded"
 
1486
 
 
1487
      elif mode == 2:
 
1488
         password = self.admin_password_entry.get_text().strip() or srv.password
 
1489
         url = "http://" + urllib.quote(srv.host) + ":" + str(srv.port) + "/admin.cgi?mode=kicksrc"
 
1490
         auth_handler.add_password("Shoutcast Server", srv.host + ":" + str(srv.port), "admin", password)
 
1491
         def check_reply(reply):
 
1492
            # Could go to lengths to check the XML stats here.
 
1493
            # Thats one whole extra HTTP request.
 
1494
            print "kick succeeded"
 
1495
            return True
 
1496
 
 
1497
      opener = urllib2.build_opener(auth_handler)
 
1498
      opener.addheaders = [('User-agent', 'Mozilla/5.0')]
 
1499
 
 
1500
      def threaded():
 
1501
         try:
 
1502
            reply = opener.open(url).read()
 
1503
         except urllib2.URLError, e:
 
1504
            print "kick failed:", e
 
1505
         else:
 
1506
            check_reply(reply)
 
1507
            post_action()
 
1508
     
 
1509
      Thread(target=threaded).start()
 
1510
  
 
1511
   def __init__(self, scg, numeric_id, indicator_lookup):
 
1512
      Tab.__init__(self, scg, numeric_id, indicator_lookup)
 
1513
      self.scg = scg
 
1514
      self.show_indicator("clear")
 
1515
      self.tab_type = "streamer"
 
1516
      self.encoder = "off"                      # can also be set to "mp3" or "ogg" depending on what is encoded
 
1517
      self.encoder_on_count = 0                 # when this counter hits zero the encoder is turned off
 
1518
      self.format_page = 0                      # the current format page
 
1519
      self.subformat_page = 0                   # the Ogg sub-format
 
1520
      self.set_spacing(10)
 
1521
           
 
1522
      self.ic_expander = gtk.Expander(_('Individual Controls'))
 
1523
      self.pack_start(self.ic_expander, False)
 
1524
      self.ic_expander.show()
 
1525
            
 
1526
      self.ic_frame = gtk.Frame()
 
1527
      ic_vbox = gtk.VBox()                 # box containing connect button and timers
 
1528
      ic_vbox.set_border_width(10)
 
1529
      ic_vbox.set_spacing(10)
 
1530
      self.ic_frame.add(ic_vbox)
 
1531
      ic_vbox.show()
 
1532
      
 
1533
      hbox = gtk.HBox()
 
1534
      hbox.set_spacing(6)
 
1535
      self.server_connect = gtk.ToggleButton()
 
1536
      set_tip(self.server_connect, _('Connect to or disconnect from the radio server. If the button does not stay in, the connection failed for some reason.\n \nIf the button is greyed out it means you are using unsupported settings. Shoutcast only supports mp3 and mp3 requires that you use one of the sample rates in the drop down box. Ogg only supports certain sample rate, bit rate, and stereo combinations. Also, the connection list must contain details for a master server.'))
 
1537
      self.server_connect.connect("toggled", self.cb_server_connect)
 
1538
      hbox.pack_start(self.server_connect, True, True, 0)
 
1539
      self.server_connect_label = gtk.Label()
 
1540
      self.server_connect_label.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
 
1541
      self.server_connect.add(self.server_connect_label)
 
1542
      self.server_connect_label.show()
 
1543
      self.server_connect.show()
 
1544
      
 
1545
      # TC: Kick whoever is on the server.
 
1546
      self.kick_incumbent = gtk.Button(_('Kick Incumbent'))
 
1547
      self.kick_incumbent.connect("clicked", self.cb_kick_incumbent)
 
1548
      set_tip(self.kick_incumbent, _('This will disconnect whoever is currently using the server, freeing it up for personal use.'))
 
1549
      hbox.pack_start(self.kick_incumbent, False)
 
1550
      self.kick_incumbent.show()
 
1551
      
 
1552
      ic_vbox.pack_start(hbox, False)
 
1553
      hbox.show()
 
1554
      
 
1555
      hbox = gtk.HBox()
 
1556
      hbox.set_spacing(6)
 
1557
      label = gtk.Label(_('Connection timer:'))
 
1558
      hbox.pack_start(label, False)
 
1559
      label.show()
 
1560
      
 
1561
      self.start_timer = TimeEntry(_('Begin'))
 
1562
      set_tip(self.start_timer, _('Automatically connect to the server at a specific time in 24 hour format, midnight being 00:00'))
 
1563
      hbox.pack_start(self.start_timer, False)
 
1564
      self.start_timer.show()
 
1565
      
 
1566
      self.kick_before_start = gtk.CheckButton(_('With kick'))
 
1567
      self.kick_before_start.set_sensitive(False)
 
1568
      set_tip(self.kick_before_start, _('Disconnect whoever is using the server just before start time.'))
 
1569
      hbox.pack_start(self.kick_before_start, False)
 
1570
      self.kick_before_start.show()
 
1571
 
 
1572
      self.start_timer.check.connect("toggled", lambda w: self.kick_before_start.set_sensitive(w.props.active))
 
1573
 
 
1574
      
 
1575
      self.stop_timer = TimeEntry(_('End'))
 
1576
      set_tip(self.stop_timer, _('Automatically disconnect from the server at a specific time in 24 hour format.'))
 
1577
      hbox.pack_end(self.stop_timer, False)
 
1578
      self.stop_timer.show()
 
1579
      
 
1580
      
 
1581
      ic_vbox.pack_start(hbox, False, False, 0)
 
1582
      hbox.show()
 
1583
      
 
1584
      hbox = gtk.HBox()                                 # box containing auto action widgets
 
1585
      hbox.set_spacing(10)
 
1586
      label = gtk.Label(_('At connect:'))
 
1587
      hbox.pack_start(label, False, False, 0)
 
1588
      label.show()
 
1589
      # TC: [x] Start player (*) 1 ( ) 2
 
1590
      self.start_player_action = AutoAction(_('Start player'), (
 
1591
                ("1", self.source_client_gui.parent.player_left.play.clicked),
 
1592
                ("2", self.source_client_gui.parent.player_right.play.clicked)))
 
1593
      hbox.pack_start(self.start_player_action, False, False, 0)
 
1594
      self.start_player_action.show()
 
1595
      set_tip(self.start_player_action, _('Have one of the players start automatically when a radio server connection is successfully made.'))
 
1596
      if PGlobs.num_recorders:
 
1597
         vseparator = gtk.VSeparator()
 
1598
         hbox.pack_start(vseparator, True, False, 0)
 
1599
         vseparator.show()
 
1600
      
 
1601
      # TC: [x] Start recorder (*) 1 ( ) 2
 
1602
      self.start_recorder_action = AutoAction(_('Start recorder'), [ (chr(ord("1") + i), t.record_buttons.record_button.activate) for i, t in enumerate(self.source_client_gui.recordtabframe.tabs) ])
 
1603
      
 
1604
      hbox.pack_end(self.start_recorder_action, False, False, 0)
 
1605
      if PGlobs.num_recorders:
 
1606
         self.start_recorder_action.show()
 
1607
      set_tip(self.start_recorder_action, _('Have a recorder start automatically when a radio server connection is successfully made.'))
 
1608
      ic_vbox.pack_start(hbox, False, False, 0)
 
1609
      hbox.show()
 
1610
 
 
1611
      frame = gtk.Frame(" %s " % _('Metadata'))
 
1612
      table = gtk.Table(3, 3)
 
1613
      table.set_border_width(6)
 
1614
      table.set_row_spacings(1)
 
1615
      table.set_col_spacings(4)
 
1616
      frame.add(table)
 
1617
      table.show()
 
1618
      ic_vbox.pack_start(frame, False)
 
1619
      frame.show()
 
1620
      
 
1621
      format_label = SmallLabel(_('Format String'))
 
1622
      # TC: Label for the metadata fallback value.
 
1623
      fallback_label = SmallLabel(_('Fallback'))
 
1624
      self.metadata = HistoryEntryWithMenu()
 
1625
      self.metadata.child.connect("changed", self.cb_new_metadata_format)
 
1626
      self.metadata_fallback = gtk.Entry()
 
1627
      self.metadata_fallback.set_width_chars(10)
 
1628
      self.metadata_fallback.set_text("<Unknown>")
 
1629
      self.metadata_update = gtk.Button()
 
1630
      image = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
 
1631
      self.metadata_update.set_image(image)
 
1632
      image.show()
 
1633
      self.metadata_update.connect("clicked", self.cb_metadata)
 
1634
      self.metadata_display = gtk.Statusbar()
 
1635
      self.metadata_display.set_has_resize_grip(False)
 
1636
 
 
1637
      set_tip(self.metadata, _('You can enter text to accompany the stream here and can specify placemarkers %r %t %l %s for the artist, title, album, and songname respectively, or leave this text field blank to use the default metadata.\n\nSongname (%s) is derived from the filename in the absence of sufficient metadata, while the other placemarkers will use the fallback text to the right.\n\nWhen blank, Ogg streams will use the standard Vorbis tags and mp3 will use %s.'))
 
1638
      set_tip(self.metadata_fallback, _('The fallback text to use when %r %t %l metadata is unavailable. See the format string to the left.'))
 
1639
      set_tip(self.metadata_update, _('Metadata normally updates only on song title changes but you can force an immediate update here.'))
 
1640
      
 
1641
      x = gtk.EXPAND
 
1642
      f = gtk.FILL
 
1643
      s = gtk.SHRINK
 
1644
      arrangement = (((format_label, x|f), (fallback_label, s|f)),
 
1645
                     ((self.metadata, x|f), (self.metadata_fallback, s), (self.metadata_update, s)))
 
1646
      
 
1647
      for r, row in enumerate(arrangement):
 
1648
         for c, (child, xopt) in enumerate(row):
 
1649
            table.attach(child, c, c + 1, r, r + 1, xopt, s|f)
 
1650
            child.show()
 
1651
      table.attach(self.metadata_display, 0, 3, 2, 3, x|f, s|f)
 
1652
      self.metadata_display.show()
 
1653
 
 
1654
      self.pack_start(self.ic_frame, False)
 
1655
      
 
1656
      self.details = gtk.Expander(_('Configuration'))
 
1657
      set_tip(self.details, _('The controls for configuring a stream.'))
 
1658
      self.pack_start(self.details, False)
 
1659
      self.details.show()
 
1660
     
 
1661
      self.details_nb = gtk.Notebook()
 
1662
      self.pack_start(self.details_nb, False)
 
1663
      
 
1664
      self.connection_pane = ConnectionPane(set_tip, self)
 
1665
      self.connection_pane.liststore.connect("row-deleted", self.update_sensitives)
 
1666
      self.connection_pane.liststore.connect("row-changed", self.update_sensitives)
 
1667
      label = gtk.Label(_('Connection'))
 
1668
      self.details_nb.append_page(self.connection_pane, label)
 
1669
      label.show()
 
1670
      self.connection_pane.show()
 
1671
       
 
1672
      vbox = gtk.VBox()          # format box
 
1673
      vbox.set_border_width(10)
 
1674
      vbox.set_spacing(14)
 
1675
      label = gtk.Label(_('Format'))
 
1676
      self.details_nb.append_page(vbox, label)
 
1677
      label.show()
 
1678
      vbox.show()
 
1679
      hbox = gtk.HBox(True)
 
1680
      hbox.set_spacing(16)
 
1681
      vbox.pack_start(hbox, False)
 
1682
      hbox.show()
 
1683
      sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_VERTICAL)
 
1684
      self.stream_resample_frame = self.ResampleFrame(self, sizegroup)  # stream resample frame
 
1685
      hbox.add(self.stream_resample_frame)
 
1686
      self.stream_resample_frame.show()
 
1687
      self.format_notebook = gtk.Notebook()     # [mp3 / ogg / file] chooser
 
1688
      hbox.add(self.format_notebook)
 
1689
      self.format_notebook.show()
 
1690
      
 
1691
      # mp3 tab
 
1692
      self.mp3tab = self.make_notebook_tab(self.format_notebook, "MP3", _('Clicking this tab selects the mp3 file format for streaming and contains settings for configuring the mp3 encoder.'))
 
1693
      self.standard_mp3_bitrate, self.custom_mp3_bitrate = self.make_radio(2)
 
1694
      set_tip(self.standard_mp3_bitrate, _('Use one of the standard mp3 bit rates.'))
 
1695
      set_tip(self.custom_mp3_bitrate, _("Freedom to choose a non standard bitrate. Note however that the use of a non-standard bit rate will result in a 'free-format' stream that cannot be handled by a great many media players."))
 
1696
      self.standard_mp3_bitrate.connect("clicked", self.cb_mp3tab, "standard")
 
1697
      self.custom_mp3_bitrate.connect("clicked", self.cb_mp3tab, "custom")
 
1698
      self.mp3_standard_bitrates = (320, 256, 224, 192, 160, 144, 128, 112, 96, 80, 64, 56, 48, 40, 32, 24, 16, 8)
 
1699
      self.mp3_mpeg1_bitrates_samplerates = ((320, 256, 224, 192, 160, 128, 112, 96, 80, 64, 56, 48, 40, 32), (48000, 44100, 32000))
 
1700
      self.mp3_mpeg2_bitrates_samplerates = ((160, 144, 128, 112, 96, 80, 64, 56, 48, 40, 32, 24, 16, 8), (24000, 22050, 16000))
 
1701
      self.mp3_mpeg2_5_bitrates_samplerates = ((160, 144, 128, 112, 96, 80, 64, 56, 48, 40, 32, 24, 16, 8), (12000, 11025, 8000))
 
1702
      self.mp3_bitrate_combo_box = self.make_combo_box(map(str, self.mp3_standard_bitrates))
 
1703
      set_tip(self.mp3_bitrate_combo_box, _('The bit-rate in kilobits per second.'))
 
1704
      self.mp3_bitrate_combo_box.set_active(6)
 
1705
      self.mp3_bitrate_combo_box.connect("changed", self.cb_mp3tab)
 
1706
      self.mp3_bitrate_spin_adj = gtk.Adjustment(128, 8, 640, 10, 100, 0)
 
1707
      self.mp3_bitrate_spin_control = gtk.SpinButton(self.mp3_bitrate_spin_adj)
 
1708
      set_tip(self.mp3_bitrate_spin_control, _('The bit-rate in kilobits per second.'))
 
1709
      self.mp3_bitrate_spin_control.connect("value-changed", self.cb_mp3tab)
 
1710
      encoding_quality_label = gtk.Label(_('Quality (0=best)'))
 
1711
      self.mp3_encoding_quality_combo_box = self.make_combo_box(("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"))
 
1712
      set_tip(self.mp3_encoding_quality_combo_box, _('This trades off sound quality against CPU efficiency. The more streams you want to run concurrently the more you might want to consider using a lower quality setting.'))
 
1713
      self.mp3_encoding_quality_combo_box.set_active(2)
 
1714
      self.mp3_encoding_quality_combo_box.connect("changed", self.cb_mp3tab)
 
1715
      self.mp3_stereo_combo_box = self.make_combo_box(("Stereo", "Mono", "Joint Stereo"))
 
1716
      set_tip(self.mp3_stereo_combo_box, _('Mono is self explanatory. Joint Stereo is recommended below 160kb/s where regular Stereo might result in metallic sounding distortion. At higher bitrates regular stereo sounds better due to superior channel separation.'))
 
1717
      self.mp3_stereo_combo_box.set_active(2)
 
1718
      self.mp3_stereo_combo_box.connect("changed", self.cb_mp3tab)
 
1719
      self.mp3_compatibility_status = gtk.Statusbar()
 
1720
      set_tip(self.mp3_compatibility_status, _("The type of mpeg header used in the mp3 stream or either s-rate or freeformat. Freeformat indicates that the bitrate is not specified in the header since it is non-standard, rather the listener client has to figure out what the bitrate is by itself and not all of them are capable of doing that. In short you'll be streaming something many listeners may not be able to listen to. S-rate indicates the sample rate you have selected is not compatible with mp3 and you'll need to change it if you want to stream."))
 
1721
      self.mp3_compatibility_status.set_has_resize_grip(False)
 
1722
      self.mp3_dummy_object = gtk.Button()
 
1723
      self.mp3_dummy_object.connect("clicked", self.cb_mp3tab)
 
1724
      self.mp3_bitrate = 128
 
1725
      self.mp3_bitrate_widget = "standard"
 
1726
      
 
1727
      if lameenabled:
 
1728
         mp3_pane = self.item_item_layout(((self.standard_mp3_bitrate, self.mp3_bitrate_combo_box),
 
1729
                                       (self.custom_mp3_bitrate, self.mp3_bitrate_spin_control),
 
1730
                                       (encoding_quality_label, self.mp3_encoding_quality_combo_box),
 
1731
                                       (self.mp3_stereo_combo_box, self.mp3_compatibility_status)), sizegroup)
 
1732
         mp3_pane.set_border_width(10)
 
1733
      else:
 
1734
         mp3_pane = gtk.VBox(True)
 
1735
         for line in _("To enable MP3 streaming\ninstall the package named\n'libmp3lame'\n and restart IDJC.").splitlines():
 
1736
            label = gtk.Label(line)
 
1737
            mp3_pane.add(label)
 
1738
            label.show()
 
1739
         set_tip(mp3_pane, _('Installing libmp3lame will allow you to stream the MP3 format to Shoutcast servers. Currently only Ogg streaming to Icecast servers is possible.'))
 
1740
      
 
1741
      self.mp3tab.add(mp3_pane)
 
1742
      mp3_pane.show()
 
1743
 
 
1744
      # Ogg tab
 
1745
      self.oggtab = self.make_notebook_tab(self.format_notebook, "Ogg", _('Clicking this tab selects the Ogg family of file formats.'))
 
1746
      self.subformat_notebook = gtk.Notebook()
 
1747
      self.oggtab.add(self.subformat_notebook)
 
1748
      self.subformat_notebook.show()
 
1749
      self.oggvorbistab = self.make_notebook_tab(self.subformat_notebook, "Vorbis", _('This chooses the Ogg/vorbis format for streaming and recording.'))
 
1750
      self.oggflactab = self.make_notebook_tab(self.subformat_notebook, "FLAC", _('This chooses the OggFLAC format for streaming and recording.'))
 
1751
      self.oggspeextab = self.make_notebook_tab(self.subformat_notebook, "Speex", _('This chooses the Speex speech format for streaming and recording.'))
 
1752
      
 
1753
      # Vorbis subtab contents
 
1754
      self.vorbis_encoding_nominal_spin_adj = gtk.Adjustment(128, 8, 500, 1, 10, 0)
 
1755
      self.vorbis_encoding_nominal_spin_control = SimpleFramedSpin(_('Bitrate'), self.vorbis_encoding_nominal_spin_adj)
 
1756
      self.vorbis_encoding_nominal_spin_control.spin.connect("value-changed", self.cb_vorbistab)
 
1757
      
 
1758
      self.vorbis_stereo_rb, self.vorbis_mono_rb = self.make_radio(2)
 
1759
      self.vorbis_stereo_rb.connect("toggled", self.cb_vorbistab)
 
1760
      radiovbox = gtk.VBox()
 
1761
      radiovbox.set_border_width(5)
 
1762
      stereohbox = gtk.HBox()
 
1763
      monohbox = gtk.HBox()
 
1764
      radiovbox.add(stereohbox)
 
1765
      radiovbox.add(monohbox)
 
1766
      stereohbox.pack_start(self.vorbis_stereo_rb, False, False, 0)
 
1767
      monohbox.pack_start(self.vorbis_mono_rb, False, False, 0)
 
1768
      label = gtk.Label(_('Stereo'))
 
1769
      stereohbox.pack_start(label)
 
1770
      label = gtk.Label(_('Mono'))
 
1771
      monohbox.pack_start(label)
 
1772
      radiovbox.show_all()
 
1773
      
 
1774
      upper_spin_adj = gtk.Adjustment(150, 100, 400, 1, 10, 0)
 
1775
      lower_spin_adj = gtk.Adjustment(50, 0, 100, 1, 10, 0)
 
1776
      # TC: The upper bitrate limit as a percentage.
 
1777
      self.vorbis_encoding_upper_spin_control = FramedSpin(_('Upper %'), upper_spin_adj, self.vorbis_encoding_nominal_spin_adj)
 
1778
      # TC: The lower bitrate limit as a percentage.
 
1779
      self.vorbis_encoding_lower_spin_control = FramedSpin(_('Lower %'), lower_spin_adj, self.vorbis_encoding_nominal_spin_adj)
 
1780
      
 
1781
      sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_VERTICAL)
 
1782
 
 
1783
      vorbis_pane = self.item_item_layout2(((self.vorbis_encoding_nominal_spin_control, self.vorbis_encoding_upper_spin_control), (radiovbox, self.vorbis_encoding_lower_spin_control)), sizegroup)
 
1784
      vorbis_pane.set_border_width(3)
 
1785
      self.oggvorbistab.add(vorbis_pane)
 
1786
      vorbis_pane.show()
 
1787
 
 
1788
      set_tip(self.vorbis_encoding_nominal_spin_control, _('The nominal Ogg/Vorbis bitrate in kilobits per second.'))
 
1789
      set_tip(self.vorbis_encoding_upper_spin_control, _('The upper bitrate limit relative to the nominal bitrate. This is an advisory limit and it may be exceeded. Normally it is safe to leave the upper limit uncapped since the bitrate will be averaged and the listeners have buffers that extend for many seconds. The checkbox enables/disables this feature.'))
 
1790
      set_tip(self.vorbis_encoding_lower_spin_control, _('The minimum bitrate in relative percentage terms. For streaming it is recommended that you set a minimum bitrate to ensure correct listener client behaviour however setting any upper or lower limit will result in a significantly higher CPU usage by a factor of at least three, and slightly degraded sound quality. The checkbox enables/disables this feature.'))
 
1791
 
 
1792
      self.vorbis_settings_valid = False
 
1793
      self.vorbis_dummy_object = gtk.Button()
 
1794
      self.vorbis_dummy_object.connect("clicked", self.cb_vorbistab)
 
1795
 
 
1796
      # FLAC subtab contents
 
1797
      self.flacstereo = gtk.CheckButton(_('Stereo'))
 
1798
      self.flacmetadata = gtk.CheckButton(_('Metadata'))
 
1799
      self.flacstereo.set_active(True)
 
1800
      self.flacmetadata.set_active(True)
 
1801
      set_tip(self.flacmetadata, _('You can prevent the sending of metadata by turning this feature off. This will prevent certain players from dropping the stream or inserting an audible gap every time the song title changes.'))
 
1802
      
 
1803
      flac_bitrates = (_('%d Bit') % x for x in (16, 20, 24))
 
1804
      self.flac16bit, self.flac20bit, self.flac24bit = self.make_radio_with_text(flac_bitrates)
 
1805
      set_tip(self.flac16bit, _('Useful for streaming but for recording choose a higher bitrate option.'))
 
1806
      set_tip(self.flac20bit, _('Ideal for very high quality streaming or recording although not as compatible as 16 bit.'))
 
1807
      set_tip(self.flac24bit, _('The highest quality audio format available within IDJC. Recommended for pre-recording.'))
 
1808
      if FGlobs.oggflacenabled:
 
1809
         flac_pane = self.item_item_layout3((self.flacstereo, self.flacmetadata),(self.flac16bit, self.flac20bit, self.flac24bit))
 
1810
      else:
 
1811
         flac_pane = gtk.Label(_('Feature Disabled'))
 
1812
      self.oggflactab.add(flac_pane)
 
1813
      flac_pane.show_all()
 
1814
      
 
1815
      # Speex subtab contents
 
1816
      self.speex_mode = gtk.combo_box_new_text()
 
1817
      # The Speex audio codec has specific modes that are user selectable (dropdown list text).
 
1818
      speex_modes = (
 
1819
         # TC: One of the modes supported by the Speex codec.
 
1820
         _('Ultra Wide Band'), 
 
1821
         # TC: One of the modes supported by the Speex codec.
 
1822
         _('Wide Band'), 
 
1823
         # TC: One of the modes supported by the Speex codec.
 
1824
         _('Narrow Band'))
 
1825
      for each in speex_modes:
 
1826
         self.speex_mode.append_text(each)
 
1827
      self.speex_mode.set_active(0)
 
1828
      self.speex_stereo = gtk.CheckButton(_('Stereo'))
 
1829
      set_tip(self.speex_stereo, _('Apply intensity stereo to the audio stream. This is a very efficient implementation of stereo but is only suited to voice.'))
 
1830
      self.speex_metadata = gtk.CheckButton(_('Metadata'))
 
1831
      set_tip(self.speex_metadata, _('Sending metadata may cause listener clients to misbehave when the metadata changes. By keeping this feature turned off you can avoid that.'))
 
1832
      self.speex_quality = gtk.combo_box_new_text()
 
1833
      for i in range(11):
 
1834
         self.speex_quality.append_text("%d" % i)
 
1835
      self.speex_quality.set_active(8)
 
1836
      self.speex_complexity = gtk.combo_box_new_text()
 
1837
      for i in range(1, 11):
 
1838
         self.speex_complexity.append_text("%d" % i)
 
1839
      self.speex_complexity.set_active(2)
 
1840
      
 
1841
      if FGlobs.speexenabled:
 
1842
         svbox = gtk.VBox()
 
1843
         svbox.set_border_width(5)
 
1844
         
 
1845
         # TC: The mode uesd by the Speex codec.
 
1846
         label = gtk.Label(_('Mode'))
 
1847
         shbox0 = gtk.HBox()
 
1848
         shbox0.set_spacing(5)
 
1849
         shbox0.pack_start(label, False, False, 0)
 
1850
         shbox0.pack_start(self.speex_mode, True, True, 0)
 
1851
         set_tip(shbox0, _('This is the audio bandwidth selector. Ultra Wide Band has a bandwidth of 16kHz; Wide Band, 8kHz; Narrow Band, 4kHz. The samplerate is twice the value of the selected bandwidth consequently all settings in the samplerate pane to the left will be disregarded apart from the resample quality setting.'))
 
1852
         svbox.pack_start(shbox0, True, False, 0)
 
1853
         shbox1 = gtk.HBox()
 
1854
         shbox1.pack_start(self.speex_stereo, True, False, 0)
 
1855
         shbox1.pack_end(self.speex_metadata, True, False, 0)
 
1856
         svbox.pack_start(shbox1, True, False, 0)
 
1857
         shbox2 = gtk.HBox()
 
1858
         shbox3 = gtk.HBox()
 
1859
         shbox3.set_spacing(5)
 
1860
         shbox4 = gtk.HBox()
 
1861
         shbox4.set_spacing(5)
 
1862
         shbox2.pack_start(shbox3, False, False, 0)
 
1863
         shbox2.pack_end(shbox4, False, False, 0)
 
1864
         
 
1865
         label = gtk.Label(_('Quality'))
 
1866
         shbox3.pack_start(label, False, False, 0)
 
1867
         shbox3.pack_start(self.speex_quality, False, False, 0)
 
1868
         set_tip(shbox3, _('This picks an appropriate bitrate for the selected bandwidth on a quality metric. Q8 is a good choice for artifact-free speech and Q10 would be the ideal choice for music.'))
 
1869
         
 
1870
         label = gtk.Label(_('CPU'))
 
1871
         shbox4.pack_start(label, False, False, 0)
 
1872
         shbox4.pack_start(self.speex_complexity, False, False, 0)
 
1873
         set_tip(shbox4, _('This sets the level of complexity in the encoder. Higher values use more CPU but result in better sounding audio though not as great an improvement as you would get by increasing the quality setting to the left.'))
 
1874
         
 
1875
         svbox.pack_start(shbox2, True, False, 0)
 
1876
         self.oggspeextab.add(svbox)
 
1877
         svbox.show_all()
 
1878
      else:
 
1879
         label = gtk.Label(_('Feature Disabled'))
 
1880
         self.oggspeextab.add(label)
 
1881
         label.show()
 
1882
      
 
1883
      format_control_bar = gtk.HBox()                           # Button box in Format frame
 
1884
      format_control_sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
 
1885
      format_control_bar.set_spacing(10)
 
1886
      vbox.pack_start(format_control_bar, False)
 
1887
      format_control_bar.show()
 
1888
      self.test_monitor = gtk.ToggleButton(_(' Test / Monitor '))
 
1889
      self.test_monitor.connect("toggled", self.cb_test_monitor)
 
1890
      format_control_sizegroup.add_widget(self.test_monitor)
 
1891
      format_control_bar.pack_start(self.test_monitor, False, False, 0)
 
1892
      #self.test_monitor.show()
 
1893
      self.format_info_bar = gtk.Statusbar()
 
1894
      self.format_info_bar.set_has_resize_grip(False)
 
1895
      format_control_bar.pack_start(self.format_info_bar, True, True, 0)
 
1896
      self.format_info_bar.show()
 
1897
      set_tip(self.format_info_bar, _('Information about how the encoder is currently configured is displayed here.'))
 
1898
      # TC: Button when pressed performs an update.
 
1899
      self.update_button = gtk.Button(_('Update'))
 
1900
      set_tip(self.update_button, _('Use this to change the encoder settings while streaming or recording.\n \nIf this button is greyed out it means that the encoder is not running, or the bitrate/samplerate combination is not supported by the encoder, or you are trying to switch between Ogg and mp3, which is not permitted.'))
 
1901
      self.update_button.connect("clicked", self.cb_update_button)
 
1902
      format_control_sizegroup.add_widget(self.update_button)
 
1903
      self.update_button.set_sensitive(False)
 
1904
      format_control_bar.pack_start(self.update_button, False, False, 0)
 
1905
      self.update_button.show()
 
1906
      self.format_notebook.connect("switch-page", self.cb_format_notebook)
 
1907
      self.subformat_notebook.connect("switch-page", self.cb_subformat_notebook)
 
1908
      self.format_notebook.set_current_page(0)
 
1909
 
 
1910
      vbox = gtk.VBox()
 
1911
      # TC: Tab heading. User can enter information about the stream here.
 
1912
      label = gtk.Label(_('Stream Info'))
 
1913
      self.details_nb.append_page(vbox, label)
 
1914
      label.show()
 
1915
      vbox.show()
 
1916
      self.dj_name_entry = DefaultEntry("eyedeejaycee")
 
1917
      set_tip(self.dj_name_entry, _('Enter your DJ name or station name here. Typically this information will be displayed by listener clients.'))
 
1918
      self.listen_url_entry = DefaultEntry("http://www.example.com")
 
1919
      set_tip(self.listen_url_entry, _('The URL of your radio station. This and the rest of the information below is intended for display on a radio station listings website.'))
 
1920
      self.description_entry = gtk.Entry()
 
1921
      set_tip(self.description_entry, _('A description of your radio station.'))
 
1922
      genre_entry_box = gtk.HBox()
 
1923
      genre_entry_box.set_spacing(12)
 
1924
      self.genre_entry = DefaultEntry("Misc")
 
1925
      set_tip(self.genre_entry, _('The musical genres you are likely to play.'))
 
1926
      genre_entry_box.pack_start(self.genre_entry, True, True, 0)
 
1927
      self.genre_entry.show()
 
1928
      self.make_public = gtk.CheckButton(_('Make Public'))
 
1929
      set_tip(self.make_public, _('Publish your radio station on a listings website. The website in question will depend on how the server to which you connect is configured.'))
 
1930
      genre_entry_box.pack_start(self.make_public, False, False, 0)
 
1931
      self.make_public.show()
 
1932
      info_sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_VERTICAL)
 
1933
      stream_details_pane = self.label_item_layout((
 
1934
                                              # TC: The DJ or Stream name.
 
1935
                                              (_('DJ name'), self.dj_name_entry),
 
1936
                                              (_('Listen URL'), self.listen_url_entry),
 
1937
                                              # TC: Station description. Typically the user enters a small paragraph of text.
 
1938
                                              (_('Description'), self.description_entry),
 
1939
                                              (_('Genre(s)'), genre_entry_box)
 
1940
                                              ), info_sizegroup)
 
1941
      stream_details_pane.set_border_width(10)
 
1942
      vbox.add(stream_details_pane)
 
1943
      stream_details_pane.show()
 
1944
      
 
1945
 
 
1946
      vbox = gtk.VBox()
 
1947
      alhbox = gtk.HBox()
 
1948
      alhbox.set_border_width(10)
 
1949
      alhbox.set_spacing(3)
 
1950
      label = gtk.Label(_('Master server admin password'))
 
1951
      alhbox.pack_start(label, False)
 
1952
      label.show()
 
1953
      self.admin_password_entry = gtk.Entry()
 
1954
      self.admin_password_entry.set_visibility(False)
 
1955
      set_tip(self.admin_password_entry, _("This is for kick and stats on Shoutcast master servers that have an administrator password. For those that don't leave this blank (the source password is sufficient for those)."))
 
1956
      alhbox.pack_start(self.admin_password_entry)
 
1957
      self.admin_password_entry.show()
 
1958
      vbox.pack_start(alhbox, False)
 
1959
      alhbox.show()
 
1960
           
 
1961
      frame = CategoryFrame(" %s " % _('Contact Details'))
 
1962
      frame.set_shadow_type(gtk.SHADOW_NONE)
 
1963
      frame.set_border_width(0)
 
1964
      self.irc_entry = gtk.Entry()
 
1965
      set_tip(self.irc_entry, _('Internet Relay Chat connection info goes here.'))
 
1966
      self.aim_entry = gtk.Entry()
 
1967
      set_tip(self.aim_entry, _('Connection info for AOL instant messenger goes here.'))
 
1968
      self.icq_entry = gtk.Entry()
 
1969
      set_tip(self.icq_entry, _('ICQ instant messenger connection info goes here.'))
 
1970
      contact_sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_VERTICAL)
 
1971
      contact_details_pane = self.label_item_layout((
 
1972
                                              (_('IRC'), self.irc_entry),
 
1973
                                              (_('AIM'), self.aim_entry),
 
1974
                                              (_('ICQ'), self.icq_entry)
 
1975
                                              ), contact_sizegroup)
 
1976
      contact_details_pane.set_border_width(10)
 
1977
      frame.add(contact_details_pane)
 
1978
      contact_details_pane.show()
 
1979
      
 
1980
      vbox.pack_start(frame, False)
 
1981
      if FGlobs.enh_libshout:
 
1982
         frame.show()
 
1983
      label = gtk.Label(_('Extra Shoutcast'))
 
1984
      self.details_nb.append_page(vbox, label)
 
1985
      label.show()
 
1986
      vbox.show()
 
1987
      
 
1988
      label = gtk.Label(_("Troubleshooting"))
 
1989
      self.troubleshooting = Troubleshooting()
 
1990
      self.details_nb.append_page(self.troubleshooting, label)
 
1991
      label.show()
 
1992
      
 
1993
      label = gtk.Label("IRC")
 
1994
      self.ircpane = IRCPane()
 
1995
      self.details_nb.append_page(self.ircpane, label)
 
1996
      label.show()
 
1997
 
 
1998
      self.details_nb.set_current_page(0)
 
1999
      
 
2000
      self.stream_resample_frame.resample_no_resample.emit("clicked")   # bogus signal to update mp3 pane
 
2001
      self.objects = {  "metadata"    : (self.metadata, "history"),
 
2002
                        "metadata_fb" : (self.metadata_fallback, "text"),
 
2003
                        "prekick"     : (self.kick_before_start, "active"),
 
2004
                        "connections" : (self.connection_pane, ("loader", "saver")),
 
2005
                        "stats_never" : (self.connection_pane.stats_never, "active"),
 
2006
                        "stats_always": (self.connection_pane.stats_always, "active"),
 
2007
                        "rs_use_jack" : (self.stream_resample_frame.resample_no_resample, "active"),
 
2008
                        "rs_use_std" : (self.stream_resample_frame.resample_standard, "active"),
 
2009
                        "rs_use_custom_rate" : (self.stream_resample_frame.resample_custom, "active"),
 
2010
                        "rs_std_rate" : (self.stream_resample_frame.resample_rate_combo_box, "active"),
 
2011
                        "rs_custom_rate" : (self.stream_resample_frame.resample_rate_spin_adj, "value"),
 
2012
                        "rs_quality" : (self.stream_resample_frame.resample_quality_combo_box, "active"),
 
2013
                        "source_type" : (self.format_notebook, "notebookpage"),
 
2014
                        "ogg_type": (self.subformat_notebook, "notebookpage"),
 
2015
                        "std_mp3bitrate" : (self.standard_mp3_bitrate, "active"),
 
2016
                        "custom_mp3_bitrate" : (self.custom_mp3_bitrate, "active"),
 
2017
                        "mp3_bitrate_combo" : (self.mp3_bitrate_combo_box, "active"),
 
2018
                        "mp3_bitrate_spin" : (self.mp3_bitrate_spin_adj, "value"),
 
2019
                        "mp3_quality" : (self.mp3_encoding_quality_combo_box, "active"),
 
2020
                        "mp3_stereo" : (self.mp3_stereo_combo_box, "active"),
 
2021
                        "vorbis_bitrate" : (self.vorbis_encoding_nominal_spin_adj, "value"),
 
2022
                        "vorbis_upper_pc": (self.vorbis_encoding_upper_spin_control.spin, "value"),
 
2023
                        "vorbis_lower_pc":  (self.vorbis_encoding_lower_spin_control.spin, "value"),
 
2024
                        "vorbis_upper_enable": (self.vorbis_encoding_upper_spin_control.check, "active"),
 
2025
                        "vorbis_lower_enable": (self.vorbis_encoding_lower_spin_control.check, "active"),
 
2026
                        "vorbis_mono": (self.vorbis_mono_rb, "active"),
 
2027
                        "flac_stereo": (self.flacstereo, "active"),
 
2028
                        "flac_metadata": (self.flacmetadata, "active"),
 
2029
                        "flac_20_bit": (self.flac20bit, "active"),
 
2030
                        "flac_24_bit": (self.flac24bit, "active"),
 
2031
                        "speex_mode": (self.speex_mode, "active"),
 
2032
                        "speex_stereo": (self.speex_stereo, "active"), 
 
2033
                        "speex_metadata": (self.speex_metadata, "active"), 
 
2034
                        "speex_quality": (self.speex_quality, "active"),
 
2035
                        "speex_complexity": (self.speex_complexity, "active"),
 
2036
                        "dj_name" : (self.dj_name_entry, "text"),
 
2037
                        "listen_url" : (self.listen_url_entry, "text"),
 
2038
                        "description" : (self.description_entry, "text"),
 
2039
                        "genre" : (self.genre_entry, "text"),
 
2040
                        "make_public" : (self.make_public, "active"),
 
2041
                        "contact_aim" : (self.aim_entry, "text"),
 
2042
                        "contact_irc" : (self.irc_entry, "text"),
 
2043
                        "contact_icq" : (self.icq_entry, "text"),
 
2044
                        "timer_start_active" : (self.start_timer.check, "active"),
 
2045
                        "timer_start_time" : (self.start_timer.entry, "text"),
 
2046
                        "timer_stop_active" : (self.stop_timer.check, "active"),
 
2047
                        "timer_stop_time" : (self.stop_timer.entry, "text"),
 
2048
                        "sc_admin_pass" : (self.admin_password_entry, "text"),
 
2049
                        "ic_expander" : (self.ic_expander, "expanded"),
 
2050
                        "conf_expander" : (self.details, "expanded"),
 
2051
                        "action_play_active" : (self.start_player_action, "active"),
 
2052
                        "action_play_which" : (self.start_player_action, "radioindex"),
 
2053
                        "action_record_active" : (self.start_recorder_action, "active"),
 
2054
                        "action_record_which" : (self.start_recorder_action, "radioindex"),
 
2055
                        "irc_data" : (self.ircpane, "marshall"),
 
2056
                        "details_nb" : (self.details_nb, "current_page"),
 
2057
      }
 
2058
                        
 
2059
      self.objects.update(self.troubleshooting.objects)
 
2060
                        
 
2061
      self.reconnection_dialog = ReconnectionDialog(self)
 
2062
 
 
2063
class RecordTab(Tab):
 
2064
   class RecordButtons(CategoryFrame):
 
2065
      def cb_recbuttons(self, widget, userdata):
 
2066
         changed_state = False
 
2067
         if userdata == "rec":
 
2068
            if widget.get_active():
 
2069
               if not self.recording:
 
2070
                  sd = self.parentobject.source_dest
 
2071
                  if sd.streamtab is not None:
 
2072
                     sd.streamtab.start_stop_encoder(ENCODER_START)
 
2073
                     num_id = sd.streamtab.numeric_id
 
2074
                  else:
 
2075
                     num_id = -1
 
2076
                  self.parentobject.send("record_source=%d\nrecord_folder=%s\ncommand=recorder_start\n" % (
 
2077
                                        num_id,
 
2078
                                        sd.file_dialog.get_current_folder()))
 
2079
                  sd.file_dialog.response(gtk.RESPONSE_CLOSE)
 
2080
                  sd.set_sensitive(False)
 
2081
                  self.parentobject.time_indicator.set_sensitive(True)
 
2082
                  self.recording = True
 
2083
                  if self.parentobject.receive() == "failed":
 
2084
                     self.stop_button.clicked()
 
2085
            else:
 
2086
               if self.stop_pressed:
 
2087
                  self.stop_pressed = False
 
2088
                  if self.recording == True:
 
2089
                     self.recording = False
 
2090
                     self.parentobject.send("command=recorder_stop\n")
 
2091
                     self.parentobject.receive()
 
2092
                  if self.parentobject.source_dest.streamtab is not None:
 
2093
                     self.parentobject.source_dest.streamtab.start_stop_encoder(ENCODER_STOP)
 
2094
                  self.parentobject.source_dest.set_sensitive(True)
 
2095
                  self.parentobject.time_indicator.set_sensitive(False)
 
2096
                  if self.pause_button.get_active():
 
2097
                     self.pause_button.set_active(False)
 
2098
               else:
 
2099
                  widget.set_active(True)
 
2100
         elif userdata == "stop":
 
2101
            if self.recording:
 
2102
               self.stop_pressed = True
 
2103
               self.record_button.set_active(False)
 
2104
            else:
 
2105
               self.pause_button.set_active(False)
 
2106
         elif userdata == "pause":
 
2107
            if self.pause_button.get_active():
 
2108
               self.parentobject.send("command=recorder_pause\n")
 
2109
            else:
 
2110
               self.parentobject.send("command=recorder_unpause\n")
 
2111
            self.parentobject.receive()
 
2112
      def path2image(self, pathname):
 
2113
         pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(pathname, 14, 14)
 
2114
         image = gtk.Image()
 
2115
         image.set_from_pixbuf(pixbuf)
 
2116
         image.show()
 
2117
         return image
 
2118
      def __init__(self, parent):
 
2119
         CategoryFrame.__init__(self)
 
2120
         self.parentobject = parent
 
2121
         self.stop_pressed = False
 
2122
         self.recording = False
 
2123
         hbox = gtk.HBox()
 
2124
         hbox.set_border_width(3)
 
2125
         hbox.set_spacing(6)
 
2126
         self.stop_button = gtk.Button()
 
2127
         self.record_button = gtk.ToggleButton()
 
2128
         self.pause_button = gtk.ToggleButton()
 
2129
         for button, gname, signal, tip_text in (
 
2130
               (self.stop_button,   "stop",  "clicked", _('Stop recording.')),
 
2131
               (self.record_button, "rec",   "toggled", _('Start recording.\n\nIf this button is greyed out it could mean the encoder settings are not valid. This can be fixed by using one of the approved sample rates for mp3 or by choosing a sensible samplerate and bitrate combination for Ogg.\n\nAlso check that you have write permission on the folder you have selected to record to.')),
 
2132
               (self.pause_button,  "pause", "toggled", _('Pause recording.'))):
 
2133
            button.set_size_request(30, -1)
 
2134
            button.add(self.path2image(FGlobs.pkgdatadir / (gname + ".png")))
 
2135
            button.connect(signal, self.cb_recbuttons, gname)
 
2136
            hbox.pack_start(button, False, False, 0)
 
2137
            button.show()
 
2138
            set_tip(button, tip_text)
 
2139
         self.add(hbox)
 
2140
         hbox.show()
 
2141
   class TimeIndicator(gtk.Entry):
 
2142
      def set_value(self, seconds):
 
2143
         if self.oldvalue != seconds:
 
2144
            self.oldvalue = seconds
 
2145
            minutes, seconds = divmod(seconds, 60)
 
2146
            hours, minutes = divmod(minutes, 60)
 
2147
            days, hours = divmod(hours, 24)
 
2148
            if days > 10:                       # shut off the recorder after 10 days continuous recording
 
2149
               self.parentobject.record_buttons.stop_button.clicked()
 
2150
            elif days >= 1:
 
2151
               self.set_text("%dd:%02d:%02d" % (days, hours, minutes))
 
2152
            else:
 
2153
               self.set_text("%02d:%02d:%02d" % (hours, minutes, seconds))
 
2154
      def button_press_cancel(self, widget, event):
 
2155
         return True
 
2156
      def __init__(self, parent):
 
2157
         self.parentobject = parent
 
2158
         gtk.Entry.__init__(self)
 
2159
         self.set_width_chars(7)
 
2160
         self.set_sensitive(False)
 
2161
         self.set_editable(False)
 
2162
         self.oldvalue = -1
 
2163
         self.set_value(0)
 
2164
         self.connect("button-press-event", self.button_press_cancel)
 
2165
         set_tip(self, _('Recording time elapsed.'))
 
2166
   class SourceDest(CategoryFrame):
 
2167
      cansave = False
 
2168
      def set_sensitive(self, boolean):
 
2169
         self.source_combo.set_sensitive(boolean)
 
2170
         self.file_chooser_button.set_sensitive(boolean)
 
2171
      def cb_source_combo(self, widget):
 
2172
         if widget.get_active() > 0:
 
2173
            self.streamtab = self.streamtabs[widget.get_active() - 1]
 
2174
         else:
 
2175
            self.streamtab = None
 
2176
         self.parentobject.record_buttons.record_button.set_sensitive(self.streamtab is None or (self.cansave and ((self.streamtab.server_connect.flags() & gtk.SENSITIVE) or self.streamtab.recorder_valid_override)))
 
2177
      def populate_stream_selector(self, text, tabs):
 
2178
         self.streamtabs = tabs
 
2179
         for index in range(len(tabs)):
 
2180
            self.source_combo.append_text(" ".join((text, str(index + 1))))
 
2181
         self.source_combo.connect("changed", self.cb_source_combo)
 
2182
         self.source_combo.set_active(0)
 
2183
      def cb_new_folder(self, filechooser):
 
2184
         self.cansave = os.access(filechooser.get_current_folder(), os.W_OK)
 
2185
         self.source_combo.emit("changed")
 
2186
      def __init__(self, parent):
 
2187
         self.parentobject = parent
 
2188
         CategoryFrame.__init__(self)
 
2189
         hbox = gtk.HBox()
 
2190
         hbox.set_spacing(6)
 
2191
         self.source_combo = gtk.combo_box_new_text()
 
2192
         self.source_combo.append_text(" FLAC+CUE")
 
2193
         hbox.pack_start(self.source_combo, False, False, 0)
 
2194
         self.source_combo.show()
 
2195
         arrow = gtk.Arrow(gtk.ARROW_RIGHT, gtk.SHADOW_IN)
 
2196
         hbox.pack_start(arrow, False, False, 0)
 
2197
         arrow.show()
 
2198
         self.file_dialog = gtk.FileChooserDialog("", None, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
 
2199
         self.file_dialog.set_do_overwrite_confirmation(True)
 
2200
         self.file_chooser_button = gtk.FileChooserButton(self.file_dialog)
 
2201
         # TC: Dialog title bar text.
 
2202
         self.file_dialog.set_title(_('Select the folder to record to') + pm.title_extra)
 
2203
         self.file_dialog.connect("current-folder-changed", self.cb_new_folder)
 
2204
         self.file_dialog.set_current_folder(os.environ["HOME"])
 
2205
         hbox.pack_start(self.file_chooser_button, True, True, 0)
 
2206
         self.file_chooser_button.show()
 
2207
         self.add(hbox)
 
2208
         hbox.show()
 
2209
         set_tip(self.source_combo, _("Choose which stream to record or the 24 bit FLAC option. If the stream isn't already running the encoder will be started automatically using whatever settings are currently configured."))
 
2210
         set_tip(self.file_chooser_button, _('Choose which directory you want to save to. All file names will be in a timestamp format and have either an oga, mp3, or flac file extension. Important: you need to select a directory to which you have adequate write permission.'))
 
2211
   def send(self, string_to_send):
 
2212
      Tab.send(self, "dev_type=recorder\n" + string_to_send)
 
2213
   def receive(self):
 
2214
      return Tab.receive(self)
 
2215
   def show_indicator(self, colour):
 
2216
      Tab.show_indicator(self, colour)
 
2217
      self.scg.parent.recording_panel.indicator[self.numeric_id].set_indicator(colour)
 
2218
      
 
2219
      
 
2220
   def __init__(self, scg, numeric_id, indicator_lookup):
 
2221
      Tab.__init__(self, scg, numeric_id, indicator_lookup)
 
2222
      self.scg = scg
 
2223
      self.show_indicator("clear")
 
2224
      self.tab_type = "recorder"
 
2225
      hbox = gtk.HBox()
 
2226
      hbox.set_spacing(10)
 
2227
      self.pack_start(hbox, False, False, 0)
 
2228
      hbox.show()
 
2229
      self.source_dest = self.SourceDest(self)
 
2230
      hbox.pack_start(self.source_dest, True, True, 0)
 
2231
      self.source_dest.show()
 
2232
      self.time_indicator = self.TimeIndicator(self)
 
2233
      hbox.pack_start(self.time_indicator, False, False, 0)
 
2234
      self.time_indicator.show()
 
2235
      self.record_buttons = self.RecordButtons(self)
 
2236
      hbox.pack_start(self.record_buttons, False, False, 0)
 
2237
      self.record_buttons.show()
 
2238
      self.objects = {  "recording_source": (self.source_dest.source_combo, "active"),
 
2239
                        "recording_directory": (self.source_dest.file_dialog, "directory") }
 
2240
 
 
2241
class TabFrame(ModuleFrame):
 
2242
   def __init__(self, scg, frametext, q_tabs, tabtype, indicatorlist, tab_tip_text):
 
2243
      ModuleFrame.__init__(self, " %s " % frametext)
 
2244
      self.notebook = gtk.Notebook()
 
2245
      self.notebook.set_border_width(8)
 
2246
      self.vbox.add(self.notebook)
 
2247
      self.notebook.show()
 
2248
      self.tabs = []
 
2249
      self.indicator_image_qty = len(indicatorlist)
 
2250
      for index in range(q_tabs):
 
2251
         labelbox = gtk.HBox()
 
2252
         labelbox.set_spacing(3)
 
2253
         numlabel = gtk.Label(str(index + 1))
 
2254
         labelbox.add(numlabel)
 
2255
         numlabel.show()
 
2256
         indicator_lookup = {}
 
2257
         for colour, indicator in indicatorlist:
 
2258
            image = gtk.Image()
 
2259
            pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(
 
2260
                     FGlobs.pkgdatadir / (indicator + ".png"), 16, 16)
 
2261
            image.set_from_pixbuf(pixbuf)
 
2262
            labelbox.add(image)
 
2263
            indicator_lookup[colour] = image
 
2264
         self.tabs.append(tabtype(scg, index, indicator_lookup))
 
2265
         self.notebook.append_page(self.tabs[-1], labelbox)
 
2266
         labelbox.show()
 
2267
         set_tip(labelbox, tab_tip_text)
 
2268
 
 
2269
class StreamTabFrame(TabFrame):
 
2270
   def forall(self, widget, f, *args):
 
2271
      for cb, tab in zip(self.togglelist, self.tabs):
 
2272
         if cb.get_active():
 
2273
            f(tab, *args)
 
2274
 
 
2275
   def cb_metadata_group_set(self, tab):
 
2276
      tab.metadata.set_text(self.metadata_group.get_text())
 
2277
            
 
2278
   def cb_metadata_group_update(self, tab):
 
2279
      self.cb_metadata_group_set(tab)
 
2280
      tab.metadata_update.clicked()
 
2281
            
 
2282
   def cb_connect_toggle(self, tab, val):
 
2283
      if tab.server_connect.flags() & gtk.SENSITIVE:
 
2284
         tab.server_connect.set_active(val)
 
2285
 
 
2286
   def cb_kick_group(self, tab):
 
2287
      tab.kick_incumbent.clicked()
 
2288
            
 
2289
   def cb_group_safety(self, widget):
 
2290
      sens = widget.get_active()
 
2291
      for each in (self.disconnect_group, self.kick_group):
 
2292
         each.set_sensitive(sens)
 
2293
         
 
2294
   def __init__(self, scg, frametext, q_tabs, tabtype, indicatorlist, tab_tip_text):
 
2295
      TabFrame.__init__(self, scg, frametext, q_tabs, tabtype, indicatorlist, tab_tip_text)
 
2296
 
 
2297
      outerframe = gtk.Frame()
 
2298
      set_tip(outerframe, _('Perform operations on multiple servers in unison.'))
 
2299
      outerframe.set_border_width(8)
 
2300
      outerframe.set_shadow_type(gtk.SHADOW_OUT)
 
2301
      gvbox = gtk.VBox()
 
2302
      gvbox.set_border_width(8)
 
2303
      gvbox.set_spacing(8)
 
2304
      outerframe.add(gvbox)
 
2305
      gvbox.show()
 
2306
      hbox = gtk.HBox()
 
2307
      hbox.set_spacing(5)
 
2308
      gvbox.pack_start(hbox, False)
 
2309
      hbox.show()
 
2310
      self.connect_group = gtk.Button(_("Connect"))
 
2311
      self.connect_group.connect("clicked", self.forall, self.cb_connect_toggle, True)
 
2312
      hbox.add(self.connect_group)
 
2313
      self.connect_group.show()
 
2314
      frame = gtk.Frame()
 
2315
      hbox.add(frame)
 
2316
      frame.show()
 
2317
      ihbox = gtk.HBox()
 
2318
      ihbox.set_border_width(3)
 
2319
      ihbox.set_spacing(6)
 
2320
      frame.add(ihbox)
 
2321
      ihbox.show()
 
2322
      self.group_safety = gtk.CheckButton()
 
2323
      self.group_safety.connect("toggled", self.cb_group_safety)
 
2324
      ihbox.pack_start(self.group_safety, False)
 
2325
      self.group_safety.show()
 
2326
      self.disconnect_group = gtk.Button(_("Disconnect"))
 
2327
      self.disconnect_group.connect("clicked", self.forall, self.cb_connect_toggle, False)
 
2328
      self.disconnect_group.connect("clicked", lambda x: self.group_safety.set_active(False))
 
2329
      self.disconnect_group.set_sensitive(False)
 
2330
      ihbox.add(self.disconnect_group)
 
2331
      self.disconnect_group.show()
 
2332
      self.kick_group = gtk.Button(_("Kick Incumbents"))
 
2333
      self.kick_group.connect("clicked", self.forall, self.cb_kick_group)
 
2334
      self.kick_group.connect("clicked", lambda x: self.group_safety.set_active(False))
 
2335
      self.kick_group.set_sensitive(False)
 
2336
      ihbox.add(self.kick_group)
 
2337
      self.kick_group.show()
 
2338
      hbox = gtk.HBox()
 
2339
      hbox.set_spacing(6)
 
2340
      label = gtk.Label("%s " % _('Metadata:'))
 
2341
      hbox.pack_start(label, False)
 
2342
      label.show()
 
2343
      self.metadata_group = HistoryEntryWithMenu()
 
2344
      hbox.pack_start(self.metadata_group)
 
2345
      self.metadata_group.show()
 
2346
      self.metadata_group_set = gtk.Button()
 
2347
      image = gtk.image_new_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU)
 
2348
      self.metadata_group_set.set_image(image)
 
2349
      image.show()
 
2350
      self.metadata_group_set.connect("clicked", self.forall, self.cb_metadata_group_set)
 
2351
      hbox.pack_start(self.metadata_group_set, False)
 
2352
      self.metadata_group_set.show()
 
2353
      self.metadata_group_update = gtk.Button()
 
2354
      image = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
 
2355
      self.metadata_group_update.set_image(image)
 
2356
      image.show()
 
2357
      self.metadata_group_update.connect("clicked", self.forall, self.cb_metadata_group_update)
 
2358
      hbox.pack_start(self.metadata_group_update, False)
 
2359
      self.metadata_group_update.show()
 
2360
      gvbox.pack_start(hbox, False)
 
2361
      hbox.show()
 
2362
      self.vbox.pack_start(outerframe, False)   
 
2363
      outerframe.show()  
 
2364
      self.vbox.reorder_child(outerframe, 0)
 
2365
      self.objects = { "group_metadata": (self.metadata_group, "history") }
 
2366
      self.togglelist = [gtk.CheckButton(str(x + 1)) for x in range(q_tabs)]
 
2367
      hbox = gtk.HBox()
 
2368
      label = gtk.Label(" %s " % _('Group Controls'))
 
2369
      hbox.pack_start(label, False)
 
2370
      label.show()
 
2371
      for i, cb in enumerate(self.togglelist):
 
2372
         hbox.pack_start(cb, False)
 
2373
         cb.show()
 
2374
         self.objects["group_toggle_" + str(i + 1)] = (cb, "active")
 
2375
      spc = gtk.HBox()
 
2376
      hbox.pack_end(spc, False, False, 2)
 
2377
      spc.show()
 
2378
      outerframe.set_label_widget(hbox)
 
2379
      
 
2380
      hbox.show()
 
2381
      
 
2382
 
 
2383
class SourceClientGui:
 
2384
   server_errmsg = "idjc: idjcsourceclient appears to have crashed -- possible segfault"
 
2385
   unexpected_reply = "unexpected reply from idjcsourceclient"
 
2386
 
 
2387
   @threadslock
 
2388
   def monitor(self):
 
2389
      self.led_alternate = not self.led_alternate
 
2390
      streaming = recording = False
 
2391
      # update the recorder LED indicators 
 
2392
      for rectab in self.recordtabframe.tabs:
 
2393
         self.send("dev_type=recorder\ntab_id=%d\ncommand=get_report\n" % rectab.numeric_id)
 
2394
         while 1:
 
2395
            reply = self.receive()
 
2396
            if reply == "succeeded" or reply == "failed":
 
2397
               break
 
2398
            if reply.startswith("recorder%dreport=" % rectab.numeric_id):
 
2399
               recorder_state, recorded_seconds = reply.split("=")[1].split(":")
 
2400
               rectab.show_indicator(("clear", "red", "amber", "clear")[int(recorder_state)])
 
2401
               rectab.time_indicator.set_value(int(recorded_seconds))
 
2402
               if recorder_state != "0":
 
2403
                  recording = True
 
2404
      update_listeners = False
 
2405
      l_count = 0
 
2406
      for streamtab in self.streamtabframe.tabs:
 
2407
         cp = streamtab.connection_pane
 
2408
         cp.timer.run()   # obtain connection stats
 
2409
         if cp.timer.n == 0:
 
2410
            update_listeners = True
 
2411
            l_count += cp.listeners
 
2412
         
 
2413
         self.send("dev_type=streamer\ntab_id=%d\ncommand=get_report\n" % streamtab.numeric_id)
 
2414
         reply = self.receive()
 
2415
         if reply != "failed":
 
2416
            self.receive()
 
2417
            if reply.startswith("streamer%dreport=" % streamtab.numeric_id):
 
2418
               streamer_state, stream_sendbuffer_pc, brand_new = reply.split("=")[1].split(":")
 
2419
               state = int(streamer_state)
 
2420
               streamtab.show_indicator(("clear", "amber", "green", "clear")[state])
 
2421
               streamtab.ircpane.connections_controller.set_stream_active(state > 1)
 
2422
               mi = self.parent.stream_indicator[streamtab.numeric_id]
 
2423
               if (streamer_state == "2"):
 
2424
                  mi.set_active(True)
 
2425
                  mi.set_value(int(stream_sendbuffer_pc))
 
2426
                  if int(stream_sendbuffer_pc) >= 100 and self.led_alternate:
 
2427
                     if streamtab.troubleshooting.sbf_discard_audio.get_active():
 
2428
                        streamtab.show_indicator("amber")
 
2429
                        mi.set_flash(True)
 
2430
                     else:
 
2431
                        streamtab.server_connect.set_active(False)
 
2432
                        streamtab.server_connect.set_active(True)
 
2433
                        print "remade the connection because stream buffer was full"
 
2434
                  else:
 
2435
                     mi.set_flash(False)
 
2436
               else:
 
2437
                  mi.set_active(False)
 
2438
                  mi.set_flash(False)
 
2439
               if brand_new == "1":
 
2440
                  # connection has just been made, do user requested actions at this time
 
2441
                  streamtab.start_recorder_action.activate()
 
2442
                  streamtab.start_player_action.activate()
 
2443
                  streamtab.reconnection_dialog.deactivate()
 
2444
               if streamer_state != "0":
 
2445
                  streaming = True
 
2446
               elif streamtab.server_connect.get_active():
 
2447
                  streamtab.server_connect.set_active(False)
 
2448
                  streamtab.reconnection_dialog.activate()
 
2449
            else:
 
2450
               print "sourceclientgui.monitor: bad reply for streamer data:", reply
 
2451
         else:
 
2452
            print "sourceclientgui.monitor: failed to get a report from the streamer"
 
2453
         # the connection start/stop timers are processed here
 
2454
         if streamtab.start_timer.get_active():
 
2455
            diff = time.localtime(time.time() - streamtab.start_timer.get_seconds_past_midnight())
 
2456
            # check hours, minutes, seconds for midnightness
 
2457
            if not (diff[3] or diff[4] or diff[5]):
 
2458
               streamtab.start_timer.check.set_active(False)
 
2459
               if streamtab.kick_before_start.get_active():
 
2460
                  streamtab.cb_kick_incumbent(None, streamtab.deferred_connect)
 
2461
               else:
 
2462
                  streamtab.server_connect.set_active(True)
 
2463
         if streamtab.stop_timer.get_active():
 
2464
            diff = time.localtime(int(time.time()) - streamtab.stop_timer.get_seconds_past_midnight())
 
2465
            if not (diff[3] or diff[4] or diff[5]):
 
2466
               streamtab.server_connect.set_active(False)
 
2467
               streamtab.stop_timer.check.set_active(False)
 
2468
               self.autoshutdown_dialog.present()
 
2469
         self.is_streaming = streaming
 
2470
         self.is_recording = recording
 
2471
         streamtab.reconnection_dialog.run()
 
2472
      if update_listeners:
 
2473
         self.parent.listener_indicator.set_text(str(l_count))
 
2474
      return True
 
2475
   def stop_streaming_all(self):
 
2476
      for streamtab in self.streamtabframe.tabs:
 
2477
         streamtab.server_connect.set_active(False)
 
2478
   def stop_irc_all(self):
 
2479
      for streamtab in self.streamtabframe.tabs:
 
2480
         streamtab.ircpane.connections_controller.cleanup()
 
2481
   def stop_recording_all(self):
 
2482
      for rectab in self.recordtabframe.tabs:
 
2483
         rectab.record_buttons.stop_button.clicked()
 
2484
   def stop_test_monitor_all(self):
 
2485
      for streamtab in self.streamtabframe.tabs:
 
2486
         streamtab.test_monitor.set_active(False)
 
2487
   def cleanup(self):
 
2488
      self.stop_recording_all()
 
2489
      self.stop_streaming_all()
 
2490
      self.stop_irc_all()
 
2491
      self.stop_test_monitor_all()
 
2492
      gobject.source_remove(self.monitor_source_id)
 
2493
   def app_exit(self):
 
2494
      if self.parent.session_loaded:
 
2495
         self.parent.destroy()
 
2496
      else:
 
2497
         self.parent.destroy_hard()
 
2498
   
 
2499
   def receive(self):
 
2500
      if not self.comms_reply_pending:
 
2501
         print "sourceclientgui.receive: nothing to receive"
 
2502
         return "failed"
 
2503
      while 1:
 
2504
         try:
 
2505
            reply = self.comms_rply.readline()
 
2506
         except:
 
2507
            return "failed"
 
2508
         if reply.startswith("idjcsc: "):
 
2509
            reply = reply[8:-1]
 
2510
            if reply == "succeeded" or reply == "failed":
 
2511
               self.comms_reply_pending = False
 
2512
            return reply
 
2513
         else:
 
2514
            print self.unexpected_reply, reply
 
2515
         if reply == "" or reply == "Segmentation Fault\n":
 
2516
            self.comms_reply_pending = False
 
2517
            return "failed"
 
2518
   def send(self, string_to_send):
 
2519
      while self.comms_reply_pending:   # dump unused replies from previous send
 
2520
         self.receive()
 
2521
      if not "tab_id=" in string_to_send:
 
2522
         string_to_send = "tab_id=-1\n" + string_to_send
 
2523
      try:
 
2524
         self.comms_cmd.write(string_to_send + "end\n")
 
2525
         self.comms_cmd.flush()
 
2526
         self.comms_reply_pending = True
 
2527
      except (ValueError, IOError):
 
2528
         print "sourceclientgui.send: send failed - idjcsourceclient crashed"
 
2529
         self.source_client_crash_count += 1
 
2530
         self.source_client_close()
 
2531
         print self.server_errmsg
 
2532
         time.sleep(0.5)
 
2533
         if self.source_client_crash_count == 3:
 
2534
            print "idjcsourceclient is crashing repeatedly - exiting\n"
 
2535
            self.app_exit()
 
2536
         self.source_client_open()
 
2537
         self.comms_reply_pending = False
 
2538
      else:
 
2539
         if self.source_client_crash_count:
 
2540
            if time.time() > self.uptime + 15.0:
 
2541
               self.source_client_crash_count -= 1
 
2542
               self.uptime = time.time()
 
2543
         
 
2544
   def new_metadata(self, artist, title, album, songname):
 
2545
      if artist:
 
2546
         artist_title = artist + " - " + title
 
2547
      else:
 
2548
         artist_title = title
 
2549
      if not self.parent.prefs_window.mp3_utf8.get_active():
 
2550
         artist_title_lat1 = artist_title.decode("utf-8", "replace").encode("iso8859-1", "replace")
 
2551
      else:
 
2552
         artist_title_lat1 = artist_title
 
2553
 
 
2554
      self.send("artist=%s\ntitle=%s\nalbum=%s\nartist_title_lat1=%s\ncommand=new_song_metadata\n" % (artist.strip(), title.strip(), album.strip(), artist_title_lat1.strip()))
 
2555
      if self.receive() == "succeeded":
 
2556
         print "updated song metadata successfully"
 
2557
 
 
2558
      common = {"artist": artist, "title": title, "album": album, "songname": songname}
 
2559
      for tab in self.streamtabframe.tabs:         # Update the custom metadata on all stream tabs.
 
2560
         tab.metadata_update.clicked()
 
2561
         ircmetadata = {"djname": tab.dj_name_entry.get_text(),
 
2562
                        "description": tab.description_entry.get_text(),
 
2563
                        "url": tab.listen_url_entry.get_text()
 
2564
         }
 
2565
         ircmetadata.update(common)
 
2566
 
 
2567
         tab.ircpane.connections_controller.new_metadata(ircmetadata)
 
2568
      
 
2569
   def source_client_open(self):
 
2570
      try:
 
2571
         sp_sc = subprocess.Popen([FGlobs.libexecdir / "idjcsourceclient"],
 
2572
            bufsize = 4096, stdin = subprocess.PIPE, stdout = subprocess.PIPE, close_fds = True)
 
2573
      except Exception, inst:
 
2574
         print inst
 
2575
         print "unable to open a pipe to the sourceclient module"
 
2576
         self.app_exit()
 
2577
      (self.comms_cmd, self.comms_rply) = (sp_sc.stdin, sp_sc.stdout)
 
2578
      self.comms_reply_pending = True
 
2579
      reply = self.receive()
 
2580
      if reply != "succeeded":
 
2581
         print self.server_errmsg
 
2582
         self.app_exit()
 
2583
      self.send("encoders=%d\nstreamers=%d\nrecorders=%d\ncommand=threads_init\n" % (
 
2584
                     PGlobs.num_encoders, PGlobs.num_streamers, PGlobs.num_recorders))
 
2585
      if self.receive() != "succeeded":
 
2586
         print self.unexpected_reply
 
2587
         print "failed to initialise threads\n"
 
2588
         self.app_exit()
 
2589
      self.send("command=jack_samplerate_request\n")
 
2590
      reply = self.receive()
 
2591
      if reply != "failed" and self.receive() == "succeeded":
 
2592
         sample_rate_string = reply
 
2593
      else:
 
2594
         print self.unexpected_reply
 
2595
         print "failed to obtain the sample rate"
 
2596
         self.app_exit()
 
2597
      if not sample_rate_string.startswith("sample_rate="):
 
2598
         print self.unexpected_reply
 
2599
         print "sample rate reply contains the following:", sample_rate_string
 
2600
         self.app_exit()
 
2601
      self.send("command=encoder_lame_availability\n")
 
2602
      reply = self.receive()
 
2603
      if reply != "failed" and self.receive() == "succeeded" and reply.startswith("lame_available="):
 
2604
         global lameenabled
 
2605
         if reply[15] == "1":
 
2606
            lameenabled = 1
 
2607
         else:
 
2608
            lameenabled = 0
 
2609
      else:
 
2610
         print self.unexpected_reply
 
2611
         self.app_exit()
 
2612
      print "threads initialised"
 
2613
      self.jack_sample_rate = int(sample_rate_string[12:])
 
2614
      print "jack sample rate is", self.jack_sample_rate
 
2615
      try:
 
2616
         for streamtab in self.streamtabframe.tabs:
 
2617
            streamtab.stream_resample_frame.jack_sample_rate = self.jack_sample_rate
 
2618
            streamtab.stream_resample_frame.resample_dummy_object.clicked()
 
2619
            # update the stream tabs with the current jack sample rate
 
2620
      except (NameError, AttributeError):
 
2621
         # If this is the initial call the stream tabs will not exist yet.
 
2622
         pass
 
2623
      self.uptime = time.time()
 
2624
 
 
2625
   def source_client_close(self):
 
2626
      try:
 
2627
         self.comms_cmd
 
2628
      except:
 
2629
         pass
 
2630
      else:
 
2631
         self.comms_cmd.close()
 
2632
 
 
2633
   def cb_delete_event(self, widget, event, data = None):
 
2634
      self.window.hide()
 
2635
      return True
 
2636
 
 
2637
   def save_session_settings(self):
 
2638
      try:                              # check the following are initilised before proceeding
 
2639
         tabframes = (self, self.streamtabframe, self.recordtabframe)
 
2640
      except AttributeError:
 
2641
         return                         # cancelled save
 
2642
 
 
2643
      try:
 
2644
         with open(pm.basedir / "s_data", "w") as f:
 
2645
            for tabframe in tabframes:
 
2646
               for tab in tabframe.tabs:
 
2647
                  f.write("".join(("[", tab.tab_type, " ", str(tab.numeric_id), "]\n")))
 
2648
                  for lvalue, (widget, method) in tab.objects.iteritems():
 
2649
                     if type(method) == tuple:
 
2650
                        rvalue = widget.__getattribute__(method[1])()
 
2651
                     elif method == "active":
 
2652
                        rvalue = str(int(widget.get_active()))
 
2653
                     elif method == "text":
 
2654
                        rvalue = widget.get_text()
 
2655
                     elif method == "value":
 
2656
                        rvalue = str(widget.get_value())
 
2657
                     elif method == "expanded":
 
2658
                        rvalue = str(int(widget.get_expanded()))
 
2659
                     elif method == "notebookpage":
 
2660
                        rvalue = str(widget.get_current_page())
 
2661
                     elif method == "password":
 
2662
                        rvalue = widget.get_text()
 
2663
                     elif method == "history":
 
2664
                        rvalue = widget.get_history()
 
2665
                     elif method == "radioindex":
 
2666
                        rvalue = str(widget.get_radio_index())
 
2667
                     elif method == "current_page":
 
2668
                        rvalue = str(widget.get_current_page())
 
2669
                     elif method == "directory":
 
2670
                        rvalue = widget.get_filename() or ""
 
2671
                     elif method == "filename":
 
2672
                        rvalue = widget.get_filename() or ""
 
2673
                     elif method == "marshall":
 
2674
                        rvalue = widget.marshall()
 
2675
                     else:
 
2676
                        print "unsupported", lvalue, widget, method
 
2677
                        continue
 
2678
                     if method != "password" or self.parent.prefs_window.keeppass.get_active():
 
2679
                        f.write("".join((lvalue, "=", rvalue, "\n")))
 
2680
                  f.write("\n")
 
2681
      except:
 
2682
         print "error attempting to write file: serverdata"
 
2683
 
 
2684
   def load_previous_session(self):
 
2685
      try:
 
2686
         with open(pm.basedir / "s_data") as f:
 
2687
            tabframe = None
 
2688
            while 1:
 
2689
               line = f.readline()
 
2690
               if line == "":
 
2691
                  break
 
2692
               else:
 
2693
                  line = line[:-1]         # strip off the newline character
 
2694
                  if line == "":
 
2695
                     continue
 
2696
               if line.startswith("[") and line.endswith("]"):
 
2697
                  try:
 
2698
                     name, numeric_id = line[1:-1].split(" ")
 
2699
                  except:
 
2700
                     print "malformed line:", line, "in serverdata file"
 
2701
                     tabframe = None
 
2702
                  else:
 
2703
                     if name == "server_window":
 
2704
                        tabframe = self
 
2705
                     elif name == "streamer":
 
2706
                        tabframe = self.streamtabframe
 
2707
                     elif name == "recorder":
 
2708
                        tabframe = self.recordtabframe
 
2709
                     else:
 
2710
                        print "unsupported element:", line, "in serverdata file"
 
2711
                        tabframe = None
 
2712
                     if tabframe is not None:
 
2713
                        try:
 
2714
                           tab = tabframe.tabs[int(numeric_id)]
 
2715
                        except:
 
2716
                           print "unsupported tab number:", line, "in serverdata file"
 
2717
                           tabframe = None
 
2718
               else:
 
2719
                  if tabframe is not None:
 
2720
                     try:
 
2721
                        lvalue, rvalue = line.split("=", 1)
 
2722
                     except:
 
2723
                        print "not a valid key, value pair:", line, "in serverdata file"
 
2724
                     else:
 
2725
                        if not lvalue:
 
2726
                           print "key value is missing:", line, "in serverdata file"        
 
2727
                        else:
 
2728
                           try:
 
2729
                              (widget, method) = tab.objects[lvalue]
 
2730
                           except KeyError:
 
2731
                              print "key value not recognised:", line, "in serverdata file"
 
2732
                           else:
 
2733
                              try:
 
2734
                                 int_rvalue = int(rvalue)
 
2735
                              except:
 
2736
                                 int_rvalue = None
 
2737
                              try:
 
2738
                                 float_rvalue = float(rvalue)
 
2739
                              except:
 
2740
                                 float_rvalue = None
 
2741
                              if type(method) == tuple:
 
2742
                                 widget.__getattribute__(method[0])(rvalue)
 
2743
                              elif method == "active":
 
2744
                                 if int_rvalue is not None:
 
2745
                                    widget.set_active(int_rvalue)
 
2746
                              elif method == "expanded":
 
2747
                                 if int_rvalue is not None:
 
2748
                                    widget.set_expanded(int_rvalue)
 
2749
                              elif method == "value":
 
2750
                                 if float_rvalue is not None:
 
2751
                                    widget.set_value(float_rvalue)
 
2752
                              elif method == "notebookpage":
 
2753
                                 if int_rvalue is not None:
 
2754
                                    widget.set_current_page(int_rvalue)
 
2755
                              elif method == "radioindex":
 
2756
                                 if int_rvalue is not None:
 
2757
                                    widget.set_radio_index(int_rvalue)
 
2758
                              elif method == "current_page":
 
2759
                                 widget.set_current_page(int_rvalue)
 
2760
                              elif method == "text":
 
2761
                                 widget.set_text(rvalue)
 
2762
                              elif method == "password":
 
2763
                                 widget.set_text(rvalue)
 
2764
                              elif method == "history":
 
2765
                                 widget.set_history(rvalue)
 
2766
                              elif method == "directory":
 
2767
                                 if rvalue:
 
2768
                                    widget.set_current_folder(rvalue)
 
2769
                              elif method == "filename":
 
2770
                                 if rvalue:
 
2771
                                    rvalue = widget.set_filename(rvalue)
 
2772
                              elif method == "marshall":
 
2773
                                 widget.unmarshall(rvalue)
 
2774
                              else:
 
2775
                                 print "method", method, "is unsupported at this time hence widget pertaining to", lvalue, "will not be set"
 
2776
      except Exception as e:
 
2777
         if isinstance(e, IOError):
 
2778
            print e
 
2779
         else:
 
2780
            traceback.print_exc()
 
2781
 
 
2782
 
 
2783
   def cb_after_realize(self, widget):
 
2784
      self.wst.apply()
 
2785
      #widget.resize(int(self.win_x), 1)
 
2786
      self.streamtabframe.connect_group.grab_focus()
 
2787
      
 
2788
   def cb_stream_details_expand(self, expander, param_spec, next_expander, sw):
 
2789
      if expander.get_expanded():
 
2790
         sw.show()
 
2791
      else:
 
2792
         sw.hide()
 
2793
      
 
2794
      if expander.get_expanded() == next_expander.get_expanded():
 
2795
         if not expander.get_expanded():
 
2796
            self.window.resize((self.wst.get_x()), 1)
 
2797
         else:
 
2798
            pass
 
2799
      else:
 
2800
         next_expander.set_expanded(expander.get_expanded())
 
2801
 
 
2802
   def cb_stream_controls_expand(self, expander, param_spec, next_expander, frame, details_shown):
 
2803
      if expander.get_expanded():
 
2804
         frame.show()
 
2805
      else:
 
2806
         frame.hide()
 
2807
      
 
2808
      if expander.get_expanded() == next_expander.get_expanded():
 
2809
         self.window.resize((self.wst.get_x()), 1)
 
2810
      else:
 
2811
         next_expander.set_expanded(expander.get_expanded())
 
2812
      
 
2813
   def update_metadata(self, text=None, filter=None):
 
2814
      for tab in self.streamtabframe.tabs:
 
2815
         if filter is None or str(tab.numeric_id) in filter:
 
2816
            if text is not None:
 
2817
               tab.metadata.set_text(text)
 
2818
            tab.metadata_update.clicked()
 
2819
 
 
2820
   def __init__(self, parent):
 
2821
      self.parent = parent
 
2822
      parent.server_window = self
 
2823
      self.source_client_crash_count = 0
 
2824
      self.source_client_open()
 
2825
      self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
 
2826
      self.parent.window_group.add_window(self.window)
 
2827
      # TC: Window title bar text.
 
2828
      self.window.set_title(_('IDJC Output') + pm.title_extra)
 
2829
      self.window.set_destroy_with_parent(True)
 
2830
      self.window.set_border_width(11)
 
2831
      self.window.set_resizable(True)
 
2832
      self.window.connect_after("realize", self.cb_after_realize)
 
2833
      self.window.connect("delete_event", self.cb_delete_event)
 
2834
      self.wst = WindowSizeTracker(self.window)
 
2835
      vbox = gtk.VBox()
 
2836
      vbox.set_spacing(10)
 
2837
      self.window.add(vbox)
 
2838
      
 
2839
      self.recordtabframe = TabFrame(self, _('Record'), PGlobs.num_recorders, RecordTab, (
 
2840
                                                                ("clear", "led_unlit_clear_border_64x64"),
 
2841
                                                                ("amber", "led_lit_amber_black_border_64x64"),
 
2842
                                                                ("red", "led_lit_red_black_border_64x64")),
 
2843
                                                                _('Each one of these tabs represents a separate stream recorder. The LED indicator colours represent the following: Clear=Stopped Yellow=Paused Red=Recording.'))
 
2844
      self.streamtabframe = StreamTabFrame(self, _('Stream'), PGlobs.num_streamers, StreamTab, (
 
2845
                                                                ("clear", "led_unlit_clear_border_64x64"),
 
2846
                                                                ("amber", "led_lit_amber_black_border_64x64"),
 
2847
                                                                ("green", "led_lit_green_black_border_64x64")),
 
2848
                                                                _('Each one of these tabs represents a separate radio streamer. The LED indicator colours represent the following: Clear=No connection Yellow=Awaiting authentication. Green=Connected. Flashing=Packet loss due to a bad connection.'))
 
2849
         
 
2850
      tab = self.streamtabframe.tabs[-1]
 
2851
      for next_tab in self.streamtabframe.tabs:
 
2852
         tab.details.connect("notify::expanded", self.cb_stream_details_expand, next_tab.details, tab.details_nb)
 
2853
         tab.ic_expander.connect("notify::expanded", self.cb_stream_controls_expand, next_tab.ic_expander, tab.ic_frame, self.streamtabframe.tabs[0].details.get_expanded)
 
2854
         tab = next_tab
 
2855
                                                                
 
2856
      self.streamtabframe.set_sensitive(True)
 
2857
      vbox.pack_start(self.streamtabframe, True, True, 0)
 
2858
      self.streamtabframe.show()
 
2859
      for rectab in self.recordtabframe.tabs:
 
2860
         rectab.source_dest.populate_stream_selector(_(' Stream '), self.streamtabframe.tabs)
 
2861
      vbox.pack_start(self.recordtabframe, False, False, 0)
 
2862
      if PGlobs.num_recorders:
 
2863
         self.recordtabframe.show()
 
2864
      vbox.show()
 
2865
      self.tabs = (self, )                      #
 
2866
      self.numeric_id = 0                       # pretend to be a tabframe and its tab for save/load purposes
 
2867
      self.tab_type = "server_window"           #
 
2868
      self.objects = {  "wst"   : (self.wst, "text"),
 
2869
                        "streamer_page": (self.streamtabframe.notebook, "notebookpage"),
 
2870
                        "recorder_page": (self.recordtabframe.notebook, "notebookpage"),
 
2871
                        "controls_shown": (self.streamtabframe.tabs[0].ic_expander, "expanded") }
 
2872
      self.objects.update(self.streamtabframe.objects)
 
2873
      self.load_previous_session()
 
2874
      self.is_streaming = False
 
2875
      self.is_recording = False
 
2876
      self.led_alternate = False
 
2877
      self.last_message_time = 0
 
2878
      self.connection_string = None
 
2879
      self.is_shoutcast = False
 
2880
 
 
2881
      self.dialog_group = dialog_group()
 
2882
      self.disconnected_dialog = disconnection_notification_dialog(self.dialog_group, self.parent.window_group, "", _('<span weight="bold" size="12000">A connection to a radio server has failed.</span>\n\nReconnection will not be attempted.'))
 
2883
 
 
2884
      self.autoshutdown_dialog = disconnection_notification_dialog(self.dialog_group, self.parent.window_group, "", _('<span weight="bold" size="12000">A scheduled stream disconnection has occurred.</span>'))
 
2885
      
 
2886
      self.monitor_source_id = gobject.timeout_add(250, self.monitor)
 
2887
      self.window.realize()   # prevent rendering bug and problems with sizegroups on certain widgets