Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/record.py
diff options
context:
space:
mode:
authorDaniel Drake <dsd@laptop.org>2011-02-02 16:27:46 (GMT)
committer Daniel Drake <dsd@laptop.org>2011-02-02 16:37:48 (GMT)
commit3bc80c776f7ef2578a4b9fd26028574a12982ea3 (patch)
tree890aa0ee4a2b07d9c9704883d11f0385e03a2e70 /record.py
parentca2bbd6d74342247de97cffa150b577246c63107 (diff)
UI rework
This is a rework of the UI that uses a more standard GTK+ principles than previously. There is still a small amount of black magic, kept away inside mediaview.py. The UI/model separation was also refined in places, and a lot of code was simplified. Overall the functionality remains identical, and I tried to keep the UI the same as before (but it is not pixel perfect). As this was quite big there may be some bugs to shake out.
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 )