[gnome-shell] Add missing magnifier files from the last commit
- From: Colin Walters <walters src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell] Add missing magnifier files from the last commit
- Date: Tue, 11 May 2010 19:00:37 +0000 (UTC)
commit dc424280a10b43b3855ff3ee247685d30e415a84
Author: Colin Walters <walters verbum org>
Date: Tue May 11 15:00:07 2010 -0400
Add missing magnifier files from the last commit
Also, this patch differs from the one in Bugzilla
in that we start/stop tracking the mouse depending
on whether the magnifier is active.
js/ui/magnifier.js | 1484 ++++++++++++++++++++++++++++++++++++++++++++++++
js/ui/magnifierDBus.js | 375 ++++++++++++
2 files changed, 1859 insertions(+), 0 deletions(-)
---
diff --git a/js/ui/magnifier.js b/js/ui/magnifier.js
new file mode 100644
index 0000000..75e2f5a
--- /dev/null
+++ b/js/ui/magnifier.js
@@ -0,0 +1,1484 @@
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const DBus = imports.dbus;
+const Gtk = imports.gi.Gtk;
+const Gdk = imports.gi.Gdk;
+const Clutter = imports.gi.Clutter;
+const Shell = imports.gi.Shell;
+const St = imports.gi.St;
+const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+const Main = imports.ui.main;
+const MagnifierDBus = imports.ui.magnifierDBus;
+
+const MouseTrackingMode = {
+ NONE: 0,
+ CENTERED: 1,
+ PUSH: 2,
+ PROPORTIONAL: 3
+};
+
+const ScreenPosition = {
+ NONE: 0,
+ FULL_SCREEN: 1,
+ TOP_HALF: 2,
+ BOTTOM_HALF: 3,
+ LEFT_HALF: 4,
+ RIGHT_HALF: 5
+};
+
+// Default settings
+const DEFAULT_X_MAGFACTOR = 2;
+const DEFAULT_Y_MAGFACTOR = 2;
+const DEFAULT_MOUSE_POLL_FREQUENCY = 50;
+const DEFAULT_LENS_MODE = false;
+const DEFAULT_SCREEN_POSITION = ScreenPosition.BOTTOM_HALF;
+const DEFAULT_MOUSE_TRACKING_MODE = MouseTrackingMode.CENTERED;
+const DEFAULT_CLAMP_SCROLLING_AT_EDGES = true;
+
+const DEFAULT_SHOW_CROSSHAIRS = false;
+const DEFAULT_CROSSHAIRS_THICKNESS = 8;
+const DEFAULT_CROSSHAIRS_OPACITY = 169; // 66%
+const DEFAULT_CROSSHAIRS_LENGTH = 4096;
+const DEFAULT_CROSSHAIRS_CLIP = false;
+const DEFAULT_CROSSHAIRS_CLIP_SIZE = [100, 100];
+const DEFAULT_CROSSHAIRS_COLOR = new Clutter.Color();
+DEFAULT_CROSSHAIRS_COLOR.from_string("Red");
+
+// GConf settings
+const A11Y_MAG_PREFS_DIR = "/desktop/gnome/accessibility/magnifier";
+const SHOW_KEY = A11Y_MAG_PREFS_DIR + "/show_magnifier";
+const SCREEN_POSITION_KEY = A11Y_MAG_PREFS_DIR + "/screen_position";
+const MAG_FACTOR_KEY = A11Y_MAG_PREFS_DIR + "/mag_factor";
+const LENS_MODE_KEY = A11Y_MAG_PREFS_DIR + "/lens_mode";
+const CLAMP_MODE_KEY = A11Y_MAG_PREFS_DIR + "/scroll_at_edges";
+const MOUSE_TRACKING_KEY = A11Y_MAG_PREFS_DIR + "/mouse_tracking";
+const SHOW_CROSS_HAIRS_KEY = A11Y_MAG_PREFS_DIR + "/show_cross_hairs";
+const CROSS_HAIRS_THICKNESS_KEY = A11Y_MAG_PREFS_DIR + "/cross_hairs_thickness";
+const CROSS_HAIRS_COLOR_KEY = A11Y_MAG_PREFS_DIR + "/cross_hairs_color";
+const CROSS_HAIRS_OPACITY_KEY = A11Y_MAG_PREFS_DIR + "/cross_hairs_opacity";
+const CROSS_HAIRS_LENGTH_KEY = A11Y_MAG_PREFS_DIR + "/cross_hairs_length";
+const CROSS_HAIRS_CLIP_KEY = A11Y_MAG_PREFS_DIR + "/cross_hairs_clip";
+
+let magDBusService = null;
+
+function Magnifier() {
+ this._init();
+}
+
+Magnifier.prototype = {
+ _init: function() {
+ // Magnifier is a manager of ZoomRegions.
+ this._zoomRegions = [];
+
+ // Create small clutter tree for the magnified mouse.
+ let xfixesCursor = Shell.XFixesCursor.get_default();
+ this._mouseSprite = new Clutter.Texture();
+ xfixesCursor.update_texture_image(this._mouseSprite);
+ this._cursorRoot = new Clutter.Group();
+ this._cursorRoot.add_actor(this._mouseSprite);
+
+ // Create the first ZoomRegion and initialize it according to the
+ // magnification GConf settings.
+ let [objUnder, xMouse, yMouse, mask] =
+ Gdk.Screen.get_default().get_root_window().get_pointer();
+ let aZoomRegion = new ZoomRegion(this, this._cursorRoot);
+ this._zoomRegions.push(aZoomRegion);
+ let showAtLaunch = this._gConfInit(aZoomRegion);
+ aZoomRegion.scrollContentsTo(xMouse, yMouse);
+
+ xfixesCursor.connect('cursor-change', Lang.bind(this, this._updateMouseSprite));
+ this._xfixesCursor = xfixesCursor;
+
+ // Export to dbus.
+ magDBusService = new MagnifierDBus.ShellMagnifier();
+ this.setActive(showAtLaunch);
+ },
+
+ /**
+ * showSystemCursor:
+ * Show the system mouse pointer.
+ */
+ showSystemCursor: function() {
+ this._xfixesCursor.show();
+ },
+
+ /**
+ * hideSystemCursor:
+ * Hide the system mouse pointer.
+ */
+ hideSystemCursor: function() {
+ this._xfixesCursor.hide();
+ },
+
+ /**
+ * setActive:
+ * Show/hide all the zoom regions.
+ * @activate: Boolean to activate or de-activate the magnifier.
+ */
+ setActive: function(activate) {
+ this._zoomRegions.forEach (function(zoomRegion, index, array) {
+ zoomRegion.setActive(activate);
+ });
+
+ if (activate)
+ this.startTrackingMouse();
+ else
+ this.stopTrackingMouse();
+
+ // Make sure system mouse pointer is shown when all zoom regions are
+ // invisible.
+ if (!activate)
+ this._xfixesCursor.show();
+ },
+
+ /**
+ * isActive:
+ * @return Whether the magnifier is active (boolean).
+ */
+ isActive: function() {
+ // Sufficient to check one ZoomRegion since Magnifier's active
+ // state applies to all of them.
+ if (this._zoomRegions.length == 0)
+ return false;
+ else
+ return this._zoomRegions[0].isActive();
+ },
+
+ /**
+ * startTrackingMouse:
+ * Turn on mouse tracking, if not already doing so.
+ */
+ startTrackingMouse: function() {
+ // initialize previous mouse coord to undefined.
+ let prevCoord = { x: NaN, y: NaN };
+ if (!this._mouseTrackingId)
+ this._mouseTrackingId = Mainloop.timeout_add(
+ DEFAULT_MOUSE_POLL_FREQUENCY,
+ Lang.bind(this, this.scrollToMousePos, prevCoord)
+ );
+ },
+
+ /**
+ * stopTrackingMouse:
+ * Turn off mouse tracking, if not already doing so.
+ */
+ stopTrackingMouse: function() {
+ if (this._mouseTrackingId)
+ Mainloop.source_remove(this._mouseTrackingId);
+
+ this._mouseTrackingId = null;
+ },
+
+ /**
+ * isTrackingMouse:
+ * Is the magnifier tracking the mouse currently?
+ */
+ isTrackingMouse: function() {
+ return !!this._mouseTrackingId;
+ },
+
+ /**
+ * scrollToMousePos:
+ * Position all zoom regions' ROI relative to the current location of the
+ * system pointer.
+ * @prevCoord: The previous mouse coordinates. Used to stop scrolling if
+ * the new position is the same as the last one (optional).
+ * @return true.
+ */
+ scrollToMousePos: function(prevCoord) {
+ let [objUnder, xMouse, yMouse, mask] =
+ Gdk.Screen.get_default().get_root_window().get_pointer();
+
+ if (!prevCoord || prevCoord.x != xMouse || prevCoord.y != yMouse) {
+ let sysMouseOverAny = false;
+ this._zoomRegions.forEach(function(zoomRegion, index, array) {
+ if (zoomRegion.scrollToMousePos())
+ sysMouseOverAny = true;
+ });
+ if (sysMouseOverAny)
+ this.hideSystemCursor();
+ else
+ this.showSystemCursor();
+
+ if (prevCoord) {
+ prevCoord.x = xMouse;
+ prevCoord.y = yMouse;
+ }
+ }
+ return true;
+ },
+
+ /**
+ * createZoomRegion:
+ * Create a ZoomRegion instance with the given properties.
+ * @xMagFactor: The power to set horizontal magnification of the
+ * ZoomRegion. A value of 1.0 means no magnification. A
+ * value of 2.0 doubles the size.
+ * @yMagFactor: The power to set the vertical magnification of the
+ * ZoomRegion.
+ * @roi Object in the form { x, y, width, height } that
+ * defines the region to magnify. Given in unmagnified
+ * coordinates.
+ * @viewPort Object in the form { x, y, width, height } that defines
+ * the position of the ZoomRegion on screen.
+ * @return The newly created ZoomRegion.
+ */
+ createZoomRegion: function(xMagFactor, yMagFactor, roi, viewPort) {
+ let zoomRegion = new ZoomRegion(this, this._cursorRoot);
+ zoomRegion.setMagFactor(xMagFactor, yMagFactor);
+ zoomRegion.setViewPort(viewPort);
+ zoomRegion.setROI(roi);
+ zoomRegion.addCrosshairs(this._crossHairs);
+ return zoomRegion;
+ },
+
+ /**
+ * addZoomRegion:
+ * Append the given ZoomRegion to the list of currently defined ZoomRegions
+ * for this Magnifier instance.
+ * @zoomRegion: The zoomRegion to add.
+ */
+ addZoomRegion: function(zoomRegion) {
+ if(zoomRegion) {
+ this._zoomRegions.push(zoomRegion);
+ if (!this.isTrackingMouse())
+ this.startTrackingMouse();
+ }
+ },
+
+ /**
+ * getZoomRegions:
+ * Return a list of ZoomRegion's for this Magnifier.
+ * @return: The Magnifier's zoom region list (array).
+ */
+ getZoomRegions: function() {
+ return this._zoomRegions;
+ },
+
+ /**
+ * clearAllZoomRegions:
+ * Remove all the zoom regions from this Magnfier's ZoomRegion list.
+ */
+ clearAllZoomRegions: function() {
+ // First ZoomRegion is special since its magnified mouse and crosshairs
+ // are the original -- all the others are Clutter.Clone's. Deal with
+ // all but first zoom region.
+ for (let i = 1; i < this._zoomRegions.length; i++) {
+ this._zoomRegions[i].setActive(false);
+ this._zoomRegions[i].removeFromStage();
+ }
+ this._zoomRegions[0].setActive(false);
+
+ // Detach the (original) magnified mouse and cross hair for later reuse
+ // before removing ZoomRegion from the stage.
+ this._cursorRoot.get_parent().remove_actor(this._cursorRoot);
+ if (this._crossHairs)
+ this._crossHairs.removeFromParent();
+
+ this._zoomRegions[0].removeFromStage();
+ this._zoomRegions.length = 0;
+ this.stopTrackingMouse();
+ this.showSystemCursor();
+ },
+
+ /**
+ * addCrosshairs:
+ * Add and show a cross hair centered on the magnified mouse.
+ * @thickness: The thickness of the vertical and horizontal lines of the
+ * crosshair.
+ * @color: The color of the crosshairs
+ * @opacity: The opacity.
+ * @length: The length of each hair.
+ * @clip: Whether the crosshairs intersection is clipped by the
+ * magnified mouse image.
+ */
+ addCrosshairs: function(thickness, color, opacity, length, clip) {
+ if (!this._crossHairs)
+ this._crossHairs = new Crosshairs();
+
+ this.setCrosshairsThickness(thickness);
+ this.setCrosshairsColor(color);
+ this.setCrosshairsOpacity(opacity);
+ this.setCrosshairsLength(length);
+ this.setCrosshairsClip(clip);
+
+ let theCrossHairs = this._crossHairs;
+ this._zoomRegions.forEach (function(zoomRegion, index, array) {
+ zoomRegion.addCrosshairs(theCrossHairs);
+ });
+ },
+
+ /**
+ * setCrosshairsVisible:
+ * Show or hide the cross hair.
+ * @visible Flag that indicates show (true) or hide (false).
+ */
+ setCrosshairsVisible: function(visible) {
+ if (visible) {
+ if (!this._crossHairs)
+ this.addCrosshairs(DEFAULT_CROSSHAIRS_THICKNESS, DEFAULT_CROSSHAIRS_COLOR, DEFAULT_CROSSHAIRS_OPACITY, DEFAULT_CROSSHAIRS_CLIP);
+ this._crossHairs.show();
+ }
+ else {
+ if (this._crossHairs)
+ this._crossHairs.hide();
+ }
+ },
+
+ /**
+ * setCrosshairsColor:
+ * Set the color of the crosshairs for all ZoomRegions.
+ * @color: The color as a string, e.g. "#ff0000ff" or "red".
+ */
+ setCrosshairsColor: function(color) {
+ if (this._crossHairs) {
+ let clutterColor = new Clutter.Color();
+ clutterColor.from_string(color);
+ this._crossHairs.setColor(clutterColor);
+ }
+ },
+
+ /**
+ * getCrosshairsColor:
+ * Get the color of the crosshairs.
+ * @return: The color as a string, e.g. "#0000ffff" for blue.
+ */
+ getCrosshairsColor: function() {
+ if (this._crossHairs) {
+ let clutterColor = this._crossHairs.getColor();
+ return clutterColor.to_string();
+ }
+ else
+ return "#00000000";
+ },
+
+ /**
+ * setCrosshairsThickness:
+ * Set the crosshairs thickness for all ZoomRegions.
+ * @thickness: The width of the vertical and horizontal lines of the
+ * crosshairs.
+ */
+ setCrosshairsThickness: function(thickness) {
+ if (this._crossHairs)
+ this._crossHairs.setThickness(thickness);
+ },
+
+ /**
+ * getCrosshairsThickness:
+ * Get the crosshairs thickness.
+ * @return: The width of the vertical and horizontal lines of the
+ * crosshairs.
+ */
+ getCrosshairsThickness: function() {
+ if (this._crossHairs)
+ return this._crossHairs.getThickness();
+ else
+ return 0;
+ },
+
+ /**
+ * setCrosshairsOpacity:
+ * @opacity: Value between 0 (transparent) and 255 (fully opaque).
+ */
+ setCrosshairsOpacity: function(opacity) {
+ if (this._crossHairs)
+ this._crossHairs.setOpacity(opacity);
+ },
+
+ /**
+ * getCrosshairsOpacity:
+ * @return: Value between 0 (transparent) and 255 (fully opaque).
+ */
+ getCrosshairsOpacity: function() {
+ if (this._crossHairs)
+ return this._crossHairs.getOpacity();
+ else
+ return 0;
+ },
+
+ /**
+ * setCrosshairsLength:
+ * Set the crosshairs length for all ZoomRegions.
+ * @length: The length of the vertical and horizontal lines making up the
+ * crosshairs.
+ */
+ setCrosshairsLength: function(length) {
+ if (this._crossHairs)
+ this._crossHairs.setLength(length);
+ },
+
+ /**
+ * getCrosshairsLength:
+ * Get the crosshairs length.
+ * @return: The length of the vertical and horizontal lines making up the
+ * crosshairs.
+ */
+ getCrosshairsLength: function() {
+ if (this._crossHairs)
+ return this._crossHairs.getLength();
+ else
+ return 0;
+ },
+
+ /**
+ * setCrosshairsClip:
+ * Set whether the crosshairs are clipped at their intersection.
+ * @clip: Flag to indicate whether to clip the crosshairs.
+ */
+ setCrosshairsClip: function(clip) {
+ if (clip) {
+ if (this._crossHairs)
+ this._crossHairs.setClip(DEFAULT_CROSSHAIRS_CLIP_SIZE);
+ }
+ else {
+ // Setting no clipping on crosshairs means a zero sized clip
+ // rectangle.
+ if (this._crossHairs)
+ this._crossHairs.setClip([0, 0]);
+ }
+ },
+
+ /**
+ * getCrosshairsClip:
+ * Get whether the crosshairs are clipped by the mouse image.
+ * @return: Whether the crosshairs are clipped.
+ */
+ getCrosshairsClip: function() {
+ if (this._crossHairs) {
+ let [clipWidth, clipHeight] = this._crossHairs.getClip();
+ return (clipWidth > 0 && clipHeight > 0);
+ }
+ else
+ return false;
+ },
+
+ //// Private methods ////
+
+ _updateMouseSprite: function() {
+ this._xfixesCursor.update_texture_image(this._mouseSprite);
+ let xHot = this._xfixesCursor.get_hot_x();
+ let yHot = this._xfixesCursor.get_hot_y();
+ this._mouseSprite.set_anchor_point(xHot, yHot);
+ },
+
+ _gConfInit: function(zoomRegion) {
+ let gConf = Shell.GConf.get_default();
+ if (zoomRegion) {
+ // Mag factor is accurate to two decimal places.
+ let aPref = parseFloat(gConf.get_float(MAG_FACTOR_KEY).toFixed(2));
+ if (aPref != 0.0)
+ zoomRegion.setMagFactor(aPref, aPref);
+
+ aPref = gConf.get_int(SCREEN_POSITION_KEY);
+ if (aPref)
+ zoomRegion.setScreenPosition(aPref);
+
+ zoomRegion.setLensMode(gConf.get_boolean(LENS_MODE_KEY));
+ zoomRegion.setClampScrollingAtEdges(!gConf.get_boolean(CLAMP_MODE_KEY));
+
+ aPref = gConf.get_int(MOUSE_TRACKING_KEY);
+ if (aPref)
+ zoomRegion.setMouseTrackingMode(aPref);
+ }
+ let showCrosshairs = gConf.get_boolean(SHOW_CROSS_HAIRS_KEY);
+ let thickness = gConf.get_int(CROSS_HAIRS_THICKNESS_KEY);
+ let color = gConf.get_string(CROSS_HAIRS_COLOR_KEY);
+ let opacity = gConf.get_int(CROSS_HAIRS_OPACITY_KEY);
+ let length = gConf.get_int(CROSS_HAIRS_LENGTH_KEY);
+ let clip = gConf.get_boolean(CROSS_HAIRS_CLIP_KEY);
+ this.addCrosshairs(thickness, color, opacity, length, clip);
+ this.setCrosshairsVisible(showCrosshairs);
+
+ gConf.watch_directory(A11Y_MAG_PREFS_DIR);
+ gConf.connect('changed::' + SHOW_KEY, Lang.bind(this, this._updateShowHide));
+ gConf.connect('changed::' + SCREEN_POSITION_KEY, Lang.bind(this, this._updateScreenPosition));
+ gConf.connect('changed::' + MAG_FACTOR_KEY, Lang.bind(this, this._updateMagFactor));
+ gConf.connect('changed::' + LENS_MODE_KEY, Lang.bind(this, this._updateLensMode));
+ gConf.connect('changed::' + CLAMP_MODE_KEY, Lang.bind(this, this._updateClampMode));
+ gConf.connect('changed::' + MOUSE_TRACKING_KEY, Lang.bind(this, this._updateMouseTrackingMode));
+ gConf.connect('changed::' + SHOW_CROSS_HAIRS_KEY, Lang.bind(this, this._updateShowCrosshairs));
+ gConf.connect('changed::' + CROSS_HAIRS_THICKNESS_KEY, Lang.bind(this, this._updateCrosshairsThickness));
+ gConf.connect('changed::' + CROSS_HAIRS_COLOR_KEY, Lang.bind(this, this._updateCrosshairsColor));
+ gConf.connect('changed::' + CROSS_HAIRS_OPACITY_KEY, Lang.bind(this, this._updateCrosshairsOpacity));
+ gConf.connect('changed::' + CROSS_HAIRS_LENGTH_KEY, Lang.bind(this, this._updateCrosshairsLength));
+ gConf.connect('changed::' + CROSS_HAIRS_CLIP_KEY, Lang.bind(this, this._updateCrosshairsClip));
+
+ return gConf.get_boolean(SHOW_KEY);
+ },
+
+ _updateShowHide: function() {
+ let gConf = Shell.GConf.get_default();
+ this.setActive(gConf.get_boolean(SHOW_KEY));
+ },
+
+ _updateScreenPosition: function() {
+ // Applies only to the first zoom region.
+ if (this._zoomRegions.length) {
+ let gConf = Shell.GConf.get_default();
+ let position = gConf.get_int(SCREEN_POSITION_KEY);
+ this._zoomRegions[0].setScreenPosition(position);
+ if (position != ScreenPosition.FULL_SCREEN)
+ this._updateLensMode();
+ }
+ },
+
+ _updateMagFactor: function() {
+ // Applies only to the first zoom region.
+ if (this._zoomRegions.length) {
+ let gConf = Shell.GConf.get_default();
+ // Mag factor is accurate to two decimal places.
+ let magFactor = parseFloat(gConf.get_float(MAG_FACTOR_KEY).toFixed(2));
+ this._zoomRegions[0].setMagFactor(magFactor, magFactor);
+ }
+ },
+
+ _updateLensMode: function() {
+ // Applies only to the first zoom region.
+ if (this._zoomRegions.length) {
+ let gConf = Shell.GConf.get_default();
+ this._zoomRegions[0].setLensMode(gConf.get_boolean(LENS_MODE_KEY));
+ }
+ },
+
+ _updateClampMode: function() {
+ // Applies only to the first zoom region.
+ if (this._zoomRegions.length) {
+ let gConf = Shell.GConf.get_default();
+ this._zoomRegions[0].setClampScrollingAtEdges(
+ !gConf.get_boolean(CLAMP_MODE_KEY)
+ );
+ }
+ },
+
+ _updateMouseTrackingMode: function() {
+ // Applies only to the first zoom region.
+ if (this._zoomRegions.length) {
+ let gConf = Shell.GConf.get_default();
+ this._zoomRegions[0].setMouseTrackingMode(
+ gConf.get_int(MOUSE_TRACKING_KEY)
+ );
+ }
+ },
+
+ _updateShowCrosshairs: function() {
+ let gConf = Shell.GConf.get_default();
+ this.setCrosshairsVisible(gConf.get_boolean(SHOW_CROSS_HAIRS_KEY));
+ },
+
+ _updateCrosshairsThickness: function() {
+ let gConf = Shell.GConf.get_default();
+ this.setCrosshairsThickness(gConf.get_int(CROSS_HAIRS_THICKNESS_KEY));
+ },
+
+ _updateCrosshairsColor: function() {
+ let gConf = Shell.GConf.get_default();
+ this.setCrosshairsColor(gConf.get_string(CROSS_HAIRS_COLOR_KEY));
+ },
+
+ _updateCrosshairsOpacity: function() {
+ let gConf = Shell.GConf.get_default();
+ this.setCrosshairsOpacity(gConf.get_int(CROSS_HAIRS_OPACITY_KEY));
+ },
+
+ _updateCrosshairsLength: function() {
+ let gConf = Shell.GConf.get_default();
+ this.setCrosshairsLength(gConf.get_int(CROSS_HAIRS_LENGTH_KEY));
+ },
+
+ _updateCrosshairsClip: function() {
+ let gConf = Shell.GConf.get_default();
+ this.setCrosshairsClip(gConf.get_boolean(CROSS_HAIRS_CLIP_KEY));
+ }
+}
+
+function ZoomRegion(magnifier, mouseRoot) {
+ this._init(magnifier, mouseRoot);
+}
+
+ZoomRegion.prototype = {
+ _init: function(magnifier, mouseRoot) {
+ this._magnifier = magnifier;
+
+ // The root actor for the zoom region
+ this._magView = new St.Bin({ style_class: 'magnifier-zoom-region', x_fill: true, y_fill: true });
+ global.stage.add_actor(this._magView);
+ this._magView.hide();
+
+ // Append a Clutter.Group to clip the contents of the magnified view.
+ this._mainGroup = new Clutter.Group({ clip_to_allocation: true });
+ this._magView.set_child(this._mainGroup);
+
+ // Add a background for when the magnified uiGroup is scrolled
+ // out of view (don't want to see desktop showing through).
+ let background = new Clutter.Rectangle({ color: Main.DEFAULT_BACKGROUND_COLOR });
+ this._mainGroup.add_actor(background);
+
+ // Clone the group that contains all of UI on the screen. This is the
+ // chrome, the windows, etc.
+ this._uiGroupClone = new Clutter.Clone({ source: Main.uiGroup });
+ this._mainGroup.add_actor(this._uiGroupClone);
+ Main.uiGroup.set_size(global.screen_width, global.screen_height);
+ background.set_size(global.screen_width, global.screen_height);
+ this._uiGroupClone.set_size(global.screen_width, global.screen_height);
+
+ // Add either the given mouseRoot to the ZoomRegion, or a clone of
+ // it.
+ if (mouseRoot.get_parent() != null)
+ this._mouseRoot = new Clutter.Clone({ source: mouseRoot });
+ else
+ this._mouseRoot = mouseRoot;
+ this._mainGroup.add_actor(this._mouseRoot);
+ this._crossHairs = null;
+
+ this.setMagFactor(DEFAULT_X_MAGFACTOR, DEFAULT_Y_MAGFACTOR);
+ this.setScreenPosition(DEFAULT_SCREEN_POSITION);
+ this.setLensMode(DEFAULT_LENS_MODE);
+ this.setClampScrollingAtEdges(DEFAULT_CLAMP_SCROLLING_AT_EDGES);
+ this.setMouseTrackingMode(DEFAULT_MOUSE_TRACKING_MODE);
+ },
+
+ /**
+ * setActive:
+ * @activate: Boolean to show/hide the ZoomRegion.
+ */
+ setActive: function(activate) {
+ if (activate) {
+ this._magView.show();
+ if (this.isMouseOverRegion())
+ this._magnifier.hideSystemCursor();
+ this._updateMousePosition(false /* mouse didn't move */);
+ }
+ else
+ this._magView.hide();
+ },
+
+ /**
+ * isActive:
+ * @return Whether this ZoomRegion is active (boolean).
+ */
+ isActive: function() {
+ return this._magView.visible;
+ },
+
+ /**
+ * removeFromStage:
+ * Remove the magnified view from the stage.
+ */
+ removeFromStage: function() {
+ global.stage.remove_actor(this._magView);
+ this._mouseRoot = null;
+ this._uiGroupClone = null;
+ this._magView = null;
+ },
+
+ /**
+ * setMagFactor:
+ * @xMagFactor: The power to set the horizontal magnification factor to
+ * of the magnified view. A value of 1.0 means no
+ * magnification. A value of 2.0 doubles the size.
+ * @yMagFactor: The power to set the vertical magnification factor to
+ * of the magnified view.
+ */
+ setMagFactor: function(xMagFactor, yMagFactor) {
+ if (xMagFactor > 0 && yMagFactor > 0) {
+ // Changing the mag factor moves the pixels along the axes of
+ // magnification. Set the view back to the point that was at the centre
+ // of the region of interest.
+ let [x, y, width, height] = this.getROI();
+ let xCentre = x + width / 2;
+ let yCentre = y + height / 2;
+ this._uiGroupClone.set_scale(xMagFactor, yMagFactor);
+ this._mouseRoot.set_scale(xMagFactor, yMagFactor);
+ this._calcRightBottomStops();
+ this._scrollToPosition(xCentre, yCentre);
+ this._updateMousePosition(false /* mouse didn't move */);
+ }
+ },
+
+ /**
+ * getMagFactor:
+ * @return an array, [xMagFactor, yMagFactor], containing the horizontal
+ * and vertical magnification powers. A value of 1.0 means no
+ * magnification. A value of 2.0 means the contents are doubled
+ * in size, and so on.
+ */
+ getMagFactor: function() {
+ return this._uiGroupClone.get_scale();
+ },
+
+ /**
+ * setMouseTrackingMode
+ * @mode: One of the enum MouseTrackingMode values.
+ */
+ setMouseTrackingMode: function(mode) {
+ if (mode >= MouseTrackingMode.NONE && mode <= MouseTrackingMode.PROPORTIONAL)
+ this._mouseTrackingMode = mode;
+ },
+
+ /**
+ * getMouseTrackingMode
+ * @return: One of the enum MouseTrackingMode values.
+ */
+ getMouseTrackingMode: function() {
+ return this._mouseTrackingMode;
+ },
+
+ /**
+ * setViewPort
+ * Sets the position and size of the ZoomRegion on screen.
+ * @viewPort: Object defining the position and size of the view port. It
+ * has the form { x, y, width, height }. The values are in
+ * stage coordinate space.
+ */
+ setViewPort: function(viewPort) {
+ let [xRoi, yRoi, wRoi, hRoi] = this.getROI();
+
+ // Remove border if the view port is the entire screen. Otherwise,
+ // ensure that the border is there.
+ if (viewPort.x == 0 && viewPort.y == 0 && viewPort.width == global.screen_width && viewPort.height == global.screen_height)
+ this._magView.add_style_class_name('full-screen');
+ else
+ this._magView.remove_style_class_name('full-screen');
+
+ this.setSize(viewPort.width, viewPort.height);
+ this.setPosition(viewPort.x, viewPort.y);
+ if (this._crossHairs)
+ this._crossHairs.reCenter();
+
+ this.scrollContentsTo(xRoi + wRoi / 2, yRoi + hRoi / 2);
+ if (this.isMouseOverRegion())
+ this._magnifier.hideSystemCursor();
+
+ this._screenPosition = ScreenPosition.NONE;
+ },
+
+ /**
+ * setROI
+ * Sets the "region of interest" that the ZoomRegion is magnifying.
+ * @roi: Object that defines the region of the screen to magnify. It
+ * has the form { x, y, width, height }. The values are in
+ * screen (unmagnified) coordinate space.
+ */
+ setROI: function(roi) {
+ let xRoiCenter = roi.x + roi.width / 2;
+ let yRoiCenter = roi.y + roi.height / 2;
+ this.scrollContentsTo(xRoiCenter, yRoiCenter);
+ },
+
+ /**
+ * setSize:
+ * @width: The width to set the magnified view to.
+ * @height: The height to set the magnified view to.
+ */
+ setSize: function(width, height) {
+ this._magView.set_size(width, height);
+ this._calcRightBottomStops();
+ },
+
+ /**
+ * getSize:
+ * @return an array, [width, height], that specifies the size of the
+ * magnified view.
+ */
+ getSize: function() {
+ return this._magView.get_size();
+ },
+
+ /**
+ * setPosition:
+ * Position the magnified view at the given coordinates.
+ * @x: The x-coord of the new position.
+ * @y: The y-coord of the new position.
+ */
+ setPosition: function(x, y) {
+ let [width, height] = this._magView.get_size();
+ if (this._clampScrollingAtEdges) {
+ // Restrict positioning so view doesn't go beyond any edge of the
+ // screen.
+ if (x < 0)
+ x = 0;
+ if (x + width > global.screen_width)
+ x = global.screen_width - width;
+ if (y < 0)
+ y = 0;
+ if (y + height > global.screen_height)
+ y = global.screen_height - height;
+ }
+ this._magView.set_position(x, y);
+ },
+
+ /**
+ * getPosition:
+ * @return an array, [x, y], that gives the position of the
+ * magnified view on screen.
+ */
+ getPosition: function() {
+ return this._magView.get_position();
+ },
+
+ /**
+ * getCenter:
+ * @return an array, [x, y], that is half the width and height of the
+ * magnified view (the center of the magnified view).
+ */
+ getCenter: function() {
+ let [width, height] = this._magView.get_size();
+ return [width / 2, height / 2];
+ },
+
+ /**
+ * isFullScreenMode:
+ * Does the magnified view occupy the whole screen?
+ */
+ isFullScreenMode: function() {
+ let [x, y] = this._magView.get_position();
+ if (x != 0 || y != 0)
+ return false;
+ [width, height] = this._magView.get_size();
+ if (width != global.screen_width || height != global.screen_height)
+ return false;
+ return true;
+ },
+
+ /**
+ * getROI:
+ * Retrieves the "region of interest" -- the rectangular bounds of that part
+ * of the desktop that the magnified view is showing (x, y, width, height).
+ * The bounds are given in non-magnified coordinates.
+ * @return an array, [x, y, width, height], representing the bounding
+ * rectangle of what is shown in the magnified view.
+ */
+ getROI: function() {
+ let [xMagnified, yMagnified] = this._uiGroupClone.get_position();
+ let [xMagFactor, yMagFactor] = this.getMagFactor();
+ let [width, height] = this.getSize();
+ let x = (0 - xMagnified) / xMagFactor;
+ let y = (0 - yMagnified) / yMagFactor;
+ return [x, y, width / xMagFactor, height / yMagFactor];
+ },
+
+ /**
+ * setLensMode:
+ * Turn lens mode on/off. In full screen mode, lens mode is alway off since
+ * a lens the size of the screen is pointless.
+ * @lensMode: A boolean to set the sense of lens mode.
+ */
+ setLensMode: function(lensMode) {
+ let fullScreen = this.isFullScreenMode();
+ this._lensMode = (lensMode && !fullScreen);
+ if (!this._lensMode && !fullScreen)
+ this.setScreenPosition (this._screenPosition);
+ },
+
+ /**
+ * isLensMode:
+ * Is lens mode on or off?
+ * @return The lens mode state as a boolean.
+ */
+ isLensMode: function() {
+ return this._lensMode;
+ },
+
+ /**
+ * setClampScrollingAtEdges:
+ * Stop vs. allow scrolling of the magnified contents when it scroll beyond
+ * the edges of the screen.
+ * @clamp: Boolean to turn on/off clamping.
+ */
+ setClampScrollingAtEdges: function(clamp) {
+ this._clampScrollingAtEdges = clamp;
+ },
+
+ /**
+ * setTopHalf:
+ * Magnifier view occupies the top half of the screen.
+ */
+ setTopHalf: function() {
+ let viewPort = {};
+ viewPort.x = 0;
+ viewPort.y = 0;
+ viewPort.width = global.screen_width;
+ viewPort.height = global.screen_height/2;
+ this.setViewPort(viewPort);
+ this._screenPosition = ScreenPosition.TOP_HALF;
+ },
+
+ /**
+ * setBottomHalf:
+ * Magnifier view occupies the bottom half of the screen.
+ */
+ setBottomHalf: function() {
+ let viewPort = {};
+ viewPort.x = 0;
+ viewPort.y = global.screen_height/2;
+ viewPort.width = global.screen_width;
+ viewPort.height = global.screen_height/2;
+ this.setViewPort(viewPort);
+ this._screenPosition = ScreenPosition.BOTTOM_HALF;
+ },
+
+ /**
+ * setLeftHalf:
+ * Magnifier view occupies the left half of the screen.
+ */
+ setLeftHalf: function() {
+ let viewPort = {};
+ viewPort.x = 0;
+ viewPort.y = 0;
+ viewPort.width = global.screen_width/2;
+ viewPort.height = global.screen_height;
+ this.setViewPort(viewPort);
+ this._screenPosition = ScreenPosition.LEFT_HALF;
+ },
+
+ /**
+ * setRightHalf:
+ * Magnifier view occupies the right half of the screen.
+ */
+ setRightHalf: function() {
+ let viewPort = {};
+ viewPort.x = global.screen_width/2;
+ viewPort.y = 0;
+ viewPort.width = global.screen_width/2;
+ viewPort.height = global.screen_height;
+ this.setViewPort(viewPort);
+ this._screenPosition = ScreenPosition.RIGHT_HALF;
+ },
+
+ /**
+ * getScreenPosition:
+ * Tell the outside world what the current mode is -- magnifiying the
+ * top half, bottom half, etc.
+ * @return: the current mode.
+ */
+ getScreenPosition: function() {
+ return this._screenPosition;
+ },
+
+ /**
+ * scrollToMousePos:
+ * Set the region of interest based on the position of the system pointer.
+ * @return: Whether the system mouse pointer is over the magnified view.
+ */
+ scrollToMousePos: function() {
+ let [objUnder, xMouse, yMouse, mask] =
+ Gdk.Screen.get_default().get_root_window().get_pointer();
+
+ if (this._mouseTrackingMode == MouseTrackingMode.PROPORTIONAL) {
+ this._setROIProportional(xMouse, yMouse);
+ }
+ else if (this._mouseTrackingMode == MouseTrackingMode.PUSH) {
+ this._setROIPush(xMouse, yMouse);
+ }
+ else if (this._mouseTrackingMode == MouseTrackingMode.CENTERED) {
+ this._setROICentered(xMouse, yMouse);
+ }
+ this._updateMousePosition(true);
+
+ // Determine whether the system mouse pointer is over this zoom region.
+ return this.isMouseOverRegion(xMouse, yMouse);
+ },
+
+ /**
+ * setFullScreenMode:
+ * Set the ZoomRegion to full-screen mode.
+ * Note: disallows lens mode.
+ */
+ setFullScreenMode: function() {
+ if (!this.isFullScreenMode()) {
+ let viewPort = {};
+ viewPort.x = 0;
+ viewPort.y = 0;
+ viewPort.width = global.screen_width;
+ viewPort.height = global.screen_height;
+ this.setViewPort(viewPort);
+ this.setLensMode(false);
+ if (this.isActive())
+ this._magnifier.hideSystemCursor();
+
+ this._screenPosition = ScreenPosition.FULL_SCREEN;
+ }
+ },
+
+ /**
+ * setScreenPosition:
+ * Positions the zoom region to one of the enumerated positions on the
+ * screen.
+ * @position: one of Magnifier.FULL_SCREEN, Magnifier.TOP_HALF,
+ * Magnifier.BOTTOM_HALF,Magnifier.LEFT_HALF, or
+ * Magnifier.RIGHT_HALF.
+ */
+ setScreenPosition: function(inPosition) {
+ switch (inPosition) {
+ case ScreenPosition.FULL_SCREEN:
+ this.setFullScreenMode();
+ break;
+ case ScreenPosition.TOP_HALF:
+ this.setTopHalf();
+ break;
+ case ScreenPosition.BOTTOM_HALF:
+ this.setBottomHalf();
+ break;
+ case ScreenPosition.LEFT_HALF:
+ this.setLeftHalf();
+ break;
+ case ScreenPosition.RIGHT_HALF:
+ this.setRightHalf();
+ break;
+ }
+ },
+
+ /**
+ * scrollContentsTo:
+ * Shift the contents of the magnified view such it is centered on the given
+ * coordinate. Also, update the position of the magnified mouse image after
+ * the shift.
+ * @x: The x-coord of the point to center on.
+ * @y: The y-coord of the point to center on.
+ */
+ scrollContentsTo: function(x, y) {
+ this._scrollToPosition(x, y);
+ this._updateMousePosition(false /* mouse didn't move */);
+ },
+
+ /**
+ * isMouseOverRegion:
+ * Return whether the system mouse sprite is over this ZoomRegion. If the
+ * mouse's position is not given, then it is fetched.
+ * @xMouse: The system mouse's x-coord. Optional.
+ * @yMouse: The system mouse's y-coord. Optional.
+ * @return: Boolean: true if the mouse is over the zoom region; false
+ * otherwise.
+ */
+ isMouseOverRegion: function(xMouse, yMouse) {
+ let mouseIsOver = false;
+ if (this.isActive()) {
+ if (!xMouse || !yMouse) {
+ let [objUnder, x, y, mask] =
+ Gdk.Screen.get_default().get_root_window().get_pointer();
+ xMouse = x;
+ yMouse = y;
+ }
+ let [x, y] = this.getPosition();
+ let [width, height] = this.getSize();
+ mouseIsOver = (
+ xMouse >= x && xMouse < (x + width) &&
+ yMouse >= y && yMouse < (y + height)
+ );
+ }
+ return mouseIsOver;
+ },
+
+ /**
+ * addCrosshairs:
+ * Add crosshairs centered on the magnified mouse.
+ * @crossHairs Clutter.Group that contains the actors for the crosshairs.
+ */
+ addCrosshairs: function(crossHairs) {
+ // If the crossHairs is not already within a larger container, add it
+ // to this zoom region. Otherwise, add a clone.
+ if (crossHairs) {
+ this._crosshairsActor = crossHairs.addToZoomRegion(this, this._mouseRoot);
+ this._crossHairs = crossHairs;
+ }
+ },
+
+ //// Private methods ////
+
+ _scrollToPosition: function(x, y) {
+ // Given the point (x, y) in non-magnified coordinates, scroll the
+ // magnified contenst such that the point is at the centre of the
+ // magnified view.
+ let [xMagFactor, yMagFactor] = this.getMagFactor();
+ let xMagnified = x * xMagFactor;
+ let yMagnified = y * yMagFactor;
+
+ let [xCenterMagView, yCenterMagView] = this.getCenter();
+ let newX = xCenterMagView - xMagnified;
+ let newY = yCenterMagView - yMagnified;
+
+ if (this._clampScrollingAtEdges) {
+ if (newX > 0)
+ newX = 0;
+ else if (newX < this._rightStop)
+ newX = this._rightStop;
+ if (newY > 0)
+ newY = 0;
+ else if (newY < this._bottomStop)
+ newY = this._bottomStop;
+ this._uiGroupClone.set_position(newX, newY);
+ }
+ else
+ this._uiGroupClone.set_position(newX, newY);
+
+ // If in lens mode, move the magnified view such that it is centered
+ // over the actual mouse. However, in full screen mode, the "lens" is
+ // the size of the screen -- pointless to move such a large lens around.
+ if (this._lensMode && !this.isFullScreenMode())
+ this.setPosition(x - xCenterMagView, y - yCenterMagView);
+ },
+
+ _calcRightBottomStops: function() {
+ // Calculate the location of the top-left corner of _uiGroupClone
+ // when its right and bottom edges are coincident with the right and
+ // bottom edges of the _magView.
+ let [contentWidth, contentHeight] = this._uiGroupClone.get_size();
+ let [viewWidth, viewHeight] = this.getSize();
+ let [xMagFactor, yMagFactor] = this.getMagFactor();
+ let rightStop = viewWidth - (contentWidth * xMagFactor);
+ let bottomStop = viewHeight - (contentHeight * yMagFactor);
+ this._rightStop = parseInt(rightStop.toFixed(1));
+ this._bottomStop = parseInt(bottomStop.toFixed(1));
+ },
+
+ _setROIPush: function(xMouse, yMouse) {
+ let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI();
+ let [cursorWidth, cursorHeight] = this._mouseRoot.get_size();
+ let xPos = xRoi + widthRoi / 2;
+ let yPos = yRoi + heightRoi / 2;
+ let xRoiRight = xRoi + widthRoi - cursorWidth;
+ let yRoiBottom = yRoi + heightRoi - cursorHeight;
+
+ if (xMouse < xRoi)
+ xPos -= (xRoi - xMouse);
+ else if (xMouse > xRoiRight)
+ xPos += (xMouse - xRoiRight);
+
+ if (yMouse < yRoi)
+ yPos -= (yRoi - yMouse);
+ else if (yMouse > yRoiBottom)
+ yPos += (yMouse - yRoiBottom);
+
+ this._scrollToPosition(xPos, yPos);
+ },
+
+ _setROIProportional: function(xMouse, yMouse) {
+ let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI();
+ let halfScreenWidth = global.screen_width / 2;
+ let halfScreenHeight = global.screen_height / 2;
+ let xProportion = (halfScreenWidth - xMouse) / halfScreenWidth;
+ let yProportion = (halfScreenHeight - yMouse) / halfScreenHeight;
+ let xPos = xMouse + xProportion * widthRoi / 2;
+ let yPos = yMouse + yProportion * heightRoi / 2;
+
+ this._scrollToPosition(xPos, yPos);
+ },
+
+ _setROICentered: function(xMouse, yMouse) {
+ this._scrollToPosition(xMouse, yMouse);
+ },
+
+ _updateMousePosition: function(mouseMoved) {
+ let [x, y] = this._uiGroupClone.get_position();
+ x = parseInt(x.toFixed(1));
+ y = parseInt(y.toFixed(1));
+ let [xCenterMagView, yCenterMagView] = this.getCenter();
+ let [objUnder, xMouse, yMouse, mask] =
+ Gdk.Screen.get_default().get_root_window().get_pointer();
+ let [xMagFactor, yMagFactor] = this.getMagFactor();
+
+ let xMagMouse = xMouse * xMagFactor + x;
+ let yMagMouse = yMouse * yMagFactor + y;
+ if (mouseMoved) {
+ if (x == 0)
+ xMagMouse = xMouse * xMagFactor;
+ else if (x == this._rightStop)
+ xMagMouse = (xMouse * xMagFactor) + this._rightStop;
+ else if (this._mouseTrackingMode == MouseTrackingMode.CENTERED)
+ xMagMouse = xCenterMagView;
+
+ if (y == 0)
+ yMagMouse = yMouse * yMagFactor;
+ else if (y == this._bottomStop)
+ yMagMouse = (yMouse * yMagFactor) + this._bottomStop;
+ else if (this._mouseTrackingMode == MouseTrackingMode.CENTERED)
+ yMagMouse = yCenterMagView;
+ }
+ this._mouseRoot.set_position(xMagMouse, yMagMouse);
+ this._updateCrosshairsPosition(xMagMouse, yMagMouse);
+ },
+
+ _updateCrosshairsPosition: function(x, y) {
+ if (this._crosshairsActor) {
+ let [groupWidth, groupHeight] = this._crosshairsActor.get_size();
+ this._crosshairsActor.set_position(x - groupWidth / 2, y - groupHeight / 2);
+ }
+ }
+}
+
+function Crosshairs() {
+ this._init();
+}
+
+Crosshairs.prototype = {
+ _init: function() {
+
+ // Set the group containing the crosshairs to three times the desktop
+ // size in case the crosshairs need to appear to be infinite in
+ // length (i.e., extend beyond the edges of the view they appear in).
+ let groupWidth = global.screen_width * 3;
+ let groupHeight = global.screen_height * 3;
+ this._actor = new Clutter.Group({
+ clip_to_allocation: false,
+ width: groupWidth,
+ height: groupHeight
+ });
+ this._horizLeftHair = new Clutter.Rectangle({
+ color: DEFAULT_CROSSHAIRS_COLOR,
+ width: groupWidth / 2,
+ height: DEFAULT_CROSSHAIRS_THICKNESS,
+ opacity: DEFAULT_CROSSHAIRS_OPACITY
+ });
+ this._horizRightHair = new Clutter.Rectangle({
+ color: DEFAULT_CROSSHAIRS_COLOR,
+ width: groupWidth / 2,
+ height: DEFAULT_CROSSHAIRS_THICKNESS,
+ opacity: DEFAULT_CROSSHAIRS_OPACITY
+ });
+ this._vertTopHair = new Clutter.Rectangle({
+ color: DEFAULT_CROSSHAIRS_COLOR,
+ width: DEFAULT_CROSSHAIRS_THICKNESS,
+ height: groupHeight / 2,
+ opacity: DEFAULT_CROSSHAIRS_OPACITY
+ });
+ this._vertBottomHair = new Clutter.Rectangle({
+ color: DEFAULT_CROSSHAIRS_COLOR,
+ width: DEFAULT_CROSSHAIRS_THICKNESS,
+ height: groupHeight / 2,
+ opacity: DEFAULT_CROSSHAIRS_OPACITY
+ });
+ this._actor.add_actor(this._horizLeftHair);
+ this._actor.add_actor(this._horizRightHair);
+ this._actor.add_actor(this._vertTopHair);
+ this._actor.add_actor(this._vertBottomHair);
+ this._clipSize = [0, 0];
+ this._clones = [];
+ this.reCenter();
+ },
+
+ /**
+ * addToZoomRegion
+ * Either add the crosshairs actor to the given ZoomRegion, or, if it is
+ * already part of some other ZoomRegion, create a clone of the crosshairs
+ * actor, and add the clone instead. Returns either the original or the
+ * clone.
+ * @zoomRegion: The container to add the crosshairs group to.
+ * @magnifiedMouse: The mouse actor for the zoom region -- used to
+ * position the crosshairs and properly layer them below
+ * the mouse.
+ * @return The crosshairs actor, or its clone.
+ */
+ addToZoomRegion: function(zoomRegion, magnifiedMouse) {
+ let crosshairsActor = null;
+ if (zoomRegion && magnifiedMouse) {
+ let container = magnifiedMouse.get_parent();
+ if (container) {
+ crosshairsActor = this._actor;
+ if (this._actor.get_parent() != null) {
+ crosshairsActor = new Clutter.Clone({ source: this._actor });
+ this._clones.push(crosshairsActor);
+ }
+ if (this._actor.visible)
+ crosshairsActor.show();
+ else
+ crosshairsActor.hide();
+
+ container.add_actor(crosshairsActor);
+ container.raise_child(magnifiedMouse, crosshairsActor);
+ let [xMouse, yMouse] = magnifiedMouse.get_position();
+ let [crosshairsWidth, crosshairsHeight] = crosshairsActor.get_size();
+ crosshairsActor.set_position(xMouse - crosshairsWidth / 2 , yMouse - crosshairsHeight / 2);
+ }
+ }
+ return crosshairsActor;
+ },
+
+ /**
+ * removeFromParent:
+ * Remove the crosshairs actor from its parent container.
+ */
+ removeFromParent: function() {
+ this._actor.get_parent().remove_actor(this._actor);
+ },
+
+ /**
+ * setColor:
+ * Set the color of the crosshairs.
+ * @clutterColor: The color as a Clutter.Color.
+ */
+ setColor: function(clutterColor) {
+ this._horizLeftHair.set_color(clutterColor);
+ this._horizRightHair.set_color(clutterColor);
+ this._vertTopHair.set_color(clutterColor);
+ this._vertBottomHair.set_color(clutterColor);
+ },
+
+ /**
+ * getColor:
+ * Get the color of the crosshairs.
+ * @color: The color as a Clutter.Color.
+ */
+ getColor: function() {
+ let clutterColor = new Clutter.Color();
+ this._horizLeftHair.get_color(clutterColor);
+ return clutterColor;
+ },
+
+ /**
+ * setThickness:
+ * Set the width of the vertical and horizontal lines of the crosshairs.
+ * @thickness
+ */
+ setThickness: function(thickness) {
+ this._horizLeftHair.set_height(thickness);
+ this._horizRightHair.set_height(thickness);
+ this._vertTopHair.set_width(thickness);
+ this._vertBottomHair.set_width(thickness);
+ this.reCenter();
+ },
+
+ /**
+ * getThickness:
+ * Get the width of the vertical and horizontal lines of the crosshairs.
+ * @return: The thickness of the crosshairs.
+ */
+ getThickness: function() {
+ return this._horizLeftHair.get_height();
+ },
+
+ /**
+ * setOpacity:
+ * Set how opaque the crosshairs are.
+ * @opacity: Value between 0 (fully transparent) and 255 (full opaque).
+ */
+ setOpacity: function(opacity) {
+ // set_opacity() throws an exception for values outside the range
+ // [0, 255].
+ if (opacity < 0)
+ opacity = 0;
+ else if (opacity > 255)
+ opacity = 255;
+
+ this._horizLeftHair.set_opacity(opacity);
+ this._horizRightHair.set_opacity(opacity);
+ this._vertTopHair.set_opacity(opacity);
+ this._vertBottomHair.set_opacity(opacity);
+ },
+
+ /**
+ * getOpacity:
+ * Retriev how opaque the crosshairs are.
+ * @return: A value between 0 (transparent) and 255 (opaque).
+ */
+ getOpacity: function() {
+ return this._horizLeftHair.get_opacity();
+ },
+
+ /**
+ * setLength:
+ * Set the length of the vertical and horizontal lines in the crosshairs.
+ * @length: The length of the crosshairs.
+ */
+ setLength: function(length) {
+ this._horizLeftHair.set_width(length);
+ this._horizRightHair.set_width(length);
+ this._vertTopHair.set_height(length);
+ this._vertBottomHair.set_height(length);
+ this.reCenter();
+ },
+
+ /**
+ * getLength:
+ * Get the length of the vertical and horizontal lines in the crosshairs.
+ * @return: The length of the crosshairs.
+ */
+ getLength: function() {
+ return this._horizLeftHair.get_width();
+ },
+
+ /**
+ * setClip:
+ * Set the width and height of the rectangle that clips the crosshairs at
+ * their intersection
+ * @size: Array of [width, height] defining the size of the clip
+ * rectangle.
+ */
+ setClip: function(size) {
+ if (size) {
+ // Take a chunk out of the crosshairs where it intersects the
+ // mouse.
+ this._clipSize = size;
+ this.reCenter();
+ }
+ else {
+ // Restore the missing chunk.
+ this._clipSize = [0, 0];
+ this.reCenter();
+ }
+ },
+
+ /**
+ * getClip:
+ * Get the dimensions of the clip rectangle.
+ * @return: An array of the form [width, height].
+ */
+ getClip: function() {
+ return this._clipSize;
+ },
+
+ /**
+ * show:
+ * Show the crosshairs.
+ */
+ show: function() {
+ this._actor.show();
+ // Clones don't share visibility.
+ for (let i = 0; i < this._clones.length; i++)
+ this._clones[i].show();
+ },
+
+ /**
+ * hide:
+ * Hide the crosshairs.
+ */
+ hide: function() {
+ this._actor.hide();
+ // Clones don't share visibility.
+ for (let i = 0; i < this._clones.length; i++)
+ this._clones[i].hide();
+ },
+
+ /**
+ * reCenter:
+ * Reposition the horizontal and vertical hairs such that they cross at
+ * the center of crosshairs group. If called with the dimensions of
+ * the clip rectangle, these are used to update the size of the clip.
+ * @clipSize: Optional. If present, an array of the form [width, height].
+ */
+ reCenter: function(clipSize) {
+ let [groupWidth, groupHeight] = this._actor.get_size();
+ let leftLength = this._horizLeftHair.get_width();
+ let rightLength = this._horizRightHair.get_width();
+ let topLength = this._vertTopHair.get_height();
+ let bottomLength = this._vertBottomHair.get_height();
+ let thickness = this._horizLeftHair.get_height();
+
+ // Deal with clip rectangle.
+ if (clipSize)
+ this._clipSize = clipSize;
+ let clipWidth = this._clipSize[0];
+ let clipHeight = this._clipSize[1];
+
+ // Note that clip, if present, is not centred on the cross hair
+ // intersection, but biased towards the top left.
+ let left = groupWidth / 2 - clipWidth * 0.25 - leftLength;
+ let right = groupWidth / 2 + clipWidth * 0.75;
+ let top = groupHeight / 2 - clipHeight * 0.25 - topLength - thickness / 2;
+ let bottom = groupHeight / 2 + clipHeight * 0.75 + thickness / 2;
+ this._horizLeftHair.set_position(left, (groupHeight - thickness) / 2);
+ this._horizRightHair.set_position(right, (groupHeight - thickness) / 2);
+ this._vertTopHair.set_position((groupWidth - thickness) / 2, top);
+ this._vertBottomHair.set_position((groupWidth - thickness) / 2, bottom);
+ }
+ }
diff --git a/js/ui/magnifierDBus.js b/js/ui/magnifierDBus.js
new file mode 100644
index 0000000..08a3cee
--- /dev/null
+++ b/js/ui/magnifierDBus.js
@@ -0,0 +1,375 @@
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const DBus = imports.dbus;
+const Main = imports.ui.main;
+
+const MAG_SERVICE_NAME = 'org.gnome.Magnifier';
+const MAG_SERVICE_PATH = '/org/gnome/Magnifier';
+const ZOOM_SERVICE_NAME = 'org.gnome.Magnifier.ZoomRegion';
+const ZOOM_SERVICE_PATH = '/org/gnome/Magnifier/ZoomRegion';
+
+// Subset of gnome-mag's Magnifier dbus interface -- to be expanded. See:
+// http://git.gnome.org/browse/gnome-mag/tree/xml/...Magnifier.xml
+const MagnifierIface = {
+ name: MAG_SERVICE_NAME,
+ methods: [
+ { name: 'setActive', inSignature: 'b', outSignature: '' },
+ { name: 'isActive', inSignature: '', outSignature: 'b' },
+ { name: 'showCursor', inSignature: '', outSignature: '' },
+ { name: 'hideCursor', inSignature: '', outSignature: '' },
+ { name: 'createZoomRegion', inSignature: 'ddaiai', outSignature: 'o' },
+ { name: 'addZoomRegion', inSignature: 'o', outSignature: 'b' },
+ { name: 'getZoomRegions', inSignature: '', outSignature: 'ao' },
+ { name: 'clearAllZoomRegions', inSignature: '', outSignature: '' },
+ { name: 'fullScreenCapable', inSignature: '', outSignature: 'b' },
+
+ { name: 'setCrosswireSize', inSignature: 'i', outSignature: '' },
+ { name: 'getCrosswireSize', inSignature: '', outSignature: 'i' },
+ { name: 'setCrosswireLength', inSignature: 'i', outSignature: '' },
+ { name: 'getCrosswireLength', inSignature: '', outSignature: 'i' },
+ { name: 'setCrosswireClip', inSignature: 'b', outSignature: '' },
+ { name: 'getCrosswireClip', inSignature: '', outSignature: 'b' },
+ { name: 'setCrosswireColor', inSignature: 'u', outSignature: '' },
+ { name: 'getCrosswireColor', inSignature: '', outSignature: 'u' }
+ ],
+ signals: [],
+ properties: []
+};
+
+// Subset of gnome-mag's ZoomRegion dbus interface -- to be expanded. See:
+// http://git.gnome.org/browse/gnome-mag/tree/xml/...ZoomRegion.xml
+const ZoomRegionIface = {
+ name: ZOOM_SERVICE_NAME,
+ methods: [
+ { name: 'setMagFactor', inSignature: 'dd', outSignature: ''},
+ { name: 'getMagFactor', inSignature: '', outSignature: 'dd' },
+ { name: 'setRoi', inSignature: 'ai', outSignature: '' },
+ { name: 'getRoi', inSignature: '', outSignature: 'ai' },
+ { name: 'shiftContentsTo', inSignature: 'ii', outSignature: 'b' },
+ { name: 'moveResize', inSignature: 'ai', outSignature: '' }
+ ],
+ signals: [],
+ properties: []
+};
+
+// For making unique ZoomRegion DBus proxy object paths of the form:
+// "/org/gnome/Magnifier/ZoomRegion/zoomer0",
+// "/org/gnome/Magnifier/ZoomRegion/zoomer1", etc.
+let _zoomRegionInstanceCount = 0;
+
+function ShellMagnifier() {
+ this._init();
+}
+
+ShellMagnifier.prototype = {
+ _init: function() {
+ this._zoomers = {};
+ DBus.session.exportObject(MAG_SERVICE_PATH, this);
+ },
+
+ /**
+ * setActive:
+ * @activate: Boolean to activate or de-activate the magnifier.
+ */
+ setActive: function(activate) {
+ Main.magnifier.setActive(activate);
+ },
+
+ /**
+ * isActive:
+ * @return Whether the magnifier is active (boolean).
+ */
+ isActive: function() {
+ return Main.magnifier.isActive();
+ },
+
+ /**
+ * showCursor:
+ * Show the system mouse pointer.
+ */
+ showCursor: function() {
+ Main.magnifier.showSystemCursor();
+ },
+
+ /**
+ * hideCursor:
+ * Hide the system mouse pointer.
+ */
+ hideCursor: function() {
+ Main.magnifier.hideSystemCursor();
+ },
+
+ /**
+ * createZoomRegion:
+ * Create a new ZoomRegion and return its object path.
+ * @xMagFactor: The power to set horizontal magnification of the
+ * ZoomRegion. A value of 1.0 means no magnification. A
+ * value of 2.0 doubles the size.
+ * @yMagFactor: The power to set the vertical magnification of the
+ * ZoomRegion.
+ * @roi Array of integers defining the region of the
+ * screen/desktop to magnify. The array has the form
+ * [x, y, width, height].
+ * @viewPort Array of integers, [ x, y, width, height ] that defines
+ * the position of the ZoomRegion on screen.
+ * @return The newly created ZoomRegion.
+ */
+ createZoomRegion: function(xMagFactor, yMagFactor, roi, viewPort) {
+ let ROI = { x: roi[0], y: roi[1], width: roi[2], height: roi[3] };
+ let viewBox = { x: viewPort[0], y: viewPort[1], width: viewPort[2], height: viewPort[3] };
+ let realZoomRegion = Main.magnifier.createZoomRegion(xMagFactor, yMagFactor, ROI, viewBox);
+ let objectPath = ZOOM_SERVICE_PATH + "/zoomer" + _zoomRegionInstanceCount;
+ _zoomRegionInstanceCount++;
+
+ let zoomRegionProxy = new ShellMagnifierZoomRegion(objectPath, realZoomRegion);
+ let proxyAndZoomRegion = {};
+ proxyAndZoomRegion.proxy = zoomRegionProxy;
+ proxyAndZoomRegion.zoomRegion = realZoomRegion;
+ this._zoomers[objectPath] = proxyAndZoomRegion;
+ return objectPath;
+ },
+
+ /**
+ * addZoomRegion:
+ * Append the given ZoomRegion to the magnifier's list of ZoomRegions.
+ * @zoomerObjectPath: The object path for the zoom region proxy.
+ */
+ addZoomRegion: function(zoomerObjectPath) {
+ let proxyAndZoomRegion = this._zoomers[zoomerObjectPath];
+ if (proxyAndZoomRegion && proxyAndZoomRegion.zoomRegion) {
+ Main.magnifier.addZoomRegion(proxyAndZoomRegion.zoomRegion);
+ return true;
+ }
+ else
+ return false;
+ },
+
+ /**
+ * getZoomRegions:
+ * Return a list of ZoomRegion object paths for this Magnifier.
+ * @return: The Magnifier's zoom region list as an array of DBus object
+ * paths.
+ */
+ getZoomRegions: function() {
+ // There may be more ZoomRegions in the magnifier itself than have
+ // been added through dbus. Make sure all of them are associated with
+ // an object path and proxy.
+ let zoomRegions = Main.magnifier.getZoomRegions();
+ let objectPaths = [];
+ let thoseZoomers = this._zoomers;
+ zoomRegions.forEach (function(aZoomRegion, index, array) {
+ let found = false;
+ for (let objectPath in thoseZoomers) {
+ let proxyAndZoomRegion = thoseZoomers[objectPath];
+ if (proxyAndZoomRegion.zoomRegion === aZoomRegion) {
+ objectPaths.push(objectPath);
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ // Got a ZoomRegion with no DBus proxy, make one.
+ let newPath = ZOOM_SERVICE_PATH + "/zoomer" + _zoomRegionInstanceCount;
+ _zoomRegionInstanceCount++;
+ let zoomRegionProxy = new ShellMagnifierZoomRegion(newPath, aZoomRegion);
+ let proxyAndZoomer = {};
+ proxyAndZoomer.proxy = zoomRegionProxy;
+ proxyAndZoomer.zoomRegion = aZoomRegion;
+ thoseZoomers[newPath] = proxyAndZoomer;
+ objectPaths.push(newPath);
+ }
+ });
+ return objectPaths;
+ },
+
+ /**
+ * clearAllZoomRegions:
+ * Remove all the zoom regions from this Magnfier's ZoomRegion list.
+ */
+ clearAllZoomRegions: function() {
+ Main.magnifier.clearAllZoomRegions();
+ for (let objectPath in this._zoomers) {
+ let proxyAndZoomer = this._zoomers[objectPath];
+ proxyAndZoomer.proxy = null;
+ proxyAndZoomer.zoomRegion = null;
+ delete this._zoomers[objectPath];
+ DBus.session.unexportObject(proxyAndZoomer);
+ }
+ this._zoomers = {};
+ },
+
+ /**
+ * fullScreenCapable:
+ * Consult if the Magnifier can magnify in full-screen mode.
+ * @return Always return true.
+ */
+ fullScreenCapable: function() {
+ return true;
+ },
+
+ /**
+ * setCrosswireSize:
+ * Set the crosswire size of all ZoomRegions.
+ * @size: The thickness of each line in the cross wire.
+ */
+ setCrosswireSize: function(size) {
+ Main.magnifier.setCrosshairsThickness(size);
+ },
+
+ /**
+ * getCrosswireSize:
+ * Get the crosswire size of all ZoomRegions.
+ * @return: The thickness of each line in the cross wire.
+ */
+ getCrosswireSize: function() {
+ return Main.magnifier.getCrosshairsThickness();
+ },
+
+ /**
+ * setCrosswireLength:
+ * Set the crosswire length of all zoom-regions..
+ * @size: The length of each line in the cross wire.
+ */
+ setCrosswireLength: function(length) {
+ Main.magnifier.setCrosshairsLength(length);
+ },
+
+ /**
+ * setCrosswireSize:
+ * Set the crosswire size of all zoom-regions.
+ * @size: The thickness of each line in the cross wire.
+ */
+ getCrosswireLength: function() {
+ return Main.magnifier.getCrosshairsLength();
+ },
+
+ /**
+ * setCrosswireClip:
+ * Set if the crosswire will be clipped by the cursor image..
+ * @clip: Flag to indicate whether to clip the crosswire.
+ */
+ setCrosswireClip: function(clip) {
+ Main.magnifier.setCrosshairsClip(clip);
+ },
+
+ /**
+ * getCrosswireClip:
+ * Get the crosswire clip value.
+ * @return: Whether the crosswire is clipped by the cursor image.
+ */
+ getCrosswireClip: function() {
+ return Main.magnifier.getCrosshairsClip();
+ },
+
+ /**
+ * setCrosswireColor:
+ * Set the crosswire color of all ZoomRegions.
+ * @color: Unsigned int of the form rrggbbaa.
+ */
+ setCrosswireColor: function(color) {
+ Main.magnifier.setCrosshairsColor('#' + color.toString(16));
+ },
+
+ /**
+ * getCrosswireClip:
+ * Get the crosswire color of all ZoomRegions.
+ * @return: The crosswire color as an unsigned int in the form rrggbbaa.
+ */
+ getCrosswireColor: function() {
+ let colorString = Main.magnifier.getCrosshairsColor();
+ // Drop the leading '#'.
+ return parseInt(colorString.slice(1), 16);
+ }
+}
+
+/**
+ * ShellMagnifierZoomRegion:
+ * Object that implements the DBus ZoomRegion interface.
+ * @zoomerObjectPath: String that is the path to a DBus ZoomRegion.
+ * @zoomRegion: The actual zoom region associated with the object path.
+ */
+function ShellMagnifierZoomRegion(zoomerObjectPath, zoomRegion) {
+ this._init(zoomerObjectPath, zoomRegion);
+}
+
+ShellMagnifierZoomRegion.prototype = {
+ _init: function(zoomerObjectPath, zoomRegion) {
+ this._zoomRegion = zoomRegion;
+ DBus.session.proxifyObject(this, ZOOM_SERVICE_NAME, zoomerObjectPath);
+ DBus.session.exportObject(zoomerObjectPath, this);
+ },
+
+ /**
+ * setMagFactor:
+ * @xMagFactor: The power to set the horizontal magnification factor to
+ * of the magnified view. A value of 1.0 means no
+ * magnification. A value of 2.0 doubles the size.
+ * @yMagFactor: The power to set the vertical magnification factor to
+ * of the magnified view.
+ */
+ setMagFactor: function(xMagFactor, yMagFactor) {
+ this._zoomRegion.setMagFactor(xMagFactor, yMagFactor);
+ },
+
+ /**
+ * getMagFactor:
+ * @return an array, [xMagFactor, yMagFactor], containing the horizontal
+ * and vertical magnification powers. A value of 1.0 means no
+ * magnification. A value of 2.0 means the contents are doubled
+ * in size, and so on.
+ */
+ getMagFactor: function() {
+ return this._zoomRegion.getMagFactor();
+ },
+
+ /**
+ * setRoi:
+ * Sets the "region of interest" that the ZoomRegion is magnifying.
+ * @roi Array, [x, y, width, height], defining the region of the screen to
+ * magnify. The values are in screen (unmagnified) coordinate
+ * space.
+ */
+ setRoi: function(roi) {
+ let roiObject = { x: roi[0], y: roi[1], width: roi[2], height: roi[3] };
+ this._zoomRegion.setROI(roiObject);
+ },
+
+ /**
+ * getRoi:
+ * Retrieves the "region of interest" -- the rectangular bounds of that part
+ * of the desktop that the magnified view is showing (x, y, width, height).
+ * The bounds are given in non-magnified coordinates.
+ * @return an array, [x, y, width, height], representing the bounding
+ * rectangle of what is shown in the magnified view.
+ */
+ getRoi: function() {
+ return this._zoomRegion.getROI();
+ },
+
+ /**
+ * Set the "region of interest" by centering the given screen coordinate
+ * within the zoom region.
+ * @x The x-coord of the point to place at the center of the zoom region.
+ * @y The y-coord.
+ * @return Whether the shift was successful (for GS-mag, this is always
+ * true).
+ */
+ shiftContentsTo: function(x, y) {
+ this._zoomRegion.scrollContentsTo(x, y);
+ return true;
+ },
+
+ /**
+ * moveResize
+ * Sets the position and size of the ZoomRegion on screen.
+ * @viewPort Array, [x, y, width, height], defining the position and size
+ * on screen to place the zoom region.
+ */
+ moveResize: function(viewPort) {
+ let viewRect = { x: viewPort[0], y: viewPort[1], width: viewPort[2], height: viewPort[3] };
+ this._zoomRegion.setViewPort(viewRect);
+ }
+}
+
+DBus.conformExport(ShellMagnifier.prototype, MagnifierIface);
+DBus.conformExport(ShellMagnifierZoomRegion.prototype, ZoomRegionIface);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]