From b89a7ee8b956c96a1dcee995ea840feddc5d4b27 Mon Sep 17 00:00:00 2001 From: Pierre Frisch Date: Thu, 22 Dec 2011 07:25:50 -0800 Subject: First commit of Ninja to ninja-internal Signed-off-by: Valerio Virgillito --- .../montage/ui/controller/array-controller.js | 796 +++++++++++++++++++++ .../montage/ui/controller/media-controller.js | 698 ++++++++++++++++++ .../montage/ui/controller/object-controller.js | 62 ++ .../ui/controller/paged-array-controller.js | 260 +++++++ 4 files changed, 1816 insertions(+) create mode 100755 node_modules/montage/ui/controller/array-controller.js create mode 100755 node_modules/montage/ui/controller/media-controller.js create mode 100755 node_modules/montage/ui/controller/object-controller.js create mode 100755 node_modules/montage/ui/controller/paged-array-controller.js (limited to 'node_modules/montage/ui/controller') diff --git a/node_modules/montage/ui/controller/array-controller.js b/node_modules/montage/ui/controller/array-controller.js new file mode 100755 index 00000000..98b0c66f --- /dev/null +++ b/node_modules/montage/ui/controller/array-controller.js @@ -0,0 +1,796 @@ +/* + This file contains proprietary software owned by Motorola Mobility, Inc.
+ No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
+ (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. +
*/ +/** + @module montage/ui/controller/array-controller + @requires montage/core/core + @requires montage/ui/controller/object-controller + @requires montage/core/event/mutable-event + */ +var Montage = require("montage").Montage, + ObjectController = require("ui/controller/object-controller").ObjectController, + MutableEvent = require("core/event/mutable-event").MutableEvent; +/** + The ArrayController helps with organizing a collection of objects, and managing user selection within that collection. + TYou can assign an ArrayController instance as the contentProvider property for a Montage List or Repetition object. + @class module:montage/ui/controller/array-controller.ArrayController + @classdesc + @extends module:montage/ui/controller/object-controller.ObjectController +*/ +var ArrayController = exports.ArrayController = Montage.create(ObjectController, /** @lends module:montage/ui/controller/array-controller.ArrayController# */ { + + /** + @private + */ + _content: { + enumerable: false, + value: null + }, + /** + The content managed by the ArrayController. + @type {Function} + @default {String} null + */ + content: { + get: function() { + return this._content; + }, + set: function(value) { + + if (this._content === value) { + return; + } + + this._content = value; + + //TODO for right now assume that any content change invalidates the selection completely; we'll need to address this of course + this.selectedObjects = null; + + this.organizeObjects(); + } + }, + + /** + The user-defined delegate object for the ArrayController.
+ If a delegate object exists, the ArrayController will notify the delegate of changes to the collection, or the selection.
+ Currently, the only supported delegate method is shouldChangeSelection, which passes the new and old selected objects.
+ If the function returns false the selection action is canceled. + @type {Object} + @default null + @example + var ArrayController = require("ui/controller/array-controller").ArrayController; + var controller = ArrayController.create(); + controller.delegate = Montage.create(Object.prototype, { + shouldChangeSelection: function(newObj, oldObj) { + console.log("New object is", newObj, "Old object is", oldObj); + } + }) + */ + delegate: { + value: null + }, + + /** + @private + */ + _organizedObjects: { + enumerable: false, + value: null + }, + + /** + The filtered and sorted content of the ArrayCollection. + @type {Function} + @default null + */ + organizedObjects: { + enumerable: false, + get: function() { + + if (this._organizedObjects) { + return this._organizedObjects; + } + + this.organizeObjects(); + + return this._organizedObjects; + } + }, + + /** + Specifies whether the ArrayCollection's content is automatically organized (false, by default). + @type {Property} + @default {Boolean} false + */ + automaticallyOrganizeObjects: { + value: false + }, + + /** + @private + */ + _sortFunction: { + enumerable: false, + value: null + }, + + /** + The sort function used to organize the array collection. + @type {Function} + @default null + @version 1.0 + */ + sortFunction: { + get: function() { + return this._sortFunction; + }, + set: function(value) { + + if (this._sortFunction === value) { + return; + } + + this._sortFunction = value; + + if (this.automaticallyOrganizeObjects) { + this.organizeObjects(); + } + } + }, + + /** + @private + */ + _filterFunction: { + enumerable: false, + value: null + }, + + /** + The filter function used to organize the array collection. + @type {Function} + @default null + @version 1.0 + */ + filterFunction: { + get: function() { + return this._filterFunction; + }, + set: function(value) { + + if (this._filterFunction === value) { + return; + } + + this._filterFunction = value; + + if (this.automaticallyOrganizeObjects) { + this.organizeObjects(); + } + } + }, + + /** + @private + */ + _startIndex: { + enumerable: false, + value: null + }, + + /** + The start index of the organized objects. + @type {Function} + @default null + @version 1.0 + */ + startIndex: { + enumerable: false, + get: function() { + return this._startIndex; + }, + set: function(value) { + + if (this._startIndex === value) { + return; + } + + this._startIndex = value; + + if (this.automaticallyOrganizeObjects) { + this.organizeObjects(); + } + } + }, + + /** + @private + */ + _endIndex: { + enumerable: false, + value: null + }, + + /** + The start index of the organized objects. + @type {Function} + @default null + @version 1.0 + */ + endIndex: { + enumerable: false, + get: function() { + return this._endIndex; + }, + set: function(value) { + + if (this._endIndex === value) { + return; + } + + this._endIndex = value; + + if (this.automaticallyOrganizeObjects) { + this.organizeObjects(); + } + } + }, + + /* + @private + */ + _organizedObjectsIndexes: { + value: null, + enumerable: false + }, + + /* + @private + */ + _rangedOrganizedObjectsIndexes: { + value: null, + enumerable: false + }, + + /** + @private + */ + _selectedIndexes: { + value: null, + enumerable: false + }, + + /** + Description TODO + @type {Function} + @default null + */ + selectedIndexes: { + get: function() { + + if (this._selectedIndexes) { + return this._selectedIndexes; + } + + if (!this._selectedContentIndexes) { + return null; + } + return this._selectedIndexes = this._convertIndexesFromContentToOrganized(this.selectedContentIndexes); + }, + set: function(value) { + if (this._selectedIndexes !== value) { + var newIndexes = value ? this._convertIndexesFromOrganizedToContent(value) : null, + newSelection = null; + if (this.delegate && typeof this.delegate.shouldChangeSelection === "function") { + if (newIndexes) { + newSelection = this.content.filter(function(value, i) { + return newIndexes.indexOf(i) >= 0; + }, this); + } + + if (this.delegate.shouldChangeSelection(this, newSelection, this._selectedObjects) === false) { + return; + } + } + + this._selectedIndexes = value; + var selectedContentIndexesChangeEvent,selectedObjectsChangeEvent; + selectedContentIndexesChangeEvent = MutableEvent.changeEventForKeyAndValue("selectedContentIndexes" , this.selectedContentIndexes); + selectedObjectsChangeEvent = MutableEvent.changeEventForKeyAndValue("selectedObjects" , this._selectedObjects); + + this._selectedContentIndexes = newIndexes; + this._selectedObjects = null; + + this.dispatchEvent(selectedContentIndexesChangeEvent.withPlusValue(this.selectedContentIndexes)); + this.dispatchEvent(selectedObjectsChangeEvent.withPlusValue(this.selectedObjects)); + } + } + }, + + /** + @private + */ + _convertIndexesFromOrganizedToContent: { + value: function(indexes) { + var index, selectedContentIndexes = [], lookup, valueLength = indexes.length, selectedIndex, lookupLength; + // if _rangedOrganizedObjectsIndexes != null we have applied a range + lookup = this._rangedOrganizedObjectsIndexes ? this._rangedOrganizedObjectsIndexes : this._organizedObjectsIndexes; + if (lookup) { + lookupLength = lookup.length; + for (index = 0; index < valueLength ; index++) { + selectedIndex = indexes[index]; + if(selectedIndex < lookupLength && selectedIndex >= 0) { + selectedContentIndexes[selectedContentIndexes.length] = lookup[selectedIndex]; + } + } + } else { + // then it's just a range with not filter or sort + for (index = 0; index < valueLength ; index++) { + selectedContentIndexes[selectedContentIndexes.length] = indexes[index] + this.startIndex; + } + } + return selectedContentIndexes.sort(); + } + }, + + /** + @private + */ + _convertIndexesFromContentToOrganized: { + value: function(indexes) { + var index, selectedOrganizedIndexes = [], lookup, valueLength = indexes.length, selectedIndex; + // if _rangedOrganizedObjectsIndexes != null we have applied a range + lookup = this._rangedOrganizedObjectsIndexes ? this._rangedOrganizedObjectsIndexes : this._organizedObjectsIndexes; + if (lookup) { + for (index = 0; index < valueLength ; index++) { + selectedIndex = indexes[index]; + if (selectedIndex >= 0) { + selectedIndex = lookup.indexOf(selectedIndex); + if (selectedIndex !== -1) { + selectedOrganizedIndexes[selectedOrganizedIndexes.length] = selectedIndex; + } + } + } + } else { + // then it's just a range with not filter or sort + for (index = 0; index < valueLength ; index++) { + selectedIndex = indexes[index] - this.startIndex; + // Check if we are within the range of the current organizedObjects + if(selectedIndex > -1 && (this.endIndex == null || selectedIndex < this.endIndex)) { + selectedOrganizedIndexes[selectedOrganizedIndexes.length] = selectedIndex; + } + } + } + return selectedOrganizedIndexes.sort(); + } + }, + + /** + Organizes the array collection using the filter and sort functions, if defined.
+ Dispatches a change event. + @function + @fires change@organizeObjects + */ + organizeObjects: { + value: function() { + + var organizedObjects = this.content, + funktion = this.filterFunction, + index = 0, + newIndex = 0, + filteredIndexes, + sortedIndexes, + startIndex = this.startIndex, + endIndex = this.endIndex, + tmpArray, item; + + if (organizedObjects && typeof funktion === "function") { + filteredIndexes = []; + organizedObjects = organizedObjects.filter(function filterFunctionWrapper(item) { + var filterValue = funktion.call(this, item); + if (filterValue) { + // we are going to keep the item in the new array + filteredIndexes[newIndex] = index; + newIndex++; + } + index++; + return filterValue; + }, this); + } + + if (typeof (funktion = this._sortFunction) === "function") { + + // need to attach index information so that we can pick it up after the sort + // this has the added side effect of creating a clone of the array so that if it wasn't filtered, + // we are not editing the content directly + sortedIndexes = []; + tmpArray = []; + index = 0; + + for (index = 0; (item = organizedObjects[index]); index++) { + if (item !== null && typeof item === "object") { + // attach the index + item._montage_array_controller_index = index; + tmpArray[index] = item; + } else { + // wrap in a host object + tmpArray[index] = { + _montage_array_controller_index : index, + _montage_array_controller_value : item + }; + } + } + + // Do the sort + tmpArray = tmpArray.sort(function sortFunctionWrapper(a, b) { + if (a._montage_array_controller_value) { + a = a._montage_array_controller_value; + } + if (b._montage_array_controller_value) { + b = b._montage_array_controller_value; + } + return funktion.call(this, a, b); + }); + + // get all the new indexes + organizedObjects = []; + for (index = 0; (item = tmpArray[index]); index++) { + newIndex = item._montage_array_controller_index; + sortedIndexes[index] = filteredIndexes ? filteredIndexes[newIndex] : newIndex; + if (item._montage_array_controller_value) { + organizedObjects[index] = item._montage_array_controller_value; + } else { + organizedObjects[index] = item; + delete item._montage_array_controller_index; + } + } + // store it for later use + this._organizedObjectsIndexes = sortedIndexes; + } else { + // store it for later use + this._organizedObjectsIndexes = filteredIndexes; + } + + this._applyRangeIfNeeded(organizedObjects); + } + }, + + /** + @private + */ + _applyRangeIfNeeded: { + value: function(organizedObjects) { + + var startIndex = this.startIndex, + endIndex = this.endIndex, + changeEvent; + + + // We apply the range after the content is filtered and sorted + if (organizedObjects && (typeof startIndex === "number" || typeof endIndex === "number")) { + startIndex = typeof startIndex === "number" && startIndex >= 0 ? startIndex : 0; + endIndex = typeof endIndex === "number" && endIndex < organizedObjects.length ? endIndex : organizedObjects.length; + // apply the range + organizedObjects = organizedObjects.slice(startIndex, endIndex); + // store the index lookup change + if(this._organizedObjectsIndexes) { + this._rangedOrganizedObjectsIndexes = this._organizedObjectsIndexes.slice(startIndex, endIndex); + } else { + this._rangedOrganizedObjectsIndexes = null; + } + } + + // I don't want to provide a setter for organizedObjects, so update the cached value and notify observers that + // the organizedObjects property changed + changeEvent = MutableEvent.changeEventForKeyAndValue("organizedObjects" , this._organizedObjects ? this._organizedObjects.slice(0) : this._organizedObjects); + + this._organizedObjects = organizedObjects ? organizedObjects : []; + + this.dispatchEvent(changeEvent.withPlusValue(this._organizedObjects)); + } + }, + + /** + @private + */ + _selectedObjects: { + enumerable: false, + value: null + }, + + /** + Gets or sets the selected objects in the collection.
+ Setting the selected objects to a new value fires an event of type change@selectedObjects. + @type {Function} + @default null + @fires change@selectedContentIndexes + */ + selectedObjects: { + enumerable: false, + get: function() { + + if (this._selectedObjects) { + return this._selectedObjects; + } + + if (!this._selectedContentIndexes) { + return null; + } + + this._selectedObjects = this.content.filter(function(value, i) { + return this._selectedContentIndexes.indexOf(i) >= 0; + }, this); + + return this._selectedObjects; + }, + set: function(value) { + + // Normalizing the value before the difference check prevents false-positive "changes" for things like [x]=>x + if (value === null || typeof value === "undefined") { + // undefined => null + value = null; + } else if (!Array.isArray(value)) { + // any single object, including false and zero + value = [value]; + } + + // TODO validate the array content maybe? + + if (this._selectedObjects === value) { + return; + } + + if (this.delegate && typeof this.delegate.shouldChangeSelection === "function") { + if (this.delegate.shouldChangeSelection(this, value, this._selectedObjects) === false) { + return; + } + } + this._selectedObjects = value; + + var selectedContentIndexesChangeEvent, + selectedIndexesChangeEvent; + + selectedContentIndexesChangeEvent = MutableEvent.changeEventForKeyAndValue("selectedContentIndexes" , this._selectedContentIndexes); + selectedIndexesChangeEvent = MutableEvent.changeEventForKeyAndValue("selectedIndexes" , this._selectedIndexes); + + this._selectedContentIndexes = null; + this._selectedIndexes = null; + + this.dispatchEvent(selectedContentIndexesChangeEvent.withPlusValue(this.selectedContentIndexes)); + this.dispatchEvent(selectedIndexesChangeEvent.withPlusValue(this.selectedIndexes)); + } + }, + + /** + @private + */ + _selectedContentIndexes: { + enumerable: false, + value: null + }, + + /** + Gets or sets the indexes of the currently selected items in the collection.
+ When set to a new set of indexes, generates a event of type "change@selectedObjects". + @type {Function} + @default null + */ + selectedContentIndexes: { + enumerable: false, + get: function() { + + if (this._selectedContentIndexes) { + return this._selectedContentIndexes; + } + + if (!this._selectedObjects) { + return null; + } + + this._selectedContentIndexes = []; + var selectedIndex; + this._selectedObjects.forEach(function(value) { + if((selectedIndex = this.content.indexOf(value)) !== -1) { + this._selectedContentIndexes.push(selectedIndex); + } + }, this); + + return this._selectedContentIndexes; + }, + set: function(value) { + var selectedIndexesChangeEvent, + selectedObjectsChangeEvent; + + // Normalizing the value before the difference check prevents false-positive "changes" for things like [x]=>x + if (value === null || value === false || typeof value === "undefined") { + // undefined, false => null + value = null; + } else if (!Array.isArray(value)) { + // any single index, including zero + value = [value]; + } + + // TODO validate the array content maybe? + + if (this._selectedContentIndexes === value) { + return; + } + + if (this.delegate && typeof this.delegate.shouldChangeSelection === "function") { + var newIndexes = value, newSelection = null; + + if (newIndexes) { + newSelection = this.content.filter(function(value, i) { + return newIndexes.indexOf(i) >= 0; + }, this); + } + + if (this.delegate.shouldChangeSelection(this, newSelection, this._selectedObjects) === false) { + return; + } + } + + this._selectedContentIndexes = value; + + selectedIndexesChangeEvent = MutableEvent.changeEventForKeyAndValue("selectedIndexes" , this._selectedIndexes); + selectedObjectsChangeEvent = MutableEvent.changeEventForKeyAndValue("selectedObjects" , this._selectedObjects); + + this._selectedIndexes = null; + this._selectedObjects = null; + + this.dispatchEvent(selectedIndexesChangeEvent.withPlusValue(this.selectedIndexes)); + this.dispatchEvent(selectedObjectsChangeEvent.withPlusValue(this.selectedObjects)); + } + }, + + /** + Initalizes the ArrayController with the specified content. + @function + @param {Object} content The collection of objects for the ArrayController to manage. + @returns {ArrayController} + */ + + initWithContent: { + value: function(content) { + this.content = content; + return this; + } + }, + + /** + A Boolean that specifies whether new objects that are added to the array collection are automatically selected. + @type {Property} + @default {Boolean} false + */ + selectObjectsOnAddition: { + enumerable: false, + value: false + }, + + /** + A Boolean that specifies whether the filter function is set to null when new objects that are added to the content. + @type {Property} + @default {Boolean} true + */ + clearFilterFunctionOnAddition: { + enumerable: false, + value: true + }, + + /** + Adds an item to the array collection's content property. + @function + @example + var ArrayCollection = require("ui/controller/array-controller").ArrayCollection; + var ac = ArrayCollection.create(); + ac.add({name: "John"}); + */ + add: { + value: function() { + + var newObject = this.newObject(); + this.content.push(newObject); + + //TODO fdf what do we do with the filterFunction if the newObject wouldn't show? + if (this.selectObjectsOnAddition) { + this.selectedContentIndexes = [this.content.length-1]; + } + if (this.clearFilterFunctionOnAddition) { + this.filterFunction = null; + } else { + + } + + if (this.automaticallyOrganizeObjects) { + this.organizeObjects(); + } + } + }, + + /** + Adds one or more items to the array controller's content property. + @function + @example + var ArrayCollection = require("ui/controller/array-controller").ArrayCollection; + var ac = ArrayCollection.create(); + ac.addObjects( {label: "News"}, {label: "Sports"}, {label: "Weather"} ); + */ + addObjects: { + value: function() { + + var objects = Array.prototype.slice.call(arguments), + i, + objectCount = objects.length, + selectedContentIndexes, firstIndex; + + for (i = 0; i < objectCount; i++) { + this.content.push(objects[i]); + } + + if (this.selectObjectsOnAddition) { + selectedContentIndexes = []; + firstIndex = this.content.length-objectCount; + for (i = 0; i < objectCount; i++) { + selectedContentIndexes[i] = firstIndex++; + } + this.selectedContentIndexes = selectedContentIndexes; + this.selectedObjects = objects; + } + + if (this.clearFilterFunctionOnAddition) { + this.filterFunction = null; + } + + if (this.automaticallyOrganizeObjects) { + this.organizeObjects(); + } + } + }, + + /** + Removes the currently selected item or items in the collection. + @function + */ + remove: { + value: function() { + + if (this.selectedObjects && this.selectedObjects.length > 0) { + this.removeObjects.apply(this, this.selectedObjects); + + if (this.automaticallyOrganizeObjects) { + this.organizeObjects(); + } + } + // TODO what do we want to do otherwise? + } + }, + + /** + Removes the specified object or objects from the collection. + @function + */ + removeObjects: { + value: function() { + var objectsToRemove = Array.prototype.slice.call(arguments), + remainingObjects; + + // TODO what do we do if there are no arguments? (should this be where we solve the problem of calling remove with no selection?) + + remainingObjects = this.content.filter(function(value) { + return objectsToRemove.indexOf(value) < 0; + }); + + this.content = remainingObjects; + + //TODO abandon selection? preserve what we can of the selection? + + if (this.automaticallyOrganizeObjects) { + this.organizeObjects(); + } + + } + } + +}); diff --git a/node_modules/montage/ui/controller/media-controller.js b/node_modules/montage/ui/controller/media-controller.js new file mode 100755 index 00000000..0663a526 --- /dev/null +++ b/node_modules/montage/ui/controller/media-controller.js @@ -0,0 +1,698 @@ +/* + This file contains proprietary software owned by Motorola Mobility, Inc.
+ No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
+ (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. +
*/ +/** + @module montage/ui/controller/media-controller + @requires montage/core/core + @requires montage/ui/component + @requires core/logger +*/ +var Montage = require("montage").Montage; +var Component = require("ui/component").Component; +var logger = require("core/logger").logger("mediacontroller"); +/** + @class module:montage/ui/controller/media-controller.MediaController + @classdesc Controls an audio/video media player. + @extends module:montage/core/core.Montage + + */ +var MediaController = exports.MediaController = Montage.create(Montage, /** @lends module:montage/ui/controller/media-controller.MediaController# */ { + /*----------------------------------------------------------------------------- + MARK: Constants + -----------------------------------------------------------------------------*/ + /** + Description TODO + @type {Property} + @default {Number} 0 + */ + STOPPED: { enumerable: true, value: 0, writable: false }, + /** + Description TODO + @type {Property} + @default {Number} 1 + */ + PLAYING: { enumerable: true, value: 1, writable: false }, + /** + Description TODO + @type {Property} + @default {Number} 2 + */ + PAUSED: { enumerable: true, value: 2, writable: false }, + /** + Description TODO + @type {Property} + @default {Number} 3 + */ + EMPTY: { enumerable: true, value: 3, writable: false }, +/** + Description TODO + @private +*/ + _TIMEUPDATE_FREQUENCY: { value: 0.25 }, // Don't refresh too often. + /*----------------------------------------------------------------------------- + MARK: Properties + -----------------------------------------------------------------------------*/ +/** + Description TODO + @private +*/ + _mediaElement: { + value: null, + enumerable: false + }, + /** + Description TODO + @type {Function} + @default null + */ + mediaElement: { + get : function() { + return this._mediaElement; + }, + set : function(elem) { + this._mediaElement = elem; + }, + enumerable: false + }, +/** + Description TODO + @private +*/ + _mediaSrc: { + value: null, + enumerable: false + }, +/** + Description TODO + @type {Function} + @default null + */ + mediaSrc: { + get: function() { + return this._mediaSrc; + }, + set: function(mediaSrc) { + this._mediaSrc = mediaSrc; + } + }, + /*----------------------------------------------------------------------------- + MARK: Status & Attributes + -----------------------------------------------------------------------------*/ +/** + Description TODO + @private +*/ + _status: { + enumerable: false, + value: 3 + }, + /** + Description TODO + @type {Function} + @default {Number} 3 + */ + status: { + enumerable: false, + get: function() { + return this._status; + }, + set: function(status) { + if (status !== this._status) { + this._status = status; + this._dispatchStateChangeEvent(); + } + } + }, +/** + Description TODO + @private +*/ + _position: { value:null, enumerable:false }, + +/** + Description TODO + @type {Function} + @default null + */ + position: { + set: function(time, shouldNotUpdate) { + this._position = time; + if (!shouldNotUpdate) { + this.currentTime = time; + } + }, + get: function() { + return this._position; + } + }, +/** + Description TODO + @private +*/ + _duration: { value: null, enumerable:false }, +/** + Description TODO + @type {Function} + @default null + */ + duration: { + set: function(time) { + if (isNaN(time)) { + if (logger.isDebug) { + logger.debug("MediaController:setDuration: duration is not valid"); + } + return; + } + if (logger.isDebug) { + logger.debug("MediaController:setDuration: duration=" + time); + } + this._duration = time; + }, + get: function() { + return this._duration; + } + }, + /*----------------------------------------------------------------------------- + MARK: Media Player Commands + -----------------------------------------------------------------------------*/ +/** + Description TODO + @type {Property} + @default {Boolean} true + */ + autoplay: { + enumerable: false, + value: true + }, +/** + Description TODO + @function + */ + play: { + value: function() { + if (logger.isDebug) { + logger.debug("MediaController:play()"); + } + this.mediaElement.play(); + } + }, +/** + Description TODO + @function + */ + pause: { + value: function() { + if (logger.isDebug) { + logger.debug("MediaController:pause()"); + } + this.mediaElement.pause(); + } + }, +/** + Description TODO + @function + @returns {Boolean} !playing (true if it is now playing) + */ + playPause: { + value: function() { + if (logger.isDebug) { + logger.debug("MediaController:playPause"); + } + + var playing = (this.status === this.PLAYING); + this.playbackRate = this.mediaElement.defaultPlaybackRate; + if (playing) { + this.pause(); + } else { + this.play(); + } + return !playing; // true if it is now playing + } + }, +/** + Description TODO + @private +*/ + _playbackRate: { + value: 1, + enumerable: false + }, +/** + Description TODO + @type {Function} + @default {Number} 1 + */ + playbackRate: { + get: function() { + return this._playbackRate; + }, + set: function(playbackRate) { + if (this._playbackRate !== playbackRate) { + this._playbackRate = playbackRate; + this.mediaElement.playbackRate = this._playbackRate; + } + } + }, +/** + Description TODO + @private +*/ + _currentTime: { + value: 0, + enumerable: false + }, +/** + Description TODO + @private +*/ + _updateCurrentTime: { + value: false, + enumerable: false + }, +/** + Description TODO + @type {Function} + @default {Number} 0 + */ + currentTime: { + get: function() { + return this.mediaElement.currentTime; + }, + + set: function(currentTime) { + try { + if (isNaN(this.mediaElement.duration)) { + logger.error("MediaController:set currentTime: duration is not valid"); + return; + } + if (logger.isDebug) { + logger.debug("current time:" + this.mediaElement.currentTime + " new time is" + currentTime); + } + this.mediaElement.currentTime = currentTime; + } + catch(err) { + logger.error("MediaController:Exception in set currentTime" + this.mediaElement.currentTime); + } + } + }, +/** + Description TODO + @function + */ + rewind: { + value: function() { + if (this.status === this.PLAYING) { + if (logger.isDebug) { + logger.debug("MediaController:rewind"); + } + this.playbackRate = -4.0; + } + } + }, +/** + Description TODO + @function + */ + fastForward: { + value: function() { + if (this.status === this.PLAYING) { + if (logger.isDebug) { + logger.debug("MediaController:fastForward"); + } + this.playbackRate = 4.0; + } + } + }, +/** + Description TODO + @function + */ + stop: { + value: function() { + if (logger.isDebug) { + logger.debug("MediaController:stop"); + } + + // Pause the playback + if (this.status === this.PLAYING) { + if (logger.isDebug) { + logger.debug("MediaController:stop while PLAYING: will pause"); + } + this.pause(); + } + + // Reset the status + this.status = this.STOPPED; + } + }, +/** + Description TODO + @function + */ + reset: { + value: function() { + if (logger.isDebug) { + logger.debug("MediaController:reset"); + } + if (this.status !== this.STOPPED) { + this.stop(); + } + + // Clear the movie + this.mediaElement.removeAttribute('src'); + } + }, +/** + Description TODO + @function + */ + loadMedia: { + value: function() { + if (logger.isDebug) { + logger.debug("MediaController:loadMedia"); + } + + this.mediaElement.src = this.mediaSrc; + this.mediaElement.load(); + } + }, +/** + Description TODO + @function + */ + toggleRepeat: { + value: function() { + this.repeat = !this.repeat; + } + }, +/** + Description TODO + @private +*/ + _repeat: { + value: false, + enumerable: false + }, +/** + Description TODO + @type {Function} + @default {Boolean} false + */ + repeat: { + get: function() { + return this._repeat; + }, + + set: function(repeat) { + if (repeat !== this._repeat) { + this._repeat = repeat; + if (repeat) { + this.mediaElement.setAttribute("loop", "true"); + } else { + this.mediaElement.removeAttribute("loop"); + } + this._dispatchStateChangeEvent(); + } + } + }, + /*----------------------------------------------------------------------------- + MARK: Volume Commands + -----------------------------------------------------------------------------*/ +/** + Description TODO + @type {Function} + @returns {Number} this.mediaElement.volume * 100 + */ + volume: { + get: function() { + return this.mediaElement.volume * 100; + }, + + set: function(vol) { + var volume = vol; + if (typeof volume === 'undefined') { + volume = 50; + } + else if (volume > 100) { + volume = 100; + } + else if (volume < 0) { + volume = 0; + } + this.mediaElement.volume = volume / 100.0; + this._dispatchStateChangeEvent(); + } + }, +/** + Description TODO + @function + */ + volumeIncrease: { + value: function() { + this.volume += 10; + } + }, +/** + Description TODO + @function + */ + volumeDecrease: { + value: function() { + this.volume -= 10; + } + }, +/** + Description TODO + @function + */ + toggleMute: { + value: function() { + this.mute = !this.mute; + } + }, +/** + Description TODO + @type {Function} + */ + mute: { + get: function() { + return this.mediaElement.muted; + }, + + set: function(muted) { + if (muted !== this.mediaElement.muted) { + this.mediaElement.muted = muted; + } + } + }, + /*----------------------------------------------------------------------------- + MARK: Event Handlers + -----------------------------------------------------------------------------*/ +/** + Description TODO + @function + @returns itself + */ + handleLoadedmetadata: { + value: function() { + if (logger.isDebug) { + logger.debug("MediaController:handleLoadedmetadata: PLAYING=" + (this.status === this.PLAYING) + " duration=" + this.mediaElement.duration); + } + if (isNaN(this.mediaElement.duration)) { + if (logger.isDebug) { + logger.debug("MediaController:handleLoadedmetadata: duration is not valid"); + } + return; + } + this.duration = this.mediaElement.duration; + if (this.autoplay) { + this.play(); + } else { + this.status = this.PAUSED; + } + } + }, +/** + Description TODO + @private +*/ + _lastCurrentTime: { + writable: true, + value: 0 + }, +/** + Description TODO + @function + */ + handleTimeupdate: { + value: function() { + if (this.status !== this.STOPPED) { // A last 'timeupdate' is sent after stop() which is unwanted because it restores the last position. + var currentTime = this.mediaElement.currentTime; + if (Math.abs(this._lastCurrentTime - currentTime) >= this._TIMEUPDATE_FREQUENCY) { + this._lastCurrentTime = currentTime; + Object.getPropertyDescriptor(this, "position").set.call(this, currentTime, true); + } + } + } + }, + +/** + Description TODO + @function + */ + handlePlay: { + value: function() { + if (logger.isDebug) { + logger.debug("MediaController:Play"); + } + this.status = this.PLAYING; + } + }, +/** + Description TODO + @function + */ + handlePlaying: { + value: function() { + if (logger.isDebug) { + logger.debug("MediaController:handlePlaying: PLAYING"); + } + this.status = this.PLAYING; + } + }, +/** + Description TODO + @function + */ + handlePause: { + value: function() { + if (this.status !== this.STOPPED) { + if (logger.isDebug) { + logger.debug("MediaController:handlePause: PAUSED"); + } + this.status = this.PAUSED; + } + else { + if (logger.isDebug) { + logger.debug("MediaController:handlePause: STOPPED"); + } + } + } + }, +/** + Description TODO + @function + */ + handleEnded: { + value: function() { + if (logger.isDebug) { + logger.debug("MediaController:handleEnded"); + } + this.status = this.STOPPED; + // If the mediaElement is not in the paused=true state + // then it won't fire a play event when you start playing again + this.mediaElement.pause(); + } + }, +/** + Description TODO + @function + */ + handleAbort: { + value: function() { + if (logger.isDebug) { + logger.debug("MediaController:handleAbort: STOPPED"); + } + this.status = this.STOPPED; + } + }, +/** + Description TODO + @function + @param {Event} event TODO + */ + handleError: { + value: function(event) { + if (logger.isDebug) { + logger.debug("MediaController:handleError: STOPPED"); + } + var error = event.target.error; + + this.status = this.STOPPED; + + if (error) { + switch (error.code) { + case error.MEDIA_ERR_ABORTED: + alert("You aborted the video playback."); + break; + case error.MEDIA_ERR_NETWORK: + alert("A network error caused the video download to fail part-way."); + break; + case error.MEDIA_ERR_DECODE: + alert("The video playback was aborted due to a corruption problem or because the video used features your browser did not support."); + break; + case error.MEDIA_ERR_SRC_NOT_SUPPORTED: + if (this.mediaElement.src.length > 0) { + alert("The video at " + this.mediaElement.src + " could not be loaded, either because the server or network failed or because the format is not supported."); + } + else { + alert("No video has been selected."); + } + break; + default: + alert("An unknown error occurred."); + break; + } + } + this._isFullScreen = false; + } + }, +/** + Description TODO + @function + */ + handleEmptied: { + value: function() { + if (logger.isDebug) { + logger.debug("MediaController:handleEmptied: STOPPED"); + } + this.status = this.STOPPED; + } + }, +/** + Description TODO + @private +*/ + _dispatchStateChangeEvent: { + value: function() { + var stateEvent = window.document.createEvent("CustomEvent"); + stateEvent.initCustomEvent("mediaStateChange", true, true, null); + this.dispatchEvent(stateEvent); + } + }, +/** + Description TODO + @private +*/ + _installControlEventHandlers: { + value: function() { + this.mediaElement.addEventListener('loadedmetadata', this, false); + this.mediaElement.addEventListener('timeupdate', this, false); + this.mediaElement.addEventListener('play', this, false); + this.mediaElement.addEventListener('playing', this, false); + this.mediaElement.addEventListener('pause', this, false); + this.mediaElement.addEventListener('abort', this, false); + this.mediaElement.addEventListener('error', this, false); + this.mediaElement.addEventListener('emptied', this, false); + this.mediaElement.addEventListener('ended', this, false); + }, + enumerable: false + } + /*----------------------------------------------------------------------------- + MARK: Configuration + -----------------------------------------------------------------------------*/ + + +}); diff --git a/node_modules/montage/ui/controller/object-controller.js b/node_modules/montage/ui/controller/object-controller.js new file mode 100755 index 00000000..54e32231 --- /dev/null +++ b/node_modules/montage/ui/controller/object-controller.js @@ -0,0 +1,62 @@ +/* + This file contains proprietary software owned by Motorola Mobility, Inc.
+ No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
+ (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. +
*/ +/** + @module montage/ui/controller/object-controller + @requires montage/core/core + */ +var Montage = require("montage").Montage; +/** + @class module:montage/ui/controller/object-controller.ObjectController + @classdesc Generic object controller. + @extends module:montage/core/core.Montage + */ +var ObjectController = exports.ObjectController = Montage.create(Montage, /** @lends module:montage/ui/controller/object-controller.ObjectController# */ { +/** + Description TODO + @type {Property} + @default null + */ + objectPrototype: { + enumerable: false, + value: null + }, + /** + Description TODO + @function + @returns this.objectPrototype.create() + */ + newObject: { + enumerable: false, + value: function() { + return this.objectPrototype.create(); + } + }, + + /** + Description TODO + @function + @param {Property} content TODO + @returns itself + */ + initWithContent: { + value: function(content) { + this.content = content; + return this; + } + }, + /** + Description TODO + @type {Property} + @default null + */ + content: { + enumerable: false, + value: null + } + + + +}); diff --git a/node_modules/montage/ui/controller/paged-array-controller.js b/node_modules/montage/ui/controller/paged-array-controller.js new file mode 100755 index 00000000..80642467 --- /dev/null +++ b/node_modules/montage/ui/controller/paged-array-controller.js @@ -0,0 +1,260 @@ +/* + This file contains proprietary software owned by Motorola Mobility, Inc.
+ No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
+ (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. +
*/ +/** + @module montage/ui/controller/paged-array-controller + @requires montage/core/core + @requires ui/controller/array-controller + @requires core/event/mutable-event +*/ +var Montage = require("montage").Montage, + ArrayController = require("ui/controller/array-controller").ArrayController, + MutableEvent = require("core/event/mutable-event").MutableEvent; +/** + @class module:montage/ui/controller/paged-array-controller.PagedArrayController + @extends module:montage/ui/controller/array-controller.ArrayController +*/ +exports.PagedArrayController = Montage.create(ArrayController,/** @lends module:montage/ui/controller/paged-array-controller.PagedArrayController# */ { +/** + Description TODO + @private +*/ + _pageSize: { + value: 10 + }, + //changing this should recalculate pageIndex to contain first item of previous current page + // via gotoOffset ? +/** + Description TODO + @type {Function} + @default {Number} 10 + */ + pageSize: { + get: function() { + return this._pageSize; + }, + set: function(value) { + if (value !== this._pageSize) { + this._calculate(value, this._pageIndex, this._padding); + } + } + }, + +/** + Description TODO + @private +*/ + _padding: { + value: 0 + }, + // changing this should change the organized object to have the new padding +/** + Description TODO + @type {Function} + @default {Number} 0 + */ + padding: { + get: function() { + return this._padding; + }, + set: function(value) { + if (value !== this._padding) { + this._calculate(this._pageSize, this._pageIndex, value); + } + } + }, +/** + Description TODO + @private +*/ + _pageIndex: { + value: 0, + enumerable: false + }, + // changing this should change the start and endIndex +/** + Description TODO + @type {Function} + @default {Number} 0 + */ + pageIndex: { + get: function() { + return this._pageIndex; + }, + set: function(pageIndex) { + if (pageIndex !== this._pageIndex) { + pageIndex = pageIndex >= this.pageCount ? this.pageCount - 1 : pageIndex < 0 ? 0 : pageIndex; + this._calculate(this._pageSize, pageIndex, this._padding); + } + } + }, + + /** + Description TODO + @private +*/ + _pageCount: { + value: null, + enumerable: false + }, +/** + Description TODO + @type {Function} + @default null + */ + pageCount: { + get: function() { + return this._pageCount; + } + }, + +/** + Description TODO + @type {Function} + @default {Array} array + */ + pageNumber: { + dependencies: ["pageIndex"], + get: function() { + return this._pageIndex + 1; + } + }, + + /** + Description TODO + @private +*/ + _calculate: { + value: function(pageSize, pageIndex, padding) { + var contentLength = this.content.length, + pageCount = this._pageCount, + startIndex, endIndex, + pageSizeChangeEvent, + pageIndexChangeEvent, + paddingChangeEvent, + pageCountChangeEvent, + startIndexChangeEvent, + endIndexChangeEvent; + + if (pageSize !== this._pageSize) { + pageSizeChangeEvent = MutableEvent.changeEventForKeyAndValue("pageSize", this._pageSize); + this._pageSize = pageSize; + } + if (pageIndex !== this._pageIndex) { + pageIndexChangeEvent = MutableEvent.changeEventForKeyAndValue("pageIndex", this._pageIndex); + this._pageIndex = pageIndex; + } + if (padding !== this._padding) { + paddingChangeEvent = MutableEvent.changeEventForKeyAndValue("padding", this._padding); + this._padding = padding; + } + + pageCountChangeEvent = MutableEvent.changeEventForKeyAndValue("pageCount", pageCount); + this._pageCount = pageCount = Math.ceil(contentLength / pageSize); + + startIndexChangeEvent = MutableEvent.changeEventForKeyAndValue("startIndex", this._startIndex); + startIndex = pageIndex * pageSize - padding; + this._startIndex = startIndex = startIndex < 0 ? 0 : startIndex; + + endIndexChangeEvent = MutableEvent.changeEventForKeyAndValue("endIndex", this._endIndex); + endIndex = pageIndex * pageSize + pageSize + padding; + this._endIndex = endIndex = endIndex > contentLength ? contentLength : endIndex; + + if (pageCountChangeEvent.minus !== pageCount) { + this.dispatchEvent(pageCountChangeEvent.withPlusValue(pageCount)); + } + if (startIndexChangeEvent.minus !== startIndex) { + this.dispatchEvent(startIndexChangeEvent.withPlusValue(startIndex)); + } + if (endIndexChangeEvent.minus !== endIndex) { + this.dispatchEvent(endIndexChangeEvent.withPlusValue(endIndex)); + } + if (pageSizeChangeEvent) { + this.dispatchEvent(pageSizeChangeEvent.withPlusValue(pageSize)); + } + if (pageIndexChangeEvent) { + this.dispatchEvent(pageIndexChangeEvent.withPlusValue(pageIndex)); + } + if (paddingChangeEvent) { + this.dispatchEvent(paddingChangeEvent.withPlusValue(padding)); + } + } + }, +/** + Description TODO + @function + */ + gotoFirstPage: { + value: function() { + this.gotoPage(0); + } + }, +/** + Description TODO + @function + */ + gotoLastPage: { + value: function() { + this.gotoPage(this.pageCount - 1); + } + }, +/** + Description TODO + @function + */ + gotoNextPage: { + value: function() { + this.gotoPage(this.pageIndex + 1); + } + }, +/** + Description TODO + @function + */ + gotoPreviousPage: { + value: function() { + this.gotoPage(this.pageIndex - 1); + } + }, +/** + Sets the currentPage so that the desired item is contained in that page. + @function + @param {Array} pageIndex The index of the item to be shown. + */ + gotoPage: { + value: function(pageIndex) { + this.pageIndex = pageIndex; + } + }, +/** + Sets the currentPage so that the desired item is contained in that page. + @function + @param {Math} offset The index of the item to be shown. + */ + gotoContentIndex: { + value: function(offset) { + this.gotoPage(Math.floor(offset / this.pageSize)); + } + }, + + /** + Description TODO + @type {Function} + @default null + */ + content: { + get: function() { + return Object.getPropertyDescriptor(ArrayController, "content").get.call(this); + }, + set: function(value) { + if (value !== this.content) { + Object.getPropertyDescriptor(ArrayController, "content").set.call(this, value); + this._calculate(this._pageSize, this._pageIndex, this._padding); + } + } + } + + +}); -- cgit v1.2.3