diff options
Diffstat (limited to 'record.py')
-rw-r--r-- | record.py | 934 |
1 files changed, 934 insertions, 0 deletions
diff --git a/record.py b/record.py new file mode 100644 index 0000000..e6c7bb3 --- /dev/null +++ b/record.py @@ -0,0 +1,934 @@ +#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 os +import logging +import shutil +from gettext import gettext as _ +from gettext import ngettext + +import gtk +from gtk import gdk +import cairo +import pango +import pangocairo +import pygst +pygst.require('0.10') +import gst + +from sugar.activity import activity +from sugar.graphics.toolcombobox import ToolComboBox +from sugar.graphics.toolbarbox import ToolbarBox +from sugar.graphics.toolbarbox import ToolbarButton +from sugar.graphics.radiotoolbutton import RadioToolButton +from sugar.activity.widgets import StopButton +from sugar.activity.widgets import ActivityToolbarButton + +from model import Model +from button import RecdButton +import constants +from instance import Instance +import utils +from tray import HTray +from mediaview import MediaView +import hw +from iconcombobox import IconComboBox + +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) +if logging.getLogger().level <= logging.DEBUG: + gst.debug_set_default_threshold(gst.LEVEL_WARNING) +else: + gst.debug_set_default_threshold(gst.LEVEL_ERROR) + +class Record(activity.Activity): + def __init__(self, handle): + super(Record, self).__init__(handle) + self.props.enable_fullscreen_mode = False + Instance(self) + + #the main classes + self.model = Model(self) + self.ui_init() + + #CSCL + self.connect("shared", self._shared_cb) + if self.get_shared_activity(): + #have you joined or shared this activity yourself? + if self.get_shared(): + self._joined_cb(self) + else: + self.connect("joined", self._joined_cb) + + # 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 + if self.model.get_has_camera(): + self.model.change_mode(constants.MODE_PHOTO) + else: + self.model.change_mode(constants.MODE_AUDIO) + + def read_file(self, path): + self.model.read_file(path) + + def write_file(self, path): + self.model.write_file(path) + + 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 _joined_cb(self, activity): + self.model.collab.joined() + + def ui_init(self): + self._fullscreen = False + self._showing_info = False + + # FIXME: if _thumb_tray becomes some kind of button group, we wouldn't + # have to track which recd is active + self._active_recd = None + + self.connect_after('key-press-event', self._key_pressed) + + self._active_toolbar_idx = 0 + + self._toolbar_box = ToolbarBox() + activity_button = ActivityToolbarButton(self) + self._toolbar_box.toolbar.insert(activity_button, 0) + self.set_toolbar_box(self._toolbar_box) + self._toolbar = self.get_toolbar_box().toolbar + + tool_group = None + if self.model.get_has_camera(): + self._photo_button = RadioToolButton() + self._photo_button.props.group = tool_group + tool_group = self._photo_button + self._photo_button.props.icon_name = 'camera-external' + self._photo_button.props.label = _('Photo') + self._photo_button.mode = constants.MODE_PHOTO + self._photo_button.connect('clicked', self._mode_button_clicked) + self._toolbar.insert(self._photo_button, -1) + + self._video_button = RadioToolButton() + self._video_button.props.group = tool_group + self._video_button.props.icon_name = 'media-video' + self._video_button.props.label = _('Video') + self._video_button.mode = constants.MODE_VIDEO + self._video_button.connect('clicked', self._mode_button_clicked) + self._toolbar.insert(self._video_button, -1) + else: + self._photo_button = None + self._video_button = None + + self._audio_button = RadioToolButton() + self._audio_button.props.group = tool_group + self._audio_button.props.icon_name = 'media-audio' + self._audio_button.props.label = _('Audio') + self._audio_button.mode = constants.MODE_AUDIO + self._audio_button.connect('clicked', self._mode_button_clicked) + self._toolbar.insert(self._audio_button, -1) + + self._toolbar.insert(gtk.SeparatorToolItem(), -1) + + self._toolbar_controls = RecordControl(self._toolbar) + + separator = gtk.SeparatorToolItem() + separator.props.draw = False + separator.set_expand(True) + self._toolbar.insert(separator, -1) + self._toolbar.insert(StopButton(self), -1) + self.get_toolbar_box().show_all() + + main_box = gtk.VBox() + self.set_canvas(main_box) + main_box.get_parent().modify_bg(gtk.STATE_NORMAL, COLOR_BLACK) + main_box.show() + + 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() + + 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) + + self._play_button = PlayButton() + self._play_button.connect('clicked', self._play_pause_clicked) + self._controls_hbox.pack_start(self._play_button, expand=False) + + self._playback_scale = PlaybackScale(self.model) + self._controls_hbox.pack_start(self._playback_scale, expand=True, fill=True) + + 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) + + 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() + + 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() + + def serialize(self): + data = {} + + data['timer'] = self._toolbar_controls.get_timer_idx() + data['duration'] = self._toolbar_controls.get_duration_idx() + data['quality'] = self._toolbar_controls.get_quality() + + return data + + def deserialize(self, data): + self._toolbar_controls.set_timer_idx(data.get('timer', 0)) + self._toolbar_controls.set_duration_idx(data.get('duration', 0)) + self._toolbar_controls.set_quality(data.get('quality', 0)) + + 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 _play_pause_clicked(self, widget): + self.model.play_pause() + + def set_mode(self, mode): + self._toolbar_controls.set_mode(mode) + + # 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._toolbar_controls.get_quality() + + def get_selected_timer(self): + return self._toolbar_controls.get_timer() + + def get_selected_duration(self): + return self._toolbar_controls.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 + + self._shutter_button.hide() + self._countdown_image.show() + self._countdown_image.set_value(value) + + 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 self._showing_info: + self._show_recd(recd, play=False) + return + + self._showing_info = True + if self.model.get_mode() in (constants.MODE_PHOTO, constants.MODE_AUDIO): + func = self._media_view.show_info_photo + else: + 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() + + func(recd.recorderName, recd.colorStroke, recd.colorFill, utils.getDateString(recd.time), recd.tags) + + def _media_view_full_clicked(self, widget): + self._toggle_fullscreen() + + 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._toolbar_box.hide() + self._thumb_tray.hide() + else: + self._toolbar_box.show() + self._thumb_tray.show() + + self._fullscreen = not self._fullscreen + self._media_view.set_fullscreen(self._fullscreen) + + def _mode_button_clicked(self, button): + self.model.change_mode(button.mode) + + 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): + radio_state = (state == constants.STATE_READY) + for item in (self._photo_button, self._audio_button, self._video_button): + if item: + item.set_sensitive(radio_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._play_button.set_pause() + + def _thumbnail_clicked(self, button, recd): + if self.model.ui_frozen(): + return + + 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 not recd.isClipboardCopyable(): + return + + 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 + + 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) + + 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(self.get_window(), w, h, -1) + 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) + + def set_normal(self): + self.set_image(self._rec_image) + + def set_recording(self): + self.set_image(self._rec_red_image) + + +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) + + 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) + + self.set_play() + + def set_play(self): + self.set_image(self._play_image) + + def set_pause(self): + self.set_image(self._pause_image) + + +class RecordControl(): + + def __init__(self, toolbar): + self._timer_combo = TimerCombo() + toolbar.insert(self._timer_combo, -1) + + self._duration_combo = DurationCombo() + toolbar.insert(self._duration_combo, -1) + + preferences_toolbar = gtk.Toolbar() + 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.quality.show_all() + preferences_toolbar.insert(self.quality, -1) + + preferences_button = ToolbarButton() + preferences_button.set_page(preferences_toolbar) + preferences_button.props.icon_name = 'preferences-system' + preferences_button.props.label = _('Preferences') + toolbar.insert(preferences_button, -1) + + def set_mode(self, mode): + if mode == constants.MODE_PHOTO: + self.quality.set_sensitive(True) + self._timer_combo.set_sensitive(True) + self._duration_combo.set_sensitive(False) + if mode == constants.MODE_VIDEO: + self.quality.set_sensitive(True) + self._timer_combo.set_sensitive(True) + self._duration_combo.set_sensitive(True) + if mode == constants.MODE_AUDIO: + self.quality.set_sensitive(False) + self._timer_combo.set_sensitive(True) + self._duration_combo.set_sensitive(True) + + 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 TimerCombo(IconComboBox): + TIMERS = (0, 5, 10) + + def __init__(self): + super(TimerCombo, self).__init__('timer') + + for i in self.TIMERS: + if i == 0: + self.append_item(i, _('Immediate')) + else: + string = TimerCombo._seconds_string(i) + self.append_item(i, string) + self.combo.set_active(0) + + def get_value(self): + return TimerCombo.TIMERS[self.combo.get_active()] + + def get_value_idx(self): + return self.combo.get_active() + + def set_value_idx(self, idx): + self.combo.set_active(idx) + + @staticmethod + def _seconds_string(x): + return ngettext('%s second', '%s seconds', x) % x + + +class DurationCombo(IconComboBox): + DURATIONS = (2, 4, 6) + + def __init__(self): + super(DurationCombo, self).__init__('duration') + + for i in self.DURATIONS: + string = DurationCombo._minutes_string(i) + self.append_item(i, string) + self.combo.set_active(0) + + def get_value(self): + return 60 * self.DURATIONS[self.combo.get_active()] + + def get_value_idx(self): + return self.combo.get_active() + + def set_value_idx(self, idx): + self.combo.set_active(idx) + + @staticmethod + def _minutes_string(x): + return ngettext('%s minute', '%s minutes', x) % x |