[the-board/cheese: 7/10] [things] Webcam support in photo elements
- From: Lucas Rocha <lucasr src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [the-board/cheese: 7/10] [things] Webcam support in photo elements
- Date: Sun, 3 Apr 2011 22:37:53 +0000 (UTC)
commit e9b4adcc32f6520b28b918d29b0062c07ac4d3e8
Author: Lucas Rocha <lucasr gnome org>
Date: Wed Mar 30 01:48:52 2011 +0100
[things] Webcam support in photo elements
https://bugzilla.gnome.org/show_bug.cgi?id=636625
configure.ac | 3 +-
data/things/photo/caption-border.png | Bin 0 -> 219 bytes
data/things/photo/style.css | 14 ++-
src/js/ui/things/photo.js | 333 +++++++++++++++++++++++++++++++---
4 files changed, 325 insertions(+), 25 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index f970443..99d1425 100644
--- a/configure.ac
+++ b/configure.ac
@@ -70,7 +70,8 @@ PKG_CHECK_MODULES(THE_BOARD,
mx-1.0 >= $MX_MIN_VERSION
gtk+-3.0 >= $GTK_MIN_VERSION
clutter-gtk-1.0 >= $CLUTTER_GTK_MIN_VERSION
- clutter-gst-1.0 >= $CLUTTER_GST_MIN_VERSION)
+ clutter-gst-1.0 >= $CLUTTER_GST_MIN_VERSION
+ cheese >= $CHEESE_MIN_VERSION)
PKG_CHECK_MODULES(TB,
glib-2.0 >= $GLIB_MIN_VERSION
diff --git a/data/things/photo/caption-border.png b/data/things/photo/caption-border.png
new file mode 100644
index 0000000..a80cd14
Binary files /dev/null and b/data/things/photo/caption-border.png differ
diff --git a/data/things/photo/style.css b/data/things/photo/style.css
index 33be058..c357f47 100644
--- a/data/things/photo/style.css
+++ b/data/things/photo/style.css
@@ -3,12 +3,24 @@ MxWidget {
}
TbBox#photo-thing-photo-box {
- spacing: 10px;
+ spacing: 5px;
padding: 10px 10px 5px 10px;
background-color: white;
}
+MxLabel#photo-thing-camera-label,
MxLabel#photo-thing-caption-label {
color: black;
font-size: 13;
+ padding: 5px 5px 2px 5px;
+ background-color: none;
+}
+
+MxLabel#photo-thing-camera-label {
+ border-image: url("caption-border.png") 3 3 3 3;
+ background-color: #F6E88A;
+}
+
+TbBox#photo-thing-camera-box {
+ background-color: black;
}
diff --git a/src/js/ui/things/photo.js b/src/js/ui/things/photo.js
index d841453..edfe071 100644
--- a/src/js/ui/things/photo.js
+++ b/src/js/ui/things/photo.js
@@ -2,11 +2,14 @@
const Gettext = imports.gettext.domain("the-board");
const Lang = imports.lang;
const Mainloop = imports.mainloop;
+const Signals = imports.signals;
const Tweener = imports.tweener.tweener;
// gi imports
+const Cheese = imports.gi.Cheese;
const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib;
+const Gst = imports.gi.Gst;
const Gtk = imports.gi.Gtk;
const Mx = imports.gi.Mx;
const Pango = imports.gi.Pango;
@@ -29,6 +32,179 @@ const _SHOW_BUTTON_BOX_TIME = 0.5;
const _UPDATE_SIZE_TIME = 0.3;
const _UPDATE_SIZE_TRANSITION = 'easeOutCubic';
+const _CAMERA_SPINNER_SIZE = 20;
+
+const _CAMERA_X_RESOLUTION = 640;
+const _CAMERA_Y_RESOLUTION = 480;
+
+const _CAPTION_LABEL_NAME_NORMAL = "photo-thing-caption-label";
+const _CAPTION_LABEL_NAME_CAMERA = "photo-thing-camera-label";
+
+const _PHOTO_TAKING_COUNTDOWN = 3; // in seconds
+
+function Camera(args) {
+ this._init(args);
+}
+
+Camera.prototype = {
+ _init : function(args) {
+ this._style = args.style;
+ this._photoTakingCountdown = -1;
+
+ this._createMainBox();
+ this._createVideoTexture();
+ this._createSpinner();
+ this._createCamera();
+ },
+
+ _createMainBox : function() {
+ this._mainBox =
+ new TheBoard.Box({ orientation: TheBoard.BoxOrientation.VERTICAL,
+ xAlign: TheBoard.BoxAlignment.FILL,
+ yAlign: TheBoard.BoxAlignment.FILL,
+ style: this._style,
+ name: "photo-thing-camera-box" });
+ },
+
+ _createVideoTexture : function() {
+ this._videoTexture =
+ new Clutter.Texture({ keepAspectRatio: true });
+
+ this._mainBox.append(this._videoTexture,
+ TheBoard.BoxPackFlags.EXPAND);
+ },
+
+ _createSpinner : function() {
+ this._spinner =
+ new Mx.Spinner({ width: _CAMERA_SPINNER_SIZE,
+ height: _CAMERA_SPINNER_SIZE,
+ name: "photo-thing-camera-spinner" });
+
+ this._mainBox.append(this._spinner,
+ TheBoard.BoxPackFlags.FIXED);
+
+ this._mainBox.set_fixed_child_align(this._spinner,
+ TheBoard.BoxAlignment.CENTER,
+ TheBoard.BoxAlignment.CENTER);
+ },
+
+ _createCamera : function() {
+ // We use FileUtil.new() here because FileUtil
+ // is a supposed to be a singleton which is returned
+ // from cheese_fileutil_new()
+ let fileUtil = Cheese.FileUtil.new();
+
+ this._filename =
+ fileUtil.get_new_media_filename(Cheese.MediaMode.PHOTO);
+
+ this._camera = Cheese.Camera.new(this._videoTexture,
+ null,
+ _CAMERA_X_RESOLUTION,
+ _CAMERA_Y_RESOLUTION);
+
+ this._camera.connect("state-flags-changed",
+ Lang.bind(this,
+ this._onCameraStateChanged));
+
+ this._camera.connect("photo-saved",
+ Lang.bind(this,
+ this._onCameraPhotoSaved));
+
+ TheBoard.cheese_camera_start_async(this._camera,
+ Lang.bind(this,
+ this._onCameraStartAsync));
+ },
+
+ _onCameraStartAsync : function(camera, result, data) {
+ try {
+ TheBoard.cheese_camera_start_finish(camera, result);
+ } catch(e) {
+ // FIXME: Show error message to user
+ }
+ },
+
+ _onCameraStateChanged : function(camera, gstState) {
+ if (gstState != Gst.State.PLAYING) {
+ return;
+ }
+
+ this._spinner.hide();
+
+ this._photoTakingCountdown = _PHOTO_TAKING_COUNTDOWN;
+
+ this.emit("caption-changed");
+
+ this._photoTakingTimeoutId =
+ Mainloop.timeout_add_seconds(1,
+ Lang.bind(this,
+ this._onPhotoTakingTimeout));
+ },
+
+ _onCameraPhotoSaved : function(camera, filename) {
+ // We have to emit this signal on idle here
+ // because apparently libcheese doesn't handle
+ // very well the case where we stop camera in
+ // response to "photo-saved" signal.
+ let emitPhotoSaved = function() {
+ this.emit("photo-saved");
+ };
+
+ Mainloop.idle_add(Lang.bind(this, emitPhotoSaved));
+ },
+
+ _onPhotoTakingTimeout : function(filename) {
+ this._photoTakingCountdown--;
+
+ // We want to give one more second for the
+ // "Say cheese" message just before actually
+ // taking the photo
+ if (this._photoTakingCountdown < 0) {
+ this._camera.take_photo(this._filename);
+ } else {
+ this.emit("caption-changed");
+ }
+
+ return (this._photoTakingCountdown >= 0);
+ },
+
+ destroy : function() {
+ if (this._photoTakingTimeoutId) {
+ Mainloop.source_remove(this._photoTakingTimeoutId);
+ delete this._photoTakingTimeoutId;
+ }
+
+ if (this._camera) {
+ this._camera.stop();
+ delete this._camera;
+ }
+
+ if (this._mainBox) {
+ this._mainBox.destroy();
+ delete this._mainBox;
+ }
+ },
+
+ get caption() {
+ if (this._photoTakingCountdown == 0) {
+ return Gettext.gettext("Say cheese!");
+ } else if (this._photoTakingCountdown > 0) {
+ return "" + this._photoTakingCountdown;
+ } else {
+ return Gettext.gettext("Starting webcam");
+ }
+ },
+
+ get filename() {
+ return this._filename;
+ },
+
+ get actor() {
+ return this._mainBox;
+ }
+}
+
+Signals.addSignalMethods(Camera.prototype);
+
function PhotoThing(args) {
this._init(args);
}
@@ -109,7 +285,7 @@ PhotoThing.prototype = {
this._captionLabel =
new Mx.Label({ xAlign: Mx.Align.MIDDLE,
style: this._style,
- name: "photo-thing-caption-label" });
+ name: _CAPTION_LABEL_NAME_NORMAL });
this._captionLabel.clutterText.lineAlignment = Pango.Alignment.CENTER;
@@ -278,38 +454,65 @@ PhotoThing.prototype = {
this._captionLabel.clutterText.editable = false;
},
- _onCaptionTextKeyPressEvent : function(o, event) {
- let key = event.get_key_symbol();
-
- switch (key) {
- case Clutter.Return:
- this._updatePhotoWithFileChooser();
- return true;
- case Clutter.Escape:
- this.emit("deactivate");
- return true;
+ _startTakingPhoto : function() {
+ if (this._camera) {
+ return;
}
- return false;
- },
+ this._updateInitialSize(_CAMERA_X_RESOLUTION,
+ _CAMERA_Y_RESOLUTION);
- _onCaptionTextChanged : function() {
- this.emit('save');
+ this._animateToPhotoSize(false /* not from state */);
+
+ this._camera = new Camera({ style: this._style });
+
+ this._cameraPhotoTakenId =
+ this._camera.connect("photo-saved",
+ Lang.bind(this, this._onPhotoSavedFromCamera));
+
+ this._cameraCaptionChangedId =
+ this._camera.connect("caption-changed",
+ Lang.bind(this, this._onCameraCaptionChanged));
+
+ this._contentBox.append(this._camera.actor,
+ TheBoard.BoxPackFlags.FIXED);
+
+ this._contentBox.set_fixed_child_align(this._camera.actor,
+ TheBoard.BoxAlignment.FILL,
+ TheBoard.BoxAlignment.FILL);
+
+ this._textBeforeTakingPhoto = this._captionLabel.text;
+
+ this._captionLabel.name = _CAPTION_LABEL_NAME_CAMERA;
+ this._updateCaptionFromCamera();
+ this._stopEditingCaption();
},
- _onPhotoSizeChange : function(photo, width, height) {
- if (this._photoWidth > 0 && this._photoHeight > 0) {
+ _stopTakingPhoto : function() {
+ if (!this._camera) {
return;
}
- this._updateInitialSize(width, height);
- },
+ if (this._cameraPhotoTakenId) {
+ this._camera.disconnect(this._cameraPhotoTakenId);
+ delete this._cameraPhotoTakenId;
+ }
- _onPhotoLoadFinished : function(texture, error, fromState) {
- this._disconnectPhotoSignals();
- this._updateSpinner();
+ if (this._cameraCaptionChangedId) {
+ this._camera.disconnect(this._cameraCaptionChangedId);
+ delete this._cameraCaptionChangedId;
+ }
+
+ this._camera.destroy();
+ delete this._camera;
+
+ this._captionLabel.name = _CAPTION_LABEL_NAME_NORMAL;
+ this._captionLabel.text = this._textBeforeTakingPhoto;
+ delete this._textBeforeTakingPhoto;
+ },
- [minTextHeight, naturalTextHeight] =
+ _animateToPhotoSize : function(fromState) {
+ let [minTextHeight, naturalTextHeight] =
this._captionLabel.get_preferred_height(-1);
let thingWidth = this._photoWidth;
@@ -335,12 +538,90 @@ PhotoThing.prototype = {
delete this._photoHeight;
},
+ _updateCaptionFromCamera : function() {
+ this._captionLabel.text = this._camera.caption;
+ },
+
+ _maybeSetDateTimeCaption : function(textBeforeTakingPhoto) {
+ if (textBeforeTakingPhoto) {
+ return;
+ }
+
+ let now = new Date();
+
+ this._captionLabel.text =
+ now.toLocaleDateString() + " " + now.toLocaleTimeString();
+ },
+
+ _onPhotoSavedFromCamera : function(camera) {
+ // Copy previous caption value before calling
+ // _stopTakingPhoto() because it will clear and
+ // possibly restore this value in captionLabel
+ let textBeforeTakingPhoto = this._textBeforeTakingPhoto;
+
+ this._stopTakingPhoto();
+
+ // This will set captionLabel value to a date
+ // in case a photo was taken while no caption had
+ // been defined
+ this._maybeSetDateTimeCaption(textBeforeTakingPhoto);
+
+ this._startEditingCaption();
+
+ this._updateImageFilename(camera.filename,
+ false /* not from state */);
+ },
+
+ _onCameraCaptionChanged : function(camera) {
+ this._updateCaptionFromCamera();
+ },
+
+ _onCaptionTextKeyPressEvent : function(o, event) {
+ let key = event.get_key_symbol();
+
+ switch (key) {
+ case Clutter.Return:
+ this._updatePhotoWithFileChooser();
+ return true;
+ case Clutter.Escape:
+ this.emit("deactivate");
+ return true;
+ }
+
+ return false;
+ },
+
+ _onCaptionTextChanged : function() {
+ // Don't trigger save when we set the label
+ // text as part of the photo taking countdown
+ if (this._camera) {
+ return;
+ }
+
+ this.emit('save');
+ },
+
+ _onPhotoSizeChange : function(photo, width, height) {
+ if (this._photoWidth > 0 && this._photoHeight > 0) {
+ return;
+ }
+
+ this._updateInitialSize(width, height);
+ },
+
+ _onPhotoLoadFinished : function(texture, error, fromState) {
+ this._disconnectPhotoSignals();
+ this._updateSpinner();
+ this._animateToPhotoSize(fromState);
+ },
+
activate : function() {
this._startEditingCaption();
},
deactivate : function() {
this._stopEditingCaption();
+ this._stopTakingPhoto();
},
loadState : function(state) {
@@ -365,6 +646,8 @@ PhotoThing.prototype = {
doAction : function(actionName, actionArgs) {
if (actionName == "chooseFile") {
this._updatePhotoWithFileChooser();
+ } else if (actionName == "takePhoto") {
+ this._startTakingPhoto();
}
},
@@ -384,6 +667,7 @@ PhotoThing.prototype = {
destroy : function() {
this._disconnectPhotoSignals();
+ this._stopTakingPhoto();
if (this._photoBox) {
this._photoBox.destroy();
@@ -427,6 +711,9 @@ function createToolbar(args) {
toolBox.addButton({ label: Gettext.gettext("File"),
actionName: "chooseFile" });
+ toolBox.addButton({ label: Gettext.gettext("Webcam"),
+ actionName: "takePhoto" });
+
toolbar.addToolBox(toolBox);
return toolbar;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]