Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/record.py
diff options
context:
space:
mode:
Diffstat (limited to 'record.py')
-rw-r--r--record.py1249
1 files changed, 818 insertions, 431 deletions
diff --git a/record.py b/record.py
index 8b69e41..500b875 100644
--- a/record.py
+++ b/record.py
@@ -18,43 +18,37 @@
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
-import gtk
-import gobject
import os
-import shutil
-import telepathy
-import telepathy.client
import logging
-import xml.dom.minidom
-import time
-from xml.dom.minidom import parse
+import shutil
+from gettext import gettext as _
+from gettext import ngettext
+
+import gtk
+from gtk import gdk
+import cairo
+import pangocairo
import pygst
pygst.require('0.10')
import gst
-import logging
-logger = logging.getLogger('record:record.py')
-
+import sugar.profile
from sugar.activity import activity
-from sugar.presence import presenceservice
-from sugar.presence.tubeconn import TubeConnection
-from sugar import util
-import port.json
+from sugar.graphics.icon import Icon
from model import Model
-from ui import UI
-from recordtube import RecordTube
-from glive import Glive
-from glivex import GliveX
-from gplay import Gplay
-from greplay import Greplay
-from recorded import Recorded
-from constants import Constants
-import instance
+from button import RecdButton
+import constants
from instance import Instance
-import serialize
import utils
+from tray import HTray
+from toolbarcombobox import ToolComboBox
+from mediaview import MediaView
+import hw
+logger = logging.getLogger('record.py')
+COLOR_BLACK = gdk.color_parse('#000000')
+COLOR_WHITE = gdk.color_parse('#ffffff')
gst.debug_set_active(True)
gst.debug_set_colored(False)
@@ -63,493 +57,886 @@ if logging.getLogger().level <= logging.DEBUG:
else:
gst.debug_set_default_threshold(gst.LEVEL_ERROR)
-
class Record(activity.Activity):
-
- log = logging.getLogger('record-activity')
-
def __init__(self, handle):
- activity.Activity.__init__(self, handle)
- #flags for controlling the writing to the datastore
- self.I_AM_CLOSING = False
- self.I_AM_SAVED = False
-
+ super(Record, self).__init__(handle)
self.props.enable_fullscreen_mode = False
Instance(self)
- Constants(self)
- self.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
-
- #wait a moment so that our debug console capture mistakes
- gobject.idle_add( self._initme, None )
-
-
- def _initme( self, userdata=None ):
- #totally tubular
- self.meshTimeoutTime = 10000
- self.recTube = None
- self.connect( "shared", self._sharedCb )
#the main classes
- self.m = Model(self)
- self.glive = Glive(self)
- self.glivex = GliveX(self)
- self.gplay = Gplay(self)
- self.ui = UI(self)
+ self.model = Model(self)
+ self.ui_init()
#CSCL
- if self._shared_activity:
+ self.connect("shared", self._shared_cb)
+ if self.get_shared_activity():
#have you joined or shared this activity yourself?
if self.get_shared():
- self._meshJoinedCb( self )
+ self._joined_cb(self)
else:
- self.connect("joined", self._meshJoinedCb)
+ self.connect("joined", self._joined_cb)
- return False
+ # Realize the video view widget so that it knows its own window XID
+ self._media_view.realize_video()
+ # Changing to the first toolbar kicks off the rest of the setup
+ self._toolbox.set_current_toolbar(1)
- def read_file(self, file):
- try:
- dom = parse(file)
- except Exception, e:
- logger.error('read_file: %s' % e)
- return
+ def read_file(self, path):
+ self.model.read_file(path)
- serialize.fillMediaHash(dom, self.m.mediaHashs)
+ def write_file(self, path):
+ self.model.write_file(path)
- for i in dom.documentElement.getElementsByTagName('ui'):
- for ui_el in i.childNodes:
- self.ui.deserialize(port.json.loads(ui_el.data))
+ def close(self):
+ self.model.gplay.stop()
+ self.model.glive.stop()
+ super(Record, self).close()
+ def _shared_cb(self, activity):
+ self.model.collab.set_activity_shared()
- def write_file(self, file):
- self.I_AM_SAVED = False
+ def _joined_cb(self, activity):
+ self.model.collab.joined()
- self.m.mediaHashs['ui'] = self.ui.serialize()
+ def ui_init(self):
+ self._fullscreen = False
+ self._showing_info = False
- dom = serialize.saveMediaHash(self.m.mediaHashs)
+ # FIXME: if _thumb_tray becomes some kind of button group, we wouldn't
+ # have to track which recd is active
+ self._active_recd = None
- ui_data = port.json.dumps(self.ui.serialize())
- ui_el = dom.createElement('ui')
- ui_el.appendChild(dom.createTextNode(ui_data))
- dom.documentElement.appendChild(ui_el)
+ self.connect('key-press-event', self._key_pressed)
- xmlFile = open( file, "w" )
- dom.writexml(xmlFile)
- xmlFile.close()
+ self._active_toolbar_idx = 0
+ self._toolbox = activity.ActivityToolbox(self)
+ self.set_toolbox(self._toolbox)
+ self._toolbar_modes = [None]
- allDone = True
- for h in range (0, len(self.m.mediaHashs)-1):
- mhash = self.m.mediaHashs[h]
- for i in range (0, len(mhash)):
- recd = mhash[i]
+ # remove Toolbox's hardcoded grey separator
+ self._toolbox.remove(self._toolbox._separator)
- if ( (not recd.savedMedia) or (not recd.savedXml) ):
- allDone = False
+ if self.model.get_has_camera():
+ self._photo_toolbar = PhotoToolbar()
+ self._toolbox.add_toolbar(_('Photo'), self._photo_toolbar)
+ self._toolbar_modes.append(constants.MODE_PHOTO)
- if (self.I_AM_CLOSING):
- mediaObject = recd.datastoreOb
- if (mediaObject != None):
- recd.datastoreOb = None
- mediaObject.destroy()
- del mediaObject
+ self._video_toolbar = VideoToolbar()
+ self._toolbox.add_toolbar(_('Video'), self._video_toolbar)
+ self._toolbar_modes.append(constants.MODE_VIDEO)
+ else:
+ self._photo_toolbar = None
+ self._video_toolbar = None
- self.I_AM_SAVED = True
- if (self.I_AM_SAVED and self.I_AM_CLOSING):
- self.destroy()
+ self._audio_toolbar = AudioToolbar()
+ self._toolbox.add_toolbar(_('Audio'), self._audio_toolbar)
+ self._toolbar_modes.append(constants.MODE_AUDIO)
+ self._toolbox.show_all()
+ self._toolbox.connect("current-toolbar-changed", self._toolbar_changed)
- def stopPipes(self):
- self.ui.doMouseListener( False )
- self.m.setUpdating( False )
+ main_box = gtk.VBox()
+ self.set_canvas(main_box)
+ main_box.get_parent().modify_bg(gtk.STATE_NORMAL, COLOR_BLACK)
+ main_box.show()
- if (self.ui.COUNTINGDOWN):
- self.m.abandonRecording()
- elif (self.m.RECORDING):
- self.m.doShutter()
- else:
- self.glive.stop()
- self.glivex.stop()
+ self._media_view = MediaView()
+ self._media_view.connect('media-clicked', self._media_view_media_clicked)
+ self._media_view.connect('pip-clicked', self._media_view_pip_clicked)
+ self._media_view.connect('info-clicked', self._media_view_info_clicked)
+ self._media_view.connect('full-clicked', self._media_view_full_clicked)
+ self._media_view.connect('tags-changed', self._media_view_tags_changed)
+ self._media_view.show()
+ self._controls_hbox = gtk.HBox()
+ self._controls_hbox.show()
- def restartPipes(self):
- if (not self.ui.TRANSCODING):
- self.ui.updateModeChange( )
- self.ui.doMouseListener( True )
+ self._shutter_button = ShutterButton()
+ self._shutter_button.connect("clicked", self._shutter_clicked)
+ self._controls_hbox.pack_start(self._shutter_button, expand=True, fill=False)
+ self._countdown_image = CountdownImage()
+ self._controls_hbox.pack_start(self._countdown_image, expand=True, fill=False)
- def close( self ):
- self.I_AM_CLOSING = True
+ self._play_button = PlayButton()
+ self._play_button.connect('clicked', self._play_pause_clicked)
+ self._controls_hbox.pack_start(self._play_button, expand=False)
- self.m.UPDATING = False
- if (self.ui != None):
- self.ui.updateButtonSensitivities( )
- self.ui.doMouseListener( False )
- self.ui.hideAllWindows()
- if (self.gplay != None):
- self.gplay.stop( )
- if (self.glive != None):
- self.glive.stop( )
- if self.glivex != None:
- self.glivex.stop()
+ self._playback_scale = PlaybackScale(self.model)
+ self._controls_hbox.pack_start(self._playback_scale, expand=True, fill=True)
- #this calls write_file
- activity.Activity.close( self )
+ self._progress = ProgressInfo()
+ self._controls_hbox.pack_start(self._progress, expand=True, fill=True)
+ self._title_label = gtk.Label()
+ self._title_label.set_markup("<b><span foreground='white'>"+_('Title:')+'</span></b>')
+ self._controls_hbox.pack_start(self._title_label, expand=False)
- def destroy( self ):
- if self.I_AM_SAVED:
- activity.Activity.destroy( self )
+ self._title_entry = gtk.Entry()
+ self._title_entry.modify_bg(gtk.STATE_INSENSITIVE, COLOR_BLACK)
+ self._title_entry.connect('changed', self._title_changed)
+ self._controls_hbox.pack_start(self._title_entry, expand=True, fill=True, padding=10)
+ container = RecordContainer(self._media_view, self._controls_hbox)
+ main_box.pack_start(container, expand=True, fill=True, padding=6)
+ container.show()
- def _sharedCb( self, activity ):
- self._setup()
+ self._thumb_tray = HTray()
+ self._thumb_tray.set_size_request(-1, 150)
+ main_box.pack_end(self._thumb_tray, expand=False)
+ self._thumb_tray.show_all()
- id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube( Constants.SERVICE, {})
+ def serialize(self):
+ data = {}
+ if self._photo_toolbar:
+ data['photo_timer'] = self._photo_toolbar.get_timer_idx()
- def _meshJoinedCb( self, activity ):
- if not self._shared_activity:
- return
+ if self._video_toolbar:
+ data['video_timer'] = self._video_toolbar.get_timer_idx()
+ data['video_duration'] = self._video_toolbar.get_duration_idx()
+ data['video_quality'] = self._video_toolbar.get_quality()
- self._setup()
+ data['audio_timer'] = self._audio_toolbar.get_timer_idx()
+ data['audio_duration'] = self._audio_toolbar.get_duration_idx()
- self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes( reply_handler=self._list_tubes_reply_cb, error_handler=self._list_tubes_error_cb)
+ return data
+ def deserialize(self, data):
+ if self._photo_toolbar:
+ self._photo_toolbar.set_timer_idx(data.get('photo_timer', 0))
- def _list_tubes_reply_cb(self, tubes):
- for tube_info in tubes:
- self._newTubeCb(*tube_info)
+ if self._video_toolbar:
+ self._video_toolbar.set_timer_idx(data.get('video_timer', 0))
+ self._video_toolbar.set_duration_idx(data.get('video_duration', 0))
+ self._video_toolbar.set_quality(data.get('video_quality', 0))
+ self._audio_toolbar.set_timer_idx(data.get('audio_timer', 0))
+ self._audio_toolbar.set_duration_idx(data.get('audio_duration'))
- def _list_tubes_error_cb(self, e):
- self.__class__.log.error('ListTubes() failed: %s', e)
+ def _key_pressed(self, widget, event):
+ if self.model.ui_frozen():
+ return False
+ key = event.keyval
+
+ if key == gtk.keysyms.KP_Page_Up: # game key O
+ if self._shutter_button.props.visible:
+ if self._shutter_button.props.sensitive:
+ self._shutter_button.clicked()
+ else: # return to live mode
+ self.model.set_state(constants.STATE_READY)
+ elif key == gtk.keysyms.c and event.state == gdk.CONTROL_MASK:
+ self._copy_to_clipboard(self._active_recd)
+ elif key == gtk.keysyms.i:
+ self._toggle_info()
+ elif key == gtk.keysyms.Escape:
+ if self._fullscreen:
+ self._toggle_fullscreen()
+
+ return False
- def _setup(self):
- #sets up the tubes...
- if self._shared_activity is None:
- self.__class__.log.error('_setup: Failed to share or join activity')
+ def _play_pause_clicked(self, widget):
+ self.model.play_pause()
+
+ # can be called from gstreamer thread, so must not do any GTK+ stuff
+ def set_glive_sink(self, sink):
+ return self._media_view.set_video_sink(sink)
+
+ # can be called from gstreamer thread, so must not do any GTK+ stuff
+ def set_gplay_sink(self, sink):
+ return self._media_view.set_video2_sink(sink)
+
+ def get_selected_quality(self):
+ return self._video_toolbar.get_quality()
+
+ def get_selected_timer(self):
+ mode = self.model.get_mode()
+ if mode == constants.MODE_PHOTO:
+ return self._photo_toolbar.get_timer()
+ elif mode == constants.MODE_VIDEO:
+ return self._video_toolbar.get_timer()
+ elif mode == constants.MODE_AUDIO:
+ return self._audio_toolbar.get_timer()
+
+ def get_selected_duration(self):
+ mode = self.model.get_mode()
+ if mode == constants.MODE_VIDEO:
+ return self._video_toolbar.get_duration()
+ elif mode == constants.MODE_AUDIO:
+ return self._audio_toolbar.get_duration()
+
+ def set_progress(self, value, text):
+ self._progress.set_progress(value)
+ self._progress.set_text(text)
+
+ def set_countdown(self, value):
+ if value == 0:
+ self._shutter_button.show()
+ self._countdown_image.hide()
+ self._countdown_image.clear()
return
- pservice = presenceservice.get_instance()
- try:
- name, path = pservice.get_preferred_connection()
- self.conn = telepathy.client.Connection(name, path)
- except:
- self.__class__.log.error('_setup: Failed to get_preferred_connection')
-
- # Work out what our room is called and whether we have Tubes already
- bus_name, conn_path, channel_paths = self._shared_activity.get_channels()
- room = None
- tubes_chan = None
- text_chan = None
- for channel_path in channel_paths:
- channel = telepathy.client.Channel(bus_name, channel_path)
- htype, handle = channel.GetHandle()
- if htype == telepathy.HANDLE_TYPE_ROOM:
- self.__class__.log.debug('Found our room: it has handle#%d "%s"', handle, self.conn.InspectHandles(htype, [handle])[0])
- room = handle
- ctype = channel.GetChannelType()
- if ctype == telepathy.CHANNEL_TYPE_TUBES:
- self.__class__.log.debug('Found our Tubes channel at %s', channel_path)
- tubes_chan = channel
- elif ctype == telepathy.CHANNEL_TYPE_TEXT:
- self.__class__.log.debug('Found our Text channel at %s', channel_path)
- text_chan = channel
-
- if room is None:
- self.__class__.log.error("Presence service didn't create a room")
+ self._shutter_button.hide()
+ self._countdown_image.set_value(value)
+ self._countdown_image.show()
+
+ def _title_changed(self, widget):
+ self._active_recd.setTitle(self._title_entry.get_text())
+
+ def _media_view_media_clicked(self, widget):
+ if self._play_button.props.visible and self._play_button.props.sensitive:
+ self._play_button.clicked()
+
+ def _media_view_pip_clicked(self, widget):
+ # clicking on the PIP always returns to live mode
+ self.model.set_state(constants.STATE_READY)
+
+ def _media_view_info_clicked(self, widget):
+ self._toggle_info()
+
+ def _toggle_info(self):
+ recd = self._active_recd
+ if not recd:
return
- if text_chan is None:
- self.__class__.log.error("Presence service didn't create a text channel")
- return
-
- # Make sure we have a Tubes channel - PS doesn't yet provide one
- if tubes_chan is None:
- self.__class__.log.debug("Didn't find our Tubes channel, requesting one...")
- tubes_chan = self.conn.request_channel(telepathy.CHANNEL_TYPE_TUBES, telepathy.HANDLE_TYPE_ROOM, room, True)
-
- self.tubes_chan = tubes_chan
- self.text_chan = text_chan
-
- tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube', self._newTubeCb)
-
-
- def _newTubeCb(self, id, initiator, type, service, params, state):
- self.__class__.log.debug('New tube: ID=%d initator=%d type=%d service=%s params=%r state=%d', id, initiator, type, service, params, state)
- if (type == telepathy.TUBE_TYPE_DBUS and service == Constants.SERVICE):
- if state == telepathy.TUBE_STATE_LOCAL_PENDING:
- self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)
- tube_conn = TubeConnection(self.conn, self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES], id, group_iface=self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP])
- self.recTube = RecordTube(tube_conn)
- self.recTube.connect("new-recd", self._newRecdCb)
- self.recTube.connect("recd-request", self._recdRequestCb)
- self.recTube.connect("recd-bits-arrived", self._recdBitsArrivedCb)
- self.recTube.connect("recd-unavailable", self._recdUnavailableCb)
-
-
- def _newRecdCb( self, objectThatSentTheSignal, recorder, xmlString ):
- self.__class__.log.debug('_newRecdCb')
- dom = None
- try:
- dom = xml.dom.minidom.parseString(xmlString)
- except:
- self.__class__.log.error('Unable to parse mesh xml')
- if (dom == None):
+
+ if self._showing_info:
+ self._show_recd(recd, play=False)
return
- recd = Recorded()
- recd = serialize.fillRecdFromNode(recd, dom.documentElement)
- if (recd != None):
- self.__class__.log.debug('_newRecdCb: adding new recd thumb')
- recd.buddy = True
- recd.downloadedFromBuddy = False
- self.m.addMeshRecd( recd )
+ self._showing_info = True
+ if self.model.get_mode() in (constants.MODE_PHOTO, constants.MODE_AUDIO):
+ func = self._media_view.show_info_photo
else:
- self.__class__.log.debug('_newRecdCb: recd is None. Unable to parse XML')
+ func = self._media_view.show_info_video
+ self._play_button.hide()
+ self._progress.hide()
+ self._playback_scale.hide()
+ self._title_entry.set_text(recd.title)
+ self._title_entry.show()
+ self._title_label.show()
- def requestMeshDownload( self, recd ):
- if (recd.meshDownloading):
- return True
+ func(recd.recorderName, recd.colorStroke, recd.colorFill, utils.getDateString(recd.time), recd.tags)
- self.m.updateXoFullStatus()
- if (self.m.FULL):
- return True
+ def _media_view_full_clicked(self, widget):
+ self._toggle_fullscreen()
- #this call will get the bits or request the bits if they're not available
- if (recd.buddy and (not recd.downloadedFromBuddy)):
- self.meshInitRoundRobin(recd)
- return True
+ def _media_view_tags_changed(self, widget, tbuffer):
+ text = tbuffer.get_text(tbuffer.get_start_iter(), tbuffer.get_end_iter())
+ self._active_recd.setTags(text)
+ def _toggle_fullscreen(self):
+ if not self._fullscreen:
+ self._toolbox.hide()
+ self._thumb_tray.hide()
else:
- return False
+ self._toolbox.show()
+ self._thumb_tray.show()
+ self._fullscreen = not self._fullscreen
+ self._media_view.set_fullscreen(self._fullscreen)
- def meshInitRoundRobin( self, recd ):
- if (recd.meshDownloading):
- self.__class__.log.debug("meshInitRoundRobin: we are in midst of downloading this file...")
+ def _toolbar_changed(self, toolbox, num):
+ if num == 0: # Activity tab
return
- if (self.recTube == None):
- gobject.idle_add(self.ui.updateMeshProgress, False, recd)
+ # Prevent mode/tab changing under certain conditions by immediately
+ # changing back to the previously-selected toolbar
+ if self.model.ui_frozen():
+ self._toolbox.set_current_toolbar(self._active_toolbar_idx)
return
- #start with who took the photo
- recd.triedMeshBuddies = []
- recd.triedMeshBuddies.append(Instance.keyHashPrintable)
- self.meshReqRecFromBuddy( recd, recd.recorderHash, recd.recorderName )
-
-
- def meshNextRoundRobinBuddy( self, recd ):
- self.__class__.log.debug('meshNextRoundRobinBuddy')
- if (recd.meshReqCallbackId != 0):
- gobject.source_remove(recd.meshReqCallbackId)
- recd.meshReqCallbackId = 0
-
- #delete any stub of a partially downloaded file
- filepath = recd.getMediaFilepath()
- if (filepath != None):
- if (os.path.exists(filepath)):
- os.remove( filepath )
-
- goodBudObj = None
- buds = self._shared_activity.get_joined_buddies()
- for i in range (0, len(buds)):
- nextBudObj = buds[i]
- nextBud = util.sha_data(nextBudObj.props.key)
- nextBud = util.printable_hash(nextBud)
- if (recd.triedMeshBuddies.count(nextBud) > 0):
- self.__class__.log.debug('mnrrb: weve already tried bud ' + str(nextBudObj.props.nick))
- else:
- self.__class__.log.debug('mnrrb: ask next buddy: ' + str(nextBudObj.props.nick))
- goodBudObj = nextBudObj
- break
-
- if (goodBudObj != None):
- goodNick = goodBudObj.props.nick
- goodBud = util.sha_data(goodBudObj.props.key)
- goodBud = util.printable_hash(goodBud)
- self.meshReqRecFromBuddy(recd, goodBud, goodNick)
- else:
- self.__class__.log.debug('weve tried all buddies here, and no one has this recd')
- recd.meshDownloading = False
- recd.triedMeshBuddies = []
- recd.triedMeshBuddies.append(Instance.keyHashPrintable)
- self.ui.updateMeshProgress(False, recd)
-
-
- def meshReqRecFromBuddy( self, recd, fromWho, fromWhosNick ):
- recd.triedMeshBuddies.append( fromWho )
- recd.meshDownloadingFrom = fromWho
- recd.meshDownloadingFromNick = fromWhosNick
- recd.meshDownloadingProgress = False
- recd.meshDownloading = True
- recd.meshDownlodingPercent = 0.0
- self.ui.updateMeshProgress(True, recd)
- recd.meshReqCallbackId = gobject.timeout_add(self.meshTimeoutTime, self._meshCheckOnRecdRequest, recd)
- self.recTube.requestRecdBits( Instance.keyHashPrintable, fromWho, recd.mediaMd5 )
-
-
- def _meshCheckOnRecdRequest( self, recdRequesting ):
- #todo: add category for "not active activity, so go ahead and delete"
-
- if (recdRequesting.downloadedFromBuddy):
- self.__class__.log.debug('_meshCheckOnRecdRequest: recdRequesting.downloadedFromBuddy')
- if (recdRequesting.meshReqCallbackId != 0):
- gobject.source_remove(recdRequesting.meshReqCallbackId)
- recdRequesting.meshReqCallbackId = 0
- return False
- if (recdRequesting.deleted):
- self.__class__.log.debug('_meshCheckOnRecdRequest: recdRequesting.deleted')
- if (recdRequesting.meshReqCallbackId != 0):
- gobject.source_remove(recdRequesting.meshReqCallbackId)
- recdRequesting.meshReqCallbackId = 0
- return False
- if (recdRequesting.meshDownloadingProgress):
- self.__class__.log.debug('_meshCheckOnRecdRequest: recdRequesting.meshDownloadingProgress')
- #we've received some bits since last we checked, so keep waiting... they'll all get here eventually!
- recdRequesting.meshDownloadingProgress = False
- return True
+ self._active_toolbar_idx = num
+ self.model.change_mode(self._toolbar_modes[num])
+
+ def _shutter_clicked(self, arg):
+ self.model.do_shutter()
+
+ def set_shutter_sensitive(self, value):
+ self._shutter_button.set_sensitive(value)
+
+ def set_state(self, state):
+ self._showing_info = False
+ if state == constants.STATE_READY:
+ self._set_cursor_default()
+ self._active_recd = None
+ self._title_entry.hide()
+ self._title_label.hide()
+ self._play_button.hide()
+ self._playback_scale.hide()
+ self._progress.hide()
+ self._controls_hbox.set_child_packing(self._shutter_button, expand=True, fill=False, padding=0, pack_type=gtk.PACK_START)
+ self._shutter_button.set_normal()
+ self._shutter_button.show()
+ self._media_view.show_live()
+ elif state == constants.STATE_RECORDING:
+ self._shutter_button.set_recording()
+ self._controls_hbox.set_child_packing(self._shutter_button, expand=False, fill=False, padding=0, pack_type=gtk.PACK_START)
+ self._progress.show()
+ elif state == constants.STATE_PROCESSING:
+ self._set_cursor_busy()
+ self._shutter_button.hide()
+ self._progress.show()
+ elif state == constants.STATE_DOWNLOADING:
+ self._shutter_button.hide()
+ self._progress.show()
+
+ def set_paused(self, value):
+ if value:
+ self._play_button.set_play()
else:
- self.__class__.log.debug('_meshCheckOnRecdRequest: ! recdRequesting.meshDownloadingProgress')
- #that buddy we asked info from isn't responding; next buddy!
- #self.meshNextRoundRobinBuddy( recdRequesting )
- gobject.idle_add(self.meshNextRoundRobinBuddy, recdRequesting)
- return False
+ self._play_button.set_pause()
+ def _thumbnail_clicked(self, button, recd):
+ if self.model.ui_frozen():
+ return
- def _recdRequestCb( self, objectThatSentTheSignal, whoWantsIt, md5sumOfIt ):
- #if we are here, it is because someone has been told we have what they want.
- #we need to send them that thing, whatever that thing is
- recd = self.m.getRecdByMd5( md5sumOfIt )
- if (recd == None):
- self.__class__.log.debug('_recdRequestCb: we dont have the recd they asked for')
- self.recTube.unavailableRecd(md5sumOfIt, Instance.keyHashPrintable, whoWantsIt)
+ self._active_recd = recd
+ self._show_recd(recd)
+
+ def add_thumbnail(self, recd, scroll_to_end):
+ button = RecdButton(recd)
+ clicked_handler = button.connect("clicked", self._thumbnail_clicked, recd)
+ remove_handler = button.connect("remove-requested", self._remove_recd)
+ clipboard_handler = button.connect("copy-clipboard-requested", self._thumbnail_copy_clipboard)
+ button.set_data('handler-ids', (clicked_handler, remove_handler, clipboard_handler))
+ self._thumb_tray.add_item(button)
+ button.show()
+ if scroll_to_end:
+ self._thumb_tray.scroll_to_end()
+
+ def _copy_to_clipboard(self, recd):
+ if recd == None:
return
- if (recd.deleted):
- self.__class__.log.debug('_recdRequestCb: we have the recd, but it has been deleted, so we wont share')
- self.recTube.unavailableRecd(md5sumOfIt, Instance.keyHashPrintable, whoWantsIt)
+ if not recd.isClipboardCopyable():
return
- if (recd.buddy and not recd.downloadedFromBuddy):
- self.__class__.log.debug('_recdRequestCb: we have an incomplete recd, so we wont share')
- self.recTube.unavailableRecd(md5sumOfIt, Instance.keyHashPrintable, whoWantsIt)
+
+ media_path = recd.getMediaFilepath()
+ tmp_path = utils.getUniqueFilepath(media_path, 0)
+ shutil.copyfile(media_path, tmp_path)
+ gtk.Clipboard().set_with_data([('text/uri-list', 0, 0)], self._clipboard_get, self._clipboard_clear, tmp_path)
+
+ def _clipboard_get(self, clipboard, selection_data, info, path):
+ selection_data.set("text/uri-list", 8, "file://" + path)
+
+ def _clipboard_clear(self, clipboard, path):
+ if os.path.exists(path):
+ os.unlink(path)
+
+ def _thumbnail_copy_clipboard(self, recdbutton):
+ self._copy_to_clipboard(recdbutton.get_recd())
+
+ def _remove_recd(self, recdbutton):
+ recd = recdbutton.get_recd()
+ self.model.delete_recd(recd)
+ if self._active_recd == recd:
+ self.model.set_state(constants.STATE_READY)
+
+ self._remove_thumbnail(recdbutton)
+
+ def _remove_thumbnail(self, recdbutton):
+ handlers = recdbutton.get_data('handler-ids')
+ for handler in handlers:
+ recdbutton.disconnect(handler)
+
+ self._thumb_tray.remove_item(recdbutton)
+ recdbutton.cleanup()
+
+ def remove_all_thumbnails(self):
+ for child in self._thumb_tray.get_children():
+ self._remove_thumbnail(child)
+
+ def show_still(self, pixbuf):
+ self._media_view.show_still(pixbuf)
+
+ def _show_photo(self, recd):
+ path = self._get_photo_path(recd)
+ self._media_view.show_photo(path)
+ self._title_entry.set_text(recd.title)
+ self._title_entry.show()
+ self._title_label.show()
+ self._shutter_button.hide()
+ self._progress.hide()
+
+ def _show_audio(self, recd, play):
+ self._progress.hide()
+ self._shutter_button.hide()
+ self._title_entry.hide()
+ self._title_label.hide()
+ self._play_button.show()
+ self._playback_scale.show()
+ path = recd.getAudioImageFilepath()
+ self._media_view.show_photo(path)
+ if play:
+ self.model.play_audio(recd)
+
+ def _show_video(self, recd, play):
+ self._progress.hide()
+ self._shutter_button.hide()
+ self._title_entry.hide()
+ self._title_label.hide()
+ self._play_button.show()
+ self._playback_scale.show()
+ self._media_view.show_video()
+ if play:
+ self.model.play_video(recd)
+
+ def set_playback_scale(self, value):
+ self._playback_scale.set_value(value)
+
+ def _get_photo_path(self, recd):
+ # FIXME should live (partially) in recd?
+
+ #downloading = self.ca.requestMeshDownload(recd)
+ #self.MESHING = downloading
+
+ if True: #not downloading:
+ #self.progressWindow.updateProgress(0, "")
+ return recd.getMediaFilepath()
+
+ #maybe it is not downloaded from the mesh yet...
+ #but we can show the low res thumb in the interim
+ return recd.getThumbFilepath()
+
+ def _show_recd(self, recd, play=True):
+ self._showing_info = False
+
+ if recd.buddy and not recd.downloadedFromBuddy:
+ self.model.request_download(recd)
+ elif recd.type == constants.TYPE_PHOTO:
+ self._show_photo(recd)
+ elif recd.type == constants.TYPE_AUDIO:
+ self._show_audio(recd, play)
+ elif recd.type == constants.TYPE_VIDEO:
+ self._show_video(recd, play)
+
+ def remote_recd_available(self, recd):
+ if recd == self._active_recd:
+ self._show_recd(recd)
+
+ def update_download_progress(self, recd):
+ if recd != self._active_recd:
return
- recd.meshUploading = True
- filepath = recd.getMediaFilepath()
+ if not recd.meshDownloading:
+ msg = _('Download failed.')
+ elif recd.meshDownloadingProgress:
+ msg = _('Downloading...')
+ else:
+ msg = _('Requesting...')
+
+ self.set_progress(recd.meshDownlodingPercent, msg)
+
+ def _set_cursor_busy(self):
+ self.window.set_cursor(gdk.Cursor(gdk.WATCH))
+
+ def _set_cursor_default(self):
+ self.window.set_cursor(None)
+
+class RecordContainer(gtk.Container):
+ """
+ A custom Container that contains a media view area, and a controls hbox.
+
+ The controls hbox is given the first height that it requests, locked in
+ for the duration of the widget.
+ The media view is given the remainder of the space, but is constrained to
+ a strict 4:3 ratio, therefore deducing its width.
+ The controls hbox is given the same width, and both elements are centered
+ horizontall.y
+ """
+ __gtype_name__ = 'RecordContainer'
+
+ def __init__(self, media_view, controls_hbox):
+ self._media_view = media_view
+ self._controls_hbox = controls_hbox
+ self._controls_hbox_height = 0
+ super(RecordContainer, self).__init__()
+
+ for widget in (self._media_view, self._controls_hbox):
+ if widget.flags() & gtk.REALIZED:
+ widget.set_parent_window(self.window)
+
+ widget.set_parent(self)
+
+ def do_realize(self):
+ self.set_flags(gtk.REALIZED)
+
+ self.window = gdk.Window(
+ self.get_parent_window(),
+ window_type=gdk.WINDOW_CHILD,
+ x=self.allocation.x,
+ y=self.allocation.y,
+ width=self.allocation.width,
+ height=self.allocation.height,
+ wclass=gdk.INPUT_OUTPUT,
+ colormap=self.get_colormap(),
+ event_mask=self.get_events() | gdk.VISIBILITY_NOTIFY_MASK | gdk.EXPOSURE_MASK)
+ self.window.set_user_data(self)
+
+ self.set_style(self.style.attach(self.window))
+
+ for widget in (self._media_view, self._controls_hbox):
+ widget.set_parent_window(self.window)
+ self.queue_resize()
+
+ # GTK+ contains on exit if remove is not implemented
+ def do_remove(self, widget):
+ pass
+
+ def do_size_request(self, req):
+ # always request 320x240 (as a minimum for video)
+ req.width = 320
+ req.height = 240
+
+ self._media_view.size_request()
+
+ w, h = self._controls_hbox.size_request()
+
+ # add on height requested by controls hbox
+ if self._controls_hbox_height == 0:
+ self._controls_hbox_height = h
+
+ req.height += self._controls_hbox_height
+
+ @staticmethod
+ def _constrain_4_3(width, height):
+ if (width % 4 == 0) and (height % 3 == 0) and ((width / 4) * 3) == height:
+ return width, height # nothing to do
+
+ ratio = 4.0 / 3.0
+ if ratio * height > width:
+ width = (width / 4) * 4
+ height = int(width / ratio)
+ else:
+ height = (height / 3) * 3
+ width = int(ratio * height)
- if (recd.type == Constants.TYPE_AUDIO):
- audioImgFilepath = recd.getAudioImageFilepath()
+ return width, height
+
+ @staticmethod
+ def _center_in_plane(plane_size, size):
+ return (plane_size - size) / 2
+
+ def do_size_allocate(self, allocation):
+ self.allocation = allocation
+
+ # give the controls hbox the height that it requested
+ remaining_height = self.allocation.height - self._controls_hbox_height
+
+ # give the mediaview the rest, constrained to 4/3 and centered
+ media_view_width, media_view_height = self._constrain_4_3(self.allocation.width, remaining_height)
+ media_view_x = self._center_in_plane(self.allocation.width, media_view_width)
+ media_view_y = self._center_in_plane(remaining_height, media_view_height)
+
+ # send allocation to mediaview
+ alloc = gdk.Rectangle()
+ alloc.width = media_view_width
+ alloc.height = media_view_height
+ alloc.x = media_view_x
+ alloc.y = media_view_y
+ self._media_view.size_allocate(alloc)
+
+ # position hbox at the bottom of the window, with the requested height,
+ # and the same width as the media view
+ alloc = gdk.Rectangle()
+ alloc.x = media_view_x
+ alloc.y = self.allocation.height - self._controls_hbox_height
+ alloc.width = media_view_width
+ alloc.height = self._controls_hbox_height
+ self._controls_hbox.size_allocate(alloc)
+
+ if self.flags() & gtk.REALIZED:
+ self.window.move_resize(*allocation)
+
+ def do_forall(self, include_internals, callback, data):
+ for widget in (self._media_view, self._controls_hbox):
+ callback(widget, data)
+
+class PlaybackScale(gtk.HScale):
+ def __init__(self, model):
+ self.model = model
+ self._change_handler = None
+ self._playback_adjustment = gtk.Adjustment(0.0, 0.00, 100.0, 0.1, 1.0, 1.0)
+ super(PlaybackScale, self).__init__(self._playback_adjustment)
+
+ self.set_draw_value(False)
+ self.set_update_policy(gtk.UPDATE_CONTINUOUS)
+ self.connect('button-press-event', self._button_press)
+ self.connect('button-release-event', self._button_release)
+
+ def set_value(self, value):
+ self._playback_adjustment.set_value(value)
+
+ def _value_changed(self, scale):
+ self.model.do_seek(scale.get_value())
+
+ def _button_press(self, widget, event):
+ self.model.start_seek()
+ self._change_handler = self.connect('value-changed', self._value_changed)
+
+ def _button_release(self, widget, event):
+ self.disconnect(self._change_handler)
+ self._change_handler = None
+ self.model.end_seek()
+
+
+class ProgressInfo(gtk.VBox):
+ def __init__(self):
+ super(ProgressInfo, self).__init__()
+
+ self._progress_bar = gtk.ProgressBar()
+ self._progress_bar.modify_bg(gtk.STATE_NORMAL, COLOR_BLACK)
+ self._progress_bar.modify_bg(gtk.STATE_INSENSITIVE, COLOR_BLACK)
+ self.pack_start(self._progress_bar, expand=True, fill=True, padding=5)
+
+ self._label = gtk.Label()
+ self._label.modify_fg(gtk.STATE_NORMAL, COLOR_WHITE)
+ self.pack_start(self._label, expand=True, fill=True)
+
+ def show(self):
+ self._progress_bar.show()
+ self._label.show()
+ super(ProgressInfo, self).show()
+
+ def hide(self):
+ self._progress_bar.hide()
+ self._label.hide()
+ super(ProgressInfo, self).hide()
+
+ def set_progress(self, value):
+ self._progress_bar.set_fraction(value)
+
+ def set_text(self, text):
+ self._label.set_text(text)
+
+
+class CountdownImage(gtk.Image):
+ def __init__(self):
+ super(CountdownImage, self).__init__()
+ self._countdown_images = {}
+
+ def _generate_image(self, num):
+ w = 55
+ h = w
+ pixmap = gdk.Pixmap(None, w, h, 24)
+ ctx = pixmap.cairo_create()
+ ctx.rectangle(0, 0, w, h)
+ ctx.set_source_rgb(0, 0, 0)
+ ctx.fill()
+
+ x = 0
+ y = 4
+ ctx.translate(x, y)
+ circle_path = os.path.join(constants.GFX_PATH, 'media-circle.png')
+ surface = cairo.ImageSurface.create_from_png(circle_path)
+ ctx.set_source_surface(surface, 0, 0)
+ ctx.paint()
+ ctx.translate(-x, -y)
+
+ ctx.set_source_rgb(255, 255, 255)
+ pctx = pangocairo.CairoContext(ctx)
+ play = pctx.create_layout()
+ font = pango.FontDescription("sans 30")
+ play.set_font_description(font)
+ play.set_text(str(num))
+ dim = play.get_pixel_extents()
+ ctx.translate(-dim[0][0], -dim[0][1])
+ xoff = (w - dim[0][2]) / 2
+ yoff = (h - dim[0][3]) / 2
+ ctx.translate(xoff, yoff)
+ ctx.translate(-3, 0)
+ pctx.show_layout(play)
+ return pixmap
+
+ def set_value(self, num):
+ if num not in self._countdown_images:
+ self._countdown_images[num] = self._generate_image(num)
+
+ self.set_from_pixmap(self._countdown_images[num], None)
+
+
+class ShutterButton(gtk.Button):
+ def __init__(self):
+ gtk.Button.__init__(self)
+ self.set_relief(gtk.RELIEF_NONE)
+ self.set_focus_on_click(False)
+ self.modify_bg(gtk.STATE_ACTIVE, COLOR_BLACK)
+
+ path = os.path.join(constants.GFX_PATH, 'media-record.png')
+ self._rec_image = gtk.image_new_from_file(path)
+
+ path = os.path.join(constants.GFX_PATH, 'media-record-red.png')
+ self._rec_red_image = gtk.image_new_from_file(path)
+
+ path = os.path.join(constants.GFX_PATH, 'media-insensitive.png')
+ self._insensitive_image = gtk.image_new_from_file(path)
+
+ self.set_normal()
+
+ def set_sensitive(self, sensitive):
+ if sensitive:
+ self.set_image(self._rec_image)
+ else:
+ self.set_image(self._insensitive_image)
+ super(ShutterButton, self).set_sensitive(sensitive)
- destPath = os.path.join(Instance.instancePath, "audioBundle")
- destPath = utils.getUniqueFilepath(destPath, 0)
- cmd = "cat " + str(filepath) + " " + str(audioImgFilepath) + " > " + str(destPath)
- self.__class__.log.debug(cmd)
- os.system(cmd)
- filepath = destPath
+ def set_normal(self):
+ self.set_image(self._rec_image)
- sent = self.recTube.broadcastRecd(recd.mediaMd5, filepath, whoWantsIt)
- recd.meshUploading = False
- #if you were deleted while uploading, now throw away those bits now
- if (recd.deleted):
- recd.doDeleteRecorded(recd)
+ def set_recording(self):
+ self.set_image(self._rec_red_image)
- def _recdBitsArrivedCb( self, objectThatSentTheSignal, md5sumOfIt, part, numparts, bytes, fromWho ):
- #self.__class__.log.debug('_recdBitsArrivedCb: ' + str(part) + "/" + str(numparts))
- recd = self.m.getRecdByMd5( md5sumOfIt )
- if (recd == None):
- self.__class__.log.debug('_recdBitsArrivedCb: thx 4 yr bits, but we dont even have that photo')
- return
- if (recd.deleted):
- self.__class__.log.debug('_recdBitsArrivedCb: thx 4 yr bits, but we deleted that photo')
- return
- if (recd.downloadedFromBuddy):
- self.__class__.log.debug('_recdBitsArrivedCb: weve already downloadedFromBuddy')
- return
- if (not recd.buddy):
- self.__class__.log.debug('_recdBitsArrivedCb: uh, we took this photo, so dont need your bits')
- return
- if (recd.meshDownloadingFrom != fromWho):
- self.__class__.log.debug('_recdBitsArrivedCb: wrong bits ' + str(fromWho) + ", exp:" + str(recd.meshDownloadingFrom))
- return
+class PlayButton(gtk.Button):
+ def __init__(self):
+ super(PlayButton, self).__init__()
+ self.set_relief(gtk.RELIEF_NONE)
+ self.set_focus_on_click(False)
+ self.modify_bg(gtk.STATE_ACTIVE, COLOR_BLACK)
- #update that we've heard back about this, reset the timeout
- gobject.source_remove(recd.meshReqCallbackId)
- recd.meshReqCallbackId = gobject.timeout_add(self.meshTimeoutTime, self._meshCheckOnRecdRequest, recd)
-
- #update the progress bar
- recd.meshDownlodingPercent = (part+0.0)/(numparts+0.0)
- recd.meshDownloadingProgress = True
- self.ui.updateMeshProgress(True, recd)
- f = open(recd.getMediaFilepath(), 'a+')
- f.write(bytes)
- f.close()
-
- if part == numparts:
- self.__class__.log.debug('Finished receiving %s' % recd.title)
- gobject.source_remove( recd.meshReqCallbackId )
- recd.meshReqCallbackId = 0
- recd.meshDownloading = False
- recd.meshDownlodingPercent = 1.0
- recd.downloadedFromBuddy = True
- if (recd.type == Constants.TYPE_AUDIO):
- filepath = recd.getMediaFilepath()
- bundlePath = os.path.join(Instance.instancePath, "audioBundle")
- bundlePath = utils.getUniqueFilepath(bundlePath, 0)
-
- cmd = "split -a 1 -b " + str(recd.mediaBytes) + " " + str(filepath) + " " + str(bundlePath)
- self.__class__.log.debug( cmd )
- os.system( cmd )
-
- bundleName = os.path.basename(bundlePath)
- mediaFilename = str(bundleName) + "a"
- mediaFilepath = os.path.join(Instance.instancePath, mediaFilename)
- mediaFilepathExt = os.path.join(Instance.instancePath, mediaFilename+".ogg")
- os.rename(mediaFilepath, mediaFilepathExt)
- audioImageFilename = str(bundleName) + "b"
- audioImageFilepath = os.path.join(Instance.instancePath, audioImageFilename)
- audioImageFilepathExt = os.path.join(Instance.instancePath, audioImageFilename+".png")
- os.rename(audioImageFilepath, audioImageFilepathExt)
-
- recd.mediaFilename = os.path.basename(mediaFilepathExt)
- recd.audioImageFilename = os.path.basename(audioImageFilepathExt)
-
- self.ui.showMeshRecd( recd )
- elif part > numparts:
- self.__class__.log.error('More parts than required have arrived')
-
-
- def _getAlbumArtCb( self, objectThatSentTheSignal, pixbuf, recd ):
-
- if (pixbuf != None):
- imagePath = os.path.join(Instance.instancePath, "audioPicture.png")
- imagePath = utils.getUniqueFilepath( imagePath, 0 )
- pixbuf.save( imagePath, "png", {} )
- recd.audioImageFilename = os.path.basename(imagePath)
-
- self.ui.showMeshRecd( recd )
- return False
+ path = os.path.join(constants.GFX_PATH, 'media-play.png')
+ self._play_image = gtk.image_new_from_file(path)
+ path = os.path.join(constants.GFX_PATH, 'media-pause.png')
+ self._pause_image = gtk.image_new_from_file(path)
- def _recdUnavailableCb( self, objectThatSentTheSignal, md5sumOfIt, whoDoesntHaveIt ):
- self.__class__.log.debug('_recdUnavailableCb: sux, we want to see that photo')
- recd = self.m.getRecdByMd5( md5sumOfIt )
- if (recd == None):
- self.__class__.log.debug('_recdUnavailableCb: actually, we dont even know about that one..')
- return
- if (recd.deleted):
- self.__class__.log.debug('_recdUnavailableCb: actually, since we asked, we deleted.')
- return
- if (not recd.buddy):
- self.__class__.log.debug('_recdUnavailableCb: uh, odd, we took that photo and have it already.')
- return
- if (recd.downloadedFromBuddy):
- self.__class__.log.debug('_recdUnavailableCb: we already downloaded it... you might have been slow responding.')
- return
- if (recd.meshDownloadingFrom != whoDoesntHaveIt):
- self.__class__.log.debug('_recdUnavailableCb: we arent asking you for a copy now. slow response, pbly.')
- return
+ self.set_play()
+
+ def set_play(self):
+ self.set_image(self._play_image)
+
+ def set_pause(self):
+ self.set_image(self._pause_image)
+
+
+class RecordToolbar(gtk.Toolbar):
+ def __init__(self, icon_name, with_quality, with_timer, with_duration):
+ super(RecordToolbar, self).__init__()
+
+ img = Icon(icon_name=icon_name)
+ color = sugar.profile.get_color()
+ img.set_property('fill-color', color.get_fill_color())
+ img.set_property('stroke-color', color.get_stroke_color())
+ toolitem = gtk.ToolItem()
+ toolitem.add(img)
+ self.insert(toolitem, -1)
+
+ separator = gtk.SeparatorToolItem()
+ separator.set_draw(False)
+ separator.set_expand(True)
+ self.insert(separator, -1)
+
+ if with_quality:
+ combo = gtk.combo_box_new_text()
+ self.quality = ToolComboBox(combo=combo, label_text=_('Quality:'))
+ self.quality.combo.append_text(_('Low'))
+ if hw.get_xo_version() != 1:
+ # Disable High quality on XO-1. The system simply isn't beefy
+ # enough for recording to work well.
+ self.quality.combo.append_text(_('High'))
+ self.quality.combo.set_active(0)
+ self.insert(self.quality, -1)
+
+ if with_timer:
+ self._timer_combo = TimerCombo()
+ self.insert(self._timer_combo, -1)
+
+ if with_duration:
+ self._duration_combo = DurationCombo()
+ self.insert(self._duration_combo, -1)
+
+ def get_timer(self):
+ return self._timer_combo.get_value()
+
+ def get_timer_idx(self):
+ return self._timer_combo.get_value_idx()
+
+ def set_timer_idx(self, idx):
+ self._timer_combo.set_value_idx(idx)
+
+ def get_duration(self):
+ return self._duration_combo.get_value()
+
+ def get_duration_idx(self):
+ return self._duration_combo.get_value_idx()
+
+ def set_duration_idx(self, idx):
+ return self._duration_combo.set_value_idx(idx)
+
+ def get_quality(self):
+ return self.quality.combo.get_active()
+
+ def set_quality(self, idx):
+ self.quality.combo.set_active(idx)
+
+class PhotoToolbar(RecordToolbar):
+ def __init__(self):
+ super(PhotoToolbar, self).__init__('media-photo', with_quality=False, with_timer=True, with_duration=False)
+
+
+class VideoToolbar(RecordToolbar):
+ def __init__(self):
+ super(VideoToolbar, self).__init__('media-video', with_quality=True, with_timer=True, with_duration=True)
+
+
+class AudioToolbar(RecordToolbar):
+ def __init__(self):
+ super(AudioToolbar, self).__init__('media-audio', with_quality=False, with_timer=True, with_duration=True)
+
+
+class TimerCombo(ToolComboBox):
+ TIMERS = (0, 5, 10)
+
+ def __init__(self):
+ self._combo_box_text = gtk.combo_box_new_text()
+ super(TimerCombo, self).__init__(combo=self._combo_box_text, label_text=_('Timer:'))
+
+ for i in self.TIMERS:
+ if i == 0:
+ self._combo_box_text.append_text(_('Immediate'))
+ else:
+ string = TimerCombo._seconds_string(i)
+ self._combo_box_text.append_text(string)
+ self._combo_box_text.set_active(0)
+
+ def get_value(self):
+ return TimerCombo.TIMERS[self._combo_box_text.get_active()]
+
+ def get_value_idx(self):
+ return self._combo_box_text.get_active()
+
+ def set_value_idx(self, idx):
+ self._combo_box_text.set_active(idx)
+
+ @staticmethod
+ def _seconds_string(x):
+ return ngettext('%s second', '%s seconds', x) % x
+
+
+class DurationCombo(ToolComboBox):
+ DURATIONS = (2, 4, 6)
+
+ def __init__(self):
+ self._combo_box_text = gtk.combo_box_new_text()
+ super(DurationCombo, self).__init__(combo=self._combo_box_text, label_text=_('Duration:'))
+
+ for i in self.DURATIONS:
+ string = DurationCombo._minutes_string(i)
+ self._combo_box_text.append_text(string)
+ self._combo_box_text.set_active(0)
+
+ def get_value(self):
+ return 60 * self.DURATIONS[self._combo_box_text.get_active()]
+
+ def get_value_idx(self):
+ return self._combo_box_text.get_active()
+
+ def set_value_idx(self, idx):
+ self._combo_box_text.set_active(idx)
+
+ @staticmethod
+ def _minutes_string(x):
+ return ngettext('%s minute', '%s minutes', x) % x
- #self.meshNextRoundRobinBuddy( recd )