Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
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
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.
-rw-r--r--aplay.py6
-rw-r--r--button.py140
-rw-r--r--collab.py339
-rw-r--r--color.py71
-rw-r--r--constants.py389
-rw-r--r--gfx/full-insensitive.pngbin813 -> 0 bytes
-rw-r--r--gfx/info-on.svg17
-rw-r--r--gfx/media-record.svg6
-rw-r--r--glive.py713
-rw-r--r--gplay.py141
-rw-r--r--greplay.py80
-rw-r--r--instance.py23
-rw-r--r--mediaview.py512
-rw-r--r--model.py558
-rw-r--r--network.py1076
-rw-r--r--p5.py190
-rw-r--r--p5_button.py173
-rw-r--r--port/AUTHORS1
-rw-r--r--port/COPYING340
-rw-r--r--port/NEWS8
-rw-r--r--port/README13
-rw-r--r--port/TODO0
-rw-r--r--port/__init__.py0
-rw-r--r--port/json.py33
-rw-r--r--record.py1249
-rw-r--r--recorded.py19
-rw-r--r--recordtube.py68
-rw-r--r--serialize.py312
-rw-r--r--tray.py10
-rw-r--r--ui.py2444
-rw-r--r--utils.py66
31 files changed, 3680 insertions, 5317 deletions
diff --git a/aplay.py b/aplay.py
index c9aa811..fd6aaee 100644
--- a/aplay.py
+++ b/aplay.py
@@ -13,8 +13,10 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import gst
-
+import os
import logging
+import constants
+
logger = logging.getLogger('record:aplay.py')
def play(file, done_cb=None):
@@ -37,7 +39,7 @@ def play(file, done_cb=None):
bus.connect('message::eos', eos_cb)
bus.connect('message::error', error_cb)
- player.props.uri = 'file://' + file
+ player.props.uri = 'file://' + os.path.join(constants.GFX_PATH, file)
player.set_state(gst.STATE_PLAYING)
diff --git a/button.py b/button.py
index 14b9700..e3bcdc6 100644
--- a/button.py
+++ b/button.py
@@ -1,108 +1,70 @@
-import gtk
-import os
import gobject
-import rsvg
-import gc
+import gtk
+from gettext import gettext as _
from sugar.graphics.palette import Palette
from sugar.graphics.tray import TrayButton
-from sugar.graphics.icon import Icon
-from sugar.graphics import style
-from constants import Constants
+import constants
import utils
-class RecdButton(TrayButton, gobject.GObject):
- def __init__(self, ui, recd):
- TrayButton.__init__(self)
- self.ui = ui
- self.recd = recd
-
- img = self.getImg( )
- self.set_icon_widget( img )
-
- self.ACTIVATE_COPY_ID = 0
- self.ACTIVATE_REMOVE_ID = 0
- self.setup_rollover_options( recd.title )
-
-
- def getImg( self ):
- img = gtk.Image()
- ipb = self.recd.getThumbPixbuf()
- xoff = 8
- yoff = 8
- pb = None
- if (self.recd.type == Constants.TYPE_PHOTO):
- if (self.recd.buddy):
- thumbPhotoSvg = utils.loadSvg(Constants.thumbPhotoSvgData, self.recd.colorStroke.hex, self.recd.colorFill.hex)
- pb = thumbPhotoSvg.get_pixbuf()
- else:
- pb = Constants.thumbPhotoSvg.get_pixbuf()
-
- elif (self.recd.type == Constants.TYPE_VIDEO):
- if (self.recd.buddy):
- thumbVideoSvg = utils.loadSvg(Constants.thumbVideoSvgData, self.recd.colorStroke.hex, self.recd.colorFill.hex)
- pb = thumbVideoSvg.get_pixbuf()
- else:
- pb = Constants.thumbVideoSvg.get_pixbuf()
-
- elif (self.recd.type == Constants.TYPE_AUDIO):
- if (self.recd.buddy):
- thumbAudioSvg = utils.loadSvg(Constants.thumbAudioSvgData, self.recd.colorStroke.hex, self.recd.colorFill.hex)
- pb = thumbAudioSvg.get_pixbuf()
- else:
- pb = Constants.thumbAudioSvg.get_pixbuf()
-
- img.set_from_pixbuf(pb)
- img.show()
- ipb.composite(pb, xoff, yoff, ipb.get_width(), ipb.get_height(), xoff, yoff, 1, 1, gtk.gdk.INTERP_BILINEAR, 255)
- img.set_from_pixbuf(pb)
-
- gc.collect()
+class RecdButton(TrayButton):
+ __gsignals__ = {
+ 'remove-requested': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+ 'copy-clipboard-requested': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+ }
- return img
-
-
- def setButtClickedId( self, id ):
- self.BUTT_CLICKED_ID = id
+ def __init__(self, recd):
+ super(RecdButton, self).__init__()
+ self._recd = recd
+ self.set_icon_widget(self.get_image())
+ self._copy_menu_item_handler = None
- def getButtClickedId( self ):
- return self.BUTT_CLICKED_ID
-
-
- def setup_rollover_options( self, info ):
- palette = Palette(info)
+ palette = Palette(recd.title)
self.set_palette(palette)
- self.rem_menu_item = gtk.MenuItem( Constants.istrRemove )
- self.ACTIVATE_REMOVE_ID = self.rem_menu_item.connect('activate', self._itemRemoveCb)
- palette.menu.append(self.rem_menu_item)
- self.rem_menu_item.show()
-
- self.addCopyMenuItem()
+ self._rem_menu_item = gtk.MenuItem(_('Remove'))
+ self._rem_menu_item_handler = self._rem_menu_item.connect('activate', self._remove_clicked)
+ palette.menu.append(self._rem_menu_item)
+ self._rem_menu_item.show()
+ self._add_copy_menu_item()
- def addCopyMenuItem( self ):
- if (self.recd.buddy and not self.recd.downloadedFromBuddy):
+ def _add_copy_menu_item( self ):
+ if self._recd.buddy and not self._recd.downloadedFromBuddy:
return
- if (self.ACTIVATE_COPY_ID != 0):
- return
-
- self.copy_menu_item = gtk.MenuItem( Constants.istrCopyToClipboard )
- self.ACTIVATE_COPY_ID = self.copy_menu_item.connect('activate', self._itemCopyToClipboardCb)
- self.get_palette().menu.append(self.copy_menu_item)
- self.copy_menu_item.show()
-
- def cleanUp( self ):
- self.rem_menu_item.disconnect( self.ACTIVATE_REMOVE_ID )
- if (self.ACTIVATE_COPY_ID != 0):
- self.copy_menu_item.disconnect( self.ACTIVATE_COPY_ID )
+ self._copy_menu_item = gtk.MenuItem(_('Copy to clipboard'))
+ self._copy_menu_item_handler = self._copy_menu_item.connect('activate', self._copy_clipboard_clicked)
+ self.get_palette().menu.append(self._copy_menu_item)
+ self._copy_menu_item.show()
+
+ def get_recd(self):
+ return self._recd
+ def get_image(self):
+ img = gtk.Image()
+ ipb = self._recd.getThumbPixbuf()
+ if self._recd.type == constants.TYPE_PHOTO:
+ path = 'object-photo.svg'
+ elif self._recd.type == constants.TYPE_VIDEO:
+ path = 'object-video.svg'
+ elif self._recd.type == constants.TYPE_AUDIO:
+ path = 'object-audio.svg'
+
+ pixbuf = utils.load_colored_svg(path, self._recd.colorStroke, self._recd.colorFill)
+ ipb.composite(pixbuf, 8, 8, ipb.get_width(), ipb.get_height(), 8, 8, 1, 1, gtk.gdk.INTERP_BILINEAR, 255)
+ img.set_from_pixbuf(pixbuf)
+ img.show()
+ return img
- def _itemRemoveCb(self, widget):
- self.ui.deleteThumbSelection( self.recd )
+ def cleanup(self):
+ self._rem_menu_item.disconnect(self._rem_menu_item_handler)
+ if self._copy_menu_item_handler != None:
+ self._copy_menu_item.disconnect(self._copy_menu_item_handler)
+ def _remove_clicked(self, widget):
+ self.emit('remove-requested')
- def _itemCopyToClipboardCb(self, widget):
- self.ui.copyToClipboard( self.recd ) \ No newline at end of file
+ def _copy_clipboard_clicked(self, widget):
+ self.emit('copy-clipboard-requested')
diff --git a/collab.py b/collab.py
new file mode 100644
index 0000000..34a0c21
--- /dev/null
+++ b/collab.py
@@ -0,0 +1,339 @@
+import logging
+import xml.dom.minidom
+import os
+
+import gobject
+import telepathy
+import telepathy.client
+
+from sugar.presence import presenceservice
+from sugar.presence.tubeconn import TubeConnection
+from sugar import util
+
+import utils
+import serialize
+import constants
+from instance import Instance
+from recordtube import RecordTube
+from recorded import Recorded
+
+logger = logging.getLogger('collab')
+
+class RecordCollab(object):
+ def __init__(self, activity_obj, model):
+ self.activity = activity_obj
+ self.model = model
+ self._tube = None
+ self._collab_timeout = 10000
+
+ def set_activity_shared(self):
+ self._setup()
+ self._tubes_channel.OfferDBusTube(constants.DBUS_SERVICE, {})
+
+ def share_recd(self, recd):
+ if not self._tube:
+ return
+ xmlstr = serialize.getRecdXmlMeshString(recd)
+ self._tube.notifyBudsOfNewRecd(Instance.keyHashPrintable, xmlstr)
+
+ def joined(self):
+ if not self.activity.get_shared_activity():
+ return
+ self._setup()
+ self._tubes_channel.ListTubes(reply_handler=self._list_tubes_reply_cb, error_handler=self._list_tubes_error_cb)
+
+ def request_download(self, recd):
+ if recd.meshDownloading:
+ logger.debug("meshInitRoundRobin: we are in midst of downloading this file...")
+ return
+
+ # start with who took the photo
+ recd.triedMeshBuddies = []
+ recd.triedMeshBuddies.append(Instance.keyHashPrintable)
+ self._req_recd_from_buddy(recd, recd.recorderHash, recd.recorderName)
+
+ def _list_tubes_reply_cb(self, tubes):
+ for tube_info in tubes:
+ self._new_tube_cb(*tube_info)
+
+ @staticmethod
+ def _list_tubes_error_cb(e):
+ logger.error('ListTubes() failed: %s', e)
+
+ def _setup(self):
+ # sets up the tubes...
+ if not self.activity.get_shared_activity():
+ logger.error('_setup: Failed to share or join activity')
+ return
+
+ pservice = presenceservice.get_instance()
+ try:
+ name, path = pservice.get_preferred_connection()
+ self._connection = telepathy.client.Connection(name, path)
+ except:
+ logger.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.activity._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:
+ logger.debug('Found our room: it has handle#%d "%s"', handle, self._connection.InspectHandles(htype, [handle])[0])
+ room = handle
+ ctype = channel.GetChannelType()
+ if ctype == telepathy.CHANNEL_TYPE_TUBES:
+ logger.debug('Found our Tubes channel at %s', channel_path)
+ tubes_chan = channel
+ elif ctype == telepathy.CHANNEL_TYPE_TEXT:
+ logger.debug('Found our Text channel at %s', channel_path)
+ text_chan = channel
+
+ if not room:
+ logger.error("Presence service didn't create a room")
+ return
+ if not text_chan:
+ logger.error("Presence service didn't create a text channel")
+ return
+
+ # Make sure we have a Tubes channel - PS doesn't yet provide one
+ if not tubes_chan:
+ logger.debug("Didn't find our Tubes channel, requesting one...")
+ tubes_chan = self._connection.request_channel(telepathy.CHANNEL_TYPE_TUBES, telepathy.HANDLE_TYPE_ROOM, room, True)
+
+ self._tubes_channel = tubes_chan[telepathy.CHANNEL_TYPE_TUBES]
+ self._text_channel = text_chan[telepathy.CHANNEL_INTERFACE_GROUP]
+
+ self._tubes_channel.connect_to_signal('NewTube', self._new_tube_cb)
+
+ def _new_tube_cb(self, id, initiator, type, service, params, state):
+ logger.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 or service != constants.DBUS_SERVICE:
+ return
+
+ if state == telepathy.TUBE_STATE_LOCAL_PENDING:
+ self._tubes_channel.AcceptDBusTube(id)
+ tube_connection = TubeConnection(self._connection, self._tubes_channel, id, group_iface=self._text_channel)
+ self._tube = RecordTube(tube_connection)
+ self._tube.connect("new-recd", self._new_recd_cb)
+ self._tube.connect("recd-request", self._recd_request_cb)
+ self._tube.connect("recd-bits-arrived", self._recd_bits_arrived_cb)
+ self._tube.connect("recd-unavailable", self._recd_unavailable_cb)
+
+ def _new_recd_cb(self, remote_object, recorder, xmlstr):
+ logger.debug('new_recd_cb')
+ dom = None
+ try:
+ dom = xml.dom.minidom.parseString(xmlstr)
+ except:
+ logger.error('Unable to parse mesh xml')
+ if not dom:
+ return
+
+ recd = Recorded()
+ recd = serialize.fillRecdFromNode(recd, dom.documentElement)
+ if not recd:
+ logger.debug('_newRecdCb: recd is None. Unable to parse XML')
+ return
+
+ logger.debug('_newRecdCb: adding new recd thumb')
+ recd.buddy = True
+ recd.downloadedFromBuddy = False
+ self.model.add_recd(recd)
+
+ def _req_recd_from_buddy(self, recd, sender, nick):
+ recd.triedMeshBuddies.append(sender)
+ recd.meshDownloadingFrom = sender
+ recd.meshDownloadingFromNick = nick
+ recd.meshDownloadingProgress = False
+ recd.meshDownloading = True
+ recd.meshDownlodingPercent = 0.0
+ self.activity.update_download_progress(recd)
+ recd.meshReqCallbackId = gobject.timeout_add(self._collab_timeout, self._check_recd_request, recd)
+ self._tube.requestRecdBits(Instance.keyHashPrintable, sender, recd.mediaMd5)
+
+ def _next_round_robin_buddy(self, recd):
+ logger.debug('meshNextRoundRobinBuddy')
+ if recd.meshReqCallbackId:
+ gobject.source_remove(recd.meshReqCallbackId)
+ recd.meshReqCallbackId = 0
+
+ # delete any stub of a partially downloaded file
+ path = recd.getMediaFilepath()
+ if path and os.path.exists(path):
+ os.remove(path)
+
+ good_buddy_obj = None
+ buds = self.activity._shared_activity.get_joined_buddies()
+ for buddy_obj in buds:
+ buddy = util.sha_data(buddy_obj.props.key)
+ buddy = util.printable_hash(buddy)
+ if recd.triedMeshBuddies.count(buddy) > 0:
+ logger.debug('mnrrb: weve already tried bud ' + buddy_obj.props.nick)
+ else:
+ logger.debug('mnrrb: ask next buddy: ' + buddy_obj.props.nick)
+ good_buddy_obj = buddy_obj
+ break
+
+ if good_buddy_obj:
+ buddy = util.sha_data(good_buddy_obj.props.key)
+ buddy = util.printable_hash(buddy)
+ self._req_recd_from_buddy(recd, buddy, good_buddy_obj.props.nick)
+ else:
+ logger.debug('weve tried all buddies here, and no one has this recd')
+ recd.meshDownloading = False
+ recd.triedMeshBuddies = []
+ recd.triedMeshBuddies.append(Instance.keyHashPrintable)
+ self.activity.update_download_progress(recd)
+
+ def _recd_request_cb(self, remote_object, remote_person, md5sum):
+ #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.model.get_recd_by_md5(md5sum)
+ if not recd:
+ logger.debug('_recdRequestCb: we dont have the recd they asked for')
+ self._tube.unavailableRecd(md5sum, Instance.keyHashPrintable, remote_person)
+ return
+
+ if recd.deleted:
+ logger.debug('_recdRequestCb: we have the recd, but it has been deleted, so we wont share')
+ self._tube.unavailableRecd(md5sum, Instance.keyHashPrintable, remote_person)
+ return
+
+ if recd.buddy and not recd.downloadedFromBuddy:
+ logger.debug('_recdRequestCb: we have an incomplete recd, so we wont share')
+ self._tube.unavailableRecd(md5sum, Instance.keyHashPrintable, remote_person)
+ return
+
+ recd.meshUploading = True
+ path = recd.getMediaFilepath()
+
+ if recd.type == constants.TYPE_AUDIO:
+ audioImgFilepath = recd.getAudioImageFilepath()
+
+ dest_path = os.path.join(Instance.instancePath, "audioBundle")
+ dest_path = utils.getUniqueFilepath(dest_path, 0)
+ cmd = "cat " + path + " " + audioImgFilepath + " > " + dest_path
+ logger.debug(cmd)
+ os.system(cmd)
+ path = dest_path
+
+ self._tube.broadcastRecd(recd.mediaMd5, path, remote_person)
+ recd.meshUploading = False
+ #if you were deleted while uploading, now throw away those bits now
+ if recd.deleted:
+ recd.doDeleteRecorded(recd)
+
+ def _check_recd_request(self, recd):
+ #todo: add category for "not active activity, so go ahead and delete"
+
+ if recd.downloadedFromBuddy:
+ logger.debug('_meshCheckOnRecdRequest: recdRequesting.downloadedFromBuddy')
+ if recd.meshReqCallbackId:
+ gobject.source_remove(recd.meshReqCallbackId)
+ recd.meshReqCallbackId = 0
+ return False
+ if recd.deleted:
+ logger.debug('_meshCheckOnRecdRequest: recdRequesting.deleted')
+ if recd.meshReqCallbackId:
+ gobject.source_remove(recd.meshReqCallbackId)
+ recd.meshReqCallbackId = 0
+ return False
+ if recd.meshDownloadingProgress:
+ logger.debug('_meshCheckOnRecdRequest: recdRequesting.meshDownloadingProgress')
+ #we've received some bits since last we checked, so keep waiting... they'll all get here eventually!
+ recd.meshDownloadingProgress = False
+ return True
+ else:
+ logger.debug('_meshCheckOnRecdRequest: ! recdRequesting.meshDownloadingProgress')
+ #that buddy we asked info from isn't responding; next buddy!
+ #self.meshNextRoundRobinBuddy( recdRequesting )
+ gobject.idle_add(self._next_round_robin_buddy, recd)
+ return False
+
+ def _recd_bits_arrived_cb(self, remote_object, md5sum, part, num_parts, bytes, sender):
+ recd = self.model.get_recd_by_md5(md5sum)
+ if not recd:
+ logger.debug('_recdBitsArrivedCb: thx 4 yr bits, but we dont even have that photo')
+ return
+ if recd.deleted:
+ logger.debug('_recdBitsArrivedCb: thx 4 yr bits, but we deleted that photo')
+ return
+ if recd.downloadedFromBuddy:
+ logger.debug('_recdBitsArrivedCb: weve already downloadedFromBuddy')
+ return
+ if not recd.buddy:
+ logger.debug('_recdBitsArrivedCb: uh, we took this photo, so dont need your bits')
+ return
+ if recd.meshDownloadingFrom != sender:
+ logger.debug('_recdBitsArrivedCb: wrong bits ' + str(sender) + ", exp:" + str(recd.meshDownloadingFrom))
+ return
+
+ #update that we've heard back about this, reset the timeout
+ gobject.source_remove(recd.meshReqCallbackId)
+ recd.meshReqCallbackId = gobject.timeout_add(self._collab_timeout, self._check_recd_request, recd)
+
+ #update the progress bar
+ recd.meshDownlodingPercent = (part+0.0)/(num_parts+0.0)
+ recd.meshDownloadingProgress = True
+ self.activity.update_download_progress(recd)
+ open(recd.getMediaFilepath(), 'a+').write(bytes)
+
+ if part > num_parts:
+ logger.error('More parts than required have arrived')
+ return
+ if part != num_parts:
+ return
+
+ logger.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:
+ path = recd.getMediaFilepath()
+ bundle_path = os.path.join(Instance.instancePath, "audioBundle")
+ bundle_path = utils.getUniqueFilepath(bundle_path, 0)
+
+ cmd = "split -a 1 -b " + str(recd.mediaBytes) + " " + path + " " + bundle_path
+ logger.debug(cmd)
+ os.system(cmd)
+
+ bundle_name = os.path.basename(bundle_path)
+ media_filename = bundle_name + "a"
+ media_path = os.path.join(Instance.instancePath, media_filename)
+ media_path_ext = os.path.join(Instance.instancePath, media_filename+".ogg")
+ os.rename(media_path, media_path_ext)
+ audio_image_name = bundle_name + "b"
+ audio_image_path = os.path.join(Instance.instancePath, audio_image_name)
+ audio_image_path_ext = os.path.join(Instance.instancePath, audio_image_name+".png")
+ os.rename(audio_image_path, audio_image_path_ext)
+
+ recd.mediaFilename = os.path.basename(media_path_ext)
+ recd.audioImageFilename = os.path.basename(audio_image_path_ext)
+
+ self.activity.remote_recd_available(recd)
+
+ def _recd_unavailable_cb(self, remote_object, md5sum, sender):
+ logger.debug('_recdUnavailableCb: sux, we want to see that photo')
+ recd = self.model.get_recd_by_md5(md5sum)
+ if not recd:
+ logger.debug('_recdUnavailableCb: actually, we dont even know about that one..')
+ return
+ if recd.deleted:
+ logger.debug('_recdUnavailableCb: actually, since we asked, we deleted.')
+ return
+ if not recd.buddy:
+ logger.debug('_recdUnavailableCb: uh, odd, we took that photo and have it already.')
+ return
+ if recd.downloadedFromBuddy:
+ logger.debug('_recdUnavailableCb: we already downloaded it... you might have been slow responding.')
+ return
+ if recd.meshDownloadingFrom != sender:
+ logger.debug('_recdUnavailableCb: we arent asking you for a copy now. slow response, pbly.')
+ return
+
diff --git a/color.py b/color.py
deleted file mode 100644
index f544405..0000000
--- a/color.py
+++ /dev/null
@@ -1,71 +0,0 @@
-#Copyright (c) 2008, Media Modifications Ltd.
-
-#Permission is hereby granted, free of charge, to any person obtaining a copy
-#of this software and associated documentation files (the "Software"), to deal
-#in the Software without restriction, including without limitation the rights
-#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-#copies of the Software, and to permit persons to whom the Software is
-#furnished to do so, subject to the following conditions:
-
-#The above copyright notice and this permission notice shall be included in
-#all copies or substantial portions of the Software.
-
-#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-#THE SOFTWARE.
-
-import gtk
-
-class Color:
-
- def __init__(self):
- pass
-
-
- def init_rgba(self, r, g, b, a):
- self._ro = r
- self._go = g
- self._bo = b
- self._ao = a;
- self._r = self._ro / 255.0
- self._g = self._go / 255.0
- self._b = self._bo / 255.0
- self._a = self._ao / 255.0
-
- self._opaque = False
- if (self._a == 1):
- self.opaque = True
-
- rgb_tup = ( self._ro, self._go, self._bo )
- self.hex = self.rgb_to_hex( rgb_tup )
- self.gColor = gtk.gdk.color_parse( self.hex )
-
-
- def init_gdk(self, col):
- self.init_hex( col.get_html() )
-
-
- def init_hex(self, hex):
- cTup = self.hex_to_rgb( hex )
- self.init_rgba( cTup[0], cTup[1], cTup[2], 255 )
-
-
- def get_int(self):
- return int(self._a * 255) + (int(self._b * 255) << 8) + (int(self._g * 255) << 16) + (int(self._r * 255) << 24)
-
-
- def rgb_to_hex(self, rgb_tup):
- hexcolor = '#%02x%02x%02x' % rgb_tup
- return hexcolor
-
-
- def hex_to_rgb(self, h):
- c = eval('0x' + h[1:])
- r = (c >> 16) & 0xFF
- g = (c >> 8) & 0xFF
- b = c & 0xFF
- return (int(r), int(g), int(b)) \ No newline at end of file
diff --git a/constants.py b/constants.py
index 1c39e97..9b0ed4a 100644
--- a/constants.py
+++ b/constants.py
@@ -1,355 +1,46 @@
# -*- coding: UTF-8 -*-
import os
-import gtk
-from gettext import gettext as gt
-from gettext import ngettext
-import hippo
+from gettext import gettext as _
-import sugar.graphics.style
from sugar.activity import activity
-from instance import Instance
-from sugar import profile
-from color import Color
-import utils
-import cairo
-import pango
-import pangocairo
+MODE_PHOTO = 0
+MODE_VIDEO = 1
+MODE_AUDIO = 2
+TYPE_PHOTO = MODE_PHOTO
+TYPE_VIDEO = MODE_VIDEO
+TYPE_AUDIO = MODE_AUDIO
+
+STATE_READY = 0
+STATE_RECORDING = 1
+STATE_PROCESSING = 2
+STATE_DOWNLOADING = 3
+
+MEDIA_INFO = {}
+MEDIA_INFO[TYPE_PHOTO] = {
+ 'name' : 'photo',
+ 'mime' : 'image/jpeg',
+ 'ext' : 'jpg',
+ 'istr' : _('Photo')
+}
+
+MEDIA_INFO[TYPE_VIDEO] = {
+ 'name' : 'video',
+ 'mime' : 'video/ogg',
+ 'ext' : 'ogg',
+ 'istr' : _('Video')
+}
+
+MEDIA_INFO[TYPE_AUDIO] = {
+ 'name' : 'audio',
+ 'mime' :'audio/ogg',
+ 'ext' : 'ogg',
+ 'istr' : _('Audio')
+}
+
+DBUS_SERVICE = "org.laptop.Record"
+DBUS_IFACE = DBUS_SERVICE
+DBUS_PATH = "/org/laptop/Record"
+
+GFX_PATH = os.path.join(activity.get_bundle_path(), "gfx")
-
-def istrMinutes(x):
- return ngettext('%(1)s minute', '%(1)s minutes', int(x)) % {'1': int(x)}
-
-
-def istrSeconds(x):
- return ngettext('%(1)s second', '%(1)s seconds', int(x)) % {'1': int(x)}
-
-
-class Constants:
-
- VERSION = 54
-
- SERVICE = "org.laptop.Record"
- IFACE = SERVICE
- PATH = "/org/laptop/Record"
- activityId = None
-
- recdTitle = "title"
- recdTags = "tags"
- recdTime = "time"
- recdRecorderName = "photographer"
- recdRecorderHash = "recorderHash"
- recdColorStroke = "colorStroke"
- recdColorFill = "colorFill"
- recdHashKey = "hashKey"
- recdBuddy = "buddy"
- recdMediaMd5 = "mediaMd5"
- recdThumbMd5 = "thumbMd5"
- recdMediaBytes = "mediaBytes"
- recdThumbBytes = "thumbBytes"
- recdBase64Thumb = "base64Thumb"
- recdDatastoreId = "datastoreId"
- recdAudioImage = "audioImage"
- recdAlbum = "album"
- recdType = "type"
- recdRecd = "recd"
- recdRecordVersion = "version"
-
- keyName = "name"
- keyMime = "mime"
- keyExt = "ext"
- keyIstr = "istr"
-
- MODE_PHOTO = 0
- MODE_VIDEO = 1
- MODE_AUDIO = 2
- TYPE_PHOTO = MODE_PHOTO
- TYPE_VIDEO = MODE_VIDEO
- TYPE_AUDIO = MODE_AUDIO
-
- TIMER_0 = 0
- TIMER_5 = 5
- TIMER_10 = 10
- TIMERS = []
- TIMERS.append(TIMER_0)
- TIMERS.append(TIMER_5)
- TIMERS.append(TIMER_10)
-
- DURATION_2 = 2
- DURATION_4 = 4
- DURATION_6 = 6
- DURATIONS = []
- DURATIONS.append(DURATION_2)
- DURATIONS.append(DURATION_4)
- DURATIONS.append(DURATION_6)
-
- colorBlack = Color()
- colorBlack.init_rgba( 0, 0, 0, 255 )
- colorWhite = Color()
- colorWhite.init_rgba( 255, 255, 255, 255 )
- colorRed = Color()
- colorRed.init_rgba( 255, 0, 0, 255)
- colorGreen = Color()
- colorGreen.init_rgba( 0, 255, 0, 255)
- colorBlue = Color()
- colorBlue.init_rgba( 0, 0, 255, 255)
- colorButton = Color()
- colorButton.init_gdk( sugar.graphics.style.COLOR_BUTTON_GREY )
-
- gfxPath = os.path.join(activity.get_bundle_path(), "gfx")
- soundClick = os.path.join(gfxPath, 'photoShutter.wav')
-
-
- #defensive method against variables not translated correctly
- def _(s):
- #todo: permanent variable
- istrsTest = {}
- for i in range (0,4):
- istrsTest[str(i)] = str(i)
-
- i = s
- try:
- #test translating the string with many replacements
- i = gt(s)
- test = i % istrsTest
- except:
- #if it doesn't work, revert
- i = s
-
- return i
-
-
- istrActivityName = _('Record')
- istrPhoto = _('Photo')
- istrVideo = _('Video')
- istrAudio = _('Audio')
- istrTimelapse = _('Time Lapse')
- istrAnimation = _('Animation')
- istrPanorama = _('Panorama')
- istrInterview= _('Interview')
- #TRANS: photo by photographer, e.g., "Photo by Mary"
- istrBy = _("%(1)s by %(2)s")
- istrTitle = _('Title:')
- istrRecorder = _('Recorder:')
- istrDate = _('Date:')
- istrTags = _('Tags:')
- istrSaving = _('Saving')
- istrFinishedRecording = _("Finished recording")
- istrRemove = _("Remove")
- istrStoppedRecording = _("Stopped recording")
- istrCopyToClipboard = _("Copy to clipboard")
- istrTimer = _("Timer:")
- istrDuration = _("Duration:")
- istrRemaining = _("Remaining:")
- istrNow = _("Immediate")
- istrPlay = _("Play")
- istrPause = _("Pause")
- istrAddFrame = _("Add frame")
- istrRemoveFrame = _("Remove frame")
- istrFramesPerSecond = _("%(1)s frames per second")
- istrQuality = _("Quality")
- istrDefault = _("Default")
- istrHighQuality = _("High")
- istrLowQuality = _("Low")
- istrLargeFile = _("Large file")
- istrSmallFile = _("Small file")
- istrSilent = _("Silent")
- istrRotate = _("Rotate")
- istrWidth = _("Width")
- istrHeight = _("Height")
- istrClickToTakePicture = _("Click to take picture")
- istrClickToAddPicture = _("Click to add picture")
- #TRANS: Downloading Photo from Mary
- istrDownloadingFrom = _("Downloading %(1)s from %(2)s")
- #TRANS: Cannot download this Photo
- istrCannotDownload = _("Cannot download this %(1)s")
- #TRANS: Save Photo to:
- istrSaveTo = _("Save %(1)s to:")
- istrYourDiskIsFull = _("Your %(1)s is full")
- istrJournal = _("Journal")
- istrUSB = _("USB")
- istrCompactFlash = _("SD Card")
- istrPreferences = _("Preferences")
- istrFreeSpace = _("Free space:")
- #TRANS: 7 photos
- istrBitrate = _("Bitrate")
- istrMaxBitrate = _("Maximum Bitrate")
- istrMinBitrate = _("Minumum Bitrate")
- istrManageBitrate = _("Manage Bitrate")
- istrBorder = _("Border")
- istrCenter = _("Center")
- istrFrames = _("Frames")
- istrKeyframeAuto = _("Automatic keyframe detection")
- istrKeyframeForce = _("Force keyframe")
- istrKeyframeFrequency = _("Keyframe frequency")
- istrKeyframeMinDist = _("Keyframe minimum distance")
- istrKeyframeThreshold = _("Keyframe threshold")
- istrNoiseSensitivity = _("Noise Sensitivity")
- istrQuick = _("Quick")
- istrSharpness = _("Sharpness")
- istrCapacity = _("Capacity")
-
- mediaTypes = {}
- mediaTypes[TYPE_PHOTO] = {keyName:"photo", keyMime:"image/jpeg", keyExt:"jpg", keyIstr:istrPhoto}
- mediaTypes[TYPE_VIDEO] = {keyName:"video", keyMime:"video/ogg", keyExt:"ogg", keyIstr:istrVideo}
- mediaTypes[TYPE_AUDIO] = {keyName:"audio", keyMime:"audio/ogg", keyExt:"ogg", keyIstr:istrAudio}
-
- thumbPhotoSvgData = None
- thumbPhotoSvg = None
- thumbVideoSvg = None
- maxEnlargeSvg = None
- maxReduceSvg = None
- infoOnSvg = None
- xoGuySvgData = None
-
- recImg = None
- recRedImg = None
- recCircleCairo = None
- recInsensitiveImg = None
- recPlayImg = None
- recPauseImg = None
- countdownImgs = {}
-
- dim_CONTROLBAR_HT = 55
-
- keepFreeKbOnXo = 100000
-
- def __init__( self, ca ):
- self.__class__.activityId = ca._activity_id
-
- thumbPhotoSvgPath = os.path.join(self.__class__.gfxPath, 'object-photo.svg')
- thumbPhotoSvgFile = open(thumbPhotoSvgPath, 'r')
- self.__class__.thumbPhotoSvgData = thumbPhotoSvgFile.read()
- self.__class__.thumbPhotoSvg = utils.loadSvg(self.__class__.thumbPhotoSvgData, Instance.colorStroke.hex, Instance.colorFill.hex)
- thumbPhotoSvgFile.close()
-
- thumbVideoSvgPath = os.path.join(self.__class__.gfxPath, 'object-video.svg')
- thumbVideoSvgFile = open(thumbVideoSvgPath, 'r')
- self.__class__.thumbVideoSvgData = thumbVideoSvgFile.read()
- self.__class__.thumbVideoSvg = utils.loadSvg(self.__class__.thumbVideoSvgData, Instance.colorStroke.hex, Instance.colorFill.hex)
- thumbVideoSvgFile.close()
-
- thumbAudioSvgPath = os.path.join(self.__class__.gfxPath, 'object-audio.svg')
- thumbAudioSvgFile = open(thumbAudioSvgPath, 'r')
- self.__class__.thumbAudioSvgData = thumbAudioSvgFile.read()
- self.__class__.thumbAudioSvg = utils.loadSvg(self.__class__.thumbAudioSvgData, Instance.colorStroke.hex, Instance.colorFill.hex)
- thumbAudioSvgFile.close()
-
- maxEnlargeSvgPath = os.path.join(self.__class__.gfxPath, 'max-enlarge.svg')
- maxEnlargeSvgFile = open(maxEnlargeSvgPath, 'r')
- maxEnlargeSvgData = maxEnlargeSvgFile.read()
- self.__class__.maxEnlargeSvg = utils.loadSvg(maxEnlargeSvgData, None, None )
- maxEnlargeSvgFile.close()
-
- maxReduceSvgPath = os.path.join(self.__class__.gfxPath, 'max-reduce.svg')
- maxReduceSvgFile = open(maxReduceSvgPath, 'r')
- maxReduceSvgData = maxReduceSvgFile.read()
- self.__class__.maxReduceSvg = utils.loadSvg(maxReduceSvgData, None, None )
- maxReduceSvgFile.close()
-
- infoOnSvgPath = os.path.join(self.__class__.gfxPath, 'corner-info.svg')
- infoOnSvgFile = open(infoOnSvgPath, 'r')
- infoOnSvgData = infoOnSvgFile.read()
- self.__class__.infoOnSvg = utils.loadSvg(infoOnSvgData, None, None )
- infoOnSvgFile.close()
-
- xoGuySvgPath = os.path.join(self.__class__.gfxPath, 'xo-guy.svg')
- xoGuySvgFile = open(xoGuySvgPath, 'r')
- self.__class__.xoGuySvgData = xoGuySvgFile.read()
- xoGuySvgFile.close()
-
- recFile = os.path.join(self.__class__.gfxPath, 'media-record.png')
- recPixbuf = gtk.gdk.pixbuf_new_from_file(recFile)
- self.__class__.recImg = gtk.Image()
- self.__class__.recImg.set_from_pixbuf( recPixbuf )
-
- recRedFile = os.path.join(self.__class__.gfxPath, 'media-record-red.png')
- recRedPixbuf = gtk.gdk.pixbuf_new_from_file(recRedFile)
- self.__class__.recRedImg = gtk.Image()
- self.__class__.recRedImg.set_from_pixbuf( recRedPixbuf )
-
- recCircleFile = os.path.join(self.__class__.gfxPath, 'media-circle.png')
- recCirclePixbuf = gtk.gdk.pixbuf_new_from_file(recCircleFile)
- self.__class__.recCircleCairo = hippo.cairo_surface_from_gdk_pixbuf(recCirclePixbuf)
-
- recInsFile = os.path.join(self.__class__.gfxPath, 'media-insensitive.png')
- recInsPixbuf = gtk.gdk.pixbuf_new_from_file(recInsFile)
- self.__class__.recInsensitiveImg = gtk.Image()
- self.__class__.recInsensitiveImg.set_from_pixbuf( recInsPixbuf )
-
- fullInsFile = os.path.join(self.__class__.gfxPath, 'full-insensitive.png')
- fullInsPixbuf = gtk.gdk.pixbuf_new_from_file(fullInsFile)
- self.__class__.fullInsensitiveImg = gtk.Image()
- self.__class__.fullInsensitiveImg.set_from_pixbuf( fullInsPixbuf )
-
- recPlayFile = os.path.join(self.__class__.gfxPath, 'media-play.png')
- recPlayPixbuf = gtk.gdk.pixbuf_new_from_file(recPlayFile)
- self.__class__.recPlayImg = gtk.Image()
- self.__class__.recPlayImg.set_from_pixbuf( recPlayPixbuf )
-
- recPauseFile = os.path.join(self.__class__.gfxPath, 'media-pause.png')
- recPausePixbuf = gtk.gdk.pixbuf_new_from_file(recPauseFile)
- self.__class__.recPauseImg = gtk.Image()
- self.__class__.recPauseImg.set_from_pixbuf( recPausePixbuf )
-
- self._ts = self.__class__.TIMERS
- longestTime = self._ts[len(self._ts)-1]
- for i in range (0, longestTime):
- self.createCountdownPng( i )
-
-
- def createCountdownPng(self, num):
- todisk = True
-
- rendered = False
- if (todisk):
- path = os.path.join(Instance.dataPath, str(num)+".png")
- if (os.path.exists(path)):
- rendered = True
-
-
- if (not rendered):
- w = self.__class__.dim_CONTROLBAR_HT
- h = w
- if (todisk):
- cimg = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
- ctx = cairo.Context(cimg)
- else:
- pixmap = gtk.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)
- ctx.set_source_surface (self.__class__.recCircleCairo, 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)
-
- img = gtk.Image()
- if (todisk):
- path = os.path.join(Instance.dataPath, str(num)+".png")
- if (not rendered):
- path = utils.getUniqueFilepath(path, 0)
- cimg.write_to_png(path)
- numPixbuf = gtk.gdk.pixbuf_new_from_file(path)
- img.set_from_pixbuf( numPixbuf )
- else:
- img.set_from_pixmap(pixmap, None)
-
- self.__class__.countdownImgs[int(num)] = img
diff --git a/gfx/full-insensitive.png b/gfx/full-insensitive.png
deleted file mode 100644
index 82c833a..0000000
--- a/gfx/full-insensitive.png
+++ /dev/null
Binary files differ
diff --git a/gfx/info-on.svg b/gfx/info-on.svg
deleted file mode 100644
index 678784a..0000000
--- a/gfx/info-on.svg
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- width="48.5px" height="48.5px" viewBox="0 0 48.5 48.5" enable-background="new 0 0 48.5 48.5" xml:space="preserve">
-
-<g>
- <rect x="1.75" y="1.75" fill="#808384" stroke="#FFFFFF" stroke-width="3.5" width="45" height="45"/>
- <g>
- <path fill="#FFFFFF" d="M23.7,32.6c-0.7,1.8-0.9,2.6-0.9,3.1c0,0.4,0.2,0.7,0.6,0.7c1-0.1,3.5-2.7,3.7-3.1c0.1-0.2,0.3-0.2,0.4,0
- l0.5,0.5c0.2,0.2,0.3,0.3,0.1,0.5c-0.3,0.4-3.6,5-8.9,5c-1,0-1.4-0.6-1.4-1.3c0-1,0.8-3.2,2.7-8.5l1.3-3.7
- c0.5-1.3,0.8-2.2,0.8-2.8c0-0.4-0.2-0.6-0.6-0.6c-0.8,0-3,2.5-3.6,3.3c-0.2,0.2-0.4,0.2-0.6,0l-0.5-0.4
- c-0.2-0.1-0.3-0.2-0.2-0.4c0.3-0.3,4.6-5.9,8.8-5.9c1.2,0,1.7,0.6,1.7,1.5s-0.6,2.7-1.4,5L23.7,32.6z M24.5,10.4
- c0-1.3,1.2-2.6,2.7-2.6c1.9,0,2.9,1.2,2.9,2.4c0,1.1-1.1,2.8-2.8,2.8C25.4,13,24.5,11.6,24.5,10.4z"/>
- </g>
-</g>
-</svg>
diff --git a/gfx/media-record.svg b/gfx/media-record.svg
deleted file mode 100644
index 0adb08f..0000000
--- a/gfx/media-record.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
- <!ENTITY stroke_color "#010101">
- <!ENTITY fill_color "#FFFFFF">
-]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="media-record">
- <path d="M27.497,5C15.073,5,4.999,15.075,4.999,27.5c0,12.427,10.074,22.5,22.498,22.5 c12.428,0,22.502-10.073,22.502-22.5C49.999,15.075,39.925,5,27.497,5z M27.501,35.389c-4.361,0-7.89-3.534-7.89-7.889 c0-4.356,3.528-7.889,7.89-7.889c4.357,0,7.889,3.532,7.889,7.889C35.39,31.854,31.858,35.389,27.501,35.389z" display="inline" fill="&fill_color;"/>
-</g></svg> \ No newline at end of file
diff --git a/glive.py b/glive.py
index 18f4cd4..4bfece9 100644
--- a/glive.py
+++ b/glive.py
@@ -18,18 +18,14 @@
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
-
import os
+from gettext import gettext as _
+import time
+
import gtk
-import pygtk
-pygtk.require('2.0')
-import sys
import gst
-import gst.interfaces
import pygst
pygst.require('0.10')
-import time
-import threading
import gobject
gobject.threads_init()
@@ -37,12 +33,10 @@ from sugar.activity.activity import get_bundle_path
import logging
from instance import Instance
-from constants import Constants
-import record
+import constants
import utils
-import ui
-logger = logging.getLogger('record:glive.py')
+logger = logging.getLogger('glive')
OGG_TRAITS = {
0: { 'width': 160, 'height': 120, 'quality': 16 },
@@ -51,70 +45,70 @@ OGG_TRAITS = {
THUMB_STUB = gtk.gdk.pixbuf_new_from_file(
os.path.join(get_bundle_path(), 'gfx', 'stub.png'))
-def _does_camera_present():
- v4l2src = gst.element_factory_make('v4l2src')
- if v4l2src.props.device_name is None:
- return False, False
-
- # Figure out if we can place a framerate limit on the v4l2 element, which
- # in theory will make it all the way down to the hardware.
- # ideally, we should be able to do this by checking caps. However, I can't
- # find a way to do this (at this time, XO-1 cafe camera driver doesn't
- # support framerate changes, but gstreamer caps suggest otherwise)
- pipeline = gst.Pipeline()
- caps = gst.Caps("video/x-raw-yuv,framerate=10/1")
- fsink = gst.element_factory_make("fakesink")
- pipeline.add(v4l2src, fsink)
- v4l2src.link(fsink, caps)
- can_limit_framerate = pipeline.set_state(gst.STATE_PAUSED) != gst.STATE_CHANGE_FAILURE
- pipeline.set_state(gst.STATE_NULL)
- return True, can_limit_framerate
-
-camera_presents, can_limit_framerate = _does_camera_present()
-
class Glive:
- def __init__(self, pca):
- self.window = None
- self.ca = pca
- self._eos_cb = None
-
- self.playing = False
- self.picExposureOpen = False
-
- self.AUDIO_TRANSCODE_ID = 0
- self.TRANSCODE_ID = 0
- self.VIDEO_TRANSCODE_ID = 0
+ PHOTO_MODE_PHOTO = 0
+ PHOTO_MODE_AUDIO = 1
- self.PHOTO_MODE_PHOTO = 0
- self.PHOTO_MODE_AUDIO = 1
+ def __init__(self, activity_obj, model):
+ self.activity = activity_obj
+ self.model = model
+ self._eos_cb = None
- self.TRANSCODE_UPDATE_INTERVAL = 200
+ self._has_camera = False
+ self._can_limit_framerate = False
+ self._playing = False
+ self._pic_exposure_open = False
+ self._thumb_exposure_open = False
+ self._photo_mode = self.PHOTO_MODE_PHOTO
+ self._audio_transcode_handler = None
+ self._transcode_id = None
+ self._video_transcode_handler = None
+ self._thumb_handoff_handler = None
- self.VIDEO_WIDTH_SMALL = 160
- self.VIDEO_HEIGHT_SMALL = 120
- self.VIDEO_FRAMERATE_SMALL = 10
+ self._audio_pixbuf = None
- self.VIDEO_WIDTH_LARGE = 200
- self.VIDEO_HEIGHT_LARGE = 150
- self.VIDEO_FRAMERATE_SMALL = 10
+ self._detect_camera()
- self.pipeline = gst.Pipeline("my-pipeline")
- self.createPhotoBin()
- self.createAudioBin()
- self.createVideoBin()
- self.createPipeline()
+ self._pipeline = gst.Pipeline("Record")
+ self._create_photobin()
+ self._create_audiobin()
+ self._create_videobin()
+ self._create_xbin()
+ self._create_pipeline()
- self.thumbPipes = []
- self.muxPipes = []
+ self._thumb_pipes = []
+ self._mux_pipes = []
- bus = self.pipeline.get_bus()
- bus.enable_sync_message_emission()
+ bus = self._pipeline.get_bus()
bus.add_signal_watch()
- self.SYNC_ID = bus.connect('sync-message::element', self._onSyncMessageCb)
- self.MESSAGE_ID = bus.connect('message', self._onMessageCb)
+ bus.connect('message', self._bus_message_handler)
+
+ def _detect_camera(self):
+ v4l2src = gst.element_factory_make('v4l2src')
+ if v4l2src.props.device_name is None:
+ return
- def createPhotoBin ( self ):
+ self._has_camera = True
+
+ # Figure out if we can place a framerate limit on the v4l2 element,
+ # which in theory will make it all the way down to the hardware.
+ # ideally, we should be able to do this by checking caps. However, I
+ # can't find a way to do this (at this time, XO-1 cafe camera driver
+ # doesn't support framerate changes, but gstreamer caps suggest
+ # otherwise)
+ pipeline = gst.Pipeline()
+ caps = gst.Caps('video/x-raw-yuv,framerate=10/1')
+ fsink = gst.element_factory_make('fakesink')
+ pipeline.add(v4l2src, fsink)
+ v4l2src.link(fsink, caps)
+ self._can_limit_framerate = pipeline.set_state(gst.STATE_PAUSED) != gst.STATE_CHANGE_FAILURE
+ pipeline.set_state(gst.STATE_NULL)
+
+ def get_has_camera(self):
+ return self._has_camera
+
+ def _create_photobin(self):
queue = gst.element_factory_make("queue", "pbqueue")
queue.set_property("leaky", True)
queue.set_property("max-size-buffers", 1)
@@ -123,18 +117,18 @@ class Glive:
jpeg = gst.element_factory_make("jpegenc", "pbjpeg")
sink = gst.element_factory_make("fakesink", "pbsink")
- self.HANDOFF_ID = sink.connect("handoff", self.copyPic)
+ sink.connect("handoff", self._photo_handoff)
sink.set_property("signal-handoffs", True)
- self.photobin = gst.Bin("photobin")
- self.photobin.add(queue, colorspace, jpeg, sink)
+ self._photobin = gst.Bin("photobin")
+ self._photobin.add(queue, colorspace, jpeg, sink)
gst.element_link_many(queue, colorspace, jpeg, sink)
pad = queue.get_static_pad("sink")
- self.photobin.add_pad(gst.GhostPad("sink", pad))
+ self._photobin.add_pad(gst.GhostPad("sink", pad))
- def createAudioBin ( self ):
+ def _create_audiobin(self):
src = gst.element_factory_make("alsasrc", "absrc")
# attempt to use direct access to the 0,0 device, solving some A/V
@@ -158,30 +152,30 @@ class Glive:
queue.set_property("leaky", True) # prefer fresh data
queue.set_property("max-size-time", 5000000000) # 5 seconds
queue.set_property("max-size-buffers", 500)
- queue.connect("overrun", self.log_queue_overrun)
+ queue.connect("overrun", self._log_queue_overrun)
enc = gst.element_factory_make("wavenc", "abenc")
sink = gst.element_factory_make("filesink", "absink")
sink.set_property("location", os.path.join(Instance.instancePath, "output.wav"))
- self.audiobin = gst.Bin("audiobin")
- self.audiobin.add(src, rate, queue, enc, sink)
+ self._audiobin = gst.Bin("audiobin")
+ self._audiobin.add(src, rate, queue, enc, sink)
src.link(rate, srccaps)
gst.element_link_many(rate, queue, enc, sink)
- def createVideoBin ( self ):
+ def _create_videobin(self):
queue = gst.element_factory_make("queue", "videoqueue")
queue.set_property("max-size-time", 5000000000) # 5 seconds
queue.set_property("max-size-bytes", 33554432) # 32mb
- queue.connect("overrun", self.log_queue_overrun)
+ queue.connect("overrun", self._log_queue_overrun)
scale = gst.element_factory_make("videoscale", "vbscale")
scalecapsfilter = gst.element_factory_make("capsfilter", "scalecaps")
- scalecaps = gst.Caps('video/x-raw-yuv,width='+str(self.VIDEO_WIDTH_SMALL)+',height='+str(self.VIDEO_HEIGHT_SMALL))
+ scalecaps = gst.Caps('video/x-raw-yuv,width=160,height=120')
scalecapsfilter.set_property("caps", scalecaps)
colorspace = gst.element_factory_make("ffmpegcolorspace", "vbcolorspace")
@@ -194,8 +188,8 @@ class Glive:
sink = gst.element_factory_make("filesink", "vbfile")
sink.set_property("location", os.path.join(Instance.instancePath, "output.ogg"))
- self.videobin = gst.Bin("videobin")
- self.videobin.add(queue, scale, scalecapsfilter, colorspace, enc, mux, sink)
+ self._videobin = gst.Bin("videobin")
+ self._videobin.add(queue, scale, scalecapsfilter, colorspace, enc, mux, sink)
queue.link(scale)
scale.link_pads(None, scalecapsfilter, "sink")
@@ -203,15 +197,31 @@ class Glive:
gst.element_link_many(colorspace, enc, mux, sink)
pad = queue.get_static_pad("sink")
- self.videobin.add_pad(gst.GhostPad("sink", pad))
+ self._videobin.add_pad(gst.GhostPad("sink", pad))
+
+ def _create_xbin(self):
+ scale = gst.element_factory_make("videoscale")
+ cspace = gst.element_factory_make("ffmpegcolorspace")
+ xsink = gst.element_factory_make("ximagesink", "xsink")
+ xsink.set_property("force-aspect-ratio", True)
+
+ # http://thread.gmane.org/gmane.comp.video.gstreamer.devel/29644
+ xsink.set_property("sync", False)
+
+ self._xbin = gst.Bin("xbin")
+ self._xbin.add(scale, cspace, xsink)
+ gst.element_link_many(scale, cspace, xsink)
+
+ pad = scale.get_static_pad("sink")
+ self._xbin.add_pad(gst.GhostPad("sink", pad))
- def cfgVideoBin (self, quality, width, height):
- vbenc = self.videobin.get_by_name("vbenc")
+ def _config_videobin(self, quality, width, height):
+ vbenc = self._videobin.get_by_name("vbenc")
vbenc.set_property("quality", 16)
- scaps = self.videobin.get_by_name("scalecaps")
+ scaps = self._videobin.get_by_name("scalecaps")
scaps.set_property("caps", gst.Caps("video/x-raw-yuv,width=%d,height=%d" % (width, height)))
- def createPipeline ( self ):
+ def _create_pipeline(self):
src = gst.element_factory_make("v4l2src", "camsrc")
try:
# old gst-plugins-good does not have this property
@@ -222,8 +232,8 @@ class Glive:
# if possible, it is important to place the framerate limit directly
# on the v4l2src so that it gets communicated all the way down to the
# camera level
- if can_limit_framerate:
- srccaps = gst.Caps('video/x-raw-yuv,framerate='+str(self.VIDEO_FRAMERATE_SMALL)+'/1')
+ if self._can_limit_framerate:
+ srccaps = gst.Caps('video/x-raw-yuv,framerate=10/1')
else:
srccaps = gst.Caps('video/x-raw-yuv')
@@ -234,7 +244,7 @@ class Glive:
# for A/V sync because OGG does not store timestamps, it just stores
# the FPS value.
rate = gst.element_factory_make("videorate")
- ratecaps = gst.Caps('video/x-raw-yuv,framerate='+str(self.VIDEO_FRAMERATE_SMALL)+'/1')
+ ratecaps = gst.Caps('video/x-raw-yuv,framerate=10/1')
tee = gst.element_factory_make("tee", "tee")
queue = gst.element_factory_make("queue", "dispqueue")
@@ -243,227 +253,200 @@ class Glive:
queue.set_property("leaky", True)
queue.set_property("max-size-buffers", 2)
- self.pipeline.add(src, rate, tee, queue)
+ self._pipeline.add(src, rate, tee, queue)
src.link(rate, srccaps)
rate.link(tee, ratecaps)
tee.link(queue)
- xvsink = gst.element_factory_make("xvimagesink", "xvsink")
- xv_available = xvsink.set_state(gst.STATE_PAUSED) != gst.STATE_CHANGE_FAILURE
- xvsink.set_state(gst.STATE_NULL)
+ self._xvsink = gst.element_factory_make("xvimagesink", "xsink")
+ self._xv_available = self._xvsink.set_state(gst.STATE_PAUSED) != gst.STATE_CHANGE_FAILURE
+ self._xvsink.set_state(gst.STATE_NULL)
- if xv_available:
- # http://thread.gmane.org/gmane.comp.video.gstreamer.devel/29644
- xvsink.set_property("sync", False)
+ # http://thread.gmane.org/gmane.comp.video.gstreamer.devel/29644
+ self._xvsink.set_property("sync", False)
- self.pipeline.add(xvsink)
- queue.link(xvsink)
- else:
- cspace = gst.element_factory_make("ffmpegcolorspace")
- xsink = gst.element_factory_make("ximagesink")
-
- # http://thread.gmane.org/gmane.comp.video.gstreamer.devel/29644
- xsink.set_property("sync", False)
-
- self.pipeline.add(cspace, xsink)
- gst.element_link_many(queue, cspace, xsink)
+ self._xvsink.set_property("force-aspect-ratio", True)
- def log_queue_overrun(self, queue):
+ def _log_queue_overrun(self, queue):
cbuffers = queue.get_property("current-level-buffers")
cbytes = queue.get_property("current-level-bytes")
ctime = queue.get_property("current-level-time")
logger.error("Buffer overrun in %s (%d buffers, %d bytes, %d time)"
% (queue.get_name(), cbuffers, cbytes, ctime))
- def thumbPipe(self):
- return self.thumbPipes[ len(self.thumbPipes)-1 ]
-
-
- def thumbEl(self, name):
- return self.thumbPipe().get_by_name(name)
-
-
- def muxPipe(self):
- return self.muxPipes[ len(self.muxPipes)-1 ]
-
+ def _thumb_element(self, name):
+ return self._thumb_pipes[-1].get_by_name(name)
+
+ def _configure_xv(self):
+ if self._pipeline.get_by_name("xsink") == self._xvsink:
+ # nothing to do, Xv already configured
+ return self._xvsink
+
+ queue = self._pipeline.get_by_name("dispqueue")
+ if self._pipeline.get_by_name("xbin"):
+ # X sink is configured, so remove it
+ queue.unlink(self._xbin)
+ self._pipeline.remove(self._xbin)
+
+ self._pipeline.add(self._xvsink)
+ queue.link(self._xvsink)
+ return self._xvsink
+
+ def _configure_x(self):
+ if self._pipeline.get_by_name("xbin") == self._xbin:
+ # nothing to do, X already configured
+ return self._xbin.get_by_name("xsink")
+
+ queue = self._pipeline.get_by_name("dispqueue")
+ xvsink = self._pipeline.get_by_name("xsink")
+
+ if xvsink:
+ # Xv sink is configured, so remove it
+ queue.unlink(xvsink)
+ self._pipeline.remove(xvsink)
+
+ self._pipeline.add(self._xbin)
+ queue.link(self._xbin)
+ return self._xbin.get_by_name("xsink")
+
+ def play(self, use_xv=True):
+ if not self._has_camera:
+ return
- def muxEl(self, name):
- return self.muxPipe().get_by_name(name)
+ if self._get_state() == gst.STATE_PLAYING:
+ return
+ if use_xv and self._xv_available:
+ xsink = self._configure_xv()
+ else:
+ xsink = self._configure_x()
- def play(self):
- if not camera_presents:
- return
+ # X overlay must be set every time, it seems to forget when you stop
+ # the pipeline.
+ self.activity.set_glive_sink(xsink)
- self.pipeline.set_state(gst.STATE_PLAYING)
- self.playing = True
+ self._pipeline.set_state(gst.STATE_PLAYING)
+ self._playing = True
def pause(self):
- self.pipeline.set_state(gst.STATE_PAUSED)
- self.playing = False
-
+ self._pipeline.set_state(gst.STATE_PAUSED)
+ self._playing = False
def stop(self):
- self.pipeline.set_state(gst.STATE_NULL)
- self.playing = False
+ self._pipeline.set_state(gst.STATE_NULL)
+ self._playing = False
def is_playing(self):
- return self.playing
+ return self._playing
- def idlePlayElement(self, element):
- element.set_state(gst.STATE_PLAYING)
- return False
+ def _get_state(self):
+ return self._pipeline.get_state()[1]
- def stopRecordingAudio( self ):
+ def stop_recording_audio(self):
# We should be able to simply pause and remove the audiobin, but
# this seems to cause a gstreamer segfault. So we stop the whole
# pipeline while manipulating it.
# http://dev.laptop.org/ticket/10183
- self.pipeline.set_state(gst.STATE_NULL)
- self.pipeline.remove(self.audiobin)
- self.pipeline.set_state(gst.STATE_PLAYING)
- gobject.idle_add( self.stoppedRecordingAudio )
-
+ self._pipeline.set_state(gst.STATE_NULL)
+ self.model.shutter_sound()
+ self._pipeline.remove(self._audiobin)
+ self.play()
- def stoppedRecordingVideo(self):
- if ( len(self.thumbPipes) > 0 ):
- thumbline = self.thumbPipes[len(self.thumbPipes)-1]
- thumbline.get_by_name('thumbFakesink').disconnect(self.THUMB_HANDOFF_ID)
-
- oggFilepath = os.path.join(Instance.instancePath, "output.ogg") #ogv
- if (not os.path.exists(oggFilepath)):
- self.record = False
- self.ca.m.cannotSaveVideo()
- self.ca.m.stoppedRecordingVideo()
+ if not self._audio_pixbuf:
+ # FIXME: inform model of failure?
return
- oggSize = os.path.getsize(oggFilepath)
- if (oggSize <= 0):
- self.record = False
- self.ca.m.cannotSaveVideo()
- self.ca.m.stoppedRecordingVideo()
+
+ audio_path = os.path.join(Instance.instancePath, "output.wav")
+ if not os.path.exists(audio_path) or os.path.getsize(audio_path) <= 0:
+ # FIXME: inform model of failure?
return
- line = 'filesrc location=' + str(oggFilepath) + ' name=thumbFilesrc ! oggdemux name=thumbOggdemux ! theoradec name=thumbTheoradec ! tee name=thumbTee ! queue name=thumbQueue ! ffmpegcolorspace name=thumbFfmpegcolorspace ! jpegenc name=thumbJPegenc ! fakesink name=thumbFakesink'
- thumbline = gst.parse_launch(line)
- thumbQueue = thumbline.get_by_name('thumbQueue')
- thumbQueue.set_property("leaky", True)
- thumbQueue.set_property("max-size-buffers", 1)
- thumbTee = thumbline.get_by_name('thumbTee')
- thumbFakesink = thumbline.get_by_name('thumbFakesink')
- self.THUMB_HANDOFF_ID = thumbFakesink.connect("handoff", self.copyThumbPic)
- thumbFakesink.set_property("signal-handoffs", True)
- self.thumbPipes.append(thumbline)
- self.thumbExposureOpen = True
- gobject.idle_add( self.idlePlayElement, thumbline )
-
-
- def stoppedRecordingAudio( self ):
- record.Record.log.debug("stoppedRecordingAudio")
- if (self.audioPixbuf != None):
- audioFilepath = os.path.join(Instance.instancePath, "output.wav")#self.el("audioFilesink").get_property("location")
- if (not os.path.exists(audioFilepath)):
- self.record = False
- self.ca.m.cannotSaveVideo()
- return
- wavSize = os.path.getsize(audioFilepath)
- if (wavSize <= 0):
- self.record = False
- self.ca.m.cannotSaveVideo()
- return
-
- self.ca.ui.setPostProcessPixBuf(self.audioPixbuf)
-
- line = 'filesrc location=' + str(audioFilepath) + ' name=audioFilesrc ! wavparse name=audioWavparse ! audioconvert name=audioAudioconvert ! vorbisenc name=audioVorbisenc ! oggmux name=audioOggmux ! filesink name=audioFilesink'
- audioline = gst.parse_launch(line)
-
- taglist = self.getTags(Constants.TYPE_AUDIO)
- base64AudioSnapshot = utils.getStringFromPixbuf(self.audioPixbuf)
- taglist[gst.TAG_EXTENDED_COMMENT] = "coverart="+str(base64AudioSnapshot)
- vorbisEnc = audioline.get_by_name('audioVorbisenc')
- vorbisEnc.merge_tags(taglist, gst.TAG_MERGE_REPLACE_ALL)
-
- audioFilesink = audioline.get_by_name('audioFilesink')
- audioOggFilepath = os.path.join(Instance.instancePath, "output.ogg")
- audioFilesink.set_property("location", audioOggFilepath )
-
- audioBus = audioline.get_bus()
- audioBus.add_signal_watch()
- self.AUDIO_TRANSCODE_ID = audioBus.connect('message', self._onMuxedAudioMessageCb, audioline)
- self.TRANSCODE_ID = gobject.timeout_add(self.TRANSCODE_UPDATE_INTERVAL, self._transcodeUpdateCb, audioline)
- gobject.idle_add( self.idlePlayElement, audioline )
- else:
- self.record = False
- self.ca.m.cannotSaveVideo()
+ self.model.still_ready(self._audio_pixbuf)
+ line = 'filesrc location=' + audio_path + ' name=audioFilesrc ! wavparse name=audioWavparse ! audioconvert name=audioAudioconvert ! vorbisenc name=audioVorbisenc ! oggmux name=audioOggmux ! filesink name=audioFilesink'
+ audioline = gst.parse_launch(line)
- def getTags( self, type ):
+ taglist = self._get_tags(constants.TYPE_AUDIO)
+ pixbuf_b64 = utils.getStringFromPixbuf(self._audio_pixbuf)
+ taglist[gst.TAG_EXTENDED_COMMENT] = "coverart=" + pixbuf_b64
+ vorbis_enc = audioline.get_by_name('audioVorbisenc')
+ vorbis_enc.merge_tags(taglist, gst.TAG_MERGE_REPLACE_ALL)
+
+ audioFilesink = audioline.get_by_name('audioFilesink')
+ audioOggFilepath = os.path.join(Instance.instancePath, "output.ogg")
+ audioFilesink.set_property("location", audioOggFilepath)
+
+ audioBus = audioline.get_bus()
+ audioBus.add_signal_watch()
+ self._audio_transcode_handler = audioBus.connect('message', self._onMuxedAudioMessageCb, audioline)
+ self._transcode_id = gobject.timeout_add(200, self._transcodeUpdateCb, audioline)
+ audioline.set_state(gst.STATE_PLAYING)
+
+ def _get_tags(self, type):
tl = gst.TagList()
- tl[gst.TAG_ARTIST] = str(Instance.nickName)
+ tl[gst.TAG_ARTIST] = self.model.get_nickname()
tl[gst.TAG_COMMENT] = "olpc"
#this is unfortunately, unreliable
#record.Record.log.debug("self.ca.metadata['title']->" + str(self.ca.metadata['title']) )
tl[gst.TAG_ALBUM] = "olpc" #self.ca.metadata['title']
tl[gst.TAG_DATE] = utils.getDateString(int(time.time()))
- stringType = Constants.mediaTypes[type][Constants.keyIstr]
- tl[gst.TAG_TITLE] = Constants.istrBy % {"1":stringType, "2":str(Instance.nickName)}
+ stringType = constants.MEDIA_INFO[type]['istr']
+
+ # Translators: photo by photographer, e.g. "Photo by Mary"
+ tl[gst.TAG_TITLE] = _('%s by %s') % (stringType, self.model.get_nickname())
return tl
def blockedCb(self, x, y, z):
pass
- def _takePhoto(self):
- if self.picExposureOpen:
+ def _take_photo(self, photo_mode):
+ if self._pic_exposure_open:
return
- self.picExposureOpen = True
- pad = self.photobin.get_static_pad("sink")
+ self._photo_mode = photo_mode
+ self._pic_exposure_open = True
+ pad = self._photobin.get_static_pad("sink")
pad.set_blocked_async(True, self.blockedCb, None)
- self.pipeline.add(self.photobin)
- self.photobin.set_state(gst.STATE_PLAYING)
- self.pipeline.get_by_name("tee").link(self.photobin)
+ self._pipeline.add(self._photobin)
+ self._photobin.set_state(gst.STATE_PLAYING)
+ self._pipeline.get_by_name("tee").link(self._photobin)
pad.set_blocked_async(False, self.blockedCb, None)
- def takePhoto(self):
- if not camera_presents:
- return
+ def take_photo(self):
+ if self._has_camera:
+ self._take_photo(self.PHOTO_MODE_PHOTO)
- self.photoMode = self.PHOTO_MODE_PHOTO
- self._takePhoto()
-
- def copyPic(self, fsink, buffer, pad, user_data=None):
- if not self.picExposureOpen:
+ def _photo_handoff(self, fsink, buffer, pad, user_data=None):
+ if not self._pic_exposure_open:
return
- pad = self.photobin.get_static_pad("sink")
+ pad = self._photobin.get_static_pad("sink")
pad.set_blocked_async(True, self.blockedCb, None)
- self.pipeline.get_by_name("tee").unlink(self.photobin)
- self.pipeline.remove(self.photobin)
+ self._pipeline.get_by_name("tee").unlink(self._photobin)
+ self._pipeline.remove(self._photobin)
pad.set_blocked_async(False, self.blockedCb, None)
- self.picExposureOpen = False
+ self._pic_exposure_open = False
pic = gtk.gdk.pixbuf_loader_new_with_mime_type("image/jpeg")
pic.write( buffer )
pic.close()
pixBuf = pic.get_pixbuf()
del pic
- self.savePhoto( pixBuf )
-
+ self.save_photo(pixBuf)
- def savePhoto(self, pixbuf):
- if self.photoMode == self.PHOTO_MODE_AUDIO:
- self.audioPixbuf = pixbuf
+ def save_photo(self, pixbuf):
+ if self._photo_mode == self.PHOTO_MODE_AUDIO:
+ self._audio_pixbuf = pixbuf
else:
- self.ca.m.savePhoto(pixbuf)
-
+ self.model.save_photo(pixbuf)
- def startRecordingVideo(self, quality):
- if not camera_presents:
+ def record_video(self, quality):
+ if not self._has_camera:
return
- self.record = True
- self.ogg_quality = quality
- self.cfgVideoBin (OGG_TRAITS[quality]['quality'],
+ self._ogg_quality = quality
+ self._config_videobin(OGG_TRAITS[quality]['quality'],
OGG_TRAITS[quality]['width'],
OGG_TRAITS[quality]['height'])
@@ -472,90 +455,106 @@ class Glive:
# If we pause the pipeline while adjusting it, the A/V sync is better
# but not perfect :(
# so we stop the whole thing while reconfiguring to get the best results
- self.pipeline.set_state(gst.STATE_NULL)
- self.pipeline.add(self.videobin)
- self.pipeline.get_by_name("tee").link(self.videobin)
- self.pipeline.add(self.audiobin)
- self.pipeline.set_state(gst.STATE_PLAYING)
-
- def startRecordingAudio(self):
- self.audioPixbuf = None
-
- self.photoMode = self.PHOTO_MODE_AUDIO
- self._takePhoto()
+ self._pipeline.set_state(gst.STATE_NULL)
+ self._pipeline.add(self._videobin)
+ self._pipeline.get_by_name("tee").link(self._videobin)
+ self._pipeline.add(self._audiobin)
+ self.play()
- self.record = True
+ def record_audio(self):
+ self._audio_pixbuf = None
+ self._take_photo(self.PHOTO_MODE_AUDIO)
# we should be able to add the audiobin on the fly, but unfortunately
# this results in several seconds of silence being added at the start
# of the recording. So we stop the whole pipeline while adjusting it.
# SL#2040
- self.pipeline.set_state(gst.STATE_NULL)
- self.pipeline.add(self.audiobin)
- self.pipeline.set_state(gst.STATE_PLAYING)
+ self._pipeline.set_state(gst.STATE_NULL)
+ self._pipeline.add(self._audiobin)
+ self.play()
- def stopRecordingVideo(self):
- if not camera_presents:
+ def stop_recording_video(self):
+ if not self._has_camera:
return
# We stop the pipeline while we are adjusting the pipeline to stop
# recording because if we do it on-the-fly, the following video live
# feed to the screen becomes several seconds delayed. Weird!
# FIXME: retest on F11
- self._eos_cb = self.stopRecordingVideoEOS
- self.pipeline.get_by_name('camsrc').send_event(gst.event_new_eos())
- self.audiobin.get_by_name('absrc').send_event(gst.event_new_eos())
-
- def stopRecordingVideoEOS(self):
- self.pipeline.set_state(gst.STATE_NULL)
- self.pipeline.get_by_name("tee").unlink(self.videobin)
- self.pipeline.remove(self.videobin)
- self.pipeline.remove(self.audiobin)
- self.pipeline.set_state(gst.STATE_PLAYING)
- gobject.idle_add( self.stoppedRecordingVideo )
+ # FIXME: could this be the result of audio shortening problems?
+ self._eos_cb = self._video_eos
+ self._pipeline.get_by_name('camsrc').send_event(gst.event_new_eos())
+ self._audiobin.get_by_name('absrc').send_event(gst.event_new_eos())
+
+ def _video_eos(self):
+ self._pipeline.set_state(gst.STATE_NULL)
+ self._pipeline.get_by_name("tee").unlink(self._videobin)
+ self._pipeline.remove(self._videobin)
+ self._pipeline.remove(self._audiobin)
+
+ self.model.shutter_sound()
+
+ if len(self._thumb_pipes) > 0:
+ thumbline = self._thumb_pipes[-1]
+ thumbline.get_by_name('thumb_fakesink').disconnect(self._thumb_handoff_handler)
+
+ ogg_path = os.path.join(Instance.instancePath, "output.ogg") #ogv
+ if not os.path.exists(ogg_path) or os.path.getsize(ogg_path) <= 0:
+ # FIXME: inform model of failure?
+ return
+ line = 'filesrc location=' + ogg_path + ' name=thumbFilesrc ! oggdemux name=thumbOggdemux ! theoradec name=thumbTheoradec ! tee name=thumb_tee ! queue name=thumb_queue ! ffmpegcolorspace name=thumbFfmpegcolorspace ! jpegenc name=thumbJPegenc ! fakesink name=thumb_fakesink'
+ thumbline = gst.parse_launch(line)
+ thumb_queue = thumbline.get_by_name('thumb_queue')
+ thumb_queue.set_property("leaky", True)
+ thumb_queue.set_property("max-size-buffers", 1)
+ thumb_tee = thumbline.get_by_name('thumb_tee')
+ thumb_fakesink = thumbline.get_by_name('thumb_fakesink')
+ self._thumb_handoff_handler = thumb_fakesink.connect("handoff", self.copyThumbPic)
+ thumb_fakesink.set_property("signal-handoffs", True)
+ self._thumb_pipes.append(thumbline)
+ self._thumb_exposure_open = True
+ thumbline.set_state(gst.STATE_PLAYING)
def copyThumbPic(self, fsink, buffer, pad, user_data=None):
- if not self.thumbExposureOpen:
+ if not self._thumb_exposure_open:
return
- self.thumbExposureOpen = False
- pic = gtk.gdk.pixbuf_loader_new_with_mime_type("image/jpeg")
- pic.write(buffer)
- pic.close()
- self.thumbBuf = pic.get_pixbuf()
- del pic
- self.thumbEl('thumbTee').unlink(self.thumbEl('thumbQueue'))
+ self._thumb_exposure_open = False
+ loader = gtk.gdk.pixbuf_loader_new_with_mime_type("image/jpeg")
+ loader.write(buffer)
+ loader.close()
+ self.thumbBuf = loader.get_pixbuf()
+ self.model.still_ready(self.thumbBuf)
- oggFilepath = os.path.join(Instance.instancePath, "output.ogg") #ogv
- self.ca.ui.setPostProcessPixBuf(self.thumbBuf)
+ self._thumb_element('thumb_tee').unlink(self._thumb_element('thumb_queue'))
+ oggFilepath = os.path.join(Instance.instancePath, "output.ogg") #ogv
wavFilepath = os.path.join(Instance.instancePath, "output.wav")
muxFilepath = os.path.join(Instance.instancePath, "mux.ogg") #ogv
muxline = gst.parse_launch('filesrc location=' + str(oggFilepath) + ' name=muxVideoFilesrc ! oggdemux name=muxOggdemux ! theoraparse ! oggmux name=muxOggmux ! filesink location=' + str(muxFilepath) + ' name=muxFilesink filesrc location=' + str(wavFilepath) + ' name=muxAudioFilesrc ! wavparse name=muxWavparse ! audioconvert name=muxAudioconvert ! vorbisenc name=muxVorbisenc ! muxOggmux.')
- taglist = self.getTags(Constants.TYPE_VIDEO)
- vorbisEnc = muxline.get_by_name('muxVorbisenc')
- vorbisEnc.merge_tags(taglist, gst.TAG_MERGE_REPLACE_ALL)
+ taglist = self._get_tags(constants.TYPE_VIDEO)
+ vorbis_enc = muxline.get_by_name('muxVorbisenc')
+ vorbis_enc.merge_tags(taglist, gst.TAG_MERGE_REPLACE_ALL)
muxBus = muxline.get_bus()
muxBus.add_signal_watch()
- self.VIDEO_TRANSCODE_ID = muxBus.connect('message', self._onMuxedVideoMessageCb, muxline)
- self.muxPipes.append(muxline)
+ self._video_transcode_handler = muxBus.connect('message', self._onMuxedVideoMessageCb, muxline)
+ self._mux_pipes.append(muxline)
#add a listener here to monitor % of transcoding...
- self.TRANSCODE_ID = gobject.timeout_add(self.TRANSCODE_UPDATE_INTERVAL, self._transcodeUpdateCb, muxline)
+ self._transcode_id = gobject.timeout_add(200, self._transcodeUpdateCb, muxline)
muxline.set_state(gst.STATE_PLAYING)
def _transcodeUpdateCb( self, pipe ):
- position, duration = self.queryPosition( pipe )
+ position, duration = self._query_position( pipe )
if position != gst.CLOCK_TIME_NONE:
value = position * 100.0 / duration
value = value/100.0
- self.ca.ui.progressWindow.updateProgress(value, Constants.istrSaving)
+ self.model.set_progress(value, _('Saving...'))
return True
-
- def queryPosition( self, pipe ):
+ def _query_position(self, pipe):
try:
position, format = pipe.query_position(gst.FORMAT_TIME)
except:
@@ -568,64 +567,45 @@ class Glive:
return (position, duration)
-
def _onMuxedVideoMessageCb(self, bus, message, pipe):
- t = message.type
- if (t == gst.MESSAGE_EOS):
- self.record = False
- gobject.source_remove(self.VIDEO_TRANSCODE_ID)
- self.VIDEO_TRANSCODE_ID = 0
- gobject.source_remove(self.TRANSCODE_ID)
- self.TRANSCODE_ID = 0
- pipe.set_state(gst.STATE_NULL)
- pipe.get_bus().remove_signal_watch()
- pipe.get_bus().disable_sync_message_emission()
-
- wavFilepath = os.path.join(Instance.instancePath, "output.wav")
- oggFilepath = os.path.join(Instance.instancePath, "output.ogg") #ogv
- muxFilepath = os.path.join(Instance.instancePath, "mux.ogg") #ogv
- os.remove( wavFilepath )
- os.remove( oggFilepath )
- ogg_w = OGG_TRAITS[self.ogg_quality]['width']
- ogg_h = OGG_TRAITS[self.ogg_quality]['height']
- self.ca.m.saveVideo(self.thumbBuf, str(muxFilepath), ogg_w, ogg_h)
- self.ca.m.stoppedRecordingVideo()
- return False
- else:
+ if message.type != gst.MESSAGE_EOS:
return True
+ gobject.source_remove(self._video_transcode_handler)
+ self._video_transcode_handler = None
+ gobject.source_remove(self._transcode_id)
+ self._transcode_id = None
+ pipe.set_state(gst.STATE_NULL)
+ pipe.get_bus().remove_signal_watch()
+ pipe.get_bus().disable_sync_message_emission()
+
+ wavFilepath = os.path.join(Instance.instancePath, "output.wav")
+ oggFilepath = os.path.join(Instance.instancePath, "output.ogg") #ogv
+ muxFilepath = os.path.join(Instance.instancePath, "mux.ogg") #ogv
+ os.remove( wavFilepath )
+ os.remove( oggFilepath )
+ self.model.save_video(muxFilepath, self.thumbBuf)
+ return False
def _onMuxedAudioMessageCb(self, bus, message, pipe):
- t = message.type
- if (t == gst.MESSAGE_EOS):
- record.Record.log.debug("audio gst.MESSAGE_EOS")
- self.record = False
- gobject.source_remove(self.AUDIO_TRANSCODE_ID)
- self.AUDIO_TRANSCODE_ID = 0
- gobject.source_remove(self.TRANSCODE_ID)
- self.TRANSCODE_ID = 0
- pipe.set_state(gst.STATE_NULL)
- pipe.get_bus().remove_signal_watch()
- pipe.get_bus().disable_sync_message_emission()
-
- wavFilepath = os.path.join(Instance.instancePath, "output.wav")
- oggFilepath = os.path.join(Instance.instancePath, "output.ogg")
- os.remove( wavFilepath )
- self.ca.m.saveAudio(oggFilepath, self.audioPixbuf)
- return False
- else:
+ if message.type != gst.MESSAGE_EOS:
return True
+ gobject.source_remove(self._audio_transcode_handler)
+ self._audio_transcode_handler = None
+ gobject.source_remove(self._transcode_id)
+ self._transcode_id = None
+ pipe.set_state(gst.STATE_NULL)
+ pipe.get_bus().remove_signal_watch()
+ pipe.get_bus().disable_sync_message_emission()
- def _onSyncMessageCb(self, bus, message):
- if message.structure is None:
- return
- if message.structure.get_name() == 'prepare-xwindow-id':
- self.window.set_sink(message.src)
- message.src.set_property('force-aspect-ratio', True)
-
+ wavFilepath = os.path.join(Instance.instancePath, "output.wav")
+ oggFilepath = os.path.join(Instance.instancePath, "output.ogg")
+ os.remove( wavFilepath )
+ self.model.save_audio(oggFilepath, self._audio_pixbuf)
+ return False
- def _onMessageCb(self, bus, message):
+ def _bus_message_handler(self, bus, message):
t = message.type
if t == gst.MESSAGE_EOS:
if self._eos_cb:
@@ -641,48 +621,23 @@ class Glive:
def abandonMedia(self):
self.stop()
- if (self.AUDIO_TRANSCODE_ID != 0):
- gobject.source_remove(self.AUDIO_TRANSCODE_ID)
- self.AUDIO_TRANSCODE_ID = 0
- if (self.TRANSCODE_ID != 0):
- gobject.source_remove(self.TRANSCODE_ID)
- self.TRANSCODE_ID = 0
- if (self.VIDEO_TRANSCODE_ID != 0):
- gobject.source_remove(self.VIDEO_TRANSCODE_ID)
- self.VIDEO_TRANSCODE_ID = 0
-
- wavFilepath = os.path.join(Instance.instancePath, "output.wav")
- if (os.path.exists(wavFilepath)):
- os.remove(wavFilepath)
- oggFilepath = os.path.join(Instance.instancePath, "output.ogg") #ogv
- if (os.path.exists(oggFilepath)):
- os.remove(oggFilepath)
- muxFilepath = os.path.join(Instance.instancePath, "mux.ogg") #ogv
- if (os.path.exists(muxFilepath)):
- os.remove(muxFilepath)
-
-
-class LiveVideoWindow(gtk.Window):
- def __init__(self, bgd ):
- gtk.Window.__init__(self)
-
- self.imagesink = None
- self.glive = None
-
- self.modify_bg( gtk.STATE_NORMAL, bgd )
- self.modify_bg( gtk.STATE_INSENSITIVE, bgd )
- self.unset_flags(gtk.DOUBLE_BUFFERED)
- self.set_flags(gtk.APP_PAINTABLE)
-
- def set_glive(self, pglive):
- self.glive = pglive
- self.glive.window = self
-
- def set_sink(self, sink):
- if (self.imagesink != None):
- assert self.window.xid
- self.imagesink = None
- del self.imagesink
+ if self._audio_transcode_handler:
+ gobject.source_remove(self._audio_transcode_handler)
+ self._audio_transcode_handler = None
+ if self._transcode_id:
+ gobject.source_remove(self._transcode_id)
+ self._transcode_id = None
+ if self._video_transcode_handler:
+ gobject.source_remove(self._video_transcode_handler)
+ self._video_transcode_handler = None
+
+ wav_path = os.path.join(Instance.instancePath, "output.wav")
+ if os.path.exists(wav_path):
+ os.remove(wav_path)
+ ogg_path = os.path.join(Instance.instancePath, "output.ogg") #ogv
+ if os.path.exists(ogg_path):
+ os.remove(ogg_path)
+ mux_path = os.path.join(Instance.instancePath, "mux.ogg") #ogv
+ if os.path.exists(mux_path):
+ os.remove(mux_path)
- self.imagesink = sink
- self.imagesink.set_xwindow_id(self.window.xid)
diff --git a/gplay.py b/gplay.py
index de5aac2..168fb22 100644
--- a/gplay.py
+++ b/gplay.py
@@ -18,114 +18,99 @@
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
-#look at jukeboxactivity.py
-
-import gtk
-import pygtk
-pygtk.require('2.0')
-import sys
+import gobject
+gobject.threads_init()
import pygst
pygst.require('0.10')
import gst
-import gst.interfaces
-import gobject
-import time
-gobject.threads_init()
import logging
logger = logging.getLogger('record:gplay.py')
-import record
-
-class Gplay:
+class Gplay(gobject.GObject):
+ __gsignals__ = {
+ 'playback-status-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT, gobject.TYPE_FLOAT)),
+ }
- def __init__(self, ca):
- self.ca = ca
- self.window = None
- self.players = []
- self.playing = False
+ def __init__(self, activity_obj):
+ super(Gplay, self).__init__()
+ self.activity = activity_obj
+ self._playback_monitor_handler = None
+ self._player = gst.element_factory_make('playbin')
- self.player = gst.element_factory_make('playbin')
-
- bus = self.player.get_bus()
- bus.enable_sync_message_emission()
+ bus = self._player.get_bus()
bus.add_signal_watch()
- bus.connect('message', self._onMessageCb)
-
- def _onMessageCb(self, bus, message):
- if message.type == gst.MESSAGE_ERROR:
- err, debug = message.parse_error()
- logger.error('_onMessageCb: error=%s debug=%s' % (err, debug))
-
- def setLocation(self, location):
- if (self.player.get_property('uri') == location):
- self.seek(gst.SECOND*0)
- return
-
- self.player.set_state(gst.STATE_READY)
- self.player.set_property('uri', location)
- ext = location[len(location)-3:]
- record.Record.log.debug("setLocation: ext->"+str(ext))
- if (ext == "jpg"):
- self.pause()
+ bus.connect('message::error', self._bus_error)
+ bus.connect('message::eos', self._bus_eos)
+ def _bus_error(self, bus, message):
+ err, debug = message.parse_error()
+ logger.error('bus error=%s debug=%s' % (err, debug))
- def queryPosition(self):
- "Returns a (position, duration) tuple"
- try:
- position, format = self.player.query_position(gst.FORMAT_TIME)
- except:
- position = gst.CLOCK_TIME_NONE
+ def _bus_eos(self, bus, message):
+ self.stop()
- try:
- duration, format = self.player.query_duration(gst.FORMAT_TIME)
- except:
- duration = gst.CLOCK_TIME_NONE
+ def set_location(self, location):
+ if self._player.get_property('uri') == location:
+ self.seek(0)
+ return
- return (position, duration)
+ self._player.set_state(gst.STATE_READY)
+ self._player.set_property('uri', location)
+ def seek(self, position):
+ if position == 0:
+ location = 0
+ else:
+ duration = self._player.query_duration(gst.FORMAT_TIME, None)[0]
+ location = duration * (position / 100)
- def seek(self, location):
event = gst.event_new_seek(1.0, gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE, gst.SEEK_TYPE_SET, location, gst.SEEK_TYPE_NONE, 0)
- res = self.player.send_event(event)
+ res = self._player.send_event(event)
if res:
- self.player.set_new_stream_time(0L)
-
+ self._player.set_new_stream_time(0L)
def pause(self):
- self.playing = False
- self.player.set_state(gst.STATE_PAUSED)
-
+ self._player.set_state(gst.STATE_PAUSED)
def play(self):
- if not self.player.props.video_sink:
+ if self.get_state() == gst.STATE_PLAYING:
+ return
+
+ if not self._player.props.video_sink:
sink = gst.element_factory_make('xvimagesink')
sink.props.force_aspect_ratio = True
- self.player.props.video_sink = sink
-
- self.player.props.video_sink.set_xwindow_id(self.window.window.xid)
- self.playing = True
- self.player.set_state(gst.STATE_PLAYING)
+ self._player.props.video_sink = sink
+ self.activity.set_gplay_sink(self._player.props.video_sink)
+ self._player.set_state(gst.STATE_PLAYING)
+ self._emit_playback_status(0)
- def stop(self):
- self.playing = False
- self.player.set_state(gst.STATE_NULL)
+ self._playback_monitor_handler = gobject.timeout_add(500, self._playback_monitor)
+ def _playback_monitor(self):
+ try:
+ position = self._player.query_position(gst.FORMAT_TIME)[0]
+ duration = self._player.query_duration(gst.FORMAT_TIME)[0]
+ except gst.QueryError:
+ return True
- def get_state(self, timeout=1):
- return self.player.get_state(timeout=timeout)
+ value = (float(position) / float(duration)) * 100.0
+ self._emit_playback_status(value)
+ return True
+ def _emit_playback_status(self, position):
+ state = self._player.get_state()[1]
+ self.emit('playback-status-changed', state, position)
- def is_playing(self):
- return self.playing
+ def get_state(self):
+ return self._player.get_state()[1]
+ def stop(self):
+ if self._playback_monitor_handler:
+ gobject.source_remove(self._playback_monitor_handler)
+ self._playback_monitor_handler = None
-class PlayVideoWindow(gtk.Window):
- def __init__(self, bgd):
- gtk.Window.__init__(self)
+ self._player.set_state(gst.STATE_NULL)
+ self._emit_playback_status(0)
- self.modify_bg( gtk.STATE_NORMAL, bgd )
- self.modify_bg( gtk.STATE_INSENSITIVE, bgd )
- self.unset_flags(gtk.DOUBLE_BUFFERED)
- self.set_flags(gtk.APP_PAINTABLE)
diff --git a/greplay.py b/greplay.py
deleted file mode 100644
index 67758a1..0000000
--- a/greplay.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#Copyright (c) 2008, Media Modifications Ltd.
-
-#Permission is hereby granted, free of charge, to any person obtaining a copy
-#of this software and associated documentation files (the "Software"), to deal
-#in the Software without restriction, including without limitation the rights
-#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-#copies of the Software, and to permit persons to whom the Software is
-#furnished to do so, subject to the following conditions:
-
-#The above copyright notice and this permission notice shall be included in
-#all copies or substantial portions of the Software.
-
-#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-#THE SOFTWARE.
-
-import gst
-import gst.interfaces
-import pygst
-pygst.require('0.10')
-import gobject
-import os
-
-import record
-import utils
-
-class Greplay(gobject.GObject):
-
- __gsignals__ = {
- 'coverart-found':
- (gobject.SIGNAL_RUN_FIRST, None, [object])
- }
-
-
- def findAlbumArt( self, path ):
- record.Record.log.debug("getAlbumArt")
- if (path == None):
- record.Record.log.debug("getAlbumArt: path==None")
- self.emit('coverart-found', None)
- return
- if (not os.path.exists(path)):
- record.Record.log.debug("getAlbumArt: path doesn't exist")
- self.emit('coverart-found', None)
- return
-
- self.pp = gst.parse_launch("filesrc location="+str(path)+" ! oggdemux ! vorbisdec ! fakesink")
- self.pp.get_bus().add_signal_watch()
- self.pp.get_bus().connect("message", self._onMessageCb)
- self.pp.set_state(gst.STATE_PLAYING)
-
-
- def _onMessageCb(self, bus, message):
- t = message.type
- if t == gst.MESSAGE_EOS:
- record.Record.log.debug("Greplay:MESSAGE_EOS")
- self.emit('coverart-found', None)
- self.pp.set_state(gst.STATE_NULL)
- return False
- elif t == gst.MESSAGE_ERROR:
- record.Record.log.debug("Greplay:MESSAGE_ERROR")
- self.emit('coverart-found', None)
- self.pp.set_state(gst.STATE_NULL)
- return False
- elif t == gst.MESSAGE_TAG:
- tags = message.parse_tag()
- for tag in tags.keys():
- if (str(tag) == "extended-comment"):
- record.Record.log.debug("Found the tag!")
- #todo, check for tagname
- base64imgString = str(tags[tag])[len("coverart="):]
-
- pixbuf = utils.getPixbufFromString(base64imgString)
- self.pp.set_state(gst.STATE_NULL)
- self.emit('coverart-found', pixbuf)
- return False
- return True
diff --git a/instance.py b/instance.py
index 085c2da..bcee466 100644
--- a/instance.py
+++ b/instance.py
@@ -2,44 +2,21 @@ import os
from sugar import profile
from sugar import util
-from sugar.activity import activity
-import shutil
-
-from color import Color
-import record
class Instance:
key = profile.get_pubkey()
keyHash = util.sha_data(key)
keyHashPrintable = util.printable_hash(keyHash)
- nickName = profile.get_nick_name()
-
- colorFill = Color()
- colorFill.init_hex( profile.get_color().get_fill_color() )
- colorStroke = Color()
- colorStroke.init_hex( profile.get_color().get_stroke_color() )
- instanceId = None
instancePath = None
- dataPath = None
def __init__(self, ca):
- self.__class__.instanceId = ca._activity_id
-
self.__class__.instancePath = os.path.join(ca.get_activity_root(), "instance")
recreateTmp()
- self.__class__.dataPath = os.path.join(ca.get_activity_root(), "data")
- recreateData()
-
def recreateTmp():
if (not os.path.exists(Instance.instancePath)):
os.makedirs(Instance.instancePath)
-
-def recreateData():
- if (not os.path.exists(Instance.dataPath)):
- os.makedirs(Instance.dataPath)
-
diff --git a/mediaview.py b/mediaview.py
new file mode 100644
index 0000000..670a0fa
--- /dev/null
+++ b/mediaview.py
@@ -0,0 +1,512 @@
+import os
+from gettext import gettext as _
+
+import gobject
+import gtk
+from gtk import gdk
+
+import constants
+import utils
+
+COLOR_BLACK = gdk.color_parse('#000000')
+COLOR_WHITE = gdk.color_parse('#ffffff')
+COLOR_GREY = gdk.color_parse('#808080')
+
+class XoIcon(gtk.Image):
+ def __init__(self):
+ super(XoIcon, self).__init__()
+
+ def set_colors(self, stroke, fill):
+ pixbuf = utils.load_colored_svg('xo-guy.svg', stroke, fill)
+ self.set_from_pixbuf(pixbuf)
+
+
+class InfoView(gtk.EventBox):
+ """
+ A metadata view/edit widget, that presents a primary view area in the top
+ right and a secondary view area in the bottom left.
+ """
+ __gsignals__ = {
+ 'primary-allocated': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
+ 'secondary-allocated': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
+ 'tags-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,)),
+ }
+
+ def __init__(self):
+ super(InfoView, self).__init__()
+ self.modify_bg(gtk.STATE_NORMAL, COLOR_GREY)
+
+ self.connect('size-allocate', self._size_allocate)
+
+ self._outer_vbox = gtk.VBox(spacing=7)
+ self.add(self._outer_vbox)
+
+ hbox = gtk.HBox()
+ self._outer_vbox.pack_start(hbox, expand=True, fill=True)
+
+ inner_vbox = gtk.VBox(spacing=5)
+ hbox.pack_start(inner_vbox, expand=True, fill=True, padding=6)
+
+ author_hbox = gtk.HBox()
+ inner_vbox.pack_start(author_hbox, expand=False)
+
+ label = gtk.Label()
+ label.set_markup('<b>' + _('Author:') + '</b>')
+ author_hbox.pack_start(label, expand=False)
+
+ self._xo_icon = XoIcon()
+ author_hbox.pack_start(self._xo_icon, expand=False)
+
+ self._author_label = gtk.Label()
+ author_hbox.pack_start(self._author_label, expand=False)
+
+ self._date_label = gtk.Label()
+ self._date_label.set_line_wrap(True)
+ alignment = gtk.Alignment(0.0, 0.5, 0.0, 0.0)
+ alignment.add(self._date_label)
+ inner_vbox.pack_start(alignment, expand=False)
+
+ label = gtk.Label()
+ label.set_markup('<b>' + _('Tags:') + '</b>')
+ alignment = gtk.Alignment(0.0, 0.5, 0.0, 0.0)
+ alignment.add(label)
+ inner_vbox.pack_start(alignment, expand=False)
+
+ textview = gtk.TextView()
+ self._tags_buffer = textview.get_buffer()
+ self._tags_buffer.connect('changed', self._tags_changed)
+ inner_vbox.pack_start(textview, expand=True, fill=True)
+
+ # the main viewing widget will be painted exactly on top of this one
+ alignment = gtk.Alignment(1.0, 0.0, 0.0, 0.0)
+ self._view_bg = gtk.EventBox()
+ self._view_bg.modify_bg(gtk.STATE_NORMAL, COLOR_BLACK)
+ alignment.add(self._view_bg)
+ hbox.pack_start(alignment, expand=False)
+
+ # the secondary viewing widget will be painted exactly on top of this one
+ alignment = gtk.Alignment(0.0, 1.0, 0.0, 0.0)
+ self._live_bg = gtk.EventBox()
+ self._live_bg.modify_bg(gtk.STATE_NORMAL, COLOR_BLACK)
+ alignment.add(self._live_bg)
+ self._outer_vbox.pack_start(alignment, expand=False)
+
+ def fit_to_allocation(self, allocation):
+ # main viewing area: 50% of each dimension
+ scale = 0.5
+ w = int(allocation.width * scale)
+ h = int(allocation.height * scale)
+ self._view_bg.set_size_request(w, h)
+
+ # live area: 1/4 of each dimension
+ scale = 0.25
+ w = int(allocation.width * scale)
+ h = int(allocation.height * scale)
+ self._live_bg.set_size_request(w, h)
+
+ def show(self):
+ self.show_all()
+
+ def hide(self):
+ self.hide_all()
+
+ def set_author(self, name, stroke, fill):
+ self._xo_icon.set_colors(stroke, fill)
+ self._author_label.set_text(name)
+
+ def set_date(self, date):
+ self._date_label.set_markup('<b>' + _('Date:') + '</b> ' + date)
+
+ def set_tags(self, tags):
+ self._tags_buffer.set_text(tags)
+
+ def _size_allocate(self, widget, allocation):
+ self.emit('primary-allocated', self._view_bg.allocation)
+ self.emit('secondary-allocated', self._live_bg.allocation)
+
+ def _tags_changed(self, widget):
+ self.emit('tags-changed', widget)
+
+class VideoBox(gtk.EventBox):
+ """
+ A widget with its own window for rendering a gstreamer video sink onto.
+ """
+ def __init__(self):
+ super(VideoBox, self).__init__()
+ self.unset_flags(gtk.DOUBLE_BUFFERED)
+ self.set_flags(gtk.APP_PAINTABLE)
+ self._sink = None
+ self._xid = None
+ self.connect('realize', self._realize)
+
+ def _realize(self, widget):
+ self._xid = self.window.xid
+
+ def do_expose_event(self):
+ if self._sink:
+ self._sink.expose()
+ return False
+ else:
+ return True
+
+ # can be called from gstreamer thread, must not do any GTK+ stuff
+ def set_sink(self, sink):
+ self._sink = sink
+ sink.set_xwindow_id(self._xid)
+
+class FullscreenButton(gtk.EventBox):
+ def __init__(self):
+ super(FullscreenButton, self).__init__()
+
+ path = os.path.join(constants.GFX_PATH, 'max-reduce.svg')
+ self._enlarge_pixbuf = gdk.pixbuf_new_from_file(path)
+ self.width = self._enlarge_pixbuf.get_width()
+ self.height = self._enlarge_pixbuf.get_height()
+
+ path = os.path.join(constants.GFX_PATH, 'max-enlarge.svg')
+ self._reduce_pixbuf = gdk.pixbuf_new_from_file_at_size(path, self.width, self.height)
+
+ self._image = gtk.Image()
+ self.set_enlarge()
+ self._image.show()
+ self.add(self._image)
+
+ def set_enlarge(self):
+ self._image.set_from_pixbuf(self._enlarge_pixbuf)
+
+ def set_reduce(self):
+ self._image.set_from_pixbuf(self._reduce_pixbuf)
+
+
+class InfoButton(gtk.EventBox):
+ def __init__(self):
+ super(InfoButton, self).__init__()
+
+ path = os.path.join(constants.GFX_PATH, 'corner-info.svg')
+ pixbuf = gdk.pixbuf_new_from_file(path)
+ self.width = pixbuf.get_width()
+ self.height = pixbuf.get_height()
+
+ self._image = gtk.image_new_from_pixbuf(pixbuf)
+ self._image.show()
+ self.add(self._image)
+
+
+class ImageBox(gtk.EventBox):
+ def __init__(self):
+ super(ImageBox, self).__init__()
+ self._pixbuf = None
+ self._image = gtk.Image()
+ self.add(self._image)
+
+ def show(self):
+ self._image.show()
+ super(ImageBox, self).show()
+
+ def hide(self):
+ self._image.hide()
+ super(ImageBox, self).hide()
+
+ def clear(self):
+ self._image.clear()
+ self._pixbuf = None
+
+ def set_pixbuf(self, pixbuf):
+ self._pixbuf = pixbuf
+
+ def set_size(self, width, height):
+ if width == self._pixbuf.get_width() and height == self._pixbuf.get_height():
+ pixbuf = self._pixbuf
+ else:
+ pixbuf = self._pixbuf.scale_simple(width, height, gdk.INTERP_BILINEAR)
+
+ self._image.set_from_pixbuf(pixbuf)
+ self._image.set_size_request(width, height)
+ self.set_size_request(width, height)
+
+class MediaView(gtk.EventBox):
+ """
+ A widget to show the main record UI with a video feed, but with some
+ extra features: possibility to show images, information UI about media,
+ etc.
+
+ It is made complicated because under the UI design, some widgets need
+ to be placed on top of others. In GTK+, this is not trivial. We achieve
+ this here by relying on the fact that GDK+ specifically allows us to
+ raise specific windows, and by packing all our Z-order-sensitive widgets
+ into EventBoxes (which have their own windows).
+ """
+ __gtype_name__ = "MediaView"
+ __gsignals__ = {
+ 'media-clicked': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+ 'pip-clicked': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+ 'full-clicked': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+ 'info-clicked': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+ 'tags-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,)),
+ }
+
+ MODE_LIVE = 0
+ MODE_PHOTO = 1
+ MODE_VIDEO = 2
+ MODE_STILL = 3
+ MODE_INFO_PHOTO = 4
+ MODE_INFO_VIDEO = 5
+
+ @staticmethod
+ def _raise_widget(widget):
+ widget.show()
+ widget.realize()
+ widget.window.raise_()
+
+ def __init__(self):
+ self._mode = None
+ self._allocation = None
+ self._hide_controls_timer = None
+
+ super(MediaView, self).__init__()
+ self.connect('size-allocate', self._size_allocate)
+ self.connect('motion-notify-event', self._motion_notify)
+ self.set_events(gdk.POINTER_MOTION_MASK | gdk.POINTER_MOTION_HINT_MASK)
+
+ self._fixed = gtk.Fixed()
+ self.add(self._fixed)
+
+ self._info_view = InfoView()
+ self._info_view.connect('primary-allocated', self._info_view_primary_allocated)
+ self._info_view.connect('secondary-allocated', self._info_view_secondary_allocated)
+ self._info_view.connect('tags-changed', self._info_view_tags_changed)
+ self._fixed.put(self._info_view, 0, 0)
+
+ self._image_box = ImageBox()
+ self._image_box.connect('button-release-event', self._image_clicked)
+ self._fixed.put(self._image_box, 0, 0)
+
+ self._video = VideoBox()
+ self._video.connect('button-release-event', self._video_clicked)
+ self._fixed.put(self._video, 0, 0)
+
+ self._video2 = VideoBox()
+ self._video2.connect('button-release-event', self._video2_clicked)
+ self._fixed.put(self._video2, 0, 0)
+
+ self._info_button = InfoButton()
+ self._info_button.connect('button-release-event', self._info_clicked)
+ self._fixed.put(self._info_button, 0, 0)
+
+ self._full_button = FullscreenButton()
+ self._full_button.connect('button-release-event', self._full_clicked)
+ self._fixed.put(self._full_button, 0, 0)
+
+ self._switch_mode(MediaView.MODE_LIVE)
+
+ def _size_allocate(self, widget, allocation):
+ # First check if we've already processed an allocation of this size.
+ # This is necessary because the operations we do in response to the
+ # size allocation cause another size allocation to happen.
+ if self._allocation == allocation:
+ return
+
+ self._allocation = allocation
+ self._place_widgets()
+
+ def _motion_notify(self, widget, event):
+ if self._hide_controls_timer:
+ # remove timer, it will be reprogrammed right after
+ gobject.source_remove(self._hide_controls_timer)
+ else:
+ self._show_controls()
+
+ self._hide_controls_timer = gobject.timeout_add(2000, self._hide_controls)
+
+ def _show_controls(self):
+ if self._mode in (MediaView.MODE_LIVE, MediaView.MODE_VIDEO, MediaView.MODE_PHOTO, MediaView.MODE_STILL):
+ self._raise_widget(self._full_button)
+
+ if self._mode in (MediaView.MODE_VIDEO, MediaView.MODE_PHOTO):
+ self._raise_widget(self._info_button)
+
+ if self._mode in (MediaView.MODE_VIDEO, MediaView.MODE_PHOTO):
+ self._raise_widget(self._video)
+
+ def _hide_controls(self):
+ if self._hide_controls_timer:
+ gobject.source_remove(self._hide_controls_timer)
+ self._hide_controls_timer = None
+
+ self._full_button.hide()
+ if self._mode not in (MediaView.MODE_INFO_PHOTO, MediaView.MODE_INFO_VIDEO):
+ self._info_button.hide()
+
+ if self._mode in (MediaView.MODE_VIDEO, MediaView.MODE_PHOTO):
+ self._video.hide()
+
+ return False
+
+ def _place_widgets(self):
+ allocation = self._allocation
+
+ self._image_box.hide()
+ self._video.hide()
+ self._video2.hide()
+ self._info_view.hide()
+ self._info_button.hide()
+
+ border = 5
+ full_button_x = allocation.width - border - self._full_button.width
+ full_button_y = border
+ self._fixed.move(self._full_button, full_button_x, full_button_y)
+
+ info_x = allocation.width - self._info_button.width
+ info_y = allocation.height - self._info_button.height
+ self._fixed.move(self._info_button, info_x, info_y)
+
+ if self._mode == MediaView.MODE_LIVE:
+ self._fixed.move(self._video, 0, 0)
+ self._video.set_size_request(allocation.width, allocation.height)
+ self._video.show()
+ self._image_box.clear()
+ elif self._mode == MediaView.MODE_VIDEO:
+ self._fixed.move(self._video2, 0, 0)
+ self._video2.set_size_request(allocation.width, allocation.height)
+ self._video2.show()
+ self._image_box.clear()
+
+ vid_h = allocation.height / 6
+ vid_w = allocation.width / 6
+ self._video.set_size_request(vid_w, vid_h)
+
+ border = 20
+ vid_x = border
+ vid_y = self.allocation.height - border - vid_h
+ self._fixed.move(self._video, vid_x, vid_y)
+ elif self._mode == MediaView.MODE_PHOTO:
+ self._fixed.move(self._image_box, 0, 0)
+ self._image_box.set_size(allocation.width, allocation.height)
+ self._image_box.show()
+
+ vid_h = allocation.height / 6
+ vid_w = allocation.width / 6
+ self._video.set_size_request(vid_w, vid_h)
+
+ border = 20
+ vid_x = border
+ vid_y = self.allocation.height - border - vid_h
+ self._fixed.move(self._video, vid_x, vid_y)
+ elif self._mode == MediaView.MODE_STILL:
+ self._fixed.move(self._image_box, 0, 0)
+ self._image_box.set_size(allocation.width, allocation.height)
+ self._image_box.show()
+ elif self._mode in (MediaView.MODE_INFO_PHOTO, MediaView.MODE_INFO_VIDEO):
+ self._full_button.hide()
+ self._info_view.set_size_request(allocation.width, allocation.height)
+ self._info_view.fit_to_allocation(allocation)
+ self._info_view.show()
+ self._raise_widget(self._info_button)
+
+ def _info_view_primary_allocated(self, widget, allocation):
+ if self._mode == MediaView.MODE_INFO_PHOTO:
+ self._fixed.move(self._image_box, allocation.x, allocation.y)
+ self._image_box.set_size(allocation.width, allocation.height)
+ self._raise_widget(self._image_box)
+ elif self._mode == MediaView.MODE_INFO_VIDEO:
+ self._fixed.move(self._video2, allocation.x, allocation.y)
+ self._video2.set_size_request(allocation.width, allocation.height)
+ self._raise_widget(self._video2)
+
+ def _info_view_secondary_allocated(self, widget, allocation):
+ if self._mode in (MediaView.MODE_INFO_PHOTO, MediaView.MODE_INFO_VIDEO):
+ self._fixed.move(self._video, allocation.x, allocation.y)
+ self._video.set_size_request(allocation.width, allocation.height)
+ self._raise_widget(self._video)
+
+ def _info_view_tags_changed(self, widget, tbuffer):
+ self.emit('tags-changed', tbuffer)
+
+ def _switch_mode(self, new_mode):
+ if self._mode == MediaView.MODE_LIVE and new_mode == MediaView.MODE_LIVE:
+ return
+ self._mode = new_mode
+
+ if self._hide_controls_timer:
+ gobject.source_remove(self._hide_controls_timer)
+ self._hide_controls_timer = None
+
+ if self._allocation:
+ self._place_widgets()
+
+ def _image_clicked(self, widget, event):
+ self.emit('media-clicked')
+
+ def _video_clicked(self, widget, event):
+ if self._mode != MediaView.MODE_LIVE:
+ self.emit('pip-clicked')
+
+ def _video2_clicked(self, widget, event):
+ self.emit('media-clicked')
+
+ def _full_clicked(self, widget, event):
+ self.emit('full-clicked')
+
+ def _info_clicked(self, widget, event):
+ self.emit('info-clicked')
+
+ def _show_info(self, mode, author, stroke, fill, date, tags):
+ self._info_view.set_author(author, stroke, fill)
+ self._info_view.set_date(date)
+ self._info_view.set_tags(tags)
+ self._switch_mode(mode)
+
+ def show_info_photo(self, author, stroke, fill, date, tags):
+ self._show_info(MediaView.MODE_INFO_PHOTO, author, stroke, fill, date, tags)
+
+ def show_info_video(self, author, stroke, fill, date, tags):
+ self._show_info(MediaView.MODE_INFO_VIDEO, author, stroke, fill, date, tags)
+
+ def set_fullscreen(self, fullscreen):
+ if self._hide_controls_timer:
+ gobject.source_remove(self._hide_controls_timer)
+ self._hide_controls_timer = None
+
+ if fullscreen:
+ self._full_button.set_reduce()
+ else:
+ self._full_button.set_enlarge()
+
+ def realize_video(self):
+ self._video.realize()
+ self._video2.realize()
+
+ # can be called from gstreamer thread, must not do any GTK+ stuff
+ def set_video_sink(self, sink):
+ self._video.set_sink(sink)
+
+ # can be called from gstreamer thread, must not do any GTK+ stuff
+ def set_video2_sink(self, sink):
+ self._video2.set_sink(sink)
+
+ def show_still(self, pixbuf):
+ # don't modify the original...
+ pixbuf = pixbuf.copy()
+ pixbuf.saturate_and_pixelate(pixbuf, 0, 0)
+ self._image_box.set_pixbuf(pixbuf)
+ self._switch_mode(MediaView.MODE_STILL)
+
+ def show_photo(self, path):
+ pixbuf = gdk.pixbuf_new_from_file(path)
+ self._image_box.set_pixbuf(pixbuf)
+ self._switch_mode(MediaView.MODE_PHOTO)
+
+ def show_video(self):
+ self._switch_mode(MediaView.MODE_VIDEO)
+
+ def show_live(self):
+ self._switch_mode(MediaView.MODE_LIVE)
+
+ def show(self):
+ self._fixed.show()
+ super(MediaView, self).show()
+
+ def hide(self):
+ self._fixed.hide()
+ super(MediaView, self).hide()
+
diff --git a/model.py b/model.py
index 9d397b2..f40061e 100644
--- a/model.py
+++ b/model.py
@@ -19,345 +19,316 @@
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
-
+from gettext import gettext as _
+from xml.dom.minidom import parse
+import logging
import uuid
-import urllib
-import string
-import fnmatch
import os
-import random
-import cairo
-import gtk
-import gtk.gdk
-import pygtk
-pygtk.require('2.0')
-import gc
-import math
import time
-import gobject
-import operator
+import json
-import logging
-logger = logging.getLogger('record:model.py')
+import gobject
+import gst
+import sugar.profile
import sugar.env
-from constants import Constants
+import aplay
+import constants
from instance import Instance
from recorded import Recorded
-from color import Color
-from ui import UI
import utils
-import record
import serialize
+from collab import RecordCollab
+from glive import Glive
+from gplay import Gplay
+logger = logging.getLogger('model')
class Model:
- def __init__( self, pca ):
- self.ca = pca
- self.MODE = Constants.MODE_PHOTO
- self.UPDATING = True
- self.RECORDING = False
- self.FULL = self._isXoFull()
+ def __init__(self, activity_obj):
+ self.activity = activity_obj
+
+ self.collab = RecordCollab(self.activity, self)
+ self.glive = Glive(self.activity, self)
+ self.gplay = Gplay(self.activity)
+ self.gplay.connect('playback-status-changed', self._playback_status_changed)
+
+ self._mode = None
+ self._state = constants.STATE_READY
+ self._countdown_value = 0
+ self._countdown_handle = None
+ self._timer_value = 0
+ self._timer_duration = 0
+ self._timer_handle = None
self.mediaHashs = {}
- for key,value in Constants.mediaTypes.items():
+ for key, value in constants.MEDIA_INFO.items():
self.mediaHashs[key] = []
+ def write_file(self, path):
+ ui_serialized = self.activity.serialize()
+ self.mediaHashs['ui'] = ui_serialized
+ dom = serialize.saveMediaHash(self.mediaHashs, self.activity)
+ ui_data = json.dumps(ui_serialized)
+ ui_el = dom.createElement('ui')
+ ui_el.appendChild(dom.createTextNode(ui_data))
+ dom.documentElement.appendChild(ui_el)
+
+ fd = open(path, "w")
+ dom.writexml(fd)
+ fd.close()
+
+ def read_file(self, path):
+ try:
+ dom = parse(path)
+ except Exception, e:
+ logger.error('read_file: %s' % e)
+ return
- def updateXoFullStatus( self ):
- self.FULL = self._isXoFull()
-
-
- def _isXoFull( self ):
- full = False
- if (utils.getFreespaceKb() <= Constants.keepFreeKbOnXo):
- full = True
-
- return full
-
-
- def getRecdByMd5( self, md5 ):
- for mh in range (0, len(self.mediaHashs)):
- for r in range (0, len(self.mediaHashs[mh])):
- recd = self.mediaHashs[mh][r]
- if (recd.thumbMd5 == md5):
- return recd
- elif (recd.mediaMd5 == md5):
- return recd
-
- return None
-
+ serialize.fillMediaHash(dom, self.mediaHashs)
+ for i in dom.documentElement.getElementsByTagName('ui'):
+ for ui_el in i.childNodes:
+ self.activity.deserialize(json.loads(ui_el.data))
- def isVideoMode( self ):
- return self.MODE == Constants.MODE_VIDEO
+ for recd in self.mediaHashs[self._mode]:
+ self.activity.add_thumbnail(recd, True)
+ def get_has_camera(self):
+ return self.glive.get_has_camera()
- def isPhotoMode( self ):
- return self.MODE == Constants.MODE_PHOTO
+ def get_nickname(self):
+ return sugar.profile.get_nick_name()
+ def get_mode(self):
+ return self._mode
- def displayThumb( self, recd, forceUpdating ):
- #to avoid Xlib: unexpected async reply error when taking a picture on a gst callback, always call with idle_add
- #this happens b/c this might get called from a gstreamer callback
- if (not recd.type == self.MODE):
+ def change_mode(self, mode):
+ if mode == self._mode:
return
- if (forceUpdating):
- self.setUpdating( True )
- hash = self.mediaHashs[recd.type]
- if (len(hash) > 0):
- self.ca.ui.addThumb(recd, forceUpdating)
- if (forceUpdating):
- self.setUpdating( False )
-
-
- def setupMode( self, type, update ):
- if (not type == self.MODE):
- return
-
- self.setUpdating( True )
- self.ca.ui.removeThumbs()
- hash = self.mediaHashs[type]
- for i in range (0, len(hash)):
- self.ca.ui.addThumb( hash[i], True )
- if (update):
- self.ca.ui.updateModeChange()
- self.setUpdating(False)
-
-
- def showNextThumb( self, shownRecd ):
- if (shownRecd == None):
- self.showLastThumb()
- else:
- hash = self.mediaHashs[self.MODE]
- if (len(hash) > 0):
- hash = self.mediaHashs[self.MODE]
- i = operator.indexOf( hash, shownRecd )
- i = i+1
- if (i>=len(hash)):
- i = 0
- self.ca.ui.showThumbSelection( hash[i] )
-
-
- def showPrevThumb( self, shownRecd ):
- if (shownRecd == None):
- self.showLastThumb()
- else:
- hash = self.mediaHashs[self.MODE]
- if (len(hash) > 0):
- hash = self.mediaHashs[self.MODE]
- i = operator.indexOf( hash, shownRecd )
- i = i-1
- if (i<0):
- i = len(hash)-1
- self.ca.ui.showThumbSelection( hash[i] )
+ self._mode = mode
+ self.activity.remove_all_thumbnails()
+ for recd in self.mediaHashs[mode]:
+ self.activity.add_thumbnail(recd, True)
- def showLastThumb( self ):
- hash = self.mediaHashs[self.MODE]
- if (len(hash) > 0):
- self.ca.ui.showThumbSelection( hash[len(hash)-1] )
+ self.set_state(constants.STATE_READY)
+ if mode == constants.MODE_PHOTO:
+ self.glive.play()
- def doShutter( self ):
- if (self.UPDATING):
- return
+ def ui_frozen(self):
+ return not self._state == constants.STATE_READY
- if (self.MODE == Constants.MODE_PHOTO):
- self.startTakingPhoto()
- elif (self.MODE == Constants.MODE_VIDEO):
- if (not self.RECORDING):
- self.startRecordingVideo()
- else:
- #post-processing begins now, so queue up this gfx
- self.ca.ui.showPostProcessGfx(True)
- self.stopRecordingVideo()
- elif (self.MODE == Constants.MODE_AUDIO):
- if (not self.RECORDING):
- self.startRecordingAudio()
- else:
- #post-processing begins now, so queue up this gfx
- self.ca.ui.showPostProcessGfx(True)
- self.stopRecordingAudio()
-
-
- def stopRecordingAudio( self ):
- gobject.source_remove( self.ca.ui.UPDATE_DURATION_ID )
- self.ca.ui.progressWindow.updateProgress( 0, "" )
- self.setUpdating( True )
- self.setRecording( False )
- self.ca.ui.FULLSCREEN = False
- self.ca.ui.updateVideoComponents()
-
- self.ca.glive.stopRecordingAudio( )
-
-
- def saveAudio( self, tmpPath, pixbuf ):
- self.setUpdating( True )
-
- recd = self.createNewRecorded( Constants.TYPE_AUDIO )
- os.rename( tmpPath, os.path.join(Instance.instancePath,recd.mediaFilename))
-
- thumbPath = os.path.join(Instance.instancePath, recd.thumbFilename)
- scale = float((UI.dim_THUMB_WIDTH+0.0)/(pixbuf.get_width()+0.0))
- thumbImg = utils.generateThumbnail(pixbuf, scale, UI.dim_THUMB_WIDTH, UI.dim_THUMB_HEIGHT)
- thumbImg.write_to_png(thumbPath)
-
- imagePath = os.path.join(Instance.instancePath, "audioPicture.png")
- imagePath = utils.getUniqueFilepath( imagePath, 0 )
- pixbuf.save( imagePath, "png", {} )
- recd.audioImageFilename = os.path.basename(imagePath)
-
- #at this point, we have both audio and thumb sapath, so we can save the recd
- self.createNewRecordedMd5Sums( recd )
+ def set_state(self, state):
+ self._state = state
- audioHash = self.mediaHashs[Constants.TYPE_AUDIO]
- audioHash.append( recd )
- gobject.idle_add(self.displayThumb, recd, True)
- self.doPostSaveVideo()
- self.meshShareRecd( recd )
+ if state == constants.STATE_READY:
+ self.gplay.stop()
+ self.glive.play()
+ self.activity.set_state(state)
- def startRecordingVideo( self ):
- self.setUpdating( True )
- self.setRecording( True )
- #let the red eye kick in before we start the video underway
- gobject.idle_add( self.beginRecordingVideo )
+ def get_state(self):
+ return self._state
+ def set_progress(self, value, text):
+ self.activity.set_progress(value, text)
- def beginRecordingVideo( self ):
- self.ca.ui.recordVideo()
- self.setUpdating( False )
+ def _timer_tick(self):
+ self._timer_value = self._timer_value - 1
+ value = self._timer_value
+ progress_value = 1 - (float(value) / float(self._timer_duration))
+ mins = value / 60
+ secs = value % 60
+ text = _('%d:%02d remaining') % (mins, secs)
- def startRecordingAudio( self ):
- self.setUpdating( True )
- self.setRecording( True )
- self.ca.ui.recordAudio()
- self.setUpdating( False )
+ self.set_progress(progress_value, text)
+ if self._timer_value <= 0:
+ self._timer_handle = None
+ self._timer_value = 0
+ self._stop_media_capture()
+ return False
- def setUpdating( self, upd ):
- self.UPDATING = upd
- self.ca.ui.updateButtonSensitivities()
+ return True
+ def _start_media_capture(self):
+ if self._mode == constants.MODE_PHOTO:
+ self.activity.set_shutter_sensitive(False)
+ self.glive.take_photo()
+ return
- def setRecording( self, rec ):
- self.RECORDING = rec
- self.ca.ui.updateButtonSensitivities()
+ self._timer_value = self.activity.get_selected_duration()
+ self._timer_duration = self._timer_value
+ self._timer_handle = gobject.timeout_add(1000, self._timer_tick)
+ self.activity.set_shutter_sensitive(True)
+ self.set_state(constants.STATE_RECORDING)
- def stopRecordingVideo( self ):
- self.ca.glive.stopRecordingVideo()
- gobject.source_remove( self.ca.ui.UPDATE_DURATION_ID )
- self.setUpdating( True )
- self.setRecording( False )
- self.ca.ui.FULLSCREEN = False
- self.ca.ui.updateVideoComponents()
+ if self._mode == constants.MODE_VIDEO:
+ quality = self.activity.get_selected_quality()
+ self.glive.record_video(quality)
+ elif self._mode == constants.MODE_AUDIO:
+ self.glive.record_audio()
+ def _stop_media_capture(self):
+ if self._timer_handle:
+ gobject.source_remove(self._timer_handle)
+ self._timer_handle = None
+ self._timer_value = 0
- def saveVideo( self, pixbuf, tmpPath, wid, hit ):
- recd = self.createNewRecorded( Constants.TYPE_VIDEO )
- os.rename( tmpPath, os.path.join(Instance.instancePath,recd.mediaFilename))
+ self.set_progress(0, '')
- thumbPath = os.path.join(Instance.instancePath, recd.thumbFilename)
- scale = float((UI.dim_THUMB_WIDTH+0.0)/(wid+0.0))
- thumbImg = utils.generateThumbnail(pixbuf, scale, UI.dim_THUMB_WIDTH, UI.dim_THUMB_HEIGHT)
- thumbImg.write_to_png(thumbPath)
+ if self._mode == constants.MODE_VIDEO:
+ self.glive.stop_recording_video()
+ elif self._mode == constants.MODE_AUDIO:
+ self.glive.stop_recording_audio()
- self.createNewRecordedMd5Sums( recd )
+ self.set_state(constants.STATE_PROCESSING)
- videoHash = self.mediaHashs[Constants.TYPE_VIDEO]
- videoHash.append( recd )
- self.doPostSaveVideo()
- gobject.idle_add(self.displayThumb, recd, True)
- self.meshShareRecd( recd )
+ def shutter_sound(self, done_cb=None):
+ aplay.play('photoShutter.wav', done_cb)
+ def _countdown_tick(self):
+ self._countdown_value = self._countdown_value - 1
+ value = self._countdown_value
+ self.activity.set_countdown(value)
- def meshShareRecd( self, recd ):
- #hey, i just took a cool video.audio.photo! let me show you!
- if (self.ca.recTube != None):
- logger.debug('meshShareRecd')
- recdXml = serialize.getRecdXmlMeshString(recd)
- self.ca.recTube.notifyBudsOfNewRecd( Instance.keyHashPrintable, recdXml )
+ if value <= 0:
+ self._countdown_handle = None
+ self._countdown_value = 0
+ self.shutter_sound(self._start_media_capture)
+ return False
+ return True
- def cannotSaveVideo( self ):
- self.doPostSaveVideo()
+ def do_shutter(self):
+ # if recording, stop
+ if self._state == constants.STATE_RECORDING:
+ self._stop_media_capture()
+ return
+ # if timer is selected, start countdown
+ timer = self.activity.get_selected_timer()
+ if timer > 0:
+ self.activity.set_shutter_sensitive(False)
+ self._countdown_value = self.activity.get_selected_timer()
+ self._countdown_handle = gobject.timeout_add(1000, self._countdown_tick)
+ return
- def doPostSaveVideo( self ):
- self.ca.ui.showPostProcessGfx(False)
+ # otherwise, capture normally
+ self.shutter_sound(self._start_media_capture)
- #prep the ui for your return
- self.ca.ui.LAST_MODE = -1
- self.ca.ui.TRANSCODING = False
+ # called from gstreamer thread
+ def still_ready(self, pixbuf):
+ gobject.idle_add(self.activity.show_still, pixbuf)
- #resume live video from the camera (if the activity is active)
- if (self.ca.ui.ACTIVE):
- self.ca.ui.updateVideoComponents()
+ def add_recd(self, recd):
+ self.mediaHashs[recd.type].append(recd)
+ if self._mode == recd.type:
+ self.activity.add_thumbnail(recd, True)
- self.ca.ui.progressWindow.updateProgress( 0, "" )
- self.setRecording( False )
- self.setUpdating( False )
+ if not recd.buddy:
+ self.collab.share_recd(recd)
+ # called from gstreamer thread
+ def save_photo(self, pixbuf):
+ recd = self.createNewRecorded(constants.TYPE_PHOTO)
- def abandonRecording( self ):
+ imgpath = os.path.join(Instance.instancePath, recd.mediaFilename)
+ pixbuf.save(imgpath, "jpeg")
- self.ca.ui.LAST_MODE = -1
- self.ca.ui.TRANSCODING = False
- self.ca.ui.completeTimer()
- self.ca.ui.completeCountdown()
- self.setRecording(False)
+ thumbpath = os.path.join(Instance.instancePath, recd.thumbFilename)
+ pixbuf = utils.generate_thumbnail(pixbuf)
+ pixbuf.save(thumbpath, "png")
- self.ca.ui.progressWindow.updateProgress( 0, "" )
+ #now that we've saved both the image and its pixbuf, we get their md5s
+ self.createNewRecordedMd5Sums( recd )
- self.ca.glive.abandonMedia()
+ gobject.idle_add(self.add_recd, recd, priority=gobject.PRIORITY_HIGH)
+ gobject.idle_add(self.activity.set_shutter_sensitive, True, priority=gobject.PRIORITY_HIGH)
+ # called from gstreamer thread
+ def save_video(self, path, still):
+ recd = self.createNewRecorded(constants.TYPE_VIDEO)
+ os.rename(path, os.path.join(Instance.instancePath, recd.mediaFilename))
- def stoppedRecordingVideo( self ):
- self.setUpdating( False )
+ thumb_path = os.path.join(Instance.instancePath, recd.thumbFilename)
+ still = utils.generate_thumbnail(still)
+ still.save(thumb_path, "png")
+ self.createNewRecordedMd5Sums( recd )
- def startTakingPhoto( self ):
- self.setUpdating( True )
- self.ca.glive.takePhoto()
+ gobject.idle_add(self.add_recd, recd, priority=gobject.PRIORITY_HIGH)
+ gobject.idle_add(self.set_state, constants.STATE_READY)
+ def save_audio(self, path, still):
+ recd = self.createNewRecorded(constants.TYPE_AUDIO)
+ os.rename(path, os.path.join(Instance.instancePath, recd.mediaFilename))
- def savePhoto( self, pixbuf ):
- recd = self.createNewRecorded( Constants.TYPE_PHOTO )
+ image_path = os.path.join(Instance.instancePath, "audioPicture.png")
+ image_path = utils.getUniqueFilepath(image_path, 0)
+ still.save(image_path, "png")
+ recd.audioImageFilename = os.path.basename(image_path)
- imgpath = os.path.join(Instance.instancePath, recd.mediaFilename)
- pixbuf.save( imgpath, "jpeg" )
+ thumb_path = os.path.join(Instance.instancePath, recd.thumbFilename)
+ still = utils.generate_thumbnail(still)
+ still.save(thumb_path, "png")
- thumbpath = os.path.join(Instance.instancePath, recd.thumbFilename)
- scale = float((UI.dim_THUMB_WIDTH+0.0)/(pixbuf.get_width()+0.0))
- thumbImg = utils.generateThumbnail(pixbuf, scale, UI.dim_THUMB_WIDTH, UI.dim_THUMB_HEIGHT)
- thumbImg.write_to_png(thumbpath)
- gc.collect()
- #now that we've saved both the image and its pixbuf, we get their md5s
self.createNewRecordedMd5Sums( recd )
- photoHash = self.mediaHashs[Constants.TYPE_PHOTO]
- photoHash.append( recd )
- gobject.idle_add(self.displayThumb, recd, True)
+ gobject.idle_add(self.add_recd, recd, priority=gobject.PRIORITY_HIGH)
+ gobject.idle_add(self.set_state, constants.STATE_READY)
+
+ def _playback_status_changed(self, widget, status, value):
+ self.activity.set_playback_scale(value)
+ if status == gst.STATE_NULL:
+ self.activity.set_paused(True)
+
+ def play_audio(self, recd):
+ self.gplay.set_location("file://" + recd.getMediaFilepath())
+ self.gplay.play()
+ self.activity.set_paused(False)
+
+ def play_video(self, recd):
+ self.gplay.set_location("file://" + recd.getMediaFilepath())
+ self.glive.stop()
+ self.gplay.play()
+ self.glive.play(use_xv=False)
+ self.activity.set_paused(False)
+
+ def play_pause(self):
+ if self.gplay.get_state() == gst.STATE_PLAYING:
+ self.gplay.pause()
+ self.activity.set_paused(True)
+ else:
+ self.gplay.play()
+ self.activity.set_paused(False)
- self.meshShareRecd( recd )
+ def start_seek(self):
+ self.gplay.pause()
+ def do_seek(self, position):
+ self.gplay.seek(position)
- def addMeshRecd( self, recd ):
- #todo: sort on time-taken, not on their arrival time over the mesh (?)
- self.mediaHashs[recd.type].append( recd )
+ def end_seek(self):
+ self.gplay.play()
- #updateUi, but don't lock up the buttons if they're recording or whatever
- gobject.idle_add(self.displayThumb, recd, False)
+ def get_recd_by_md5(self, md5):
+ for mh in self.mediaHashs.values():
+ for recd in mh:
+ if recd.thumbMd5 == md5 or recd.mediaMd5 == md5:
+ return recd
+ return None
- def createNewRecorded( self, type ):
- recd = Recorded( )
+ def createNewRecorded(self, type):
+ recd = Recorded()
- recd.recorderName = Instance.nickName
+ recd.recorderName = self.get_nickname()
recd.recorderHash = Instance.keyHashPrintable
#to create a file, use the hardware_id+time *and* check if available or not
@@ -367,7 +338,7 @@ class Model:
mediaThumbFilename = str(recd.recorderHash) + "_" + str(recd.time)
mediaFilename = mediaThumbFilename
- mediaFilename = mediaFilename + "." + Constants.mediaTypes[type][Constants.keyExt]
+ mediaFilename = mediaFilename + "." + constants.MEDIA_INFO[type]['ext']
mediaFilepath = os.path.join( Instance.instancePath, mediaFilename )
mediaFilepath = utils.getUniqueFilepath( mediaFilepath, 0 )
recd.mediaFilename = os.path.basename( mediaFilepath )
@@ -377,16 +348,18 @@ class Model:
thumbFilepath = utils.getUniqueFilepath( thumbFilepath, 0 )
recd.thumbFilename = os.path.basename( thumbFilepath )
- stringType = Constants.mediaTypes[type][Constants.keyIstr]
- recd.title = Constants.istrBy % {"1":stringType, "2":str(recd.recorderName)}
+ stringType = constants.MEDIA_INFO[type]['istr']
+
+ # Translators: photo by photographer, e.g. "Photo by Mary"
+ recd.title = _('%s by %s') % (stringType, recd.recorderName)
- recd.colorStroke = Instance.colorStroke
- recd.colorFill = Instance.colorFill
+ color = sugar.profile.get_color()
+ recd.colorStroke = color.get_stroke_color()
+ recd.colorFill = color.get_fill_color()
logger.debug('createNewRecorded: ' + str(recd) + ", thumbFilename:" + str(recd.thumbFilename))
return recd
-
def createNewRecordedMd5Sums( self, recd ):
recd.thumbMd5 = recd.mediaMd5 = str(uuid.uuid4())
@@ -402,56 +375,29 @@ class Model:
mBytes = os.stat(mediaFile)[6]
recd.mediaBytes = mBytes
-
- def deleteRecorded( self, recd ):
+ def delete_recd(self, recd):
recd.deleted = True
+ self.mediaHashs[recd.type].remove(recd)
- #clear the index
- hash = self.mediaHashs[recd.type]
- index = hash.index(recd)
- hash.remove( recd )
-
- if (not recd.meshUploading):
- self.doDeleteRecorded( recd )
-
+ if recd.meshUploading:
+ return
- def doDeleteRecorded( self, recd ):
#remove files from the filesystem if not on the datastore
- if (recd.datastoreId == None):
+ if recd.datastoreId == None:
mediaFile = recd.getMediaFilepath()
- if (os.path.exists(mediaFile)):
+ if os.path.exists(mediaFile):
os.remove(mediaFile)
- thumbFile = recd.getThumbFilepath( )
- if (os.path.exists(thumbFile)):
+ thumbFile = recd.getThumbFilepath()
+ if os.path.exists(thumbFile):
os.remove(thumbFile)
else:
#remove from the datastore here, since once gone, it is gone...
- serialize.removeMediaFromDatastore( recd )
-
-
- def doVideoMode( self ):
- if (self.MODE == Constants.MODE_VIDEO):
- return
+ serialize.removeMediaFromDatastore(recd)
- self.MODE = Constants.MODE_VIDEO
- self.setUpdating(True)
- gobject.idle_add( self.setupMode, self.MODE, True )
-
-
- def doPhotoMode( self ):
- if (self.MODE == Constants.MODE_PHOTO):
- return
-
- self.MODE = Constants.MODE_PHOTO
- self.setUpdating(True)
- gobject.idle_add( self.setupMode, self.MODE, True )
-
-
- def doAudioMode( self ):
- if (self.MODE == Constants.MODE_AUDIO):
- return
+ def request_download(self, recd):
+ self.activity.show_still(recd.getThumbPixbuf())
+ self.set_state(constants.STATE_DOWNLOADING)
+ self.collab.request_download(recd)
+ self.activity.update_download_progress(recd)
- self.MODE = Constants.MODE_AUDIO
- self.setUpdating(True)
- gobject.idle_add( self.setupMode, self.MODE, True )
diff --git a/network.py b/network.py
new file mode 100644
index 0000000..bd4848a
--- /dev/null
+++ b/network.py
@@ -0,0 +1,1076 @@
+#
+# Copyright (C) 2008-2010 One Laptop Per Child
+# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer
+# Copyright (C) 2009 Paraguay Educa, Martin Abente
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+import logging
+import sha
+import socket
+import struct
+import re
+import datetime
+import time
+import gtk
+import gobject
+import gconf
+import dbus
+
+from sugar.graphics.icon import get_icon_state
+from sugar.graphics import style
+from sugar.graphics.palette import Palette
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.tray import TrayIcon
+from sugar.graphics import xocolor
+from sugar.util import unique_id
+from sugar import profile
+
+from jarabe.model import network
+from jarabe.model.network import Settings
+from jarabe.model.network import IP4Config
+from jarabe.frame.frameinvoker import FrameWidgetInvoker
+from jarabe.view.pulsingicon import PulsingIcon
+
+IP_ADDRESS_TEXT_TEMPLATE = _("IP address: %s")
+
+_NM_SERVICE = 'org.freedesktop.NetworkManager'
+_NM_IFACE = 'org.freedesktop.NetworkManager'
+_NM_PATH = '/org/freedesktop/NetworkManager'
+_NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device'
+_NM_WIRED_IFACE = 'org.freedesktop.NetworkManager.Device.Wired'
+_NM_WIRELESS_IFACE = 'org.freedesktop.NetworkManager.Device.Wireless'
+_NM_OLPC_MESH_IFACE = 'org.freedesktop.NetworkManager.Device.OlpcMesh'
+_NM_SERIAL_IFACE = 'org.freedesktop.NetworkManager.Device.Serial'
+_NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint'
+_NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active'
+
+_GSM_STATE_NOT_READY = 0
+_GSM_STATE_DISCONNECTED = 1
+_GSM_STATE_CONNECTING = 2
+_GSM_STATE_CONNECTED = 3
+_GSM_STATE_NEED_AUTH = 4
+
+def frequency_to_channel(frequency):
+ ftoc = { 2412: 1, 2417: 2, 2422: 3, 2427: 4,
+ 2432: 5, 2437: 6, 2442: 7, 2447: 8,
+ 2452: 9, 2457: 10, 2462: 11, 2467: 12,
+ 2472: 13}
+ return ftoc[frequency]
+
+class WirelessPalette(Palette):
+ __gtype_name__ = 'SugarWirelessPalette'
+
+ __gsignals__ = {
+ 'deactivate-connection' : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([])),
+ 'create-connection' : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([])),
+ }
+
+ def __init__(self, primary_text, can_create=True):
+ Palette.__init__(self, label=primary_text)
+
+ self._disconnect_item = None
+
+ self._channel_label = gtk.Label()
+ self._channel_label.props.xalign = 0.0
+ self._channel_label.show()
+
+ self._ip_address_label = gtk.Label()
+
+ self._info = gtk.VBox()
+
+ def _padded(child, xalign=0, yalign=0.5):
+ padder = gtk.Alignment(xalign=xalign, yalign=yalign,
+ xscale=1, yscale=0.33)
+ padder.set_padding(style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING)
+ padder.add(child)
+ return padder
+
+ self._info.pack_start(_padded(self._channel_label))
+ self._info.pack_start(_padded(self._ip_address_label))
+ self._info.show_all()
+
+ self._disconnect_item = gtk.MenuItem(_('Disconnect...'))
+ self._disconnect_item.connect('activate', self.__disconnect_activate_cb)
+ self.menu.append(self._disconnect_item)
+
+ if can_create:
+ self._adhoc_item = gtk.MenuItem(_('Create new wireless network'))
+ self._adhoc_item.connect('activate', self.__adhoc_activate_cb)
+ self.menu.append(self._adhoc_item)
+ self._adhoc_item.show()
+
+ def set_connecting(self):
+ self.props.secondary_text = _('Connecting...')
+
+ def _set_connected(self, iaddress):
+ self.set_content(self._info)
+ self.props.secondary_text = _('Connected')
+ self._set_ip_address(iaddress)
+ self._disconnect_item.show()
+
+ def set_connected_with_frequency(self, frequency, iaddress):
+ self._set_connected(iaddress)
+ self._set_frequency(frequency)
+
+ def set_connected_with_channel(self, channel, iaddress):
+ self._set_connected(iaddress)
+ self._set_channel(channel)
+
+ def set_disconnected(self):
+ self.props.primary_text = ''
+ self.props.secondary_text = ''
+ self._disconnect_item.hide()
+ self.set_content(None)
+
+ def __disconnect_activate_cb(self, menuitem):
+ self.emit('deactivate-connection')
+
+ def __adhoc_activate_cb(self, menuitem):
+ self.emit('create-connection')
+
+ def _set_frequency(self, frequency):
+ try:
+ channel = frequency_to_channel(frequency)
+ except KeyError:
+ channel = 0
+ self._set_channel(channel)
+
+ def _set_channel(self, channel):
+ self._channel_label.set_text("%s: %d" % (_("Channel"), channel))
+
+ def _set_ip_address(self, ip_address):
+ if ip_address is not None:
+ ip_address_text = IP_ADDRESS_TEXT_TEMPLATE % \
+ socket.inet_ntoa(struct.pack('I', ip_address))
+ else:
+ ip_address_text = ""
+ self._ip_address_label.set_text(ip_address_text)
+
+
+class WiredPalette(Palette):
+ __gtype_name__ = 'SugarWiredPalette'
+
+ def __init__(self):
+ Palette.__init__(self, label=_('Wired Network'))
+
+ self._speed_label = gtk.Label()
+ self._speed_label.props.xalign = 0.0
+ self._speed_label.show()
+
+ self._ip_address_label = gtk.Label()
+
+ self._info = gtk.VBox()
+
+ def _padded(child, xalign=0, yalign=0.5):
+ padder = gtk.Alignment(xalign=xalign, yalign=yalign,
+ xscale=1, yscale=0.33)
+ padder.set_padding(style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING)
+ padder.add(child)
+ return padder
+
+ self._info.pack_start(_padded(self._speed_label))
+ self._info.pack_start(_padded(self._ip_address_label))
+ self._info.show_all()
+
+ self.set_content(self._info)
+ self.props.secondary_text = _('Connected')
+
+ def set_connected(self, speed, iaddress):
+ self._speed_label.set_text('%s: %d Mb/s' % (_('Speed'), speed))
+ self._set_ip_address(iaddress)
+
+ def _inet_ntoa(self, iaddress):
+ address = ['%s' % ((iaddress >> i) % 256) for i in [0, 8, 16, 24]]
+ return ".".join(address)
+
+ def _set_ip_address(self, ip_address):
+ if ip_address is not None:
+ ip_address_text = IP_ADDRESS_TEXT_TEMPLATE % \
+ socket.inet_ntoa(struct.pack('I', ip_address))
+ else:
+ ip_address_text = ""
+ self._ip_address_label.set_text(ip_address_text)
+
+class GsmPalette(Palette):
+ __gtype_name__ = 'SugarGsmPalette'
+
+ __gsignals__ = {
+ 'gsm-connect' : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([])),
+ 'gsm-disconnect' : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([])),
+ }
+
+ def __init__(self):
+ Palette.__init__(self, label=_('Wireless modem'))
+
+ self._current_state = None
+
+ self._toggle_state_item = gtk.MenuItem('')
+ self._toggle_state_item.connect('activate', self.__toggle_state_cb)
+ self.menu.append(self._toggle_state_item)
+ self._toggle_state_item.show()
+
+ self.set_gsm_state(_GSM_STATE_NOT_READY)
+
+ self.info_box = gtk.VBox()
+
+ self.data_label = gtk.Label()
+ self.data_label.props.xalign = 0.0
+ label_alignment = self._add_widget_with_padding(self.data_label)
+ self.info_box.pack_start(label_alignment)
+ self.data_label.show()
+ label_alignment.show()
+
+ self.connection_time_label = gtk.Label()
+ self.connection_time_label.props.xalign = 0.0
+ label_alignment = self._add_widget_with_padding( \
+ self.connection_time_label)
+ self.info_box.pack_start(label_alignment)
+ self.connection_time_label.show()
+ label_alignment.show()
+
+ self.info_box.show()
+ self.set_content(self.info_box)
+
+ def _add_widget_with_padding(self, child, xalign=0, yalign=0.5):
+ alignment = gtk.Alignment(xalign=xalign, yalign=yalign,
+ xscale=1, yscale=0.33)
+ alignment.set_padding(style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING)
+ alignment.add(child)
+ return alignment
+
+ def set_gsm_state(self, state):
+ self._current_state = state
+ self._update_label_and_text()
+
+ def _update_label_and_text(self):
+ if self._current_state == _GSM_STATE_NOT_READY:
+ self._toggle_state_item.get_child().set_label('...')
+ self.props.secondary_text = _('Please wait...')
+
+ elif self._current_state == _GSM_STATE_DISCONNECTED:
+ self._toggle_state_item.get_child().set_label(_('Connect'))
+ self.props.secondary_text = _('Disconnected')
+
+ elif self._current_state == _GSM_STATE_CONNECTING:
+ self._toggle_state_item.get_child().set_label(_('Cancel'))
+ self.props.secondary_text = _('Connecting...')
+
+ elif self._current_state == _GSM_STATE_CONNECTED:
+ self._toggle_state_item.get_child().set_label(_('Disconnect'))
+ self.props.secondary_text = _('Connected')
+
+ elif self._current_state == _GSM_STATE_NEED_AUTH:
+ self._toggle_state_item.get_child().set_label(_('Sim requires Pin/Puk'))
+ self.props.secondary_text = _('Authentication Error')
+
+ else:
+ raise ValueError('Invalid GSM state while updating label and ' \
+ 'text, %s' % str(self._current_state))
+
+ def __toggle_state_cb(self, menuitem):
+ if self._current_state == _GSM_STATE_NOT_READY:
+ pass
+ elif self._current_state == _GSM_STATE_DISCONNECTED:
+ self.emit('gsm-connect')
+ elif self._current_state == _GSM_STATE_CONNECTING:
+ self.emit('gsm-disconnect')
+ elif self._current_state == _GSM_STATE_CONNECTED:
+ self.emit('gsm-disconnect')
+ elif self._current_state == _GSM_STATE_NEED_AUTH:
+ self.emit('gsm-disconnect')
+ else:
+ raise ValueError('Invalid GSM state while emitting signal, %s' % \
+ str(self._current_state))
+
+
+class WirelessDeviceView(ToolButton):
+
+ _ICON_NAME = 'network-wireless'
+ FRAME_POSITION_RELATIVE = 302
+
+ def __init__(self, device):
+ ToolButton.__init__(self)
+
+ self._bus = dbus.SystemBus()
+ self._device = device
+ self._device_props = None
+ self._flags = 0
+ self._name = ''
+ self._mode = network.NM_802_11_MODE_UNKNOWN
+ self._strength = 0
+ self._frequency = 0
+ self._device_state = None
+ self._color = None
+ self._active_ap_op = None
+
+ self._icon = PulsingIcon()
+ self._icon.props.icon_name = get_icon_state(self._ICON_NAME, 0)
+ self._inactive_color = xocolor.XoColor( \
+ "%s,%s" % (style.COLOR_BUTTON_GREY.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ self._icon.props.pulse_color = self._inactive_color
+ self._icon.props.base_color = self._inactive_color
+
+ self.set_icon_widget(self._icon)
+ self._icon.show()
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+ self._palette = WirelessPalette(self._name)
+ self._palette.connect('deactivate-connection',
+ self.__deactivate_connection_cb)
+ self._palette.connect('create-connection',
+ self.__create_connection_cb)
+ self.set_palette(self._palette)
+ self._palette.set_group_id('frame')
+
+ self._device_props = dbus.Interface(self._device, dbus.PROPERTIES_IFACE)
+ self._device_props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True,
+ reply_handler=self.__get_device_props_reply_cb,
+ error_handler=self.__get_device_props_error_cb)
+
+ self._device_props.Get(_NM_WIRELESS_IFACE, 'ActiveAccessPoint',
+ reply_handler=self.__get_active_ap_reply_cb,
+ error_handler=self.__get_active_ap_error_cb)
+
+ self._bus.add_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def disconnect(self):
+ self._bus.remove_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def __get_device_props_reply_cb(self, properties):
+ if 'State' in properties:
+ self._device_state = properties['State']
+ self._update_state()
+
+ def __get_device_props_error_cb(self, err):
+ logging.error('Error getting the device properties: %s', err)
+
+ def __get_active_ap_reply_cb(self, active_ap_op):
+ if self._active_ap_op != active_ap_op:
+ if self._active_ap_op is not None:
+ self._bus.remove_signal_receiver(
+ self.__ap_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=self._active_ap_op,
+ dbus_interface=_NM_ACCESSPOINT_IFACE)
+ if active_ap_op == '/':
+ self._active_ap_op = None
+ return
+ self._active_ap_op = active_ap_op
+ active_ap = self._bus.get_object(_NM_SERVICE, active_ap_op)
+ props = dbus.Interface(active_ap, dbus.PROPERTIES_IFACE)
+
+ props.GetAll(_NM_ACCESSPOINT_IFACE, byte_arrays=True,
+ reply_handler=self.__get_all_ap_props_reply_cb,
+ error_handler=self.__get_all_ap_props_error_cb)
+
+ self._bus.add_signal_receiver(self.__ap_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=self._active_ap_op,
+ dbus_interface=_NM_ACCESSPOINT_IFACE)
+
+ def __get_active_ap_error_cb(self, err):
+ logging.error('Error getting the active access point: %s', err)
+
+ def __state_changed_cb(self, new_state, old_state, reason):
+ self._device_state = new_state
+ self._update_state()
+ self._device_props.Get(_NM_WIRELESS_IFACE, 'ActiveAccessPoint',
+ reply_handler=self.__get_active_ap_reply_cb,
+ error_handler=self.__get_active_ap_error_cb)
+
+ def __ap_properties_changed_cb(self, properties):
+ self._update_properties(properties)
+
+ def _name_encodes_colors(self):
+ """Match #XXXXXX,#YYYYYY at the end of the network name"""
+ return self._name[-7] == '#' and self._name[-8] == ',' \
+ and self._name[-15] == '#'
+
+ def _update_properties(self, properties):
+ if 'Mode' in properties:
+ self._mode = properties['Mode']
+ self._color = None
+ if 'Ssid' in properties:
+ self._name = properties['Ssid']
+ self._color = None
+ if 'Strength' in properties:
+ self._strength = properties['Strength']
+ if 'Flags' in properties:
+ self._flags = properties['Flags']
+ if 'Frequency' in properties:
+ self._frequency = properties['Frequency']
+
+ if self._color == None:
+ if self._mode == network.NM_802_11_MODE_ADHOC \
+ and self._name_encodes_colors():
+ encoded_color = self._name.split("#", 1)
+ if len(encoded_color) == 2:
+ self._color = xocolor.XoColor('#' + encoded_color[1])
+ else:
+ sh = sha.new()
+ data = self._name + hex(self._flags)
+ sh.update(data)
+ h = hash(sh.digest())
+ idx = h % len(xocolor.colors)
+
+ self._color = xocolor.XoColor('%s,%s' %
+ (xocolor.colors[idx][0],
+ xocolor.colors[idx][1]))
+ self._update()
+
+ def __get_all_ap_props_reply_cb(self, properties):
+ self._update_properties(properties)
+
+ def __get_all_ap_props_error_cb(self, err):
+ logging.error('Error getting the access point properties: %s', err)
+
+ def _update(self):
+ if self._flags == network.NM_802_11_AP_FLAGS_PRIVACY:
+ self._icon.props.badge_name = "emblem-locked"
+ else:
+ self._icon.props.badge_name = None
+
+ self._palette.props.primary_text = self._name
+
+ self._update_state()
+ self._update_color()
+
+ def _update_state(self):
+ if self._active_ap_op is not None:
+ state = self._device_state
+ else:
+ state = network.DEVICE_STATE_UNKNOWN
+
+ if state == network.DEVICE_STATE_ACTIVATED:
+ icon_name = '%s-connected' % self._ICON_NAME
+ else:
+ icon_name = self._ICON_NAME
+
+ icon_name = get_icon_state(icon_name, self._strength)
+ if icon_name:
+ self._icon.props.icon_name = icon_name
+
+ if state == network.DEVICE_STATE_PREPARE or \
+ state == network.DEVICE_STATE_CONFIG or \
+ state == network.DEVICE_STATE_NEED_AUTH or \
+ state == network.DEVICE_STATE_IP_CONFIG:
+ self._palette.set_connecting()
+ self._icon.props.pulsing = True
+ elif state == network.DEVICE_STATE_ACTIVATED:
+ address = self._device_props.Get(_NM_DEVICE_IFACE, 'Ip4Address')
+ self._palette.set_connected_with_frequency(self._frequency,
+ address)
+ self._icon.props.pulsing = False
+ else:
+ self._icon.props.badge_name = None
+ self._icon.props.pulsing = False
+ self._icon.props.pulse_color = self._inactive_color
+ self._icon.props.base_color = self._inactive_color
+ self._palette.set_disconnected()
+
+ def _update_color(self):
+ self._icon.props.base_color = self._color
+
+ def __deactivate_connection_cb(self, palette, data=None):
+ connection = network.find_connection_by_ssid(self._name)
+ if connection:
+ if self._mode == network.NM_802_11_MODE_INFRA:
+ connection.set_disconnected()
+
+ if self._active_ap_op is not None:
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+ netmgr_props = dbus.Interface(netmgr, dbus.PROPERTIES_IFACE)
+ active_connections_o = netmgr_props.Get(_NM_IFACE,
+ 'ActiveConnections')
+
+ for conn_o in active_connections_o:
+ obj = self._bus.get_object(_NM_IFACE, conn_o)
+ props = dbus.Interface(obj, dbus.PROPERTIES_IFACE)
+ ap_op = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject')
+ if ap_op == self._active_ap_op:
+ netmgr.DeactivateConnection(conn_o)
+ break
+
+ def __create_connection_cb(self, palette, data=None):
+ """Create an 802.11 IBSS network.
+
+ The user's color is encoded at the end of the network name. The network
+ name is truncated so that it does not exceed the 32 byte SSID limit.
+ """
+ client = gconf.client_get_default()
+ nick = client.get_string('/desktop/sugar/user/nick').decode('utf-8')
+ color = client.get_string('/desktop/sugar/user/color')
+ color_suffix = ' %s' % color
+
+ format = _('%s\'s network').encode('utf-8')
+ extra_length = (len(format) - len('%s')) + len(color_suffix)
+ name_limit = 32 - extra_length
+
+ # truncate the nick and use a regex to drop any partial characters
+ # at the end
+ nick = nick.encode('utf-8')[:name_limit]
+ pattern = "([\xf6-\xf7][\x80-\xbf]{0,2}|[\xe0-\xef][\x80-\xbf]{0,1}|[\xc0-\xdf])$"
+ nick = re.sub(pattern, '', nick)
+
+ connection_name = format % nick
+ connection_name += color_suffix
+
+ connection = network.find_connection_by_ssid(connection_name)
+ if connection is None:
+ settings = Settings()
+ settings.connection.id = 'Auto ' + connection_name
+ uuid = settings.connection.uuid = unique_id()
+ settings.connection.type = '802-11-wireless'
+ settings.wireless.ssid = dbus.ByteArray(connection_name)
+ settings.wireless.band = 'bg'
+ settings.wireless.mode = 'adhoc'
+ settings.ip4_config = IP4Config()
+ settings.ip4_config.method = 'link-local'
+
+ connection = network.add_connection(uuid, settings)
+
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+
+ netmgr.ActivateConnection(network.SETTINGS_SERVICE,
+ connection.path,
+ self._device.object_path,
+ '/',
+ reply_handler=self.__activate_reply_cb,
+ error_handler=self.__activate_error_cb)
+
+ def __activate_reply_cb(self, connection):
+ logging.debug('Network created: %s', connection)
+
+ def __activate_error_cb(self, err):
+ logging.debug('Failed to create network: %s', err)
+
+class OlpcMeshDeviceView(ToolButton):
+ _ICON_NAME = 'network-mesh'
+ FRAME_POSITION_RELATIVE = 302
+
+ def __init__(self, device, state):
+ ToolButton.__init__(self)
+
+ self._bus = dbus.SystemBus()
+ self._device = device
+ self._device_props = None
+ self._device_state = None
+ self._channel = 0
+
+ self._icon = PulsingIcon(icon_name=self._ICON_NAME)
+ self._inactive_color = xocolor.XoColor( \
+ "%s,%s" % (style.COLOR_BUTTON_GREY.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ self._icon.props.pulse_color = profile.get_color()
+ self._icon.props.base_color = self._inactive_color
+
+ self.set_icon_widget(self._icon)
+ self._icon.show()
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+ self._palette = WirelessPalette(_("Mesh Network"), can_create=False)
+ self._palette.connect('deactivate-connection',
+ self.__deactivate_connection)
+ self.set_palette(self._palette)
+ self._palette.set_group_id('frame')
+
+ self.update_state(state)
+
+ self._device_props = dbus.Interface(self._device,
+ 'org.freedesktop.DBus.Properties')
+ self._device_props.Get(_NM_OLPC_MESH_IFACE, 'ActiveChannel',
+ reply_handler=self.__get_active_channel_reply_cb,
+ error_handler=self.__get_active_channel_error_cb)
+
+ self._bus.add_signal_receiver(self.__wireless_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=device.object_path,
+ dbus_interface=_NM_OLPC_MESH_IFACE)
+
+ def disconnect(self):
+ self._bus.remove_signal_receiver(self.__wireless_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_OLPC_MESH_IFACE)
+
+ def __get_active_channel_reply_cb(self, channel):
+ self._channel = channel
+ self._update_text()
+
+ def __get_active_channel_error_cb(self, err):
+ logging.error('Error getting the active channel: %s', err)
+
+ def __state_changed_cb(self, new_state, old_state, reason):
+ self._device_state = new_state
+ self._update()
+
+ def __wireless_properties_changed_cb(self, properties):
+ if 'ActiveChannel' in properties:
+ self._channel = properties['ActiveChannel']
+ self._update_text()
+
+ def _update_text(self):
+ state = self._device_state
+ if state in (network.DEVICE_STATE_PREPARE, network.DEVICE_STATE_CONFIG,
+ network.DEVICE_STATE_NEED_AUTH,
+ network.DEVICE_STATE_IP_CONFIG,
+ network.DEVICE_STATE_ACTIVATED):
+ text = _("Mesh Network") + " " + str(self._channel)
+ else:
+ text = _("Mesh Network")
+ self._palette.props.primary_text = text
+
+ def _update(self):
+ state = self._device_state
+
+ if state in [network.DEVICE_STATE_PREPARE,
+ network.DEVICE_STATE_CONFIG,
+ network.DEVICE_STATE_NEED_AUTH,
+ network.DEVICE_STATE_IP_CONFIG]:
+ self._icon.props.base_color = self._inactive_color
+ self._icon.props.pulse_color = profile.get_color()
+ self._palette.set_connecting()
+ self._icon.props.pulsing = True
+ elif state == network.DEVICE_STATE_ACTIVATED:
+ address = self._device_props.Get(_NM_DEVICE_IFACE, 'Ip4Address')
+ self._palette.set_connected_with_channel(self._channel, address)
+ self._icon.props.base_color = profile.get_color()
+ self._icon.props.pulsing = False
+ self._update_text()
+
+ def update_state(self, state):
+ self._device_state = state
+ self._update()
+
+ def __deactivate_connection(self, palette, data=None):
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+ netmgr_props = dbus.Interface(netmgr, 'org.freedesktop.DBus.Properties')
+ active_connections_o = netmgr_props.Get(_NM_IFACE,
+ 'ActiveConnections')
+
+ for conn_o in active_connections_o:
+ # The connection path for a mesh connection is the device itself.
+ obj = self._bus.get_object(_NM_IFACE, conn_o)
+ props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+ ap_op = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject')
+
+ try:
+ obj = self._bus.get_object(_NM_IFACE, ap_op)
+ props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+ type = props.Get(_NM_DEVICE_IFACE, 'DeviceType')
+ if type == network.DEVICE_TYPE_802_11_OLPC_MESH:
+ netmgr.DeactivateConnection(conn_o)
+ break
+ except dbus.exceptions.DBusException:
+ pass
+
+class WiredDeviceView(TrayIcon):
+
+ _ICON_NAME = 'network-wired'
+ FRAME_POSITION_RELATIVE = 301
+
+ def __init__(self, speed, address):
+ client = gconf.client_get_default()
+ color = xocolor.XoColor(client.get_string('/desktop/sugar/user/color'))
+
+ TrayIcon.__init__(self, icon_name=self._ICON_NAME, xo_color=color)
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+ self._palette = WiredPalette()
+ self.set_palette(self._palette)
+ self._palette.set_group_id('frame')
+ self._palette.set_connected(speed, address)
+
+
+class GsmDeviceView(TrayIcon):
+
+ _ICON_NAME = 'network-gsm'
+ FRAME_POSITION_RELATIVE = 303
+
+ def __init__(self, device):
+ self._connection_time_handler = None
+ self._connection_timestamp = 0
+
+ client = gconf.client_get_default()
+ color = xocolor.XoColor(client.get_string('/desktop/sugar/user/color'))
+
+ TrayIcon.__init__(self, icon_name=self._ICON_NAME, xo_color=color)
+
+ self._bus = dbus.SystemBus()
+ self._device = device
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+ self._palette = self._create_gsm_palette()
+ self.set_palette(self._palette)
+
+ self._bus.add_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+ self._bus.add_signal_receiver(self.__ppp_stats_changed_cb,
+ signal_name='PppStats',
+ path=self._device.object_path,
+ dbus_interface=_NM_SERIAL_IFACE)
+
+ def _create_gsm_palette(self):
+ palette = GsmPalette()
+
+ palette.set_group_id('frame')
+ palette.connect('gsm-connect', self.__gsm_connect_cb)
+ palette.connect('gsm-disconnect', self.__gsm_disconnect_cb)
+
+ props = dbus.Interface(self._device, 'org.freedesktop.DBus.Properties')
+ props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True,
+ reply_handler=self.__current_state_check_cb,
+ error_handler=self.__current_state_check_error_cb)
+
+ return palette
+
+ def __gsm_connect_cb(self, palette, data=None):
+ connection = network.find_gsm_connection()
+ if connection is not None:
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+ netmgr.ActivateConnection(network.SETTINGS_SERVICE,
+ connection.path,
+ self._device.object_path,
+ '/',
+ reply_handler=self.__connect_cb,
+ error_handler=self.__connect_error_cb)
+
+ def __connect_cb(self, active_connection):
+ logging.debug('Connected successfully to gsm device, %s',
+ active_connection)
+
+ def __connect_error_cb(self, error):
+ raise RuntimeError('Error when connecting to gsm device, %s' % error)
+
+ def __gsm_disconnect_cb(self, palette, data=None):
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+ netmgr_props = dbus.Interface(netmgr, 'org.freedesktop.DBus.Properties')
+ active_connections_o = netmgr_props.Get(_NM_IFACE, 'ActiveConnections')
+
+ for conn_o in active_connections_o:
+ obj = self._bus.get_object(_NM_IFACE, conn_o)
+ props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+ devices = props.Get(_NM_ACTIVE_CONN_IFACE, 'Devices')
+ if self._device.object_path in devices:
+ netmgr.DeactivateConnection(
+ conn_o,
+ reply_handler=self.__disconnect_cb,
+ error_handler=self.__disconnect_error_cb)
+ break
+
+ def __disconnect_cb(self):
+ logging.debug('Disconnected successfully gsm device')
+
+ def __disconnect_error_cb(self, error):
+ raise RuntimeError('Error when disconnecting gsm device, %s' % error)
+
+ def __state_changed_cb(self, new_state, old_state, reason):
+ logging.debug('GSM State: %s to %s, reason %s', old_state, new_state, reason)
+ self._update_state(int(new_state))
+
+ def __current_state_check_cb(self, properties):
+ self._update_state(int(properties['State']))
+
+ def __current_state_check_error_cb(self, error):
+ raise RuntimeError('Error when checking gsm device state, %s' % error)
+
+ def _update_state(self, state):
+ gsm_state = None
+
+ if state == network.DEVICE_STATE_ACTIVATED:
+ gsm_state = _GSM_STATE_CONNECTED
+ connection = network.find_gsm_connection()
+ if connection is not None:
+ connection.set_connected()
+ self._connection_timestamp = time.time() - \
+ connection.get_settings().connection.timestamp
+ self._connection_time_handler = gobject.timeout_add( \
+ 1000, self.__connection_timecount_cb)
+ self._update_stats(0, 0)
+ self._update_connection_time()
+ self._palette.info_box.show()
+
+ if state == network.DEVICE_STATE_DISCONNECTED:
+ gsm_state = _GSM_STATE_DISCONNECTED
+ self._connection_timestamp = 0
+ if self._connection_time_handler is not None:
+ gobject.source_remove(self._connection_time_handler)
+ self._palette.info_box.hide()
+
+ elif state in [network.DEVICE_STATE_UNMANAGED,
+ network.DEVICE_STATE_UNAVAILABLE,
+ network.DEVICE_STATE_UNKNOWN]:
+ gsm_state = _GSM_STATE_NOT_READY
+
+ elif state in [network.DEVICE_STATE_PREPARE,
+ network.DEVICE_STATE_CONFIG,
+ network.DEVICE_STATE_IP_CONFIG]:
+ gsm_state = _GSM_STATE_CONNECTING
+
+ elif state in [network.DEVICE_STATE_NEED_AUTH]:
+ gsm_state = _GSM_STATE_NEED_AUTH
+
+ if self._palette is not None:
+ self._palette.set_gsm_state(gsm_state)
+
+ def disconnect(self):
+ self._bus.remove_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def __ppp_stats_changed_cb(self, in_bytes, out_bytes):
+ self._update_stats(in_bytes, out_bytes)
+
+ def _update_stats(self, in_bytes, out_bytes):
+ in_kbytes = in_bytes / 1024
+ out_kbytes = out_bytes / 1024
+ text = _("Data sent %d kb / received %d kb") % (out_kbytes, in_kbytes)
+ self._palette.data_label.set_text(text)
+
+ def __connection_timecount_cb(self):
+ self._connection_timestamp = self._connection_timestamp + 1
+ self._update_connection_time()
+ return True
+
+ def _update_connection_time(self):
+ connection_time = datetime.datetime.fromtimestamp( \
+ self._connection_timestamp)
+ text = _("Connection time ") + connection_time.strftime('%H : %M : %S')
+ self._palette.connection_time_label.set_text(text)
+
+class WirelessDeviceObserver(object):
+ def __init__(self, device, tray):
+ self._device = device
+ self._device_view = None
+ self._tray = tray
+ self._device_view = WirelessDeviceView(self._device)
+ self._tray.add_device(self._device_view)
+
+ def disconnect(self):
+ self._device_view.disconnect()
+ self._tray.remove_device(self._device_view)
+ del self._device_view
+ self._device_view = None
+
+
+class MeshDeviceObserver(object):
+ def __init__(self, device, tray):
+ self._bus = dbus.SystemBus()
+ self._device = device
+ self._device_view = None
+ self._tray = tray
+
+ props = dbus.Interface(self._device, dbus.PROPERTIES_IFACE)
+ props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True,
+ reply_handler=self.__get_device_props_reply_cb,
+ error_handler=self.__get_device_props_error_cb)
+
+ self._bus.add_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def _remove_device_view(self):
+ self._device_view.disconnect()
+ self._tray.remove_device(self._device_view)
+ del self._device_view
+ self._device_view = None
+
+
+
+ def disconnect(self):
+ if self._device_view is not None:
+ self._remove_device_view()
+
+ self._bus.remove_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def __get_device_props_reply_cb(self, properties):
+ if 'State' in properties:
+ self._update_state(properties['State'])
+
+ def __get_device_props_error_cb(self, err):
+ logging.error('Error getting the device properties: %s', err)
+
+ def __state_changed_cb(self, new_state, old_state, reason):
+ self._update_state(new_state)
+
+ def _update_state(self, state):
+ if state >= network.DEVICE_STATE_PREPARE \
+ and state <= network.DEVICE_STATE_ACTIVATED:
+ if self._device_view is not None:
+ self._device_view.update_state(state)
+ return
+
+ self._device_view = OlpcMeshDeviceView(self._device, state)
+ self._tray.add_device(self._device_view)
+ else:
+ if self._device_view is not None:
+ self._remove_device_view()
+
+
+class WiredDeviceObserver(object):
+ def __init__(self, device, tray):
+ self._bus = dbus.SystemBus()
+ self._device = device
+ self._device_state = None
+ self._device_view = None
+ self._tray = tray
+
+ props = dbus.Interface(self._device, dbus.PROPERTIES_IFACE)
+ props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True,
+ reply_handler=self.__get_device_props_reply_cb,
+ error_handler=self.__get_device_props_error_cb)
+
+ self._bus.add_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def disconnect(self):
+ self._bus.remove_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def __get_device_props_reply_cb(self, properties):
+ if 'State' in properties:
+ self._update_state(properties['State'])
+
+ def __get_device_props_error_cb(self, err):
+ logging.error('Error getting the device properties: %s', err)
+
+ def __state_changed_cb(self, new_state, old_state, reason):
+ self._update_state(new_state)
+
+ def _update_state(self, state):
+ if state == network.DEVICE_STATE_ACTIVATED:
+ props = dbus.Interface(self._device, dbus.PROPERTIES_IFACE)
+ address = props.Get(_NM_DEVICE_IFACE, 'Ip4Address')
+ speed = props.Get(_NM_WIRED_IFACE, 'Speed')
+ self._device_view = WiredDeviceView(speed, address)
+ self._tray.add_device(self._device_view)
+ else:
+ if self._device_view is not None:
+ self._tray.remove_device(self._device_view)
+ del self._device_view
+ self._device_view = None
+
+class GsmDeviceObserver(object):
+ def __init__(self, device, tray):
+ self._device = device
+ self._device_view = None
+ self._tray = tray
+
+ self._device_view = GsmDeviceView(device)
+ self._tray.add_device(self._device_view)
+
+ def disconnect(self):
+ self._device_view.disconnect()
+ self._tray.remove_device(self._device_view)
+ self._device_view = None
+
+class NetworkManagerObserver(object):
+ def __init__(self, tray):
+ self._bus = dbus.SystemBus()
+ self._devices = {}
+ self._netmgr = None
+ self._tray = tray
+
+ try:
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ self._netmgr = dbus.Interface(obj, _NM_IFACE)
+ except dbus.DBusException:
+ logging.error('%s service not available', _NM_SERVICE)
+ return
+
+ self._netmgr.GetDevices(reply_handler=self.__get_devices_reply_cb,
+ error_handler=self.__get_devices_error_cb)
+
+ self._bus.add_signal_receiver(self.__device_added_cb,
+ signal_name='DeviceAdded',
+ dbus_interface=_NM_IFACE)
+ self._bus.add_signal_receiver(self.__device_removed_cb,
+ signal_name='DeviceRemoved',
+ dbus_interface=_NM_IFACE)
+
+ def __get_devices_reply_cb(self, devices):
+ for device_op in devices:
+ self._check_device(device_op)
+
+ def __get_devices_error_cb(self, err):
+ logging.error('Failed to get devices: %s', err)
+
+ def _check_device(self, device_op):
+ nm_device = self._bus.get_object(_NM_SERVICE, device_op)
+ props = dbus.Interface(nm_device, dbus.PROPERTIES_IFACE)
+
+ device_type = props.Get(_NM_DEVICE_IFACE, 'DeviceType')
+ if device_type == network.DEVICE_TYPE_802_3_ETHERNET:
+ device = WiredDeviceObserver(nm_device, self._tray)
+ self._devices[device_op] = device
+ elif device_type == network.DEVICE_TYPE_802_11_WIRELESS:
+ device = WirelessDeviceObserver(nm_device, self._tray)
+ self._devices[device_op] = device
+ elif device_type == network.DEVICE_TYPE_802_11_OLPC_MESH:
+ device = MeshDeviceObserver(nm_device, self._tray)
+ self._devices[device_op] = device
+ elif device_type == network.DEVICE_TYPE_GSM_MODEM:
+ device = GsmDeviceObserver(nm_device, self._tray)
+ self._devices[device_op] = device
+
+ def __device_added_cb(self, device_op):
+ self._check_device(device_op)
+
+ def __device_removed_cb(self, device_op):
+ if device_op in self._devices:
+ device = self._devices[device_op]
+ device.disconnect()
+ del self._devices[device_op]
+
+def setup(tray):
+ device_observer = NetworkManagerObserver(tray)
diff --git a/p5.py b/p5.py
deleted file mode 100644
index e785f94..0000000
--- a/p5.py
+++ /dev/null
@@ -1,190 +0,0 @@
-#Copyright (c) 2008, Media Modifications Ltd.
-
-#Permission is hereby granted, free of charge, to any person obtaining a copy
-#of this software and associated documentation files (the "Software"), to deal
-#in the Software without restriction, including without limitation the rights
-#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-#copies of the Software, and to permit persons to whom the Software is
-#furnished to do so, subject to the following conditions:
-
-#The above copyright notice and this permission notice shall be included in
-#all copies or substantial portions of the Software.
-
-#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-#THE SOFTWARE.
-
-import gtk
-from gtk import gdk
-import gobject
-import cairo
-import math
-
-#Sometimes a gtk.Image is a useful alternative to a drawing area. You can put a gtk.gdk.Pixmap in the gtk.Image and draw to the gtk.gdk.Pixmap, calling the gtk.Widget.queue_draw() method on the gtk.Image when you want to refresh to the screen.
-
-class P5(gtk.DrawingArea):
- def __init__(self):
- super(P5, self).__init__()
-
- # gtk.Widget signals
- self.connect("expose_event", self.expose)
- self.connect("button_press_event", self.button_press)
- self.connect("button_release_event", self.button_release)
- self.connect("motion_notify_event", self.motion_notify)
-
- # ok, we have to listen to mouse events here too
- self.add_events(gdk.BUTTON_PRESS_MASK | gdk.BUTTON_RELEASE_MASK | gdk.POINTER_MOTION_MASK)
- self._dragging = False
- self._mouseX = 0
- self._mouseY = 0
-
- self._w = -1
- self._h = -1
-
-
- #ok, this calls an initial painting & setting of painterly variables
- #e.g. time for the clock widget
- #(but not through to redraw_canvas when called here first time)
- self._msecUpdate = 100
- self._looping = False
- self.noloop()
-
-
- def loop(self):
- if (self._looping):
- return
- else:
- self._looping = True
- # this is our maybe-threaded refresh (in millisecs)
- gobject.timeout_add( self._msecUpdate, self.update )
-
-
- def noloop(self):
- self._looping = False
- self.redraw()
-
-
- def redraw(self):
- self.update()
-
-
- def expose(self, widget, event):
- ctx = widget.window.cairo_create()
-
- # set a clip region for the expose event
- ctx.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
- ctx.clip()
-
- rect = widget.allocation
- #self.draw(ctx, event.area.width, event.area.height)
- self.draw( ctx, rect.width, rect.height )
-
-
- def button_press(self, widget, event):
- self._mouseX = event.x
- self._mouseY = event.y
- self._dragging = True
-
-
- def button_release(self, widget, event):
- if self._dragging:
- self._dragging = False
-
-
- def motion_notify(self, widget, event):
- self._mouseX = event.x
- self._mouseY = event.y
-
-
- def draw(self, ctx, w, h):
- ctx.set_antialias( cairo.ANTIALIAS_NONE )
- ctx.set_line_width( 1 )
- ctx.identity_matrix( )
- if ((w != self._w) or (h != self._h)):
- self._w = w
- self._h = h
- self.doResize( )
-
-
- def doResize(self):
- pass
-
-
- #called from update
- def redraw_canvas(self):
- if self.window:
- alloc = self.get_allocation()
- #this is odd behavior, but once we add this widget to a parent (vbox)
- #it requires setting the q_d_a x,y to 0, 0
- #self.queue_draw_area(alloc.x, alloc.y, alloc.width, alloc.height)
- self.queue_draw_area(0, 0, alloc.width, alloc.height)
- self.window.process_updates(True)
-
-
- def update(self):
- #paint thread -- call redraw_canvas, which calls expose
- self.redraw_canvas()
- if (self._looping):
- return True # keep running this event
- else:
- return False
-
-
- def drawShape( self, ctx, poly, col ):
- self.setColor( ctx, col )
-
- for i in range ( 0, len(poly._xs) ):
- ctx.line_to ( poly._xs[i], poly._ys[i] )
- ctx.close_path()
- ctx.set_line_width(1)
- ctx.stroke()
-
-
- def fillShape( self, ctx, poly, col ):
- self.setColor( ctx, col )
- for i in range ( 0, len(poly._xs) ):
- ctx.line_to (poly._xs[i], poly._ys[i])
- ctx.close_path()
-
- ctx.fill()
-
-
- def background( self, ctx, col, w, h ):
- self.setColor( ctx, col )
-
- ctx.line_to(0, 0)
- ctx.line_to(w, 0)
- ctx.line_to(w, h)
- ctx.line_to(0, h)
- ctx.close_path()
-
- ctx.fill()
-
-
- def rect( self, ctx, x, y, w, h ):
- ctx.line_to(x, y)
- ctx.line_to(x+w, y)
- ctx.line_to(x+w, y+h)
- ctx.line_to(x, y+h)
- ctx.close_path()
-
-
- def setColor( self, ctx, col ):
- if (not col._opaque):
- ctx.set_source_rgba( col._r, col._g, col._b, col._a )
- else:
- ctx.set_source_rgb( col._r, col._g, col._b )
-
-
- def line( self, ctx, x1, y1, x2, y2 ):
- ctx.move_to (x1, y1)
- ctx.line_to (x2, y2)
- ctx.stroke()
-
-
- def point( self, ctx, x1, y1 ):
- self.line( ctx, x1, y1, x1+1, y1 ) \ No newline at end of file
diff --git a/p5_button.py b/p5_button.py
deleted file mode 100644
index cf76a34..0000000
--- a/p5_button.py
+++ /dev/null
@@ -1,173 +0,0 @@
-#Copyright (c) 2008, Media Modifications Ltd.
-
-#Permission is hereby granted, free of charge, to any person obtaining a copy
-#of this software and associated documentation files (the "Software"), to deal
-#in the Software without restriction, including without limitation the rights
-#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-#copies of the Software, and to permit persons to whom the Software is
-#furnished to do so, subject to the following conditions:
-
-#The above copyright notice and this permission notice shall be included in
-#all copies or substantial portions of the Software.
-
-#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-#THE SOFTWARE.
-
-from p5 import P5
-
-class P5Button(P5):
-
- def __init__(self):
- P5.__init__(self)
- self.noloop()
- self._butts = []
- self._buttonPressed = False
-
-
- def button_press(self, widget, event):
- P5.button_press(self, widget, event)
-
- #iterate through the buttons to see if you've pressed any down
- bp = False
- for i in range ( 0, len(self._butts) ):
- if (self._butts[i]._enabled):
- contains = self._butts[i].contains(event.x, event.y)
- self._butts[i]._pressed = contains
- if (contains):
- bp = True
-
- self._buttonPressed = bp
- self.redraw()
-
-
- def button_release(self, widget, event):
- P5.button_release(self, widget, event)
- self._buttonPressed = False
-
- pressed = []
- #iterate through the buttons to see if you've released on any
- for i in range ( 0, len(self._butts) ):
- if (self._butts[i]._enabled):
- if (self._butts[i]._pressed):
- if (self._butts[i].contains(event.x, event.y)):
- pressed.append( self._butts[i] )
-
- if (self._butts[i]._toggle):
- self._butts[i]._pressed = not self._butts[i]._pressed
- else:
- self._butts[i]._pressed = False
-
- for i in range( 0, len(pressed) ):
- pressed[i].doPressed()
-
- self.redraw()
-
-
-class Polygon:
-
- def __init__( self, xs, ys ):
- self.setPoints( xs, ys )
-
-
- def setPoints( self, xs, ys ):
- self._xs = xs
- self._ys = ys
-
- self._boundingX = self._xs[0]
- self._boundingY = self._ys[0]
- self._boundingW = self._xs[0]
- self._boundingH = self._ys[0]
-
- for i in range ( 1, len(self._xs) ):
- if (self._xs[i] > self._boundingW):
- self._boundingW = self._xs[i]
- if (self._ys[i] > self._boundingH):
- self._boundingH = self._ys[i]
- if (self._xs[i] < self._boundingX):
- self._boundingX = self._xs[i]
- if (self._ys[i] < self._boundingY):
- self._boundingY = self._ys[i]
-
-
- def contains( self, mx, my ):
- if (not self.bbox_contains(mx, my)):
- return False
-
- #insert simple path tracing check on the polygon here
-
- return True
-
-
- def bbox_contains( self, mx, my ):
- if ( not((mx>=self._boundingX) and (my>=self._boundingY) and (mx<self._boundingW) and (my<self._boundingH)) ):
- return False
- else:
- return True
-
-
-class Button:
-
- def __init__(self, poly, offX, offY):
- self._poly = poly
- self._offX = offX
- self._offY = offY
-
- self._enabled = True
- self._pressed = False
- self._toggle = False
-
- self._listeners = []
-
- self._actionCommand = None
-
- self._img = None
-
-
- def setOffsets(self, offs):
- self._offX = offs[0]
- self._offY = offs[1]
-
-
- def addActionListener(self, listen):
- self._listeners.append(listen)
-
-
- def removeActionListener(self, listen):
- try:
- self._listeners.remove(listen)
- except ValueError:
- pass
-
-
- def setActionCommand(self, command):
- self._actionCommand = command
-
-
- def getActionCommand(self):
- return self._actionCommand
-
-
- def setImage(self, img):
- self._img = img
-
-
- def contains( self, mx, my ):
- x = mx - self._offX
- y = my - self._offY
-
- contains = self._poly.contains( x, y )
- return contains
-
-
- def doPressed( self ):
- for i in range ( 0, len(self._listeners) ):
- self._listeners[i].fireButton( self._actionCommand )
-
-
- def isImg( self ):
- return self._img != None \ No newline at end of file
diff --git a/port/AUTHORS b/port/AUTHORS
deleted file mode 100644
index 47ead6c..0000000
--- a/port/AUTHORS
+++ /dev/null
@@ -1 +0,0 @@
-Aleksey Lim <alsroot@member.fsf.org>
diff --git a/port/COPYING b/port/COPYING
deleted file mode 100644
index 623b625..0000000
--- a/port/COPYING
+++ /dev/null
@@ -1,340 +0,0 @@
- GNU GENERAL PUBLIC LICENSE
- Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users. This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it. (Some other Free Software Foundation software is covered by
-the GNU Library General Public License instead.) You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
- To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have. You must make sure that they, too, receive or can get the
-source code. And you must show them these terms so they know their
-rights.
-
- We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
- Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software. If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
- Finally, any free program is threatened constantly by software
-patents. We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary. To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- GNU GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License. The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language. (Hereinafter, translation is included without limitation in
-the term "modification".) Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
- 1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
- 2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
- a) You must cause the modified files to carry prominent notices
- stating that you changed the files and the date of any change.
-
- b) You must cause any work that you distribute or publish, that in
- whole or in part contains or is derived from the Program or any
- part thereof, to be licensed as a whole at no charge to all third
- parties under the terms of this License.
-
- c) If the modified program normally reads commands interactively
- when run, you must cause it, when started running for such
- interactive use in the most ordinary way, to print or display an
- announcement including an appropriate copyright notice and a
- notice that there is no warranty (or else, saying that you provide
- a warranty) and that users may redistribute the program under
- these conditions, and telling the user how to view a copy of this
- License. (Exception: if the Program itself is interactive but
- does not normally print such an announcement, your work based on
- the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
- 3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
- a) Accompany it with the complete corresponding machine-readable
- source code, which must be distributed under the terms of Sections
- 1 and 2 above on a medium customarily used for software interchange; or,
-
- b) Accompany it with a written offer, valid for at least three
- years, to give any third party, for a charge no more than your
- cost of physically performing source distribution, a complete
- machine-readable copy of the corresponding source code, to be
- distributed under the terms of Sections 1 and 2 above on a medium
- customarily used for software interchange; or,
-
- c) Accompany it with the information you received as to the offer
- to distribute corresponding source code. (This alternative is
- allowed only for noncommercial distribution and only if you
- received the program in object code or executable form with such
- an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it. For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable. However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
- 4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License. Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
- 5. You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Program or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
- 6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions. You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
- 7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all. For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
- 8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded. In such case, this License incorporates
-the limitation as if written in the body of this License.
-
- 9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number. If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation. If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
- 10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission. For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this. Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
- NO WARRANTY
-
- 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
- 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
- <one line to give the program's name and a brief idea of what it does.>
- Copyright (C) <year> <name of author>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
- Gnomovision version 69, Copyright (C) year name of author
- Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the program
- `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
- <signature of Ty Coon>, 1 April 1989
- Ty Coon, President of Vice
-
-This General Public License does not permit incorporating your program into
-proprietary programs. If your program is a subroutine library, you may
-consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Library General
-Public License instead of this License.
diff --git a/port/NEWS b/port/NEWS
deleted file mode 100644
index 0dcfeb5..0000000
--- a/port/NEWS
+++ /dev/null
@@ -1,8 +0,0 @@
-1
-
-* Add tarball.py
-* Add json import wrapper
-* Add object chooser
-* Add activity classes
-* Add pixbuf methods
-* Add TempoSlider and ScrolledBox widgets
diff --git a/port/README b/port/README
deleted file mode 100644
index bd0dade..0000000
--- a/port/README
+++ /dev/null
@@ -1,13 +0,0 @@
-About
------
-
-A set of sugar components/libraries/etc to simplify writing activities.
-
-Cornerstone purposes for this project:
-* Total backwards compatibility for sugar-port API
-* Run on all sugar platforms beginning from 0.82
-
-Get it
-------
-
-http://wiki.sugarlabs.org/go/Development_Team/sugar-port
diff --git a/port/TODO b/port/TODO
deleted file mode 100644
index e69de29..0000000
--- a/port/TODO
+++ /dev/null
diff --git a/port/__init__.py b/port/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/port/__init__.py
+++ /dev/null
diff --git a/port/json.py b/port/json.py
deleted file mode 100644
index d464abb..0000000
--- a/port/json.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-
-"""
-Unify usage of simplejson in Python 2.5/2.6
-
-In Python 2.5 it imports simplejson module, in 2.6 native json module.
-
-Usage:
-
- import port.json as json
-
- # and using regular simplejson interface with module json
- json.dumps([])
-
-"""
-
-try:
- from json import *
- dumps
-except (ImportError, NameError):
- from simplejson import *
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 )
diff --git a/recorded.py b/recorded.py
index 8639a70..27250d3 100644
--- a/recorded.py
+++ b/recorded.py
@@ -20,16 +20,13 @@
import os
import gtk
-from gtk import gdk
-from constants import Constants
+import constants
from instance import Instance
import utils
import serialize
-import record
class Recorded:
-
def __init__( self ):
self.type = -1
self.time = None
@@ -78,6 +75,8 @@ class Recorded:
def setTitle( self, newTitle ):
+ if self.title == newTitle:
+ return
self.title = newTitle
self.metaChange = True
@@ -106,11 +105,11 @@ class Recorded:
#relaunch, their old media -- datastoreObject->file (hold onto the datastore object, delete if deleted) | ([request->]) Journal/session/buddy
def getThumbPixbuf( self ):
- thumbPixbuf = None
thumbFilepath = self.getThumbFilepath()
- if ( os.path.isfile(thumbFilepath) ):
- thumbPixbuf = gtk.gdk.pixbuf_new_from_file(thumbFilepath)
- return thumbPixbuf
+ if os.path.isfile(thumbFilepath):
+ return gtk.gdk.pixbuf_new_from_file(thumbFilepath)
+ else:
+ return None
def getThumbFilepath( self ):
@@ -152,7 +151,7 @@ class Recorded:
else:
if self.mediaFilename == None:
#creating a new filepath, probably just got here from the mesh
- ext = Constants.mediaTypes[self.type][Constants.keyExt]
+ ext = constants.MEDIA_INFO[self.type]['ext']
recdPath = os.path.join(Instance.instancePath, "recdFile_"+self.mediaMd5+"."+ext)
recdPath = utils.getUniqueFilepath(recdPath, 0)
self.mediaFilename = os.path.basename(recdPath)
@@ -170,4 +169,4 @@ class Recorded:
print("RecordActivity error -- unable to get datastore object in getMediaFilepath")
return None
- return self.datastoreOb.file_path \ No newline at end of file
+ return self.datastoreOb.file_path
diff --git a/recordtube.py b/recordtube.py
index 7f63922..c2a60bb 100644
--- a/recordtube.py
+++ b/recordtube.py
@@ -1,12 +1,14 @@
+import os
+import logging
+
import gobject
-from dbus import Interface
-from dbus.service import method, signal
+from dbus.service import signal
from dbus.gobject_service import ExportedGObject
-import os
-from constants import Constants
+import constants
from instance import Instance
-import record
+
+logger = logging.getLogger('recordtube')
class RecordTube(ExportedGObject):
@@ -23,46 +25,46 @@ class RecordTube(ExportedGObject):
def __init__(self, tube):
- super(RecordTube, self).__init__(tube, Constants.PATH)
+ super(RecordTube, self).__init__(tube, constants.DBUS_PATH)
self.tube = tube
- self.idNotify = self.tube.add_signal_receiver(self._newRecdTubeCb, 'notifyBudsOfNewRecd', Constants.IFACE, path=Constants.PATH, sender_keyword='sender')
- self.idRequest = self.tube.add_signal_receiver(self._reqRecdTubeCb, 'requestRecdBits', Constants.IFACE, path=Constants.PATH, sender_keyword='sender')
- self.idBroadcast = self.tube.add_signal_receiver(self._getRecdTubeCb, 'broadcastRecdBits', Constants.IFACE, path=Constants.PATH, sender_keyword='sender', byte_arrays=True)
- self.idUnavailable = self.tube.add_signal_receiver(self._unavailableRecdTubeCb, 'unavailableRecd', Constants.IFACE, path=Constants.PATH, sender_keyword='sender')
+ self.idNotify = self.tube.add_signal_receiver(self._newRecdTubeCb, 'notifyBudsOfNewRecd', constants.DBUS_IFACE, path=constants.DBUS_PATH, sender_keyword='sender')
+ self.idRequest = self.tube.add_signal_receiver(self._reqRecdTubeCb, 'requestRecdBits', constants.DBUS_IFACE, path=constants.DBUS_PATH, sender_keyword='sender')
+ self.idBroadcast = self.tube.add_signal_receiver(self._getRecdTubeCb, 'broadcastRecdBits', constants.DBUS_IFACE, path=constants.DBUS_PATH, sender_keyword='sender', byte_arrays=True)
+ self.idUnavailable = self.tube.add_signal_receiver(self._unavailableRecdTubeCb, 'unavailableRecd', constants.DBUS_IFACE, path=constants.DBUS_PATH, sender_keyword='sender')
- @signal(dbus_interface=Constants.IFACE, signature='ss') #dual s for 2x strings
+ @signal(dbus_interface=constants.DBUS_IFACE, signature='ss') #dual s for 2x strings
def notifyBudsOfNewRecd(self, recorder, recdXml):
- record.Record.log.debug('Ive taken a new pho-ideo-audio! I hereby send you an xml thumb of said media via this interface.')
+ logger.debug('Ive taken a new pho-ideo-audio! I hereby send you an xml thumb of said media via this interface.')
def _newRecdTubeCb(self, recorder, recdXml, sender=None):
- record.Record.log.debug("_newRecdTubeCb from " + recorder )
+ logger.debug("_newRecdTubeCb from " + recorder )
if sender == self.tube.get_unique_name():
- record.Record.log.debug("_newRecdTubeCb: sender is my bus name, so ignore my own signal")
+ logger.debug("_newRecdTubeCb: sender is my bus name, so ignore my own signal")
return
elif (recorder == Instance.keyHashPrintable):
- record.Record.log.debug('_newRecdTubeCb: excuse me? you are asking me to share a photo with myself?')
+ logger.debug('_newRecdTubeCb: excuse me? you are asking me to share a photo with myself?')
return
self.emit( "new-recd", str(recorder), str(recdXml) )
- @signal(dbus_interface=Constants.IFACE, signature='sss') #triple s for 3x strings
+ @signal(dbus_interface=constants.DBUS_IFACE, signature='sss') #triple s for 3x strings
def requestRecdBits(self, whoWantsIt, whoTheyWantItFrom, recdMd5sumOfIt ):
- record.Record.log.debug('I am requesting a high-res version of someones media.')
+ logger.debug('I am requesting a high-res version of someones media.')
def _reqRecdTubeCb(self, whoWantsIt, whoTheyWantItFrom, recdMd5sumOfIt, sender=None):
if sender == self.tube.get_unique_name():
- record.Record.log.debug("_reqRecdTubeCb: sender is my bus name, so ignore my own signal")
+ logger.debug("_reqRecdTubeCb: sender is my bus name, so ignore my own signal")
return
elif (whoWantsIt == Instance.keyHashPrintable):
- record.Record.log.debug('_reqRecdTubeCb: excuse me? you are asking me to share a photo with myself?')
+ logger.debug('_reqRecdTubeCb: excuse me? you are asking me to share a photo with myself?')
return
elif (whoTheyWantItFrom != Instance.keyHashPrintable):
- record.Record.log.debug('_reqRecdTubeCb: ive overhead someone wants a photo, but not from me')
+ logger.debug('_reqRecdTubeCb: ive overhead someone wants a photo, but not from me')
return
self.emit( "recd-request", str(whoWantsIt), str(recdMd5sumOfIt) )
@@ -78,17 +80,17 @@ class RecordTube(ExportedGObject):
for chunk in range(chunks):
bytes = f.read(chunk_size)
- if (chunk == 0):
- record.Record.log.debug("sending " + str(chunk+1) + " of " + str(chunks) + " to " + sendThisTo )
- if (chunk == chunks-1):
- record.Record.log.debug("sending " + str(chunk+1) + " of " + str(chunks) + " to " + sendThisTo )
+ if chunk == 0:
+ logger.debug("sending " + str(chunk+1) + " of " + str(chunks) + " to " + sendThisTo )
+ if chunk == chunks-1:
+ logger.debug("sending " + str(chunk+1) + " of " + str(chunks) + " to " + sendThisTo )
self.broadcastRecdBits(md5, chunk+1, chunks, bytes, sendThisTo, Instance.keyHashPrintable)
f.close()
return True
- @signal(dbus_interface=Constants.IFACE, signature='suuayss')
+ @signal(dbus_interface=constants.DBUS_IFACE, signature='suuayss')
def broadcastRecdBits(self, md5, part, numparts, bytes, sendTo, fromWho ):
pass
@@ -107,20 +109,20 @@ class RecordTube(ExportedGObject):
self.emit( "recd-bits-arrived", md5, part, numparts, bytes, fromWho )
- @signal(dbus_interface=Constants.IFACE, signature='sss') #triple s for 3x strings
+ @signal(dbus_interface=constants.DBUS_IFACE, signature='sss') #triple s for 3x strings
def unavailableRecd(self, md5sumOfIt, whoDoesntHaveIt, whoAskedForIt):
- record.Record.log.debug('unavailableRecd: id love to share this photo, but i am without a copy meself chum')
+ logger.debug('unavailableRecd: id love to share this photo, but i am without a copy meself chum')
def _unavailableRecdTubeCb( self, md5sumOfIt, whoDoesntHaveIt, whoAskedForIt, sender=None):
if sender == self.tube.get_unique_name():
- record.Record.log.debug("_unavailableRecdTubeCb: sender is my bus name, so ignore my own signal")
+ logger.debug("_unavailableRecdTubeCb: sender is my bus name, so ignore my own signal")
return
- if (whoDoesntHaveIt == Instance.keyHashPrintable):
- record.Record.log.debug('_unavailableRecdTubeCb: yes, i know i dont have it, i just told you/me/us.')
+ if whoDoesntHaveIt == Instance.keyHashPrintable:
+ logger.debug('_unavailableRecdTubeCb: yes, i know i dont have it, i just told you/me/us.')
return
- if (whoAskedForIt != Instance.keyHashPrintable):
- record.Record.log.debug('_unavailableRecdTubeCb: ive overheard someone doesnt have a photo, but i didnt ask for that one anyways')
+ if whoAskedForIt != Instance.keyHashPrintable:
+ logger.debug('_unavailableRecdTubeCb: ive overheard someone doesnt have a photo, but i didnt ask for that one anyways')
return
- self.emit("recd-unavailable", md5sumOfIt, whoDoesntHaveIt) \ No newline at end of file
+ self.emit("recd-unavailable", md5sumOfIt, whoDoesntHaveIt)
diff --git a/serialize.py b/serialize.py
index 8e56d5f..82618fc 100644
--- a/serialize.py
+++ b/serialize.py
@@ -1,162 +1,134 @@
-import xml.dom.minidom
from xml.dom.minidom import getDOMImplementation
-from xml.dom.minidom import parse
import cStringIO
import os
import gtk
+import logging
from sugar.datastore import datastore
-from constants import Constants
+import constants
from instance import Instance
-from color import Color
-import record
-import utils
import recorded
+import utils
-def fillMediaHash( doc, mediaHashs ):
- for key,value in Constants.mediaTypes.items():
- recdElements = doc.documentElement.getElementsByTagName(value[Constants.keyName])
+logger = logging.getLogger('serialize')
+
+def fillMediaHash(doc, mediaHashs):
+ for key, value in constants.MEDIA_INFO.items():
+ recdElements = doc.documentElement.getElementsByTagName(value['name'])
for el in recdElements:
_loadMediaIntoHash( el, mediaHashs[key] )
-
-def _loadMediaIntoHash( el, hash ):
+def _loadMediaIntoHash(el, hash):
addToHash = True
- recd = record.Recorded()
+ recd = recorded.Recorded()
recd = fillRecdFromNode(recd, el)
- if (recd != None):
- if (recd.datastoreId != None):
+ if recd:
+ if recd.datastoreId:
#quickly check: if you have a datastoreId that the file hasn't been deleted,
#cause if you do, we need to flag your removal
#2904 trac
recd.datastoreOb = getMediaFromDatastore( recd )
- if (recd.datastoreOb == None):
+ if not recd.datastoreOb:
addToHash = False
else:
#name might have been changed in the journal, so reflect that here
- if (recd.title != recd.datastoreOb.metadata['title']):
+ if recd.title != recd.datastoreOb.metadata['title']:
recd.setTitle(recd.datastoreOb.metadata['title'])
- if (recd.tags != recd.datastoreOb.metadata['tags']):
+ if recd.tags != recd.datastoreOb.metadata['tags']:
recd.setTags(recd.datastoreOb.metadata['tags'])
- if (recd.buddy):
+ if recd.buddy:
recd.downloadedFromBuddy = True
recd.datastoreOb == None
- if (addToHash):
- hash.append( recd )
+ if addToHash:
+ hash.append(recd )
-
-def getMediaFromDatastore( recd ):
- if (recd.datastoreId == None):
+def getMediaFromDatastore(recd):
+ if not recd.datastoreId:
return None
- if (recd.datastoreOb != None):
+ if recd.datastoreOb:
#already have the object
return recd.datastoreOb
mediaObject = None
try:
- mediaObject = datastore.get( recd.datastoreId )
+ mediaObject = datastore.get(recd.datastoreId)
finally:
- if (mediaObject == None):
- return None
-
- return mediaObject
+ return mediaObject
-
-def removeMediaFromDatastore( recd ):
+def removeMediaFromDatastore(recd):
#before this method is called, the media are removed from the file
- if (recd.datastoreId == None):
- return
- if (recd.datastoreOb == None):
+ if not recd.datastoreId or not recd.datastoreOb:
return
try:
recd.datastoreOb.destroy()
- datastore.delete( recd.datastoreId )
+ datastore.delete(recd.datastoreId)
- del recd.datastoreId
recd.datastoreId = None
-
- del recd.datastoreOb
recd.datastoreOb = None
-
finally:
#todo: add error message here
pass
+def fillRecdFromNode(recd, el):
+ if el.getAttributeNode('type'):
+ recd.type = int(el.getAttribute('type'))
-def fillRecdFromNode( recd, el ):
- if (el.getAttributeNode(Constants.recdType) != None):
- typeInt = int(el.getAttribute(Constants.recdType))
- recd.type = typeInt
+ if el.getAttributeNode('title'):
+ recd.title = el.getAttribute('title')
- if (el.getAttributeNode(Constants.recdTitle) != None):
- recd.title = el.getAttribute(Constants.recdTitle)
+ if el.getAttributeNode('time'):
+ recd.time = int(el.getAttribute('time'))
- if (el.getAttributeNode(Constants.recdTime) != None):
- timeInt = int(el.getAttribute(Constants.recdTime))
- recd.time = timeInt
+ if el.getAttributeNode('photographer'):
+ recd.recorderName = el.getAttribute('photographer')
- if (el.getAttributeNode(Constants.recdRecorderName) != None):
- recd.recorderName = el.getAttribute(Constants.recdRecorderName)
-
- if (el.getAttributeNode(Constants.recdTags) != None):
- recd.tags = el.getAttribute(Constants.recdTags)
+ if el.getAttributeNode('tags'):
+ recd.tags = el.getAttribute('tags')
else:
recd.tags = ""
- if (el.getAttributeNode(Constants.recdRecorderHash) != None):
- recd.recorderHash = el.getAttribute(Constants.recdRecorderHash)
+ if el.getAttributeNode('recorderHash'):
+ recd.recorderHash = el.getAttribute('recorderHash')
- if (el.getAttributeNode(Constants.recdColorStroke) != None):
- try:
- colorStrokeHex = el.getAttribute(Constants.recdColorStroke)
- colorStroke = Color()
- colorStroke.init_hex(colorStrokeHex)
- recd.colorStroke = colorStroke
- except:
- record.Record.log.error("unable to load recd colorStroke")
+ if el.getAttributeNode('colorStroke'):
+ recd.colorStroke = el.getAttribute('colorStroke')
- if (el.getAttributeNode(Constants.recdColorFill) != None):
- try:
- colorFillHex = el.getAttribute(Constants.recdColorFill)
- colorFill = Color()
- colorFill.init_hex( colorFillHex )
- recd.colorFill = colorFill
- except:
- record.Record.log.error("unable to load recd colorFill")
+ if el.getAttributeNode('colorFill'):
+ recd.colorFill = el.getAttribute('colorFill')
- if (el.getAttributeNode(Constants.recdBuddy) != None):
- recd.buddy = (el.getAttribute(Constants.recdBuddy) == "True")
+ if el.getAttributeNode('buddy'):
+ recd.buddy = (el.getAttribute('buddy') == "True")
- if (el.getAttributeNode(Constants.recdMediaMd5) != None):
- recd.mediaMd5 = el.getAttribute(Constants.recdMediaMd5)
+ if el.getAttributeNode('mediaMd5'):
+ recd.mediaMd5 = el.getAttribute('mediaMd5')
- if (el.getAttributeNode(Constants.recdThumbMd5) != None):
- recd.thumbMd5 = el.getAttribute(Constants.recdThumbMd5)
+ if el.getAttributeNode('thumbMd5'):
+ recd.thumbMd5 = el.getAttribute('thumbMd5')
- if (el.getAttributeNode(Constants.recdMediaBytes) != None):
- recd.mediaBytes = el.getAttribute(Constants.recdMediaBytes)
+ if el.getAttributeNode('mediaBytes'):
+ recd.mediaBytes = el.getAttribute('mediaBytes')
- if (el.getAttributeNode(Constants.recdThumbBytes) != None):
- recd.thumbBytes = el.getAttribute(Constants.recdThumbBytes)
+ if el.getAttributeNode('thumbBytes'):
+ recd.thumbBytes = el.getAttribute('thumbBytes')
- bt = el.getAttributeNode(Constants.recdBase64Thumb)
- if (bt != None):
+ bt = el.getAttributeNode('base64Thumb')
+ if bt:
try:
thumbPath = os.path.join(Instance.instancePath, "datastoreThumb.jpg")
- thumbPath = utils.getUniqueFilepath( thumbPath, 0 )
- thumbImg = utils.getPixbufFromString( bt.nodeValue )
+ thumbPath = utils.getUniqueFilepath(thumbPath, 0)
+ thumbImg = utils.getPixbufFromString(bt.nodeValue)
thumbImg.save(thumbPath, "jpeg", {"quality":"85"} )
recd.thumbFilename = os.path.basename(thumbPath)
- record.Record.log.debug("saved thumbFilename")
+ logger.debug("saved thumbFilename")
except:
- record.Record.log.error("unable to getRecdBase64Thumb")
+ logger.error("unable to getRecdBase64Thumb")
- ai = el.getAttributeNode(Constants.recdAudioImage)
+ ai = el.getAttributeNode('audioImage')
if (not ai == None):
try:
audioImagePath = os.path.join(Instance.instancePath, "audioImage.png")
@@ -164,129 +136,120 @@ def fillRecdFromNode( recd, el ):
audioImage = utils.getPixbufFromString( ai.nodeValue )
audioImage.save(audioImagePath, "png", {} )
recd.audioImageFilename = os.path.basename(audioImagePath)
- record.Record.log.debug("loaded audio image and set audioImageFilename")
+ logger.debug("loaded audio image and set audioImageFilename")
except:
- record.Record.log.error("unable to load audio image")
+ logger.error("unable to load audio image")
- datastoreNode = el.getAttributeNode(Constants.recdDatastoreId)
- if (datastoreNode != None):
+ datastoreNode = el.getAttributeNode('datastoreId')
+ if datastoreNode:
recd.datastoreId = datastoreNode.nodeValue
return recd
-def getRecdXmlMeshString( recd ):
+def getRecdXmlMeshString(recd):
impl = getDOMImplementation()
- recdXml = impl.createDocument(None, Constants.recdRecd, None)
+ recdXml = impl.createDocument(None, 'recd', None)
root = recdXml.documentElement
- _addRecdXmlAttrs( root, recd, True )
+ _addRecdXmlAttrs(root, recd, True)
writer = cStringIO.StringIO()
recdXml.writexml(writer)
return writer.getvalue()
-
-def _addRecdXmlAttrs( el, recd, forMeshTransmit ):
- el.setAttribute(Constants.recdType, str(recd.type))
-
- if ((recd.type == Constants.TYPE_AUDIO) and (not forMeshTransmit)):
- aiPixbuf = recd.getAudioImagePixbuf( )
- aiPixbufString = str( utils.getStringFromPixbuf(aiPixbuf) )
- el.setAttribute(Constants.recdAudioImage, aiPixbufString)
-
- if ((recd.datastoreId != None) and (not forMeshTransmit)):
- el.setAttribute(Constants.recdDatastoreId, str(recd.datastoreId))
-
- el.setAttribute(Constants.recdTitle, recd.title)
- el.setAttribute(Constants.recdTime, str(recd.time))
- el.setAttribute(Constants.recdRecorderName, recd.recorderName)
- el.setAttribute(Constants.recdRecorderHash, str(recd.recorderHash) )
- el.setAttribute(Constants.recdColorStroke, str(recd.colorStroke.hex) )
- el.setAttribute(Constants.recdColorFill, str(recd.colorFill.hex) )
- el.setAttribute(Constants.recdBuddy, str(recd.buddy))
- el.setAttribute(Constants.recdMediaMd5, str(recd.mediaMd5))
- el.setAttribute(Constants.recdThumbMd5, str(recd.thumbMd5))
- el.setAttribute(Constants.recdMediaBytes, str(recd.mediaBytes))
- el.setAttribute(Constants.recdThumbBytes, str(recd.thumbBytes))
- el.setAttribute(Constants.recdRecordVersion, str(Constants.VERSION))
-
- pixbuf = recd.getThumbPixbuf( )
- thumb64 = str( utils.getStringFromPixbuf(pixbuf) )
- el.setAttribute(Constants.recdBase64Thumb, thumb64)
-
-
-def saveMediaHash( mediaHashs ):
+def _addRecdXmlAttrs(el, recd, forMeshTransmit):
+ el.setAttribute('type', str(recd.type))
+
+ if (recd.type == constants.TYPE_AUDIO) and (not forMeshTransmit):
+ aiPixbuf = recd.getAudioImagePixbuf()
+ aiPixbufString = str(utils.getStringFromPixbuf(aiPixbuf))
+ el.setAttribute('audioImage', aiPixbufString)
+
+ if (recd.datastoreId != None) and (not forMeshTransmit):
+ el.setAttribute('datastoreId', str(recd.datastoreId))
+
+ el.setAttribute('title', recd.title)
+ el.setAttribute('time', str(recd.time))
+ el.setAttribute('photographer', recd.recorderName)
+ el.setAttribute('recorderHash', str(recd.recorderHash) )
+ el.setAttribute('colorStroke', str(recd.colorStroke) )
+ el.setAttribute('colorFill', str(recd.colorFill) )
+ el.setAttribute('buddy', str(recd.buddy))
+ el.setAttribute('mediaMd5', str(recd.mediaMd5))
+ el.setAttribute('thumbMd5', str(recd.thumbMd5))
+ el.setAttribute('mediaBytes', str(recd.mediaBytes))
+ el.setAttribute('thumbBytes', str(recd.thumbBytes))
+
+ # FIXME: can this be removed, or at least autodetected? has not been
+ # changed for ages, should not be relevant
+ el.setAttribute('version', '54')
+
+ pixbuf = recd.getThumbPixbuf()
+ thumb64 = str(utils.getStringFromPixbuf(pixbuf))
+ el.setAttribute('base64Thumb', thumb64)
+
+def saveMediaHash(mediaHashs, activity):
impl = getDOMImplementation()
- album = impl.createDocument(None, Constants.recdAlbum, None)
+ album = impl.createDocument(None, 'album', None)
root = album.documentElement
#flag everything for saving...
atLeastOne = False
- for type,value in Constants.mediaTypes.items():
- typeName = value[Constants.keyName]
- hash = mediaHashs[type]
- for i in range (0, len(hash)):
- recd = hash[i]
+ for type, value in constants.MEDIA_INFO.items():
+ typeName = value['name']
+ for recd in mediaHashs[type]:
recd.savedXml = False
recd.savedMedia = False
atLeastOne = True
#and if there is anything to save, save it
- if (atLeastOne):
- for type,value in Constants.mediaTypes.items():
- typeName = value[Constants.keyName]
- hash = mediaHashs[type]
-
- for i in range (0, len(hash)):
- recd = hash[i]
- mediaEl = album.createElement( typeName )
- root.appendChild( mediaEl )
- _saveMedia( mediaEl, recd )
+ if atLeastOne:
+ for type, value in constants.MEDIA_INFO.items():
+ typeName = value['name']
+ for recd in mediaHashs[type]:
+ mediaEl = album.createElement(typeName)
+ root.appendChild(mediaEl)
+ _saveMedia(mediaEl, recd, activity)
return album
-
-def _saveMedia( el, recd ):
- if ( (recd.buddy == True) and (recd.datastoreId == None) and (not recd.downloadedFromBuddy) ):
+def _saveMedia(el, recd, activity):
+ if recd.buddy == True and recd.datastoreId == None and not recd.downloadedFromBuddy:
recd.savedMedia = True
- _saveXml( el, recd )
+ _saveXml(el, recd)
else:
recd.savedMedia = False
- _saveMediaToDatastore( el, recd )
+ _saveMediaToDatastore(el, recd, activity)
-
-def _saveXml( el, recd ):
- if (recd.thumbFilename != None):
- _addRecdXmlAttrs( el, recd, False )
+def _saveXml(el, recd):
+ if recd.thumbFilename:
+ _addRecdXmlAttrs(el, recd, False)
else:
- record.Record.log.debug("WOAH, ERROR: recd has no thumbFilename?! " + str(recd) )
+ logger.debug("WOAH, ERROR: recd has no thumbFilename?! " + str(recd) )
recd.savedXml = True
-
-def _saveMediaToDatastore( el, recd ):
+def _saveMediaToDatastore(el, recd, activity):
#note that we update the recds that go through here to how they would
#look on a fresh load from file since this won't just happen on close()
- if (recd.datastoreId != None):
+ if recd.datastoreId:
#already saved to the datastore, don't need to re-rewrite the file since the mediums are immutable
#However, they might have changed the name of the file
- if (recd.metaChange):
- recd.datastoreOb = getMediaFromDatastore( recd )
- if (recd.datastoreOb.metadata['title'] != recd.title):
+ if recd.metaChange:
+ recd.datastoreOb = getMediaFromDatastore(recd)
+ if recd.datastoreOb.metadata['title'] != recd.title:
recd.datastoreOb.metadata['title'] = recd.title
datastore.write(recd.datastoreOb)
- if (recd.datastoreOb.metadata['tags'] != recd.tags):
+ if recd.datastoreOb.metadata['tags'] != recd.tags:
recd.datastoreOb.metadata['tags'] = recd.tags
datastore.write(recd.datastoreOb)
#reset for the next title change if not closing...
recd.metaChange = False
- #save the title to the xml
- recd.savedMedia = True
- _saveXml( el, recd )
- else:
- recd.savedMedia = True
- _saveXml( el, recd )
+
+ #save the title to the xml
+ recd.savedMedia = True
+ _saveXml(el, recd)
else:
#this will remove the media from being accessed on the local disk since it puts it away into cold storage
@@ -296,38 +259,37 @@ def _saveMediaToDatastore( el, recd ):
mediaObject.metadata['tags'] = recd.tags
datastorePreviewPixbuf = recd.getThumbPixbuf()
- if (recd.type == Constants.TYPE_AUDIO):
+ if recd.type == constants.TYPE_AUDIO:
datastorePreviewPixbuf = recd.getAudioImagePixbuf()
- elif (recd.type == Constants.TYPE_PHOTO):
+ elif recd.type == constants.TYPE_PHOTO:
datastorePreviewFilepath = recd.getMediaFilepath()
datastorePreviewPixbuf = gtk.gdk.pixbuf_new_from_file(datastorePreviewFilepath)
datastorePreviewWidth = 300
datastorePreviewHeight = 225
- if (datastorePreviewPixbuf.get_width() != datastorePreviewWidth):
+ if datastorePreviewPixbuf.get_width() != datastorePreviewWidth:
datastorePreviewPixbuf = datastorePreviewPixbuf.scale_simple(datastorePreviewWidth, datastorePreviewHeight, gtk.gdk.INTERP_NEAREST)
datastorePreviewBase64 = utils.getStringFromPixbuf(datastorePreviewPixbuf)
mediaObject.metadata['preview'] = datastorePreviewBase64
- colors = str(recd.colorStroke.hex) + "," + str(recd.colorFill.hex)
+ colors = str(recd.colorStroke) + "," + str(recd.colorFill)
mediaObject.metadata['icon-color'] = colors
- mtype = Constants.mediaTypes[recd.type]
- mime = mtype[Constants.keyMime]
- mediaObject.metadata['mime_type'] = mime
+ mtype = constants.MEDIA_INFO[recd.type]
+ mediaObject.metadata['mime_type'] = mtype['mime']
- mediaObject.metadata['activity_id'] = Constants.activityId
+ mediaObject.metadata['activity_id'] = activity._activity_id
mediaFile = recd.getMediaFilepath()
mediaObject.file_path = mediaFile
mediaObject.transfer_ownership = True
- datastore.write( mediaObject )
+ datastore.write(mediaObject)
recd.datastoreId = mediaObject.object_id
recd.savedMedia = True
- _saveXml( el, recd )
+ _saveXml(el, recd)
recd.mediaFilename = None
diff --git a/tray.py b/tray.py
index 5cf3fa5..4f7956d 100644
--- a/tray.py
+++ b/tray.py
@@ -21,12 +21,8 @@ import hippo
import sugar
from sugar.graphics import style
-from sugar.graphics.palette import Palette, ToolInvoker
-from sugar.graphics.toolbutton import ToolButton
from sugar.graphics.icon import Icon
-from constants import Constants
-
_PREVIOUS_PAGE = 0
_NEXT_PAGE = 1
@@ -148,8 +144,8 @@ class HTray(gtk.VBox):
separator = hippo.Canvas()
box = hippo.CanvasBox(
- border_color=Constants.colorWhite.get_int(),
- background_color=Constants.colorWhite.get_int(),
+ border_color=0xffffffff,
+ background_color=0xffffffff,
box_height=1,
border_bottom=1)
separator.set_root(box)
@@ -203,4 +199,4 @@ class HTray(gtk.VBox):
return self._viewport.traybar.get_item_index(item)
def scroll_to_end(self):
- self._viewport._scroll_to_end() \ No newline at end of file
+ self._viewport._scroll_to_end()
diff --git a/ui.py b/ui.py
deleted file mode 100644
index 21e27be..0000000
--- a/ui.py
+++ /dev/null
@@ -1,2444 +0,0 @@
-#Copyright (c) 2008, Media Modifications Ltd.
-
-#Permission is hereby granted, free of charge, to any person obtaining a copy
-#of this software and associated documentation files (the "Software"), to deal
-#in the Software without restriction, including without limitation the rights
-#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-#copies of the Software, and to permit persons to whom the Software is
-#furnished to do so, subject to the following conditions:
-
-#The above copyright notice and this permission notice shall be included in
-#all copies or substantial portions of the Software.
-
-#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-#THE SOFTWARE.
-
-import gtk
-from gtk import gdk
-from gtk import keysyms
-import gobject
-import cairo
-import os
-import pygst
-pygst.require('0.10')
-import gst
-import gst.interfaces
-import math
-import shutil
-import time
-import pango
-import hippo
-
-import logging
-logger = logging.getLogger('record:ui.py')
-
-#from sugar.graphics.toolcombobox import ToolComboBox
-#from sugar.graphics.tray import HTray
-from sugar.graphics.toolbutton import ToolButton
-from sugar import profile
-from sugar import util
-from sugar.activity import activity
-from sugar.graphics import style
-
-from instance import Instance
-from constants import Constants, istrMinutes, istrSeconds
-from color import Color
-from p5 import P5
-from p5_button import P5Button
-from p5_button import Polygon
-from p5_button import Button
-import glive
-from glive import LiveVideoWindow
-from glivex import SlowLiveVideoWindow
-from gplay import PlayVideoWindow
-from recorded import Recorded
-from button import RecdButton
-import utils
-import record
-import aplay
-from tray import HTray
-from toolbarcombobox import ToolComboBox
-import hw
-
-class UI:
-
- dim_THUMB_WIDTH = 108
- dim_THUMB_HEIGHT = 81
- dim_INSET = 10
- dim_PIPW = 160
- dim_PIPH = 120 #pipBorder
- dim_PIP_BORDER = 4
- dim_PGDW = dim_PIPW + (dim_PIP_BORDER*2)
- dim_PGDH = dim_PIPH + (dim_PIP_BORDER*2)
- dim_CONTROLBAR_HT = 55
-
- def __init__( self, pca ):
- self.ca = pca
- self.ACTIVE = False
- self.LAUNCHING = True
- self.ca.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK)
- self.ca.connect("visibility-notify-event", self._visibleNotifyCb)
-
- self.inset = self.__class__.dim_INSET
- self.pgdh = self.__class__.dim_PGDH
- self.pgdw = self.__class__.dim_PGDW
- self.thumbTrayHt = 150 #todo: get sugar constant here
- self.thumbSvgW = 124
- self.thumbSvgH = 124
- self.maxw = 49
- self.maxh = 49
- self.controlBarHt = 60
- self.recordButtWd = 55
- self.pipw = self.__class__.dim_PIPW
- self.piph = self.__class__.dim_PIPH
-
- #ui modes
-
- # True when we're in full-screen mode, False otherwise
- self.FULLSCREEN = False
-
- # True when we're showing live video feed in the primary screen
- # area, False otherwise (even when we are still showing live video
- # in a p-i-p)
- self.LIVEMODE = True
-
- self.LAST_MODE = -1
- self.LAST_FULLSCREEN = False
- self.LAST_LIVE = True
- self.LAST_MESHING = False
- self.LAST_RECD_INFO = False
- self.LAST_TRANSCODING = False
- self.TRANSCODING = False
- self.MESHING = False
-
- # RECD_INFO_ON is True when the 'info' for a recording is being
- # display on-screen (who recorded it, tags, etc), and False otherwise.
- self.RECD_INFO_ON = False
-
- self.UPDATE_DURATION_ID = 0
- self.UPDATE_TIMER_ID = 0
- self.COUNTINGDOWN = False
-
- #init
- self.mapped = False
- self.centered = False
- self.setup = False
-
- #prep for when to show
- self.shownRecd = None
-
- #this includes the default sharing tab
- self.toolbox = activity.ActivityToolbox(self.ca)
- self.ca.set_toolbox(self.toolbox)
-
- if glive.camera_presents:
- self.photoToolbar = PhotoToolbar()
- self.photoToolbar.set_sensitive( False )
- self.toolbox.add_toolbar( Constants.istrPhoto, self.photoToolbar )
-
- self.videoToolbar = VideoToolbar()
- self.videoToolbar.set_sensitive( False )
- self.toolbox.add_toolbar( Constants.istrVideo, self.videoToolbar )
-
- self.tbars = { Constants.MODE_PHOTO: 1,
- Constants.MODE_VIDEO: 2,
- Constants.MODE_AUDIO: 3 }
- else:
- self.photoToolbar = None
- self.videoToolbar = None
- self.tbars = { Constants.MODE_AUDIO: 1 }
- self.ca.m.MODE = Constants.MODE_AUDIO
-
- self.audioToolbar = AudioToolbar()
- self.audioToolbar.set_sensitive( False )
- self.toolbox.add_toolbar( Constants.istrAudio, self.audioToolbar )
-
- self.toolbox.set_current_toolbar(self.tbars[self.ca.m.MODE])
-
- self.toolbox.remove(self.toolbox._separator)
- #taken directly from toolbox.py b/c I don't know how to mod the hongry hippo
- separator = hippo.Canvas()
- box = hippo.CanvasBox(
- border_color=Constants.colorBlack.get_int(),
- background_color=Constants.colorBlack.get_int(),
- box_height=style.TOOLBOX_SEPARATOR_HEIGHT,
- border_bottom=style.LINE_WIDTH)
- separator.set_root(box)
- self.toolbox.pack_start(separator, False)
- self.toolbox.separator = separator
-
- self.TOOLBOX_SIZE_ALLOCATE_ID = self.toolbox.connect_after("size-allocate", self._toolboxSizeAllocateCb)
- self.toolbox._notebook.set_property("can-focus", False)
- self.toolbox.connect("current-toolbar-changed", self._toolbarChangeCb)
- self.toolbox.show_all()
-
- def serialize(self):
- data = {}
-
- if self.photoToolbar:
- data['photo_timer'] = self.photoToolbar.timerCb.combo.get_active()
-
- if self.videoToolbar:
- data['video_timer'] = self.videoToolbar.timerCb.combo.get_active()
- data['video_duration'] = self.videoToolbar.durCb.combo.get_active()
- data['video_quality'] = self.videoToolbar.quality.combo.get_active()
-
- data['audio_timer'] = self.audioToolbar.timerCb.combo.get_active()
- data['audio_duration'] = self.audioToolbar.durCb.combo.get_active()
-
- return data
-
- def deserialize(self, data):
- if self.photoToolbar:
- self.photoToolbar.timerCb.combo.set_active(
- data.get('photo_timer', 0))
-
- if self.videoToolbar:
- self.videoToolbar.timerCb.combo.set_active(
- data.get('video_timer', 0))
- self.videoToolbar.durCb.combo.set_active(
- data.get('video_duration', 0))
- self.videoToolbar.quality.combo.set_active(
- data.get('video_quality', 0))
-
- self.audioToolbar.timerCb.combo.set_active(data.get('audio_timer', 0))
- self.audioToolbar.durCb.combo.set_active(data.get('audio_duration'))
-
- def _toolboxSizeAllocateCb( self, widget, event ):
- self.toolbox.disconnect( self.TOOLBOX_SIZE_ALLOCATE_ID)
-
- toolboxHt = self.toolbox.size_request()[1]
- self.vh = gtk.gdk.screen_height()-(self.thumbTrayHt+toolboxHt+self.controlBarHt)
- self.vw = int(self.vh/.75)
- self.letterBoxW = (gtk.gdk.screen_width() - self.vw)/2
- self.letterBoxVW = (self.vw/2)-(self.inset*2)
- self.letterBoxVH = int(self.letterBoxVW*.75)
-
- self.setUpWindows()
-
- #now that we know how big the toolbox is, we can layout more
- gobject.idle_add( self.layout )
-
-
- def layout( self ):
- self.mainBox = gtk.VBox()
- self.ca.set_canvas(self.mainBox)
-
- topBox = gtk.HBox()
- self.mainBox.pack_start(topBox, expand=True)
-
- leftFill = gtk.VBox()
- leftFill.set_size_request( self.letterBoxW, -1 )
- self.leftFillBox = gtk.EventBox( )
- self.leftFillBox.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
- leftFill.add( self.leftFillBox )
-
- topBox.pack_start( leftFill, expand=True )
-
- centerVBox = gtk.VBox()
- centerVBox.modify_bg(gtk.STATE_NORMAL, Constants.colorBlack.gColor)
- topBox.pack_start( centerVBox, expand=True )
- self.centerBox = gtk.EventBox()
- self.centerBox.set_size_request(self.vw, -1)
- self.centerBox.modify_bg(gtk.STATE_NORMAL, Constants.colorBlack.gColor)
- centerVBox.pack_start( self.centerBox, expand=True )
- centerSizer = gtk.VBox()
- centerSizer.set_size_request(self.vw, -1)
- centerSizer.modify_bg(gtk.STATE_NORMAL, Constants.colorBlack.gColor)
- self.centerBox.add(centerSizer)
-
- self.bottomCenter = gtk.EventBox()
- self.bottomCenter.modify_bg(gtk.STATE_NORMAL, Constants.colorBlack.gColor)
- self.bottomCenter.set_size_request(self.vw, self.controlBarHt)
- centerVBox.pack_start( self.bottomCenter, expand=False )
-
- #into the center box we can put this guy...
- self.backgdCanvasBox = gtk.VBox()
- self.backgdCanvasBox.modify_bg(gtk.STATE_NORMAL, Constants.colorBlack.gColor)
- self.backgdCanvasBox.set_size_request(self.vw, -1)
- self.backgdCanvas = PhotoCanvas()
- self.backgdCanvas.set_size_request(self.vw, self.vh)
- self.backgdCanvasBox.pack_start( self.backgdCanvas, expand=False )
-
- #or this guy...
- self.infoBox = gtk.EventBox()
- self.infoBox.modify_bg( gtk.STATE_NORMAL, Constants.colorButton.gColor )
- iinfoBox = gtk.VBox(spacing=self.inset)
- self.infoBox.add( iinfoBox )
- iinfoBox.set_size_request(self.vw, -1)
- iinfoBox.set_border_width(self.inset)
-
- rightFill = gtk.VBox()
- rightFill.set_size_request( self.letterBoxW, -1 )
- rightFillBox = gtk.EventBox()
- rightFillBox.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
- rightFill.add( rightFillBox )
- topBox.pack_start( rightFill, expand=True )
-
- #info box innards:
- self.infoBoxTop = gtk.HBox()
- iinfoBox.pack_start( self.infoBoxTop, expand=True )
- self.infoBoxTopLeft = gtk.VBox(spacing=self.inset)
- self.infoBoxTop.pack_start( self.infoBoxTopLeft )
- self.infoBoxTopRight = gtk.VBox()
- self.infoBoxTopRight.set_size_request(self.letterBoxVW, -1)
- self.infoBoxTop.pack_start( self.infoBoxTopRight )
-
- self.namePanel = gtk.HBox()
- leftInfBalance = gtk.VBox()
- self.nameLabel = gtk.Label("<b><span foreground='white'>"+Constants.istrTitle+"</span></b>")
- self.nameLabel.set_use_markup( True )
- self.namePanel.pack_start( self.nameLabel, expand=False, padding=self.inset )
- self.nameLabel.set_alignment(0, .5)
- self.nameTextfield = gtk.Entry(140)
- self.nameTextfield.modify_bg( gtk.STATE_INSENSITIVE, Constants.colorBlack.gColor )
- self.nameTextfield.connect('changed', self._nameTextfieldEditedCb )
- self.nameTextfield.set_alignment(0)
- self.nameTextfield.set_size_request( -1, self.controlBarHt-(self.inset*2) )
- self.namePanel.pack_start(self.nameTextfield)
-
- self.photographerPanel = gtk.VBox(spacing=self.inset)
- self.infoBoxTopLeft.pack_start(self.photographerPanel, expand=False)
- photographerLabel = gtk.Label("<b>" + Constants.istrRecorder + "</b>")
- photographerLabel.set_use_markup( True )
- self.photographerPanel.pack_start(photographerLabel, expand=False)
- photographerLabel.set_alignment(0, .5)
- photoNamePanel = gtk.HBox(spacing=self.inset)
- self.photographerPanel.pack_start(photoNamePanel)
-
- self.photoXoPanel = xoPanel()
- photoNamePanel.pack_start( self.photoXoPanel, expand=False )
- self.photoXoPanel.set_size_request( 40, 40 )
-
- self.photographerNameLabel = gtk.Label("")
- self.photographerNameLabel.set_alignment(0, .5)
- photoNamePanel.pack_start(self.photographerNameLabel)
-
- self.datePanel = gtk.HBox(spacing=self.inset)
- self.infoBoxTopLeft.pack_start(self.datePanel, expand=False)
- dateLabel = gtk.Label("<b>"+Constants.istrDate+"</b>")
- dateLabel.set_use_markup(True)
- self.datePanel.pack_start(dateLabel, expand=False)
- self.dateDateLabel = gtk.Label("")
- self.dateDateLabel.set_alignment(0, .5)
- self.datePanel.pack_start(self.dateDateLabel)
-
- self.tagsPanel = gtk.VBox(spacing=self.inset)
- tagsLabel = gtk.Label("<b>"+Constants.istrTags+"</b>")
- tagsLabel.set_use_markup(True)
- tagsLabel.set_alignment(0, .5)
- self.tagsPanel.pack_start(tagsLabel, expand=False)
- self.tagsBuffer = gtk.TextBuffer()
- self.tagsBuffer.connect('changed', self._tagsBufferEditedCb)
- self.tagsField = gtk.TextView(self.tagsBuffer)
- self.tagsField.set_size_request( 100, 100 )
- self.tagsPanel.pack_start(self.tagsField, expand=True)
- self.infoBoxTopLeft.pack_start(self.tagsPanel, expand=True)
-
- infoBotBox = gtk.HBox()
- infoBotBox.set_size_request( -1, self.pgdh+self.inset )
- iinfoBox.pack_start(infoBotBox, expand=False)
-
- thumbnailsEventBox = gtk.EventBox()
- thumbnailsEventBox.set_size_request( -1, self.thumbTrayHt )
- thumbnailsBox = gtk.HBox( )
- thumbnailsEventBox.add( thumbnailsBox )
-
- self.thumbTray = HTray()
- self.thumbTray.set_size_request( -1, self.thumbTrayHt )
- self.mainBox.pack_end( self.thumbTray, expand=False )
- self.thumbTray.show()
-
- self.CENTER_SIZE_ALLOCATE_ID = self.centerBox.connect_after("size-allocate", self._centerSizeAllocateCb)
- self.ca.show_all()
-
-
- def _centerSizeAllocateCb( self, widget, event ):
- #initial setup of the panels
- self.centerBox.disconnect(self.CENTER_SIZE_ALLOCATE_ID)
- self.centerBoxPos = self.centerBox.translate_coordinates( self.ca, 0, 0 )
-
- centerKid = self.centerBox.get_child()
- if (centerKid != None):
- self.centerBox.remove( centerKid )
-
- self.centered = True
- self.setUp()
-
-
- def _mapEventCb( self, widget, event ):
- #when your parent window is ready, turn on the feed of live video
- self.liveVideoWindow.disconnect(self.MAP_EVENT_ID)
- self.mapped = True
- self.setUp()
-
-
- def setUp( self ):
- if (self.mapped and self.centered and not self.setup):
- self.setup = True
-
- #set correct window sizes
- self.setUpWindowsSizes()
-
- #listen for ctrl+c & game key buttons
- self.ca.connect('key-press-event', self._keyPressEventCb)
- #overlay widgets can go away after they've been on screen for a while
- self.HIDE_WIDGET_TIMEOUT_ID = 0
- self.hiddenWidgets = False
- self.resetWidgetFadeTimer()
- self.showLiveVideoTags()
-
- if self.photoToolbar:
- self.photoToolbar.set_sensitive( True )
- if self.videoToolbar:
- self.videoToolbar.set_sensitive( True )
- self.audioToolbar.set_sensitive( True )
-
- #initialize the app with the default thumbs
- self.ca.m.setupMode( self.ca.m.MODE, True )
-
- gobject.idle_add( self.finalSetUp )
-
-
- def finalSetUp( self ):
- self.LAUNCHING = False
- self.ACTIVE = self.ca.get_property( "visible" )
- self.updateVideoComponents()
-
- if (self.ACTIVE):
- self.ca.glive.play()
-
-
- def setUpWindows( self ):
- #image windows
- self.windowStack = []
-
- #live video windows
- self.livePhotoWindow = gtk.Window()
- self.livePhotoWindow.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
- self.livePhotoWindow.modify_bg( gtk.STATE_INSENSITIVE, Constants.colorBlack.gColor )
- self.addToWindowStack( self.livePhotoWindow, self.ca )
- self.livePhotoCanvas = PhotoCanvas()
- self.livePhotoWindow.add(self.livePhotoCanvas)
- self.livePhotoWindow.connect("button_release_event", self._mediaClickedForPlayback)
- self.livePhotoWindow.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK)
- self.livePhotoWindow.connect("visibility-notify-event", self._visibleNotifyCb)
-
- #video playback windows
- self.playOggWindow = PlayVideoWindow(Constants.colorBlack.gColor)
- self.addToWindowStack( self.playOggWindow, self.windowStack[len(self.windowStack)-1] )
- #self.playOggWindow.set_gplay(self.ca.gplay)
- self.ca.gplay.window = self.playOggWindow
- self.playOggWindow.set_events(gtk.gdk.BUTTON_RELEASE_MASK)
- self.playOggWindow.connect("button_release_event", self._mediaClickedForPlayback)
- self.playOggWindow.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK)
- self.playOggWindow.connect("visibility-notify-event", self._visibleNotifyCb)
-
- #border behind
- self.pipBgdWindow = gtk.Window()
- self.pipBgdWindow.modify_bg( gtk.STATE_NORMAL, Constants.colorWhite.gColor )
- self.pipBgdWindow.modify_bg( gtk.STATE_INSENSITIVE, Constants.colorWhite.gColor )
- self.addToWindowStack( self.pipBgdWindow, self.windowStack[len(self.windowStack)-1] )
-
- self.liveVideoWindow = LiveVideoWindow(Constants.colorBlack.gColor)
- self.addToWindowStack( self.liveVideoWindow, self.windowStack[len(self.windowStack)-1] )
- self.liveVideoWindow.set_glive(self.ca.glive)
- self.liveVideoWindow.set_events(gtk.gdk.BUTTON_RELEASE_MASK)
- self.liveVideoWindow.connect("button_release_event", self._liveButtonReleaseCb)
- self.liveVideoWindow.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK)
- self.liveVideoWindow.connect("visibility-notify-event", self._visibleNotifyCb)
-
- self.slowLiveVideoWindow = SlowLiveVideoWindow(Constants.colorBlack.gColor)
- self.addToWindowStack( self.slowLiveVideoWindow, self.windowStack[len(self.windowStack)-1] )
- self.slowLiveVideoWindow.set_glivex(self.ca.glivex)
- self.slowLiveVideoWindow.set_events(gtk.gdk.BUTTON_RELEASE_MASK)
- self.slowLiveVideoWindow.connect("button_release_event", self._liveButtonReleaseCb)
- self.slowLiveVideoWindow.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK)
- self.slowLiveVideoWindow.connect("visibility-notify-event", self._visibleNotifyCb)
-
- self.recordWindow = RecordWindow(self)
- self.addToWindowStack( self.recordWindow, self.windowStack[len(self.windowStack)-1] )
-
- self.progressWindow = ProgressWindow(self)
- self.addToWindowStack( self.progressWindow, self.windowStack[len(self.windowStack)-1] )
-
- self.maxWindow = gtk.Window()
- self.maxWindow.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
- self.maxWindow.modify_bg( gtk.STATE_INSENSITIVE, Constants.colorBlack.gColor )
- maxButton = MaxButton(self)
- self.maxWindow.add( maxButton )
- self.addToWindowStack( self.maxWindow, self.windowStack[len(self.windowStack)-1] )
-
- self.scrubWindow = ScrubberWindow(self)
- self.addToWindowStack( self.scrubWindow, self.windowStack[len(self.windowStack)-1] )
-
- self.infWindow = gtk.Window()
- self.infWindow.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
- self.infWindow.modify_bg( gtk.STATE_INSENSITIVE, Constants.colorBlack.gColor )
- infButton= InfButton(self)
- self.infWindow.add(infButton)
- self.addToWindowStack( self.infWindow, self.windowStack[len(self.windowStack)-1] )
-
- self.hideAllWindows()
- self.MAP_EVENT_ID = self.liveVideoWindow.connect_after("map-event", self._mapEventCb)
- for i in range (0, len(self.windowStack)):
-# self.windowStack[i].add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK)
-# self.windowStack[i].connect("visibility-notify-event", self._visibleNotifyCb)
- self.windowStack[i].show_all()
-
-
- def _visibleNotifyCb( self, widget, event ):
-
- if (self.LAUNCHING):
- return
-
- temp_ACTIVE = True
-
- if (event.state == gtk.gdk.VISIBILITY_FULLY_OBSCURED):
-
- if (not self.FULLSCREEN):
- if (widget == self.ca):
- temp_ACTIVE = False
-
- else:
- if (self.ca.m.MODE == Constants.MODE_PHOTO):
- if (not self.LIVEMODE and widget == self.livePhotoWindow):
- temp_ACTIVE = False
- if ( self.LIVEMODE and widget == self.liveVideoWindow):
- temp_ACTIVE = False
-
- if (self.ca.m.MODE == Constants.MODE_VIDEO):
- if (not self.LIVEMODE and widget == self.playOggWindow):
- temp_ACTIVE = False
- if ( self.LIVEMODE and widget == self.liveVideoWindow):
- temp_ACTIVE = False
-
-
- if (temp_ACTIVE != self.ACTIVE):
- self.ACTIVE = temp_ACTIVE
- if (self.ACTIVE):
- self.ca.restartPipes()
- else:
- self.ca.stopPipes()
-
-
- def setUpWindowsSizes( self ):
- pipDim = self.getPipDim(False)
- eyeDim = self.getEyeDim(False)
- imgDim = self.getImgDim( False )
- pgdDim = self.getPgdDim( False )
- maxDim = self.getMaxDim( False )
- prgDim = self.getPrgDim( False )
- infDim = self.getInfDim( False )
- self.livePhotoWindow.resize( imgDim[0], imgDim[1] )
- self.pipBgdWindow.resize( pgdDim[0], pgdDim[1] )
- self.liveVideoWindow.resize( imgDim[0], imgDim[1] )
- self.playOggWindow.resize( imgDim[0], imgDim[1] )
- self.recordWindow.resize( eyeDim[0], eyeDim[1] )
- self.maxWindow.resize( maxDim[0], maxDim[1] )
- self.progressWindow.resize( prgDim[0], prgDim[1] )
- self.infWindow.resize( infDim[0], infDim[1] )
-
-
- def _toolbarChangeCb( self, tbox, num ):
- if (num != 0) and (self.ca.m.RECORDING or self.ca.m.UPDATING):
- self.toolbox.set_current_toolbar(self.tbars[self.ca.m.MODE])
- else:
- mode = [mode for mode, i in self.tbars.items() if i == num]
- if not mode:
- return
- if (mode[0] == Constants.MODE_PHOTO) and \
- (self.ca.m.MODE != Constants.MODE_PHOTO):
- self.ca.m.doPhotoMode()
- elif(mode[0] == Constants.MODE_VIDEO) and \
- (self.ca.m.MODE != Constants.MODE_VIDEO):
- self.ca.m.doVideoMode()
- elif(mode[0] == Constants.MODE_AUDIO) and \
- (self.ca.m.MODE != Constants.MODE_AUDIO):
- self.ca.m.doAudioMode()
-
-
- def addToWindowStack( self, win, parent ):
- self.windowStack.append( win )
- win.set_transient_for( parent )
- win.set_type_hint( gtk.gdk.WINDOW_TYPE_HINT_DIALOG )
- win.set_decorated( False )
- win.set_focus_on_map( False )
- win.set_property("accept-focus", False)
- win.props.destroy_with_parent = True
-
-
- def resetWidgetFadeTimer( self ):
- #only show the clutter when the mouse moves
- self.mx = -1
- self.my = -1
- self.hideWidgetsTimer = time.time()
- if (self.hiddenWidgets):
- self.showWidgets()
- self.hiddenWidgets = False
-
- #remove, then add
- self.doMouseListener( False )
- if (self.HIDE_WIDGET_TIMEOUT_ID != 0):
- gobject.source_remove( self.HIDE_WIDGET_TIMEOUT_ID)
-
- self.HIDE_WIDGET_TIMEOUT_ID = gobject.timeout_add( 500, self._mouseMightaMovedCb )
-
-
- def doMouseListener( self, listen ):
- if (listen):
- self.resetWidgetFadeTimer()
- else:
- if (self.HIDE_WIDGET_TIMEOUT_ID != None):
- if (self.HIDE_WIDGET_TIMEOUT_ID != 0):
- gobject.source_remove( self.HIDE_WIDGET_TIMEOUT_ID )
-
-
- def hideWidgets( self ):
- self.moveWinOffscreen( self.maxWindow )
- self.moveWinOffscreen( self.pipBgdWindow )
- self.moveWinOffscreen( self.infWindow )
- self.moveWinOffscreen( self.slowLiveVideoWindow )
-
- if (self.FULLSCREEN):
- self.moveWinOffscreen( self.recordWindow )
- self.moveWinOffscreen( self.progressWindow )
- self.moveWinOffscreen( self.scrubWindow )
-
- if (self.ca.m.MODE == Constants.MODE_PHOTO):
- if (not self.LIVEMODE):
- self.moveWinOffscreen( self.liveVideoWindow )
- elif (self.ca.m.MODE == Constants.MODE_VIDEO):
- if (not self.LIVEMODE):
- self.moveWinOffscreen( self.liveVideoWindow )
- elif (self.ca.m.MODE == Constants.MODE_AUDIO):
- if (not self.LIVEMODE):
- self.moveWinOffscreen( self.liveVideoWindow )
- self.LAST_MODE = -1
-
-
- def _mouseMightaMovedCb( self ):
- x, y = self.ca.get_pointer()
- passedTime = 0
-
- if (x != self.mx or y != self.my):
- self.hideWidgetsTimer = time.time()
- if (self.hiddenWidgets):
- self.showWidgets()
- self.hiddenWidgets = False
- else:
- passedTime = time.time() - self.hideWidgetsTimer
-
- if (self.ca.m.RECORDING):
- self.hideWidgetsTimer = time.time()
- passedTime = 0
-
- if (passedTime >= 3):
- if (not self.hiddenWidgets):
- if (self.mouseInWidget(x,y)):
- self.hideWidgetsTimer = time.time()
- elif (self.RECD_INFO_ON):
- self.hideWidgetsTimer = time.time()
- elif (self.UPDATE_TIMER_ID != 0):
- self.hideWidgetsTimer = time.time()
- else:
- self.hideWidgets()
- self.hiddenWidgets = True
-
- self.mx = x
- self.my = y
- return True
-
-
- def mouseInWidget( self, mx, my ):
- if (self.ca.m.MODE != Constants.MODE_AUDIO):
- if (self.inWidget( mx, my, self.getLoc("max", self.FULLSCREEN), self.getDim("max", self.FULLSCREEN))):
- return True
-
- if (not self.LIVEMODE):
- if (self.inWidget( mx, my, self.getLoc("pgd", self.FULLSCREEN), self.getDim("pgd", self.FULLSCREEN))):
- return True
-
- if (self.inWidget( mx, my, self.getLoc("inb", self.FULLSCREEN), self.getDim("inb", self.FULLSCREEN))):
- return True
-
- if (self.inWidget( mx, my, self.getLoc("prg", self.FULLSCREEN), self.getDim("prg", self.FULLSCREEN))):
- return True
-
- if (self.inWidget( mx, my, self.getLoc("inf", self.FULLSCREEN), self.getDim("inf", self.FULLSCREEN))):
- return True
-
- if (self.LIVEMODE):
- if (self.inWidget( mx, my, self.getLoc("eye", self.FULLSCREEN), self.getDim("eye", self.FULLSCREEN))):
- return True
-
- return False
-
-
- def _mediaClickedForPlayback(self, widget, event):
- if (not self.LIVEMODE):
- if (self.shownRecd != None):
- if (self.ca.m.MODE != Constants.MODE_PHOTO):
- self.showThumbSelection( self.shownRecd )
-
-
- def inWidget( self, mx, my, loc, dim ):
- if ( (mx > loc[0]) and (my > loc[1]) ):
- if ( (mx < loc[0]+dim[0]) and (my < loc[1]+dim[1]) ):
- return True
-
-
- def _nameTextfieldEditedCb(self, widget):
- if (self.shownRecd != None):
- if (self.nameTextfield.get_text() != self.shownRecd.title):
- self.shownRecd.setTitle( self.nameTextfield.get_text() )
-
-
- def _tagsBufferEditedCb(self, widget):
- if (self.shownRecd != None):
- txt = self.tagsBuffer.get_text( self.tagsBuffer.get_start_iter(), self.tagsBuffer.get_end_iter() )
- if (txt != self.shownRecd.tags):
- self.shownRecd.setTags( txt )
-
-
- def _keyPressEventCb( self, widget, event):
- #todo: trac #4144
-
- self.resetWidgetFadeTimer()
-
- #we listen here for CTRL+C events and game keys, and pass on events to gtk.Entry fields
- keyname = gtk.gdk.keyval_name(event.keyval)
-
- if (keyname == 'KP_Page_Up'): #O, up
- if (self.LIVEMODE):
- if (not self.ca.m.UPDATING):
- self.doShutter()
- else:
- if (self.COUNTINGDOWN):
- self.doShutter()
- else:
- if (self.ca.m.MODE == Constants.MODE_PHOTO):
- self.resumeLiveVideo()
- else:
- self.resumePlayLiveVideo()
- elif (keyname == 'KP_Page_Down'): #x, down
- if (not self.ca.m.UPDATING and not self.ca.m.RECORDING):
- self.ca.m.showLastThumb()
- elif (keyname == 'KP_Home'): #square, left
- if (not self.ca.m.UPDATING and not self.ca.m.RECORDING and not self.LIVEMODE):
- self.ca.m.showPrevThumb( self.shownRecd )
- elif (keyname == 'KP_End'): #check, right
- if (not self.ca.m.UPDATING and not self.ca.m.RECORDING and not self.LIVEMODE):
- self.ca.m.showNextThumb( self.shownRecd )
- elif (keyname == 'c' and event.state == gtk.gdk.CONTROL_MASK):
- if (self.shownRecd != None):
- self.copyToClipboard( self.shownRecd )
- elif (keyname == 'Escape'):
- if (self.FULLSCREEN):
- self.FULLSCREEN = False
- if (self.RECD_INFO_ON):
- self.infoButtonClicked()
- else:
- self.updateVideoComponents()
- elif (keyname == 'i' and event.state == gtk.gdk.CONTROL_MASK):
- if (not self.LIVEMODE):
- self.infoButtonClicked()
-
- return False
-
-
- def copyToClipboard( self, recd ):
- if (recd.isClipboardCopyable( )):
- tmpImgPath = self.doClipboardCopyStart( recd )
- gtk.Clipboard().set_with_data( [('text/uri-list', 0, 0)], self._clipboardGetFuncCb, self._clipboardClearFuncCb, tmpImgPath )
- return True
-
-
- def doClipboardCopyStart( self, recd ):
- imgPath_s = recd.getMediaFilepath()
- if (imgPath_s == None):
- record.Record.log.error("doClipboardCopyStart: imgPath_s==None")
- return None
-
- tmpImgPath = recd.getMediaFilepath()
- tmpImgPath = utils.getUniqueFilepath(tmpImgPath, 0)
- shutil.copyfile( imgPath_s, tmpImgPath )
- return tmpImgPath
-
-
- def doClipboardCopyCopy( self, tmpImgPath, selection_data ):
- tmpImgUri = "file://" + tmpImgPath
- selection_data.set( "text/uri-list", 8, tmpImgUri )
-
-
- def doClipboardCopyFinish( self, tmpImgPath ):
- if (tmpImgPath != None):
- if (os.path.exists(tmpImgPath)):
- os.remove( tmpImgPath )
- tmpImgPath = None
-
-
- def _clipboardGetFuncCb( self, clipboard, selection_data, info, data):
- self.doClipboardCopyCopy( data, selection_data )
-
-
- def _clipboardClearFuncCb( self, clipboard, data):
- self.doClipboardCopyFinish( data )
-
-
- def showPhoto( self, recd ):
- pixbuf = self.getPhotoPixbuf( recd )
- if (pixbuf != None):
- #self.shownRecd = recd
-
- img = hippo.cairo_surface_from_gdk_pixbuf(pixbuf)
- self.livePhotoCanvas.setImage( img )
-
- self.LIVEMODE = False
- self.updateVideoComponents()
-
- self.showRecdMeta(recd)
-
-
- def getPhotoPixbuf( self, recd ):
- pixbuf = None
-
- downloading = self.ca.requestMeshDownload(recd)
- self.MESHING = downloading
-
- if (not downloading):
- self.progressWindow.updateProgress(0, "")
- imgPath = recd.getMediaFilepath()
- if (not imgPath == None):
- if ( os.path.isfile(imgPath) ):
- pixbuf = gtk.gdk.pixbuf_new_from_file(imgPath)
-
- if (pixbuf == None):
- #maybe it is not downloaded from the mesh yet...
- #but we can show the low res thumb in the interim
- pixbuf = recd.getThumbPixbuf()
-
- return pixbuf
-
-
- def showLiveVideoTags( self ):
- self.shownRecd = None
- self.livePhotoCanvas.setImage( None )
- self.nameTextfield.set_text("")
- self.tagsBuffer.set_text("")
-
- self.scrubWindow.removeCallbacks()
- self.scrubWindow.reset()
- self.MESHING = False
- self.progressWindow.updateProgress( 0, "" )
-
- self.resetWidgetFadeTimer( )
-
-
- def updateButtonSensitivities( self ):
- switchStuff = ((not self.ca.m.UPDATING) and (not self.ca.m.RECORDING))
-
- if self.photoToolbar:
- self.photoToolbar.set_sensitive( switchStuff )
- if self.videoToolbar:
- self.videoToolbar.set_sensitive( switchStuff )
- self.audioToolbar.set_sensitive( switchStuff )
-
- if (not self.COUNTINGDOWN):
- if (self.ca.m.UPDATING):
- self.ca.ui.setWaitCursor( self.ca.window )
- for i in range (0, len(self.windowStack)):
- self.ca.ui.setWaitCursor( self.windowStack[i].window )
- else:
- self.ca.ui.setDefaultCursor( self.ca.window )
- for i in range (0, len(self.windowStack)):
- self.ca.ui.setDefaultCursor( self.windowStack[i].window )
-
- #display disc is full messages
- self.ca.m.updateXoFullStatus()
- self.recordWindow.displayDiscFullText(self.ca.m.FULL)
- if (self.ca.m.FULL):
- self.recordWindow.shutterButton.set_sensitive( False, True)
- fullMessage = Constants.istrYourDiskIsFull % {"1":Constants.istrJournal}
- self.progressWindow.updateProgress( 1, fullMessage, "gray" )
- else:
- self.recordWindow.shutterButton.set_sensitive( not self.ca.m.UPDATING, False )
- if (self.ca.m.RECORDING):
- self.recordWindow.shutterButton.doRecordButton()
- else:
- self.recordWindow.shutterButton.doNormalButton()
-
- kids = self.thumbTray.get_children()
- for i in range (0, len(kids)):
- if (self.ca.m.UPDATING or self.ca.m.RECORDING):
- if (kids[i].getButtClickedId() != 0):
- kids[i].disconnect( kids[i].getButtClickedId() )
- kids[i].setButtClickedId(0)
- else:
- if (kids[i].getButtClickedId() == 0):
- BUTT_CLICKED_ID = kids[i].connect( "clicked", self._thumbClicked, kids[i].recd )
- kids[i].setButtClickedId(BUTT_CLICKED_ID)
-
-
- def hideAllWindows( self ):
- for i in range (0, len(self.windowStack)):
- self.moveWinOffscreen( self.windowStack[i] )
-
-
- def _liveButtonReleaseCb(self, widget, event):
- self.ca.gplay.stop()
- self.ca.glivex.stop()
- self.ca.glive.play()
- self.resumeLiveVideo()
-
-
- def resumeLiveVideo( self ):
- self.livePhotoCanvas.setImage( None )
-
- bottomKid = self.bottomCenter.get_child()
- if (bottomKid != None):
- self.bottomCenter.remove( bottomKid )
-
- self.RECD_INFO_ON = False
-
- if (not self.LIVEMODE):
- self.ca.m.setUpdating(True)
- self.ca.gplay.stop()
- self.showLiveVideoTags()
- self.LIVEMODE = True
- self.updateVideoComponents()
- self.ca.m.setUpdating(False)
-
-
- def _playLiveButtonReleaseCb(self, widget, event):
- self.resumePlayLiveVideo()
-
-
- def resumePlayLiveVideo( self ):
- self.ca.gplay.stop()
-
- self.RECD_INFO_ON = False
- #if you are big on the screen, don't go changing anything, ok?
- if (self.LIVEMODE):
- return
-
- self.showLiveVideoTags()
- self.LIVEMODE = True
- self.startLiveVideo()
- self.updateVideoComponents()
-
-
- def recordVideo( self ):
- self.ca.glive.startRecordingVideo(self.videoToolbar.getQuality())
- self.beginRecordingTimer( )
-
-
- def recordAudio( self ):
- self.ca.glive.startRecordingAudio( )
- self.beginRecordingTimer( )
-
-
- def beginRecordingTimer( self ):
- self.recTime = time.time()
- self.UPDATE_DURATION_ID = gobject.timeout_add( 500, self._updateDurationCb )
-
-
- def _updateDurationCb( self ):
- passedTime = time.time() - self.recTime
-
- duration = 10.0
- if (self.ca.m.MODE == Constants.MODE_VIDEO):
- duration = self.videoToolbar.getDuration()+0.0
- elif (self.ca.m.MODE == Constants.MODE_AUDIO):
- duration = self.audioToolbar.getDuration()+0.0
-
- if (passedTime >= duration ):
- self.completeCountdown()
- self.progressWindow.updateProgress( 1, Constants.istrFinishedRecording )
- if (self.ca.m.RECORDING):
- gobject.idle_add( self.doShutter )
-
- return False
- else:
- secsRemaining = duration - passedTime
- if (secsRemaining >= 60):
- mins = int( secsRemaining/60 )
- secs = int( secsRemaining%60 )
- timeRemainStr = istrMinutes(mins) + ', ' + istrSeconds(secs)
- else:
- timeRemainStr = istrSeconds(secsRemaining)
-
- self.progressWindow.updateProgress( passedTime/duration,
- Constants.istrRemaining + " " + timeRemainStr )
- return True
-
-
- def completeCountdown( self ):
- if (self.UPDATE_DURATION_ID != 0):
- gobject.source_remove( self.UPDATE_DURATION_ID )
- self.UPDATE_DURATION_ID = 0
-
-
- def updateModeChange(self):
- #this is called when a menubar button is clicked
- self.LIVEMODE = True
- self.FULLSCREEN = False
- self.RECD_INFO_ON = False
- self.MESHING = False
-
- self.progressWindow.updateProgress(0, "")
-
- #set up the x & xv x-ition (if need be)
- self.ca.gplay.stop()
- self.startLiveVideo()
-
- bottomKid = self.bottomCenter.get_child()
- if (bottomKid != None):
- self.bottomCenter.remove( bottomKid )
-
- self.doMouseListener( True )
- self.showLiveVideoTags()
- self.LAST_MODE = -1 #force an update
- self.updateVideoComponents()
- self.resetWidgetFadeTimer()
-
-
- def startLiveVideo(self):
- self.ca.glivex.stop()
- self.ca.glive.play()
-
-
- def doFullscreen( self ):
- self.FULLSCREEN = not self.FULLSCREEN
- self.updateVideoComponents()
-
-
- def moveWinOffscreen( self, win ):
- #we move offscreen to resize or else we get flashes on screen, and setting hide() doesn't allow resize & moves
- offW = (gtk.gdk.screen_width() + 100)
- offH = (gtk.gdk.screen_height() + 100)
- self.smartMove(win, offW, offH)
-
-
- def setImgLocDim( self, win ):
- imgDim = self.getImgDim( self.FULLSCREEN )
- self.smartResize( win, imgDim[0], imgDim[1] )
- imgLoc = self.getImgLoc( self.FULLSCREEN )
- self.smartMove( win, imgLoc[0], imgLoc[1] )
-
-
- def setPrgLocDim( self, win ):
- prgDim = self.getPrgDim( self.FULLSCREEN )
- self.smartResize( win, prgDim[0], prgDim[1] )
- prgLoc = self.getPrgLoc( self.FULLSCREEN )
- self.smartMove( win, prgLoc[0], prgLoc[1] )
-
-
- def setTmrLocDim( self, win ):
- tmrDim = self.getTmrDim( self.FULLSCREEN )
- self.smartResize( win, tmrDim[0], tmrDim[1] )
- tmrLoc = self.getTmrLoc( self.FULLSCREEN )
- self.smartMove( win, tmrLoc[0], tmrLoc[1] )
-
-
- def setScrLocDim( self, win ):
- scrDim = self.getScrDim( self.FULLSCREEN )
- self.smartResize( win, scrDim[0], scrDim[1] )
- scrLoc = self.getScrLoc( self.FULLSCREEN )
- self.smartMove( win, scrLoc[0], scrLoc[1] )
-
-
- def setInfLocDim( self, win ):
- infDim = self.getInfDim( self.FULLSCREEN )
- self.smartResize( win, infDim[0], infDim[1] )
- infLoc = self.getInfLoc( self.FULLSCREEN )
- self.smartMove( win, infLoc[0], infLoc[1] )
-
-
- def getScrDim( self, full ):
- if (full):
- return [gtk.gdk.screen_width()-(self.inset+self.pgdw+self.inset+self.inset), self.controlBarHt]
- else:
- return [self.vw, self.controlBarHt]
-
-
- def getScrLoc( self, full ):
- if (full):
- return [(self.inset+self.pgdw+self.inset), gtk.gdk.screen_height()-(self.inset+self.controlBarHt)]
- else:
- return [self.centerBoxPos[0], self.centerBoxPos[1]+self.vh]
-
-
- def getImgDim( self, full ):
- if (full):
- return [gtk.gdk.screen_width(), gtk.gdk.screen_height()]
- else:
- return [self.vw, self.vh]
-
-
- def getImgLoc( self, full ):
- if (full):
- return[0, 0]
- else:
- return[self.centerBoxPos[0], self.centerBoxPos[1]]
-
-
- def getTmrLoc( self, full ):
- if (not full):
- return [self.centerBoxPos[0], self.centerBoxPos[1]+self.vh]
- else:
- return [self.inset, gtk.gdk.screen_height()-(self.inset+self.controlBarHt)]
-
-
- def getTmrDim( self, full ):
- if (not full):
- return [self.vw, self.controlBarHt]
- else:
- return [gtk.gdk.screen_width()-(self.inset+self.inset), self.controlBarHt]
-
-
- def setPipLocDim( self, win ):
- self.smartResize( win, self.pipw, self.piph )
-
- loc = self.getPipLoc( self.FULLSCREEN )
- self.smartMove( win, loc[0], loc[1] )
-
-
- def getPipLoc( self, full ):
- if (full):
- return [self.inset+self.__class__.dim_PIP_BORDER, gtk.gdk.screen_height()-(self.inset+self.piph+self.__class__.dim_PIP_BORDER)]
- else:
- return [self.centerBoxPos[0]+self.inset+self.__class__.dim_PIP_BORDER, (self.centerBoxPos[1]+self.vh)-(self.inset+self.piph+self.__class__.dim_PIP_BORDER)]
-
-
- def setPipBgdLocDim( self, win ):
- pgdLoc = self.getPgdLoc( self.FULLSCREEN )
- self.smartMove( win, pgdLoc[0], pgdLoc[1] )
-
-
- def getPgdLoc( self, full ):
- if (full):
- return [self.inset, gtk.gdk.screen_height()-(self.inset+self.pgdh)]
- else:
- return [self.centerBoxPos[0]+self.inset, (self.centerBoxPos[1]+self.vh)-(self.inset+self.pgdh)]
-
-
- def setMaxLocDim( self, win ):
- maxLoc = self.getMaxLoc( self.FULLSCREEN )
- self.smartMove( win, maxLoc[0], maxLoc[1] )
-
-
- def getMaxLoc( self, full ):
- if (full):
- return [gtk.gdk.screen_width()-(self.maxw+self.inset), self.inset]
- else:
- return [(self.centerBoxPos[0]+self.vw)-(self.inset+self.maxw), self.centerBoxPos[1]+self.inset]
-
-
- def getInfLoc( self, full ):
- if (full):
- return [gtk.gdk.screen_width()+100,gtk.gdk.screen_height()+100 ]
- else:
- dim = self.getInfDim(self.FULLSCREEN)
- return [(self.centerBoxPos[0]+self.vw)-dim[0], (self.centerBoxPos[1]+self.vh)-dim[1]]
-
-
- def setEyeLocDim( self, win ):
- dim = self.getEyeDim( self.FULLSCREEN )
- self.smartResize( win, dim[0], dim[1] )
- loc = self.getEyeLoc( self.FULLSCREEN )
- self.smartMove( win, loc[0], loc[1] )
-
-
- def getEyeLoc( self, full ):
- if (not full):
- return [self.centerBoxPos[0], self.centerBoxPos[1]+self.vh]
- else:
- return [self.inset, gtk.gdk.screen_height()-(self.inset+self.controlBarHt)]
-
-
- def getEyeDim( self, full ):
- if (not full):
- if (self.ca.m.MODE == Constants.MODE_PHOTO):
- return [self.vw, self.controlBarHt]
- else:
- return [self.recordButtWd, self.controlBarHt]
- else:
- if (self.ca.m.MODE == Constants.MODE_PHOTO):
- return [gtk.gdk.screen_width()-(self.inset*2), self.controlBarHt]
- else:
- return [self.recordButtWd, self.controlBarHt]
-
-
- def getInbLoc( self, full ):
- return [(self.centerBoxPos[0]+self.vw)-(self.inset+self.letterBoxVW), self.centerBoxPos[1]+self.inset]
-
-
- def setInbLocDim( self, win ):
- dim = self.getInbDim( self.FULLSCREEN )
- self.smartResize( win, dim[0], dim[1] )
- loc = self.getInbLoc(self.FULLSCREEN)
- self.smartMove( win, loc[0], loc[1] )
-
-
- def smartResize( self, win, w, h ):
- winSize = win.get_size()
- if ( (winSize[0] != w) or (winSize[1] != h) ):
- win.resize( w, h )
- return True
- else:
- return False
-
-
- def smartMove( self, win, x, y ):
- winLoc = win.get_position()
- if ( (winLoc[0] != x) or (winLoc[1] != y) ):
- win.move( x, y )
- return True
- else:
- return False
-
-
- def getDim( self, pos, full ):
- if (pos == "pip"):
- return self.getPipDim( full )
- elif(pos == "pgd"):
- return self.getPgdDim( full )
- elif(pos == "max"):
- return self.getMaxDim( full )
- elif(pos == "img"):
- return self.getImgDim( full )
- elif(pos == "eye"):
- return self.getEyeDim( full )
- elif(pos == "inb"):
- return self.getInbDim( full )
- elif(pos == "prg"):
- return self.getPrgDim( full )
- elif(pos == "inf"):
- return self.getInfDim( full )
-
-
- def getMaxDim( self, full ):
- return [self.maxw, self.maxh]
-
-
- def getInfDim( self, full ):
- return [75, 75]
-
-
- def getPipDim( self, full ):
- return [self.pipw, self.piph]
-
-
- def getPgdDim( self, full ):
- return [self.pgdw, self.pgdh]
-
-
- def getInbDim( self, full ):
- return [self.letterBoxVW, self.letterBoxVH]
-
-
- def getPrgDim( self, full ):
- if (not full):
- return [self.vw-self.recordButtWd, self.controlBarHt]
- else:
- return [gtk.gdk.screen_width()-(self.inset+self.inset+self.recordButtWd), self.controlBarHt]
-
-
- def getPrgLoc( self, full ):
- if (not full):
- return [self.centerBoxPos[0]+self.recordButtWd, self.centerBoxPos[1]+self.vh]
- else:
- return [self.inset+self.recordButtWd, gtk.gdk.screen_height()-(self.inset+self.controlBarHt)]
-
-
- def getLoc( self, pos, full ):
- if (pos == "pip"):
- return self.getPipLoc( full )
- elif(pos == "pgd"):
- return self.getPgdLoc( full )
- elif(pos == "max"):
- return self.getMaxLoc( full )
- elif(pos == "img"):
- return self.getImgLoc( full )
- elif(pos == "eye"):
- return self.getEyeLoc( full )
- elif(pos == "inb"):
- return self.getInbLoc( full )
- elif(pos == "prg"):
- return self.getPrgLoc( full )
- elif(pos == "inf"):
- return self.getInfLoc( full )
-
-
- def _shutterClickCb( self, arg ):
- self.doShutter()
-
-
- def doShutter( self ):
- if (self.UPDATE_TIMER_ID == 0):
- if (not self.ca.m.RECORDING):
-
- self.ca.m.updateXoFullStatus()
- if (self.ca.m.FULL):
- self.updateButtonSensitivities()
- return
-
- #there is no update timer running, so we need to find out if there is a timer needed
- timerTime = 0
- if (self.ca.m.MODE == Constants.MODE_PHOTO):
- timerTime = self.photoToolbar.getTimer()
- elif (self.ca.m.MODE == Constants.MODE_VIDEO):
- timerTime = self.videoToolbar.getTimer()
- elif (self.ca.m.MODE == Constants.MODE_AUDIO):
- timerTime = self.audioToolbar.getTimer()
-
- if (timerTime > 0):
- self.timerStartTime = time.time()
- self.UPDATE_TIMER_ID = gobject.timeout_add( 500, self._updateTimerCb )
- self.COUNTINGDOWN = True
- self.ca.m.setUpdating(True)
- else:
- self.clickShutter()
- else:
- #or, if there is no countdown, it might be because we are recording
- self.clickShutter()
-
- else:
- #we're timing down something, but interrupted by user click or the timer completing
- self.completeTimer()
- gobject.idle_add( self.clickShutter )
-
-
- def completeTimer( self ):
- self.COUNTINGDOWN = False
- self.ca.m.setUpdating(False)
- self.recordWindow.updateCountdown(-1)
- self.progressWindow.updateProgress( 1, "" )
- gobject.source_remove( self.UPDATE_TIMER_ID )
- self.UPDATE_TIMER_ID = 0
-
-
- def _updateTimerCb( self ):
- nowTime = time.time()
- passedTime = nowTime - self.timerStartTime
-
- timerTime = 0
- if (self.ca.m.MODE == Constants.MODE_PHOTO):
- timerTime = self.photoToolbar.getTimer()
- elif (self.ca.m.MODE == Constants.MODE_VIDEO):
- timerTime = self.videoToolbar.getTimer()
- elif (self.ca.m.MODE == Constants.MODE_AUDIO):
- timerTime = self.audioToolbar.getTimer()
-
- if (passedTime >= timerTime):
- self.COUNTINGDOWN = False
- self.ca.m.setUpdating(False)
- self.doShutter()
- return False
- else:
- secsRemaining = timerTime-passedTime
- timeRemainStr = istrSeconds(secsRemaining)
- self.progressWindow.updateProgress( passedTime/timerTime,
- Constants.istrRemaining + " " + timeRemainStr)
- self.recordWindow.updateCountdown( int(secsRemaining) )
- return True
-
-
- def clickShutter( self ):
- if self.ca.m.RECORDING:
- self.ca.m.doShutter()
- aplay.play(Constants.soundClick)
- else:
- aplay.play(Constants.soundClick, self.ca.m.doShutter)
-
-
- def updateVideoComponents( self ):
- logger.debug('updateVideoComponents: MODE=(%s,%s) FULLSCREEN=(%s,%s)' \
- ' LIVE=(%s,%s) RECD_INFO=(%s,%s) TRANSCODING=(%s,%s)' \
- ' MESHING=(%s,%s) windowStack=%s' \
- % (self.LAST_MODE, self.ca.m.MODE,
- self.LAST_FULLSCREEN, self.FULLSCREEN,
- self.LAST_LIVE, self.LIVEMODE,
- self.LAST_RECD_INFO, self.RECD_INFO_ON,
- self.LAST_TRANSCODING, self.TRANSCODING,
- self.LAST_MESHING, self.MESHING,
- len(self.windowStack)))
-
- if ( (self.LAST_MODE == self.ca.m.MODE)
- and (self.LAST_FULLSCREEN == self.FULLSCREEN)
- and (self.LAST_LIVE == self.LIVEMODE)
- and (self.LAST_RECD_INFO == self.RECD_INFO_ON)
- and (self.LAST_TRANSCODING == self.TRANSCODING)
- and (self.LAST_MESHING == self.MESHING)
- ):
- return
-
- #something's changing so start counting anew
- self.resetWidgetFadeTimer()
-
- pos = []
- if (self.RECD_INFO_ON and not self.TRANSCODING):
- if (self.ca.m.MODE == Constants.MODE_PHOTO):
- pos.append({"position":"pgd", "window":self.pipBgdWindow} )
- pos.append({"position":"pip", "window":self.liveVideoWindow} )
- pos.append({"position":"inb", "window":self.livePhotoWindow} )
- pos.append({"position":"inf", "window":self.infWindow} )
- elif (self.ca.m.MODE == Constants.MODE_VIDEO):
- pos.append({"position":"pgd", "window":self.pipBgdWindow} )
- pos.append({"position":"pip", "window":self.slowLiveVideoWindow} )
- pos.append({"position":"inb", "window":self.playOggWindow} )
- pos.append({"position":"inf", "window":self.infWindow} )
- elif (self.ca.m.MODE == Constants.MODE_AUDIO):
- pos.append({"position":"pgd", "window":self.pipBgdWindow} )
- pos.append({"position":"pip", "window":self.liveVideoWindow} )
- pos.append({"position":"inb", "window":self.livePhotoWindow} )
- pos.append({"position":"inf", "window":self.infWindow} )
- elif (not self.RECD_INFO_ON and not self.TRANSCODING):
- if (self.ca.m.MODE == Constants.MODE_PHOTO):
- if (self.LIVEMODE):
- pos.append({"position":"img", "window":self.liveVideoWindow} )
- pos.append({"position":"max", "window":self.maxWindow} )
- pos.append({"position":"eye", "window":self.recordWindow} )
- else:
- pos.append({"position":"img", "window":self.livePhotoWindow} )
- pos.append({"position":"pgd", "window":self.pipBgdWindow} )
- pos.append({"position":"pip", "window":self.liveVideoWindow} )
- if (not self.MESHING):
- pos.append({"position":"max", "window":self.maxWindow} )
- pos.append({"position":"inf", "window":self.infWindow} )
- else:
- pos.append({"position":"tmr", "window":self.progressWindow} )
- elif (self.ca.m.MODE == Constants.MODE_VIDEO):
- if (self.LIVEMODE):
- pos.append({"position":"img", "window":self.liveVideoWindow} )
- pos.append({"position":"max", "window":self.maxWindow} )
- pos.append({"position":"eye", "window":self.recordWindow} )
- pos.append({"position":"prg", "window":self.progressWindow} )
- else:
- pos.append({"position":"img", "window":self.playOggWindow} )
- pos.append({"position":"pgd", "window":self.pipBgdWindow} )
- pos.append({"position":"pip", "window":self.slowLiveVideoWindow} )
- if (not self.MESHING):
- pos.append({"position":"max", "window":self.maxWindow} )
- pos.append({"position":"scr", "window":self.scrubWindow} )
- pos.append({"position":"inf", "window":self.infWindow} )
- else:
- pos.append({"position":"tmr", "window":self.progressWindow} )
- elif (self.ca.m.MODE == Constants.MODE_AUDIO):
- if (self.LIVEMODE):
- pos.append({"position":"img", "window":self.liveVideoWindow} )
- pos.append({"position":"eye", "window":self.recordWindow} )
- pos.append({"position":"prg", "window":self.progressWindow} )
- else:
- pos.append({"position":"img", "window":self.livePhotoWindow} )
- pos.append({"position":"pgd", "window":self.pipBgdWindow} )
- pos.append({"position":"pip", "window":self.liveVideoWindow} )
- if (not self.MESHING):
- pos.append({"position":"scr", "window":self.scrubWindow} )
- pos.append({"position":"inf", "window":self.infWindow} )
- else:
- pos.append({"position":"tmr", "window":self.progressWindow} )
- elif (self.TRANSCODING):
- pos.append({"position":"tmr", "window":self.progressWindow} )
-
- for i in range (0, len(self.windowStack)):
- self.windowStack[i].hide_all()
-
- self.hideAllWindows()
- self.updatePos( pos )
-
- for i in range (0, len(self.windowStack)):
- self.windowStack[i].show_all()
-
- self.LAST_MODE = self.ca.m.MODE
- self.LAST_FULLSCREEN = self.FULLSCREEN
- self.LAST_LIVE = self.LIVEMODE
- self.LAST_RECD_INFO = self.RECD_INFO_ON
- self.LAST_TRANSCODING = self.TRANSCODING
- self.LAST_MESHING = self.MESHING
-
-
- def debugWindows( self ):
- for i in range (0, len(self.windowStack)):
- print self.windowStack[i], self.windowStack[i].get_size(), self.windowStack[i].get_position()
-
-
- def showWidgets( self ):
- pos = []
- if (self.ca.m.MODE == Constants.MODE_PHOTO):
- if (not self.LIVEMODE):
- pos.append({"position":"pgd", "window":self.pipBgdWindow} )
- pos.append({"position":"pip", "window":self.liveVideoWindow} )
- if (not self.MESHING):
- pos.append({"position":"max", "window":self.maxWindow} )
- pos.append({"position":"inf", "window":self.infWindow} )
- else:
- pos.append({"position":"max", "window":self.maxWindow} )
- pos.append({"position":"eye", "window":self.recordWindow} )
- elif (self.ca.m.MODE == Constants.MODE_VIDEO):
- if (not self.LIVEMODE):
- pos.append({"position":"pgd", "window":self.pipBgdWindow} )
- pos.append({"position":"pip", "window":self.slowLiveVideoWindow} )
- if (not self.MESHING):
- pos.append({"position":"max", "window":self.maxWindow} )
- pos.append({"position":"scr", "window":self.scrubWindow} )
- pos.append({"position":"inf", "window":self.infWindow} )
- else:
- pos.append({"position":"max", "window":self.maxWindow} )
- pos.append({"position":"eye", "window":self.recordWindow} )
- pos.append({"position":"prg", "window":self.progressWindow} )
- elif (self.ca.m.MODE == Constants.MODE_AUDIO):
- if (not self.LIVEMODE):
- pos.append({"position":"pgd", "window":self.pipBgdWindow} )
- pos.append({"position":"pip", "window":self.liveVideoWindow} )
- if (not self.MESHING):
- pos.append({"position":"scr", "window":self.scrubWindow} )
- pos.append({"position":"inf", "window":self.infWindow} )
- else:
- pos.append({"position":"eye", "window":self.recordWindow} )
- pos.append({"position":"prg", "window":self.progressWindow} )
-
- self.updatePos( pos )
-
-
- def updatePos( self, pos ):
- #now move those pieces where they need to be...
- for i in range (0, len(self.windowStack)):
- for j in range (0, len(pos)):
- if (self.windowStack[i] == pos[j]["window"]):
- if (pos[j]["position"] == "img"):
- self.setImgLocDim( pos[j]["window"] )
- elif (pos[j]["position"] == "max"):
- self.setMaxLocDim( pos[j]["window"] )
- elif (pos[j]["position"] == "pip"):
- self.setPipLocDim( pos[j]["window"] )
- elif (pos[j]["position"] == "pgd"):
- self.setPipBgdLocDim( pos[j]["window"] )
- elif (pos[j]["position"] == "eye"):
- self.setEyeLocDim( pos[j]["window"] )
- elif (pos[j]["position"] == "inb"):
- self.setInbLocDim( pos[j]["window"])
- elif (pos[j]["position"] == "prg"):
- self.setPrgLocDim( pos[j]["window"])
- elif (pos[j]["position"] == "tmr"):
- self.setTmrLocDim( pos[j]["window"])
- elif (pos[j]["position"] == "scr"):
- self.setScrLocDim( pos[j]["window"])
- elif (pos[j]["position"] == "inf"):
- self.setInfLocDim( pos[j]["window"])
-
-
- def removeThumb( self, recd ):
- kids = self.thumbTray.get_children()
- for i in range (0, len(kids)):
- if (kids[i].recd == recd):
- self.thumbTray.remove_item(kids[i])
- kids[i].cleanUp()
- kids[i].disconnect( kids[i].getButtClickedId() )
- kids[i].setButtClickedId(0)
-
-
- def addThumb( self, recd, forceScroll ):
- butt = RecdButton( self, recd )
- BUTT_CLICKED_ID = butt.connect( "clicked", self._thumbClicked, recd )
- butt.setButtClickedId(BUTT_CLICKED_ID)
- self.thumbTray.add_item( butt, len(self.thumbTray.get_children()) )
- butt.show()
- if (forceScroll):
- self.thumbTray.scroll_to_end()
-
-
- def removeThumbs( self ):
- kids = self.thumbTray.get_children()
- for i in range (0, len(kids)):
- self.thumbTray.remove_item(kids[i])
- kids[i].cleanUp()
- if (kids[i].getButtClickedId() != 0):
- kids[i].disconnect( kids[i].getButtClickedId() )
-
-
- def _thumbClicked( self, button, recd ):
- self.showThumbSelection( recd )
-
-
- def infoButtonClicked( self ):
- self.RECD_INFO_ON = not self.RECD_INFO_ON
-
- centerKid = self.centerBox.get_child()
- if (centerKid != None):
- self.centerBox.remove( centerKid )
-
- bottomKid = self.bottomCenter.get_child()
- if (bottomKid != None):
- self.bottomCenter.remove( bottomKid )
-
- if (not self.RECD_INFO_ON):
- if (self.ca.m.MODE == Constants.MODE_PHOTO):
- self.bottomCenter.add( self.namePanel )
- self.bottomCenter.show_all( )
- else:
- self.centerBox.add( self.infoBox )
- self.centerBox.show_all( )
- self.bottomCenter.add( self.namePanel )
- self.bottomCenter.show_all( )
-
- self.updateVideoComponents( )
-
-
- def showMeshRecd( self, recd ):
- record.Record.log.debug('showMeshRecd: heres the downloaded recd to display...')
-
- #if this thumbnail is being shown, add the option to copy it now
- kids = self.thumbTray.get_children()
- for i in range (0, len(kids)):
- if (kids[i].recd == recd):
- kids[i].addCopyMenuItem()
-
- if (recd == self.shownRecd):
- record.Record.log.debug('showMeshRecd: and since were still looking at same recd, here it is!')
- self.showThumbSelection( recd )
-
-
- def updateMeshProgress( self, progressMade, recd ):
- self.resetWidgetFadeTimer()
- if (self.shownRecd != recd):
- if (self.shownRecd == None):
- type = Constants.mediaTypes[recd.type][Constants.keyIstr]
- if (progressMade):
- msg = Constants.istrDownloadingFrom% {"1":type, "2":recd.meshDownloadingFromNick}
- self.progressWindow.updateProgress(recd.meshDownlodingPercent, msg)
-
- else:
- type = Constants.mediaTypes[recd.type][Constants.keyIstr]
- if (progressMade):
- msg = Constants.istrDownloadingFrom% {"1":type, "2":recd.meshDownloadingFromNick}
- self.progressWindow.updateProgress(recd.meshDownlodingPercent, msg)
-
- else:
- type = Constants.mediaTypes[recd.type][Constants.keyIstr]
- msg = Constants.istrCannotDownload % {"1":type}
- self.progressWindow.updateProgress(0, msg)
-
-
- def showThumbSelection( self, recd ):
- lastRecd = self.shownRecd
- self.shownRecd = recd
-
- #do we need to know the type, since we're showing based on the mode of the app?
- if (recd.type == Constants.TYPE_PHOTO):
- self.showPhoto( recd )
- elif (recd.type == Constants.TYPE_VIDEO):
- self.showVideo( recd )
- elif (recd.type == Constants.TYPE_AUDIO):
- self.showAudio( recd )
-
- if (self.shownRecd != lastRecd):
- self.photoXoPanel.updateXoColors(self.shownRecd.colorStroke.hex, self.shownRecd.colorFill.hex)
-
- bottomKid = self.bottomCenter.get_child()
- if (bottomKid != None):
- self.bottomCenter.remove( bottomKid )
-
- if (recd.type == Constants.TYPE_PHOTO):
- self.bottomCenter.add( self.namePanel )
- elif (recd.type == Constants.TYPE_VIDEO or recd.type == Constants.TYPE_AUDIO):
- if (self.RECD_INFO_ON):
- self.bottomCenter.add( self.namePanel )
- self.bottomCenter.show_all()
-
- self.resetWidgetFadeTimer()
-
-
- def showAudio( self, recd ):
- self.LIVEMODE = False
-
- #if (recd != self.shownRecd):
- pixbuf = recd.getAudioImagePixbuf()
- img = hippo.cairo_surface_from_gdk_pixbuf(pixbuf)
- self.livePhotoCanvas.setImage( img )
- #self.shownRecd = recd
- self.showRecdMeta(recd)
-
- downloading = self.ca.requestMeshDownload(recd)
- self.MESHING = downloading
- record.Record.log.debug("showAudio: downloading->" + str(downloading))
- if (not downloading):
- self.progressWindow.updateProgress(0, "")
- mediaFilepath = recd.getMediaFilepath( )
- record.Record.log.debug("showAudio: mediaFilepath->" + str(mediaFilepath))
- if (mediaFilepath != None):
- videoUrl = "file://" + str( mediaFilepath )
- self.ca.gplay.setLocation(videoUrl)
- self.scrubWindow.doPlay()
-
- self.updateVideoComponents()
-
-
- def showVideo( self, recd ):
- logger.debug('showVideo')
-
- downloading = self.ca.requestMeshDownload(recd)
-
- if (not downloading):
- self.progressWindow.updateProgress(0, "")
-
- self.MESHING = downloading
- self.LIVEMODE = False
- #self.shownRecd = recd
- self.updateVideoComponents()
- gobject.idle_add( self.showVideo2, recd, downloading )
-
-
- def showVideo2( self, recd, downloading ):
- self.showRecdMeta(recd)
-
- ableToShowVideo = False
- if (not downloading):
- mediaFilepath = recd.getMediaFilepath()
- if (mediaFilepath != None):
- logger.debug('showVideo2 file=%s' % mediaFilepath)
- self.ca.glive.stop()
- self.ca.glivex.play()
- videoUrl = "file://" + str( mediaFilepath )
- self.ca.gplay.setLocation(videoUrl)
- self.scrubWindow.doPlay()
- ableToShowVideo = True
-
- if (not ableToShowVideo):
- # FIXME is this correct?
- self.ca.glive.stop()
- self.ca.glivex.play()
- thumbFilepath = recd.getThumbFilepath( )
- logger.debug('showVideo3 file=%s' % thumbFilepath)
- thumbUrl = "file://" + str( thumbFilepath )
- self.ca.gplay.setLocation(thumbUrl)
-
-
- def deleteThumbSelection( self, recd ):
- self.ca.m.deleteRecorded( recd )
- self.ca.glive.play()
- self.removeThumb( recd )
- self.removeIfSelectedRecorded( recd )
-
-
- def removeIfSelectedRecorded( self, recd ):
- if (recd == self.shownRecd):
- if (recd.type == Constants.TYPE_PHOTO):
- self.livePhotoCanvas.setImage( None )
- elif (recd.type == Constants.TYPE_VIDEO):
- self.ca.gplay.stop()
- self.startLiveVideo()
- elif (recd.type == Constants.TYPE_AUDIO):
- self.livePhotoCanvas.setImage( None )
- self.startLiveAudio()
-
- self.RECD_INFO_ON = False
- self.LIVEMODE = True
- self.updateVideoComponents()
-
- self.showLiveVideoTags()
-
-
- def startLiveAudio( self ):
- self.ca.m.setUpdating(True)
- self.ca.gplay.stop()
-
- self.showLiveVideoTags()
- self.LIVEMODE = True
- self.updateVideoComponents()
- self.ca.m.setUpdating(False)
-
-
- def showPostProcessGfx( self, show ):
- #not self.FULLSCREEN
- centerKid = self.centerBox.get_child()
- if (centerKid != None):
- self.centerBox.remove( centerKid )
-
- if ( show ):
- self.centerBox.add( self.backgdCanvasBox )
- self.centerBox.show_all()
- else:
- self.backgdCanvas.setImage( None )
-
-
- def setPostProcessPixBuf( self, pixbuf ):
- if (pixbuf.get_width()>self.__class__.dim_THUMB_WIDTH):
- pixbuf = pixbuf.scale_simple(self.__class__.dim_THUMB_WIDTH, self.__class__.dim_THUMB_HEIGHT, gtk.gdk.INTERP_NEAREST)
-
- pixbuf = utils.grayScalePixBuf(pixbuf, True)
- img = hippo.cairo_surface_from_gdk_pixbuf(pixbuf)
- self.backgdCanvas.setImage(img)
-
-
- def showRecdMeta( self, recd ):
- self.photographerNameLabel.set_label( recd.recorderName )
- self.nameTextfield.set_text( recd.title )
- self.nameTextfield.set_sensitive( True )
- self.tagsBuffer.set_text( recd.tags )
- self.dateDateLabel.set_label( utils.getDateString(recd.time) )
-
- self.photographerPanel.show()
- self.namePanel.show()
- self.datePanel.show()
- self.tagsPanel.show()
- self.tagsField.set_sensitive(True)
-
-
- def setWaitCursor( self, win ):
- win.set_cursor( gtk.gdk.Cursor(gtk.gdk.WATCH) )
-
-
- def setDefaultCursor( self, win ):
- win.set_cursor( None )
-
-
-class PhotoCanvas(P5):
- def __init__(self):
- P5.__init__(self)
- self.img = None
- self.drawImg = None
- self.SCALING_IMG_ID = 0
- self.cacheWid = -1
- self.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
- self.modify_bg( gtk.STATE_INSENSITIVE, Constants.colorBlack.gColor )
-
-
- def draw(self, ctx, w, h):
- self.background( ctx, Constants.colorBlack, w, h )
-
- if (self.img != None):
-
- if (w == self.img.get_width()):
- self.cacheWid == w
- self.drawImg = self.img
-
- #only scale images when you need to, otherwise you're wasting cycles, fool!
- if (self.cacheWid != w):
- if (self.SCALING_IMG_ID == 0):
- self.drawImg = None
- self.SCALING_IMG_ID = gobject.idle_add( self.resizeImage, w, h )
-
- if (self.drawImg != None):
- #center the image based on the image size, and w & h
- ctx.set_source_surface(self.drawImg, (w/2)-(self.drawImg.get_width()/2), (h/2)-(self.drawImg.get_height()/2))
- ctx.paint()
-
- self.cacheWid = w
-
-
- def setImage(self, img):
- self.cacheWid = -1
- self.img = img
- self.drawImg = None
- self.queue_draw()
-
-
- def resizeImage(self, w, h):
- self.SCALING_IMG_ID = 0
- if (self.img == None):
- return
-
- #use image size in case 640 no more
- scaleImg = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
- sCtx = cairo.Context(scaleImg)
- sScl = (w+0.0)/(self.img.get_width()+0.0)
- sCtx.scale( sScl, sScl )
- sCtx.set_source_surface( self.img, 0, 0 )
- sCtx.paint()
- self.drawImg = scaleImg
- self.cacheWid = w
- self.queue_draw()
-
-
-class xoPanel(P5):
- def __init__(self):
- P5.__init__(self)
- self.xoGuy = None
- self.lastStroke = None
- self.lastFill = None
-
-
- def updateXoColors( self, strokeHex, fillHex ):
- if (self.lastStroke != None):
- if ((self.lastStroke == strokeHex) and (self.lastFill == fillHex)):
- return
-
- lastStroke = strokeHex
- lastFill = fillHex
- self.xoGuy = utils.loadSvg(Constants.xoGuySvgData, strokeHex, fillHex)
- self.queue_draw()
-
-
- def draw(self, ctx, w, h):
- #todo: 2x buffer
- self.background( ctx, Constants.colorButton, w, h )
-
- if (self.xoGuy != None):
- #todo: scale mr xo to fit in his box
- ctx.scale( .6, .6 )
- self.xoGuy.render_cairo( ctx )
-
-
-class ScrubberWindow(gtk.Window):
- def __init__(self, ui):
- gtk.Window.__init__(self)
- self.ui = ui
- self.UPDATE_INTERVAL = 500
- self.UPDATE_SCALE_ID = 0
- self.CHANGED_ID = 0
- self.was_playing = False
- self.p_position = gst.CLOCK_TIME_NONE
- self.p_duration = gst.CLOCK_TIME_NONE
-
- self.hbox = gtk.HBox()
- self.hbox.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
- self.hbox.modify_bg( gtk.STATE_INSENSITIVE, Constants.colorBlack.gColor )
- self.add( self.hbox )
-
- self.button = gtk.Button()
- buttBox = gtk.EventBox()
- buttBox.add(self.button)
- buttBox.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
- self.button.set_image( Constants.recPlayImg )
- self.button.set_property('can-default', True)
- self.button.set_relief(gtk.RELIEF_NONE)
- self.button.set_size_request( self.ui.recordButtWd, self.ui.recordButtWd )
- buttBox.set_size_request( self.ui.recordButtWd, self.ui.recordButtWd )
- #self.button.set_border_width( UI.dim_INSET/2 )
- self.button.show()
-
- buttBox.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
- self.button.modify_bg( gtk.STATE_ACTIVE, Constants.colorBlack.gColor )
-
- self.button.connect('clicked', self._buttonClickedCb)
- self.hbox.pack_start(buttBox, expand=False)
-
- self.adjustment = gtk.Adjustment(0.0, 0.00, 100.0, 0.1, 1.0, 1.0)
- self.hscale = gtk.HScale(self.adjustment)
- self.hscale.set_draw_value(False)
- self.hscale.set_update_policy(gtk.UPDATE_CONTINUOUS)
- hscaleBox = gtk.EventBox()
- hscaleBox.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
- hscaleBox.add( self.hscale )
- self.hscale.connect('button-press-event', self._scaleButtonPressCb)
- self.hscale.connect('button-release-event', self._scaleButtonReleaseCb)
- self.hbox.pack_start(hscaleBox, expand=True)
-
-
- def removeCallbacks( self ):
- if (self.UPDATE_SCALE_ID != 0):
- gobject.source_remove(self.UPDATE_SCALE_ID)
- self.UPDATE_SCALE_ID = 0
- if (self.CHANGED_ID != 0):
- gobject.source_remove(self.CHANGED_ID)
- self.CHANGED_ID = 0
-
-
- def reset(self):
- self.adjustment.set_value(0)
-
-
- def _buttonClickedCb(self, widget):
- self.play_toggled()
-
-
- def set_button_play(self):
- self.button.set_image(Constants.recPlayImg)
-
-
- def set_button_pause(self):
- self.button.set_image(Constants.recPauseImg)
-
-
- def play_toggled(self):
- self.p_position, self.p_duration = self.ui.ca.gplay.queryPosition()
- if (self.p_position == self.p_duration):
- self.ui.ca.gplay.seek(0)
- self.ui.ca.gplay.pause()
-
- if self.ui.ca.gplay.is_playing():
- self.ui.ca.gplay.pause()
- self.set_button_play()
- else:
- #if self.ui.ca.gplay.error:
- # #todo: check if we have "error", and also to disable everything
- # self.button.set_disabled()
- #else:
- self.doPlay()
-
-
- def doPlay(self):
- self.ui.ca.gplay.play()
- if self.UPDATE_SCALE_ID == 0:
- self.UPDATE_SCALE_ID = gobject.timeout_add(self.UPDATE_INTERVAL, self._updateScaleCb)
- self.set_button_pause()
-
-
- def _scaleButtonPressCb(self, widget, event):
- #self.button.set_sensitive(False)
- self.was_playing = self.ui.ca.gplay.is_playing()
- if self.was_playing:
- self.ui.ca.gplay.pause()
-
- # don't timeout-update position during seek
- if self.UPDATE_SCALE_ID != 0:
- gobject.source_remove(self.UPDATE_SCALE_ID)
- self.UPDATE_SCALE_ID = 0
-
- # make sure we get changed notifies
- if self.CHANGED_ID == 0:
- self.CHANGED_ID = self.hscale.connect('value-changed', self._scaleValueChangedCb)
-
-
- def _scaleButtonReleaseCb(self, widget, event):
- # see seek.cstop_seek
- widget.disconnect(self.CHANGED_ID)
- self.CHANGED_ID = 0
-
- #self.button.set_sensitive(True)
- if self.was_playing:
- self.ui.ca.gplay.play()
-
- if self.UPDATE_SCALE_ID != 0:
- pass
- #print('Had a previous update timeout id')
- else:
- self.UPDATE_SCALE_ID = gobject.timeout_add(self.UPDATE_INTERVAL, self._updateScaleCb)
-
-
- def _scaleValueChangedCb(self, scale):
- real = long(scale.get_value() * self.p_duration / 100) # in ns
- self.ui.ca.gplay.seek(real)
- # allow for a preroll
- self.ui.ca.gplay.get_state(timeout=50*gst.MSECOND) # 50 ms
-
-
- def _updateScaleCb(self):
- self.p_position, self.p_duration = self.ui.ca.gplay.queryPosition()
- if self.p_position != gst.CLOCK_TIME_NONE:
- value = self.p_position * 100.0 / self.p_duration
- if (value > 99):
- value = 99
- elif (value < 0):
- value = 0
-
- self.adjustment.set_value(value)
-
- if self.ui.ca.gplay.is_playing() and (self.p_position == self.p_duration):
- self.ui.ca.gplay.pause()
- self.set_button_play()
-
- return True
-
-
-class MaxButton(P5Button):
- def __init__(self, ui):
- P5Button.__init__(self)
- self.ui = ui
-
- self.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
- self.modify_bg( gtk.STATE_INSENSITIVE, Constants.colorBlack.gColor )
-
- xs = []
- ys = []
- xs.append(0)
- ys.append(0)
- xs.append(self.ui.maxw)
- ys.append(0)
- xs.append(self.ui.maxw)
- ys.append(self.ui.maxh)
- xs.append(0)
- ys.append(self.ui.maxh)
- poly = Polygon( xs, ys )
- butt = Button( poly, 0, 0)
- butt.addActionListener( self )
- self.maxS = "max"
- butt.setActionCommand( self.maxS )
- self._butts.append( butt )
-
-
- def draw(self, ctx, w, h):
- if (self.ui.FULLSCREEN):
- Constants.maxEnlargeSvg.render_cairo( ctx )
- else:
- Constants.maxReduceSvg.render_cairo( ctx )
-
-
- def fireButton(self, actionCommand):
- if (actionCommand == self.maxS):
- self.ui.doFullscreen()
-
-
-class InfButton(P5Button):
- #todo: just a gtk.Image here, no?
- def __init__(self, ui):
- P5Button.__init__(self)
- self.ui = ui
-
- self.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
- self.modify_bg( gtk.STATE_INSENSITIVE, Constants.colorBlack.gColor )
-
- self.set_size_request( 75, 75 )
-
- xs = []
- ys = []
- xs.append(0)
- ys.append(0)
- xs.append(75)
- ys.append(0)
- xs.append(75)
- ys.append(75)
- xs.append(0)
- ys.append(75)
- poly = Polygon( xs, ys )
- butt = Button( poly, 0, 0)
- butt.addActionListener( self )
- self.infS = "inf"
- butt.setActionCommand( self.infS )
- self._butts.append( butt )
-
-
- def draw(self, ctx, w, h):
- self.background( ctx, Constants.colorBlack, w, h )
- Constants.infoOnSvg.render_cairo( ctx )
-
-
- def fireButton(self, actionCommand):
- if (actionCommand == self.infS):
- self.ui.infoButtonClicked()
-
-
-class RecordButton(gtk.Button):
- def __init__(self):
- gtk.Button.__init__(self)
- self.sens = True
- self.img = None
- #todo: check on record state, compare button imgs
-
-
- def set_sensitive(self, sen, full):
- if (sen == self.sens):
- return
- self.sens = sen
-
- if (self.sens):
- self.set_image( Constants.recImg )
- else:
- if (full):
- self.set_image( Constants.fullInsensitiveImg )
- else:
- self.set_image( Constants.recInsensitiveImg )
-
- super(RecordButton, self).set_sensitive(self.sens)
-
-
- def doRecordButton(self):
- if (not self.sens):
- return
- self.set_image( Constants.recRedImg )
-
-
- def doNormalButton(self):
- if (not self.sens):
- return
- self.set_image( Constants.recImg )
-
-
-class RecordWindow(gtk.Window):
- def __init__(self, ui):
- gtk.Window.__init__(self)
- self.ui = ui
- self.num = -1
-
- self.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
-
- self.shutterButton = RecordButton()
- self.shutterButton.set_size_request(self.ui.recordButtWd, self.ui.recordButtWd)
- self.shutterButton.set_relief(gtk.RELIEF_NONE)
- self.shutterButton.set_image( Constants.recImg )
- self.shutterButton.connect("clicked", self.ui._shutterClickCb)
-
- shutterBox = gtk.EventBox()
- shutterBox.add( self.shutterButton )
- shutterBox.set_size_request( self.ui.controlBarHt, self.ui.controlBarHt )
-
- shutterBox.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
- self.shutterButton.modify_bg( gtk.STATE_ACTIVE, Constants.colorBlack.gColor )
-
- hbox = gtk.HBox()
- self.add( hbox )
- leftPanel = gtk.VBox()
- self.leftEvent = gtk.EventBox()
- self.leftEvent.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
- self.leftEvent.add( leftPanel )
- self.leftEvent.set_size_request(self.ui.vw/2-self.ui.controlBarHt, -1)
- hbox.pack_start( self.leftEvent, expand=True )
-
- hbox.pack_start( shutterBox, expand=False )
-
- rightPanel = gtk.VBox()
- self.rightEvent = gtk.EventBox()
- self.rightEvent.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
- self.rightEvent.add( rightPanel )
- hbox.pack_start( self.rightEvent, expand=True )
-
- self.rightPanelLabel = gtk.Label()
- rightPanel.pack_start( self.rightPanelLabel )
-
-
- def updateCountdown(self, num):
- if(num>0):
- if (num != self.num):
- ok = self.getCairoCountdown(num)
- self.shutterButton.set_image(ok)
- self.num = num
- else:
- self.num = -1
-
-
- def getCairoCountdown(self, num):
- return Constants.countdownImgs[int(num)]
-
-
- def minimize( self ):
- self.leftEvent.set_size_request(-1, -1)
- self.rightEvent.set_size_request(-1, -1)
-
-
- def maximize( self ):
- w = self.ui.vw/2-self.ui.controlBarHt
- self.rightEvent.set_size_request(w, -1)
- self.leftEvent.set_size_request(w, -1)
-
-
- def displayDiscFullText( self, full ):
- if (not full or self.ui.ca.m.MODE != Constants.MODE_PHOTO):
- self.rightPanelLabel.set_text("")
- self.minimize()
- else:
- fullMessage = Constants.istrYourDiskIsFull % {"1":Constants.istrJournal}
- self.rightPanelLabel.set_text("<b><span foreground='gray'>" + fullMessage + "</span></b>")
- self.rightPanelLabel.set_use_markup( True )
- self.rightPanelLabel.set_alignment(1, 1)
- self.maximize()
-
-
-class ProgressWindow(gtk.Window):
- def __init__(self, ui):
- gtk.Window.__init__(self)
- self.ui = ui
- self.update = ""
-
- self.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
- self.modify_bg( gtk.STATE_INSENSITIVE, Constants.colorBlack.gColor )
-
- eb = gtk.EventBox()
- eb.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
- eb.modify_bg( gtk.STATE_INSENSITIVE, Constants.colorBlack.gColor )
- self.add( eb )
-
- vb = gtk.VBox()
- vb.set_border_width(5) #todo: use variable
- eb.add(vb)
-
- self.progBar = gtk.ProgressBar()
- self.progBar.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
- self.progBar.modify_bg( gtk.STATE_ACTIVE, Constants.colorBlack.gColor )
- self.progBar.modify_bg( gtk.STATE_PRELIGHT, Constants.colorBlack.gColor )
- self.progBar.modify_bg( gtk.STATE_SELECTED, Constants.colorBlack.gColor )
- self.progBar.modify_bg( gtk.STATE_INSENSITIVE, Constants.colorBlack.gColor )
- vb.add( self.progBar )
-
- hbox = gtk.HBox()
- vb.add( hbox )
- self.infoLabel = gtk.Label()
- self.infoLabel.set_alignment( 1, .5 )
- self.infoLabel.set_text( "<b><span foreground='black'>SPACE</span></b>")
- self.infoLabel.set_use_markup( True )
- hbox.pack_start(self.infoLabel)
-
-
- def updateProgress( self, amt, update, color='white' ):
- logging.debug('updateProgress %s' % amt)
-
- self.progBar.set_fraction( amt )
- if (update != None and update != self.update):
- self.update = update
- self.infoLabel.set_text( "<b><span foreground='" + color + "'>"+self.update+"</span></b>")
- self.infoLabel.set_use_markup( True )
-
- if (self.update==""):
- self.infoLabel.set_text( "<b><span foreground='black'>SPACE</span></b>")
- self.infoLabel.set_use_markup( True )
-
- if (amt >= 1):
- self.progBar.set_fraction( 0 )
-
-
-class PhotoToolbar(gtk.Toolbar):
- def __init__(self):
- gtk.Toolbar.__init__(self)
-
- img = ToolButton('media-photo')
- img.connect('clicked', self._shutterClickCb)
- img.get_icon_widget().set_property( 'fill-color', Instance.colorFill.hex )
- img.get_icon_widget().set_property( 'stroke-color', Instance.colorStroke.hex )
- self.insert(img, -1)
- img.set_sensitive(False)
- img.show()
-
- separator = gtk.SeparatorToolItem()
- separator.set_draw(False)
- separator.set_expand(True)
- self.insert(separator, -1)
- separator.show()
-
- timerCbb = gtk.combo_box_new_text()
- self.timerCb = ToolComboBox(combo=timerCbb, label_text=Constants.istrTimer)
- for i in range (0, len(Constants.TIMERS)):
- if (i == 0):
- self.timerCb.combo.append_text( Constants.istrNow )
- else:
- self.timerCb.combo.append_text(istrSeconds(Constants.TIMERS[i]))
- self.timerCb.combo.set_active(0)
- self.insert( self.timerCb, -1 )
-
-
- def _shutterClickCb(self, button):
- pass
- #self.ui.doShutter()
-
-
- def getTimer(self):
- return Constants.TIMERS[self.timerCb.combo.get_active()]
-
-
-class VideoToolbar(gtk.Toolbar):
- def __init__(self):
- gtk.Toolbar.__init__(self)
-
- img = ToolButton('media-video')
- img.connect('clicked', self._shutterClickCb)
- img.get_icon_widget().set_property( 'fill-color', Instance.colorFill.hex )
- img.get_icon_widget().set_property( 'stroke-color', Instance.colorStroke.hex )
- self.insert(img, -1)
- img.set_sensitive(False)
- img.show()
-
- separator = gtk.SeparatorToolItem()
- separator.set_draw(False)
- separator.set_expand(True)
- self.insert(separator, -1)
- separator.show()
-
- combo = gtk.combo_box_new_text()
- self.quality = ToolComboBox(combo=combo,
- label_text=Constants.istrQuality+':')
- self.quality.combo.append_text(Constants.istrLowQuality)
- 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(Constants.istrHighQuality)
- self.quality.combo.set_active(0)
- self.insert(self.quality, -1 )
-
- timerCbb = gtk.combo_box_new_text()
- self.timerCb = ToolComboBox(combo=timerCbb, label_text=Constants.istrTimer)
- for i in range (0, len(Constants.TIMERS)):
- if (i == 0):
- self.timerCb.combo.append_text( Constants.istrNow )
- else:
- self.timerCb.combo.append_text(istrSeconds(Constants.TIMERS[i]))
- self.timerCb.combo.set_active(0)
- self.insert( self.timerCb, -1 )
-
- separator2 = gtk.SeparatorToolItem()
- separator2.set_draw(False)
- separator2.set_expand(False)
- separator2.set_size_request(UI.dim_INSET, -1)
- self.insert( separator2, -1 )
-
- durCbb = gtk.combo_box_new_text()
- self.durCb = ToolComboBox(combo=durCbb, label_text=Constants.istrDuration)
- for i in range (0, len(Constants.DURATIONS)):
- self.durCb.combo.append_text(istrMinutes(Constants.DURATIONS[i]))
- self.durCb.combo.set_active(0)
- self.insert(self.durCb, -1 )
-
-
- def _shutterClickCb(self, button):
- pass
- #self.ui.doShutter()
-
-
- def getTimer(self):
- return Constants.TIMERS[self.timerCb.combo.get_active()]
-
-
- def getDuration(self):
- return 60 * Constants.DURATIONS[self.durCb.combo.get_active()]
-
-
- def getQuality(self):
- return self.quality.combo.get_active()
-
-class AudioToolbar(gtk.Toolbar):
- def __init__(self):
- gtk.Toolbar.__init__(self)
-
- img = ToolButton('media-audio')
- img.connect('clicked', self._shutterClickCb)
- img.get_icon_widget().set_property( 'fill-color', Instance.colorFill.hex )
- img.get_icon_widget().set_property( 'stroke-color', Instance.colorStroke.hex )
- self.insert(img, -1)
- img.set_sensitive(False)
- img.show()
-
- separator = gtk.SeparatorToolItem()
- separator.set_draw(False)
- separator.set_expand(True)
- self.insert(separator, -1)
- separator.show()
-
- timerCbb = gtk.combo_box_new_text()
- self.timerCb = ToolComboBox(combo=timerCbb, label_text=Constants.istrTimer)
- for i in range (0, len(Constants.TIMERS)):
- if (i == 0):
- self.timerCb.combo.append_text( Constants.istrNow )
- else:
- self.timerCb.combo.append_text(istrSeconds(Constants.TIMERS[i]))
- self.timerCb.combo.set_active(0)
- self.insert( self.timerCb, -1 )
-
- separator2 = gtk.SeparatorToolItem()
- separator2.set_draw(False)
- separator2.set_expand(False)
- separator2.set_size_request(UI.dim_INSET, -1)
- self.insert( separator2, -1 )
-
- durCbb = gtk.combo_box_new_text()
- self.durCb = ToolComboBox(combo=durCbb, label_text=Constants.istrDuration)
- for i in range (0, len(Constants.DURATIONS)):
- self.durCb.combo.append_text(istrMinutes(Constants.DURATIONS[i]))
- self.durCb.combo.set_active(0)
- self.insert(self.durCb, -1 )
-
-
- def _shutterClickCb(self, button):
- pass
- #self.ui.doShutter()
-
-
- def getTimer(self):
- return Constants.TIMERS[self.timerCb.combo.get_active()]
-
-
- def getDuration(self):
- return 60 * Constants.DURATIONS[self.durCb.combo.get_active()]
diff --git a/utils.py b/utils.py
index 9848d86..28adc9c 100644
--- a/utils.py
+++ b/utils.py
@@ -2,15 +2,11 @@ import base64
import rsvg
import re
import os
-import statvfs
-import cairo
-import gc
import gtk
import time
from time import strftime
-import hippo
-from sugar import util
+import constants
def getStringFromPixbuf(pixbuf):
data = [""]
@@ -31,9 +27,9 @@ def getPixbufFromString(str):
return pbl.get_pixbuf()
-def loadSvg( data, stroke, fill ):
- if ((stroke == None) or (fill == None)):
- return rsvg.Handle( data=data )
+def load_colored_svg(filename, stroke, fill):
+ path = os.path.join(constants.GFX_PATH, filename)
+ data = open(path, 'r').read()
entity = '<!ENTITY fill_color "%s">' % fill
data = re.sub('<!ENTITY fill_color .*>', entity, data)
@@ -41,8 +37,7 @@ def loadSvg( data, stroke, fill ):
entity = '<!ENTITY stroke_color "%s">' % stroke
data = re.sub('<!ENTITY stroke_color .*>', entity, data)
- return rsvg.Handle( data=data )
-
+ return rsvg.Handle(data=data).get_pixbuf()
def getUniqueFilepath( path, i ):
pathOb = os.path.abspath( path )
@@ -53,56 +48,9 @@ def getUniqueFilepath( path, i ):
else:
return os.path.abspath( newPath )
-
-def generateThumbnail( pixbuf, scale, thumbw, thumbh ):
- #need to generate thumbnail version here
- thumbImg = cairo.ImageSurface(cairo.FORMAT_ARGB32, thumbw, thumbh)
- tctx = cairo.Context(thumbImg)
- img = hippo.cairo_surface_from_gdk_pixbuf(pixbuf)
- tctx.scale(scale, scale)
- tctx.set_source_surface(img, 0, 0)
- tctx.paint()
- gc.collect()
- return thumbImg
-
-
-def scaleSvgToDim( handle, dim ):
- #todo...
- scale = 1.0
-
- svgDim = handle.get_dimension_data()
- if (svgDim[0] > dim[0]):
- pass
-
- return scale
-
+def generate_thumbnail(pixbuf):
+ return pixbuf.scale_simple(108, 81, gtk.gdk.INTERP_BILINEAR)
def getDateString( when ):
return strftime( "%c", time.localtime(when) )
-
-def grayScalePixBuf2( pb, copy ):
- arr = pb.get_pixels_array()
- if (copy):
- arr = arr.copy()
- for row in arr:
- for pxl in row:
- y = 0.3*pxl[0][0]+0.59*pxl[1][0]+0.11*pxl[2][0]
- pxl[0][0] = y
- pxl[1][0] = y
- pxl[2][0] = y
- return gtk.gdk.pixbuf_new_from_array(arr, pb.get_colorspace(), pb.get_bits_per_sample())
-
-
-def grayScalePixBuf( pb, copy ):
- pb2 = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, pb.get_width(), pb.get_height())
- pb.saturate_and_pixelate(pb2, 0, 0)
- return pb2
-
-
-def getFreespaceKb( ):
- stat = os.statvfs("/home")
- freebytes = stat[statvfs.F_BSIZE] * stat[statvfs.F_BAVAIL]
- freekb = freebytes / 1024
- return freekb
-