| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | /********************************************************************** | 
					
						
							|  |  |  | *  | 
					
						
							|  |  |  | * | 
					
						
							|  |  |  | * | 
					
						
							|  |  |  | **********************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:22:24 +04:00
										 |  |  | // General format info...
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:03:52 +04:00
										 |  |  | //
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:32:32 +04:00
										 |  |  | // Version format:
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:03:52 +04:00
										 |  |  | // 	<major>.<minor>
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Major version change mean a significant incompatibility.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Minor version changes mean some detail changed and can be handled
 | 
					
						
							|  |  |  | // by it's specific handler seamlessly. Backwards compatible.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // For more info see:
 | 
					
						
							|  |  |  | // 	DATA			- main data
 | 
					
						
							|  |  |  | // 	IMAGES			- image data
 | 
					
						
							|  |  |  | // 	MARKED			- marks data
 | 
					
						
							|  |  |  | // 	BOOKMARKS		- bookmarks data
 | 
					
						
							|  |  |  | // 	BOOKMARKS_DATA	- bookmarks metadata
 | 
					
						
							|  |  |  | // 	TAGS			- tag data
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:32:32 +04:00
										 |  |  | // Data format change history:
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:03:52 +04:00
										 |  |  | // 	none - Gen1 data format, mostly experimental,
 | 
					
						
							|  |  |  | // 			- has no explicit version set,
 | 
					
						
							|  |  |  | // 			- not used for real data.
 | 
					
						
							|  |  |  | // 	2.0 - Gen3 data format, still experimental,
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:22:24 +04:00
										 |  |  | // 			- completely new and incompatible structure,
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:03:52 +04:00
										 |  |  | // 			- use convertDataGen1(..) to convert Gen1 to 2.0 
 | 
					
						
							|  |  |  | // 			- used for my archive, not public,
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:22:24 +04:00
										 |  |  | // 			- auto-convert form gen1 on load...
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:03:52 +04:00
										 |  |  | // 	2.1 - Minor update to format spec,	
 | 
					
						
							|  |  |  | // 			- MARKED now maintained sorted, live,
 | 
					
						
							|  |  |  | // 			- will auto-sort marks on load of 2.0 data and change 
 | 
					
						
							|  |  |  | // 			  data version to 2.1, will need a re-save,
 | 
					
						
							|  |  |  | // 	2.2 - Minor update to how data is handled and saved
 | 
					
						
							|  |  |  | // 			- now DATA.current is saved separately in current.json,
 | 
					
						
							|  |  |  | // 			  loading is done from current.json and if not found from
 | 
					
						
							|  |  |  | // 			  data.json.
 | 
					
						
							|  |  |  | // 			  the file is optional.
 | 
					
						
							|  |  |  | // 			- data, marks, bookmarks, tags are now saved only if updated
 | 
					
						
							|  |  |  | // 	2.3 - Minor update to sorting restrictions
 | 
					
						
							|  |  |  | // 			- now MARKED and BOOKMARKS do not need to be sorted 
 | 
					
						
							|  |  |  | // 			  explicitly in json, they are now sorted as a side-effect 
 | 
					
						
							|  |  |  | // 			  of being sparse.
 | 
					
						
							|  |  |  | // 			  This negates some restrictions posed in 2.1, including 
 | 
					
						
							|  |  |  | // 			  conversion of 2.0 data.
 | 
					
						
							|  |  |  | // 			  NOTE: TAGS gid sets are still compact lists, thus are 
 | 
					
						
							|  |  |  | // 			  		actively maintained sorted.
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:22:24 +04:00
										 |  |  | // 			  		...still thinking of whether making them sparse is
 | 
					
						
							|  |  |  | // 			  		worth the work...
 | 
					
						
							|  |  |  | // 	3.0	- Gen4 DATA format, introduced several backwards incompatible 
 | 
					
						
							|  |  |  | // 		cahnges:
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:03:52 +04:00
										 |  |  | // 			- added ribbon GIDs, .ribbons now is a gid indexed object
 | 
					
						
							|  |  |  | // 			- added .ribbon_order
 | 
					
						
							|  |  |  | // 			- added base ribbon
 | 
					
						
							|  |  |  | // 			- ribbons are now sparse in memory but can be compact when
 | 
					
						
							|  |  |  | // 			  serialized.
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:22:24 +04:00
										 |  |  | // 			- auto-convert from gen1 (no version) and gen3 (2.*) on load
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:03:52 +04:00
										 |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // NOTE: Gen1 and Gen3 refer to code generations rather than data format
 | 
					
						
							|  |  |  | // 		iterations, Gen2 is skipped here as it is a different project 
 | 
					
						
							|  |  |  | // 		(PortableMag) started on the same code base as ImageGrid.Viewer
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:32:32 +04:00
										 |  |  | // 		generation 1 and advanced from there, backporting some of the 
 | 
					
						
							|  |  |  | // 		results eventually formed Gen3...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:22:24 +04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*********************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Data class methods and API...
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2014-03-19 20:07:40 +04:00
										 |  |  | var DataClassPrototype = { | 
					
						
							| 
									
										
										
										
											2014-03-19 21:58:42 +04:00
										 |  |  | 	// NOTE: we consider the input list sorted...
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	fromList: function(list){ | 
					
						
							|  |  |  | 		var res = new Data() | 
					
						
							|  |  |  | 		// XXX make a real ribbon gid...
 | 
					
						
							|  |  |  | 		var gid = res.newGid('R') | 
					
						
							|  |  |  | 		res.order = list | 
					
						
							|  |  |  | 		res.ribbon_order.push(gid) | 
					
						
							|  |  |  | 		res.ribbons[gid] = list.slice() | 
					
						
							|  |  |  | 		return res | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	// XXX is this the right way to construct data???
 | 
					
						
							|  |  |  | 	fromJSON: function(data){ | 
					
						
							|  |  |  | 		return new Data().loadJSON(data) | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:22:24 +04:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Data object methods and API...
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:32:32 +04:00
										 |  |  | // DATA format:
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	.current (gid)
 | 
					
						
							|  |  |  | // 		gid of the current image
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	.base (gid)
 | 
					
						
							|  |  |  | // 		gid of the base ribbon
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	.order
 | 
					
						
							|  |  |  | // 		List of image gids setting the image order
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 		format:
 | 
					
						
							|  |  |  | //	 		[ gid, .. ]
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //	 	NOTE: this list may contain gids not loaded at the moment, a 
 | 
					
						
							|  |  |  | //	 		common case for this is when data is cropped.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	.ribbon_order
 | 
					
						
							|  |  |  | // 		List of ribbon gids setting the ribbon order.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 		format:
 | 
					
						
							|  |  |  | // 			[ gid, .. ]
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	.ribbons
 | 
					
						
							|  |  |  | // 		Dict of ribbons, indexed by ribbon gid, each ribbon is a sparse
 | 
					
						
							|  |  |  | // 		list of image gids.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 		format:
 | 
					
						
							|  |  |  | // 			{ gid: [ gid, .. ], .. }
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 		NOTE: ribbons are sparse...
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | var DataPrototype = { | 
					
						
							| 
									
										
										
										
											2014-03-20 01:36:23 +04:00
										 |  |  | 	_reset: function(){ | 
					
						
							|  |  |  | 		this.base = null | 
					
						
							|  |  |  | 		this.current = null | 
					
						
							|  |  |  | 		this.order = []  | 
					
						
							|  |  |  | 		this.ribbon_order = []  | 
					
						
							|  |  |  | 		this.ribbons = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this._gid_cache = [] | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	// util methods...
 | 
					
						
							|  |  |  | 	compactSparseList: function(list){ | 
					
						
							|  |  |  | 		return list.filter(function(){return true}) | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	makeSparseImages: function(gids, target){ | 
					
						
							|  |  |  | 		target = target == null ? [] : target | 
					
						
							|  |  |  | 		order = this.order | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gids.forEach(function(e){ | 
					
						
							|  |  |  | 			i = order.indexOf(e) | 
					
						
							|  |  |  | 			if(i >= 0){ | 
					
						
							|  |  |  | 				target[i] = e | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return target | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	// XXX generate a real gid...
 | 
					
						
							|  |  |  | 	newGid: function(prefix){ | 
					
						
							|  |  |  | 		prefix = prefix == null ? 'G' : prefix | 
					
						
							|  |  |  | 		var gid = prefix + Date.now() | 
					
						
							|  |  |  | 		while(this.ribbon_order.indexOf(gid) >= 0  | 
					
						
							|  |  |  | 				|| this.order.indexOf(gid) >= 0 | 
					
						
							|  |  |  | 				|| this._gid_cache.indexOf(gid) >= 0){ | 
					
						
							|  |  |  | 			gid = prefix + Date.now() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// XXX not sure if this is a good idea...
 | 
					
						
							|  |  |  | 		this._gid_cache.push(gid) | 
					
						
							|  |  |  | 		return gid | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	 | 
					
						
							| 
									
										
										
										
											2014-03-19 21:00:28 +04:00
										 |  |  | 	// This is signature compatible with .getImage(..), see it for more
 | 
					
						
							|  |  |  | 	// info...
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2014-03-19 20:45:33 +04:00
										 |  |  | 	// XXX do we need more specific focus operations like:
 | 
					
						
							| 
									
										
										
										
											2014-03-19 21:00:28 +04:00
										 |  |  | 	// 		.focusImageOffset(offset)
 | 
					
						
							|  |  |  | 	// 			XXX reference impelementation:
 | 
					
						
							|  |  |  | 	// 				return this.focusImage('current', offset)
 | 
					
						
							|  |  |  | 	// 		.focusRibbonOffset(offset)
 | 
					
						
							|  |  |  | 	// 			XXX reference impelementation:
 | 
					
						
							|  |  |  | 	// 				var c = this.getRibbonOrder()
 | 
					
						
							|  |  |  | 	// 				var t = c+offset
 | 
					
						
							|  |  |  | 	// 				return this.focusImage('current', (t < c ? 'after' : 'before'), t)
 | 
					
						
							| 
									
										
										
										
											2014-03-19 20:45:33 +04:00
										 |  |  | 	focusImage: function(target, mode, list){ | 
					
						
							|  |  |  | 		var current = this.getImage(target, mode, list) | 
					
						
							| 
									
										
										
										
											2014-03-19 21:00:28 +04:00
										 |  |  | 		if(this.order.indexOf(current) >= 0){ | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 			this.current = current | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 	},	 | 
					
						
							| 
									
										
										
										
											2014-03-19 21:00:28 +04:00
										 |  |  | 	// This is signature compatible with .getRibbon(..), see it for more
 | 
					
						
							|  |  |  | 	// info...
 | 
					
						
							|  |  |  | 	setBase: function(target, offset){ | 
					
						
							|  |  |  | 		var base = this.getRibbon(target, offset) | 
					
						
							|  |  |  | 		if(base in this.ribbons){ | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 			this.base = base | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Create empty ribbon...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX above/below/at...
 | 
					
						
							|  |  |  | 	// XXX do we remove ribbons and how...
 | 
					
						
							| 
									
										
										
										
											2014-03-19 20:45:33 +04:00
										 |  |  | 	// XXX test
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	newRibbon: function(target, mode){ | 
					
						
							|  |  |  | 		var gid = this.newGid('R') | 
					
						
							|  |  |  | 		var i = this.getRibbonOrder(target) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this.ribbon_order.splice(i, 0, gid) | 
					
						
							|  |  |  | 		this.ribbons[gid] = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// XXX should we return this or gid???
 | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	// Merge ribbons
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	.mergeRibbons('all')
 | 
					
						
							|  |  |  | 	//	.mergeRibbons(ribbon, ribbon, ...)
 | 
					
						
							|  |  |  | 	//		-> data
 | 
					
						
							|  |  |  | 	// If 'all' is the first argumet, this will merge all the ribbons.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// This will merge the ribbons into the first.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX we should update .base
 | 
					
						
							| 
									
										
										
										
											2014-03-19 20:45:33 +04:00
										 |  |  | 	// XXX test
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	mergeRibbons: function(target, other){ | 
					
						
							|  |  |  | 		var targets = target == 'all' ? this.ribbon_order : arguments | 
					
						
							|  |  |  | 		var base = arguments[0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for(var i=1; i < arguments.length; i++){ | 
					
						
							|  |  |  | 			var r = arguments[i] | 
					
						
							|  |  |  | 			this.makeSparseImages(this.ribbons[r], this.ribbons[base]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			delete this.ribbons[r] | 
					
						
							|  |  |  | 			this.ribbon_order.splice(this.ribbon_order.indexOf(r), 1) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// XXX we should update .base
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get image
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	.getImage()
 | 
					
						
							| 
									
										
										
										
											2014-03-19 21:58:42 +04:00
										 |  |  | 	//	.getImage('current')
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	// 		-> current image gid
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.getImage(gid|order)
 | 
					
						
							| 
									
										
										
										
											2014-03-19 20:45:33 +04:00
										 |  |  | 	// 	.getImage(gid|order, list|ribbon)
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	// 		-> gid if the image is loaded/exists
 | 
					
						
							|  |  |  | 	// 		-> null if the image is not loaded or does not exist
 | 
					
						
							| 
									
										
										
										
											2014-03-19 21:58:42 +04:00
										 |  |  | 	// 		NOTE: the second argument must not be an int (ribbon order)
 | 
					
						
							|  |  |  | 	// 				to avoid conflict with the offset case below.
 | 
					
						
							| 
									
										
										
										
											2014-03-19 20:45:33 +04:00
										 |  |  | 	//		NOTE: image order can be negative, thus getting an image 
 | 
					
						
							|  |  |  | 	//				from the tail.
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.getImage('first'[, ribbon])
 | 
					
						
							|  |  |  | 	// 	.getImage('last'[, ribbon])
 | 
					
						
							|  |  |  | 	// 		-> gid of first/last image in ribbon 
 | 
					
						
							| 
									
										
										
										
											2014-03-19 21:58:42 +04:00
										 |  |  | 	// 		-> null
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	// 		NOTE: the second argument must be .getRibbon(..) compatible.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.getImage(list|ribbon[, 'before'|'after'])
 | 
					
						
							| 
									
										
										
										
											2014-03-19 21:58:42 +04:00
										 |  |  | 	// 		-> gid of image closest to current in list|ribbon
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	// 		-> null
 | 
					
						
							| 
									
										
										
										
											2014-03-19 21:58:42 +04:00
										 |  |  | 	// 		NOTE: 'before' is default.
 | 
					
						
							|  |  |  | 	// 		NOTE: the first argument must not be a number.
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.getImage('before'[, list|ribbon])
 | 
					
						
							|  |  |  | 	// 	.getImage(gid|order, 'before'[, list|ribbon])
 | 
					
						
							|  |  |  | 	// 		-> gid of the image before gid|order
 | 
					
						
							|  |  |  | 	// 		-> null of nothing is before
 | 
					
						
							|  |  |  | 	// 	.getImage('after'[, list|ribbon])
 | 
					
						
							|  |  |  | 	// 	.getImage(gid|order, 'after'[, list|ribbon])
 | 
					
						
							|  |  |  | 	// 		-> gid of the image after git|order
 | 
					
						
							|  |  |  | 	// 		-> null of nothing is after
 | 
					
						
							|  |  |  | 	// 		NOTE: in both the above cases if gid|order is found explicitly
 | 
					
						
							|  |  |  | 	// 			it will be returned.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2014-03-19 20:45:33 +04:00
										 |  |  | 	// 	.getImage(gid|order, offset[, list|ribbon])
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	// 		-> gid if the image at offset from current
 | 
					
						
							|  |  |  | 	// 		-> null if there is no image at that offset
 | 
					
						
							|  |  |  | 	// 		NOTE: the 'relative' string is required as there is no way to
 | 
					
						
							|  |  |  | 	// 			destinguish between order and offset...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2014-03-19 21:58:42 +04:00
										 |  |  | 	// NOTE: If gid|order is not given, current image is assumed.
 | 
					
						
							|  |  |  | 	// 		Similarly, if list|ribbon is not given then the current 
 | 
					
						
							|  |  |  | 	// 		ribbon is used.
 | 
					
						
							|  |  |  | 	// NOTE: if input gid is invalid this will return -1 (XXX is this good???)
 | 
					
						
							| 
									
										
										
										
											2014-03-19 20:45:33 +04:00
										 |  |  | 	// NOTE: the folowing are equivalent:
 | 
					
						
							|  |  |  | 	// 			D.getImage('current', -1, R)
 | 
					
						
							|  |  |  | 	// 			D.getImage('before', R) 
 | 
					
						
							|  |  |  | 	// 			D.getImage('current', 'before', R)
 | 
					
						
							| 
									
										
										
										
											2014-03-19 21:58:42 +04:00
										 |  |  | 	// 		where D is a Data object and R a ribbon id/index different 
 | 
					
						
							|  |  |  | 	// 		from the current ribbon (see next note for details).
 | 
					
						
							|  |  |  | 	// NOTE: in before/after modes, if the target image is found then it
 | 
					
						
							|  |  |  | 	// 		will be returned, use offset to explicitly get the image 
 | 
					
						
							|  |  |  | 	// 		before/after target.
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:03:52 +04:00
										 |  |  | 	// XXX most of the complexity here comes from argument DSL parsing,
 | 
					
						
							|  |  |  | 	// 		might be good to revise argument syntax and handling...
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	getImage: function(target, mode, list){ | 
					
						
							| 
									
										
										
										
											2014-03-19 21:58:42 +04:00
										 |  |  | 		// no args...
 | 
					
						
							|  |  |  | 		if(target == null && mode == null && list == null){ | 
					
						
							|  |  |  | 			return this.current | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// current image shorthand...
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 		if(target == 'current'){ | 
					
						
							|  |  |  | 			target = this.current | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2014-03-19 21:58:42 +04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// order -> gid special case...
 | 
					
						
							| 
									
										
										
										
											2014-03-19 20:45:33 +04:00
										 |  |  | 		if(typeof(target) == typeof(123)){ | 
					
						
							| 
									
										
										
										
											2014-03-19 21:58:42 +04:00
										 |  |  | 			target = target < 0 ?  | 
					
						
							|  |  |  | 					this.order[this.order.length+target]  | 
					
						
							|  |  |  | 				: this.order[target] | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// first/last special case...
 | 
					
						
							|  |  |  | 		if(target == 'first'){ | 
					
						
							|  |  |  | 			list = this.ribbons[this.getRibbon(mode)] | 
					
						
							|  |  |  | 			for(var res in list){ | 
					
						
							|  |  |  | 				return list[res] | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return null | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if(target == 'last'){ | 
					
						
							|  |  |  | 			list = this.ribbons[this.getRibbon(mode)] | 
					
						
							|  |  |  | 			for(var i=list.length; i >= 0; i--){ | 
					
						
							|  |  |  | 				if(list[i] != null){ | 
					
						
							|  |  |  | 					return list[i] | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return null | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-19 21:58:42 +04:00
										 |  |  | 		// nirmalize target...
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 		if(target in this.ribbons || target.constructor.name == 'Array'){ | 
					
						
							|  |  |  | 			list = target | 
					
						
							|  |  |  | 			target = this.current | 
					
						
							| 
									
										
										
										
											2014-03-19 21:58:42 +04:00
										 |  |  | 		} else if(target == 'before' || target == 'after'){ | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 			list = mode | 
					
						
							|  |  |  | 			mode = target | 
					
						
							|  |  |  | 			target = this.current | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2014-03-19 21:58:42 +04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// normalize mode...
 | 
					
						
							|  |  |  | 		if(mode != null  | 
					
						
							|  |  |  | 				&& mode.constructor.name == 'Array'  | 
					
						
							|  |  |  | 				|| mode in this.ribbons){ | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 			list = mode | 
					
						
							|  |  |  | 			mode = null | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// relative mode and offset...
 | 
					
						
							|  |  |  | 		if(typeof(mode) == typeof(123)){ | 
					
						
							| 
									
										
										
										
											2014-03-19 20:45:33 +04:00
										 |  |  | 			var offset = mode | 
					
						
							|  |  |  | 			mode = offset < 0 ? 'before' | 
					
						
							|  |  |  | 				: offset > 0 ? 'after' | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 				: mode | 
					
						
							|  |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2014-03-19 20:45:33 +04:00
										 |  |  | 			var offset = 0  | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 			mode = mode == null ? 'before' : mode | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		offset = Math.abs(offset) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var i = this.order.indexOf(target) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-19 21:58:42 +04:00
										 |  |  | 		// invalid gid...
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 		// XXX need a better way to report errors...
 | 
					
						
							|  |  |  | 		if(i == -1){ | 
					
						
							|  |  |  | 			return -1 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// normalize the list to a sparse list of gids...
 | 
					
						
							|  |  |  | 		list = list == null ?  | 
					
						
							|  |  |  | 				this.ribbons[this.getRibbon(target)] | 
					
						
							|  |  |  | 			: list.constructor.name == 'Array' ?  | 
					
						
							|  |  |  | 				this.makeSparseImages(list) | 
					
						
							|  |  |  | 			: this.ribbons[this.getRibbon(list)] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var res = list[i] | 
					
						
							|  |  |  | 		// we have a direct hit...
 | 
					
						
							| 
									
										
										
										
											2014-03-19 20:45:33 +04:00
										 |  |  | 		if(res != null && offset == 0){ | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 			return res | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-19 21:58:42 +04:00
										 |  |  | 		// prepare for the search...
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 		if(mode == 'before'){ | 
					
						
							|  |  |  | 			var step = -1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		} else if(mode == 'after'){ | 
					
						
							|  |  |  | 			var step = 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// strict -- no hit means there is no point in searching...
 | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			return null | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-19 21:58:42 +04:00
										 |  |  | 		// skip the current elem...
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 		i += step | 
					
						
							| 
									
										
										
										
											2014-03-19 21:58:42 +04:00
										 |  |  | 		// get the first non-null, also accounting for offset...
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 		// NOTE: we are using this.order.length here as ribbons might 
 | 
					
						
							|  |  |  | 		// 		be truncated...
 | 
					
						
							|  |  |  | 		for(; i >= 0 && i < this.order.length; i+=step){ | 
					
						
							|  |  |  | 			var cur = list[i] | 
					
						
							|  |  |  | 			if(cur == null){ | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			offset -= 1 | 
					
						
							| 
									
										
										
										
											2014-03-19 20:45:33 +04:00
										 |  |  | 			if(offset <= 0){ | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 				return cur | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// target is either first or last...
 | 
					
						
							|  |  |  | 		return null | 
					
						
							|  |  |  | 	},	 | 
					
						
							|  |  |  | 	// same as .getImage(..) but return image order.
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:03:52 +04:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	// XXX should be able to get order in the following contexts:
 | 
					
						
							|  |  |  | 	// 		- all (default)
 | 
					
						
							|  |  |  | 	// 		- loaded
 | 
					
						
							|  |  |  | 	// 		- ribbon
 | 
					
						
							|  |  |  | 	// 		- list
 | 
					
						
							|  |  |  | 	getImageOrder: function(target, mode, list){ | 
					
						
							|  |  |  | 		return this.order.indexOf(this.getImage(target, mode, list)) | 
					
						
							|  |  |  | 	},	 | 
					
						
							|  |  |  | 	// Get a list of gids...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	.getImages()
 | 
					
						
							|  |  |  | 	//	.getImages('loaded')
 | 
					
						
							|  |  |  | 	//		-> list of currently loaded images, from all ribbons
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	.getImages('all')
 | 
					
						
							|  |  |  | 	//		-> list of all images, both loaded and not
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:03:52 +04:00
										 |  |  | 	//	.getImages('current')
 | 
					
						
							|  |  |  | 	//		-> list of images, in current ribbon
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	//	.getImages(list)
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:03:52 +04:00
										 |  |  | 	//		-> return only loaded images from list
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//	.getImages(gid|order|ribbon)
 | 
					
						
							|  |  |  | 	//		-> get loaded images from ribbon
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	.getImages(gid|order|ribbon, count)
 | 
					
						
							|  |  |  | 	//	.getImages(gid|order|ribbon, count, 'around')
 | 
					
						
							|  |  |  | 	//	.getImages(gid|order|ribbon, count, 'after')
 | 
					
						
							|  |  |  | 	//	.getImages(gid|order|ribbon, count, 'before')
 | 
					
						
							|  |  |  | 	//		-> get a fixed number of gids around (default) before or
 | 
					
						
							|  |  |  | 	//			after the target
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// If no image is given the current image/ribbon is assumed as target.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// This will allways return count images if there is enough images 
 | 
					
						
							|  |  |  | 	// in ribbon from the requested sides of target.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:03:52 +04:00
										 |  |  | 	// NOTE: this expects ribbon order and not image order.
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	// NOTE: if count is even, it will return 1 more image to the left 
 | 
					
						
							|  |  |  | 	// 		(before) the target.
 | 
					
						
							|  |  |  | 	// NOTE: if the target is present in the image-set it will be included
 | 
					
						
							|  |  |  | 	// 		in the result regardless of mode...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX for some reason negative target number (ribbon number) 
 | 
					
						
							|  |  |  | 	// 		breaks this...
 | 
					
						
							|  |  |  | 	getImages: function(target, count, mode){ | 
					
						
							|  |  |  | 		target = (target == null && count == null) ? 'loaded' : target | 
					
						
							|  |  |  | 		mode = mode == null ? 'around' : mode | 
					
						
							|  |  |  | 		var list | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// normalize target and build the source list...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:03:52 +04:00
										 |  |  | 		// 'current' ribbon...
 | 
					
						
							|  |  |  | 		target = target == 'current' ? this.current : target | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 		// get all gids...
 | 
					
						
							|  |  |  | 		if(target == 'all'){ | 
					
						
							|  |  |  | 			list = this.order | 
					
						
							|  |  |  | 			target = null | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// get loaded only gids...
 | 
					
						
							|  |  |  | 		} else if(target == 'loaded'){ | 
					
						
							|  |  |  | 			var res = [] | 
					
						
							|  |  |  | 			var ribbons = this.ribbons | 
					
						
							|  |  |  | 			for(var k in ribbons){ | 
					
						
							|  |  |  | 				this.makeSparseImages(ribbons[k], res) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			list = this.compactSparseList(res) | 
					
						
							|  |  |  | 			target = null  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// filter out the unloaded gids from given list...
 | 
					
						
							|  |  |  | 		} else if(target != null && target.constructor.name == 'Array'){ | 
					
						
							|  |  |  | 			var loaded = this.getImages('loaded') | 
					
						
							|  |  |  | 			list = target.filter(function(e){ | 
					
						
							|  |  |  | 				return loaded.indexOf(e) >= 0 | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			target = null  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// target is ribbon gid...
 | 
					
						
							|  |  |  | 		} else if(target in this.ribbons){ | 
					
						
							|  |  |  | 			list = this.ribbons[target] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// NOTE: list can be null if we got an image gid or ribbon order...
 | 
					
						
							|  |  |  | 		// get the ribbon gids...
 | 
					
						
							|  |  |  | 		if(list == null){ | 
					
						
							|  |  |  | 			list = this.ribbons[this.getRibbon(target)]  | 
					
						
							|  |  |  | 			list = list != null ? this.compactSparseList(list) : [] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if(count == null){ | 
					
						
							|  |  |  | 			return list.slice() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		target = this.getImage(target) | 
					
						
							|  |  |  | 		var i = list.indexOf(target) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// prepare to slice the list...
 | 
					
						
							|  |  |  | 		if(mode == 'around'){ | 
					
						
							|  |  |  | 			count = count/2 | 
					
						
							|  |  |  | 			var from = i - Math.floor(count) | 
					
						
							|  |  |  | 			var to = i + Math.ceil(count) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		} else if(mode == 'before'){ | 
					
						
							|  |  |  | 			// NOTE: we are shifting by 1 to include the target...
 | 
					
						
							|  |  |  | 			var from = (i - count) + 1 | 
					
						
							|  |  |  | 			var to = i + 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		} else if(mode == 'after'){ | 
					
						
							|  |  |  | 			var from = i | 
					
						
							|  |  |  | 			var to = i + count | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			// XXX bad mode....
 | 
					
						
							|  |  |  | 			return null | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// get the actual bounds...
 | 
					
						
							|  |  |  | 		from = Math.max(0, from) | 
					
						
							|  |  |  | 		to = Math.min(list.length-1, to) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return list.slice(from, to) | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get ribbon...
 | 
					
						
							|  |  |  | 	// 
 | 
					
						
							|  |  |  | 	//	.getRibbon()
 | 
					
						
							|  |  |  | 	//		-> current ribbon gid
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	.getRibbon(ribbon|order|gid)
 | 
					
						
							|  |  |  | 	//		-> ribbon gid
 | 
					
						
							|  |  |  | 	//		-> null -- invalid target
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	.getRibbon(ribbon|order|gid, 'before')
 | 
					
						
							|  |  |  | 	//	.getRibbon(ribbon|order|gid, 'after')
 | 
					
						
							|  |  |  | 	//		-> ribbon gid
 | 
					
						
							|  |  |  | 	//		-> null -- invalid target
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	.getRibbon(ribbon|order|gid, offset)
 | 
					
						
							|  |  |  | 	//		-> ribbon gid
 | 
					
						
							|  |  |  | 	//		-> null -- invalid target
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: this expects ribbon order and not image order.
 | 
					
						
							|  |  |  | 	//		
 | 
					
						
							|  |  |  | 	getRibbon: function(target, offset){ | 
					
						
							|  |  |  | 		target = target == null ? this.current : target | 
					
						
							|  |  |  | 		offset = offset == null ? 0 : offset | 
					
						
							|  |  |  | 		offset = offset == 'before' ? -1 : offset | 
					
						
							|  |  |  | 		offset = offset == 'after' ? 1 : offset | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var ribbons = this.ribbons | 
					
						
							|  |  |  | 		var o | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// we got a ribbon gid...
 | 
					
						
							|  |  |  | 		if(target in ribbons){ | 
					
						
							|  |  |  | 			o = this.ribbon_order.indexOf(target) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// we got a ribbon order...
 | 
					
						
							|  |  |  | 		} else if(typeof(target) == typeof(123)){ | 
					
						
							|  |  |  | 			o = target | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// image gid...
 | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			var i = this.order.indexOf(target) | 
					
						
							|  |  |  | 			var k | 
					
						
							|  |  |  | 			for(k in ribbons){ | 
					
						
							|  |  |  | 				if(ribbons[k][i] != null){ | 
					
						
							|  |  |  | 					o = this.ribbon_order.indexOf(k) | 
					
						
							|  |  |  | 					break | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if(o != null){ | 
					
						
							|  |  |  | 			o += offset | 
					
						
							|  |  |  | 			if(o < 0 || o > this.ribbon_order.length){ | 
					
						
							|  |  |  | 				// ERROR: offset out of bounds...
 | 
					
						
							|  |  |  | 				return null | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return this.ribbon_order[o] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// ERROR: invalid target...
 | 
					
						
							|  |  |  | 		return null | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	// same as .getRibbon(..) but returns ribbon order...
 | 
					
						
							|  |  |  | 	getRibbonOrder: function(target, offset){ | 
					
						
							|  |  |  | 		return this.ribbon_order.indexOf(this.getRibbon(target, offset)) | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:03:52 +04:00
										 |  |  | 	// Sort images in ribbons via .order...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: this sorts in-place
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	sortImages: function(){ | 
					
						
							|  |  |  | 		var ribbons = this.ribbons | 
					
						
							|  |  |  | 		for(k in ribbons){ | 
					
						
							|  |  |  | 			ribbons[k] = this.makeSparseImages(ribbons[k]) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2014-03-20 01:03:52 +04:00
										 |  |  | 	// Reverse .order and all the ribbons...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: this sorts in-place
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	// XXX this depends on setting length of an array, it works in Chrome
 | 
					
						
							|  |  |  | 	// 		but will it work the same in other systems???
 | 
					
						
							|  |  |  | 	reverseImages: function(){ | 
					
						
							|  |  |  | 		this.order.reverse() | 
					
						
							|  |  |  | 		var l = this.order.length | 
					
						
							|  |  |  | 		for(k in ribbons){ | 
					
						
							|  |  |  | 			// XXX will this work everywhere???
 | 
					
						
							|  |  |  | 			// NOTE: ribbons may be truncated, so we need to explicitly 
 | 
					
						
							|  |  |  | 			// 		set their length...
 | 
					
						
							|  |  |  | 			ribbons[k].length = l | 
					
						
							|  |  |  | 			ribbons[k].reverse() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Shift image...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	.shiftImage(from, gid|ribbon)
 | 
					
						
							|  |  |  | 	//	.shiftImage(from, gid|ribbon, 'before')
 | 
					
						
							|  |  |  | 	//	.shiftImage(from, gid|ribbon, 'after')
 | 
					
						
							|  |  |  | 	//		-> data
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	.shiftImage(from, offset, 'offset')
 | 
					
						
							|  |  |  | 	//		-> data
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// from must be a .getImage(..) compatible object. usually an image
 | 
					
						
							|  |  |  | 	// gid, order, or null, see .getImage(..) for more info.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: this will not create new ribbons.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX process from as a list of gids...
 | 
					
						
							| 
									
										
										
										
											2014-03-19 20:45:33 +04:00
										 |  |  | 	// XXX test
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	shiftImage: function(from, target, mode){ | 
					
						
							|  |  |  | 		from = from.constructor.name != 'Array' ? [from] : from | 
					
						
							|  |  |  | 		mode = mode == null ? 'before' : mode | 
					
						
							|  |  |  | 		var ribbons = this.ribbons | 
					
						
							|  |  |  | 		var order = this.order | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		first = this.getImage(from[0]) | 
					
						
							|  |  |  | 		var f = order.indexOf(first) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// target is an offset...
 | 
					
						
							|  |  |  | 		if(mode == 'offset'){ | 
					
						
							|  |  |  | 			target = target == null ? 0 : target | 
					
						
							|  |  |  | 			var t = f + target | 
					
						
							|  |  |  | 			t = t > order.length ? order.length-1 | 
					
						
							|  |  |  | 				: t < 0 ? 0 | 
					
						
							|  |  |  | 				: t | 
					
						
							|  |  |  | 			target = order[t] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var ribbon = this.getRibbon(target) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// target is a ribbon gid...
 | 
					
						
							|  |  |  | 		} else if(target in this.ribbons){ | 
					
						
							|  |  |  | 			var t = f | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var ribbon = target | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// target is a gid or order...
 | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			target = this.getImage(target) | 
					
						
							|  |  |  | 			var t = order.indexOf(target) | 
					
						
							|  |  |  | 			t = mode == 'after' ? t+1 : t | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var ribbon = this.getRibbon(target) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var from_ribbon = this.getRibbon(first) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// do vertical shift...
 | 
					
						
							|  |  |  | 		if(ribbon != from_ribbon || from.length > 1){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 			from.forEach(function(e){ | 
					
						
							|  |  |  | 				var i = order.indexOf(e) | 
					
						
							|  |  |  | 				var from_ribbon = that.getRibbon(e) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				that.ribbons[ribbon][i] = e | 
					
						
							|  |  |  | 				delete that.ribbons[from_ribbon][i] | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// do horizontal shift...
 | 
					
						
							|  |  |  | 		if(f != t){ | 
					
						
							|  |  |  | 			for(var i=0; i<from.length; i++){ | 
					
						
							|  |  |  | 				f = order.indexOf(from[i]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// update order...
 | 
					
						
							|  |  |  | 				order.splice(t+i, 0, this.splice(f, 1)[0]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// update ribbons...
 | 
					
						
							|  |  |  | 				for(k in ribbons){ | 
					
						
							|  |  |  | 					ribbons[k].splice(t+i, 0, ribbons[k].splice(f, 1)[0]) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return this  | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Split data into 2 or more sections...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: this might result in empty ribbons, if no images are in a 
 | 
					
						
							|  |  |  | 	// 		given ribbon in the section to be split...
 | 
					
						
							| 
									
										
										
										
											2014-03-19 20:45:33 +04:00
										 |  |  | 	// XXX test
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	split: function(target){ | 
					
						
							|  |  |  | 		if(target.constructor.name != 'Array'){ | 
					
						
							|  |  |  | 			target = [target] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		var res = [] | 
					
						
							|  |  |  | 		var tail = this.clone() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		target.forEach(function(i){ | 
					
						
							|  |  |  | 			i = tail.getImageOrder(i)-1 | 
					
						
							|  |  |  | 			var n = new Data() | 
					
						
							|  |  |  | 			n.base = tail.base | 
					
						
							|  |  |  | 			n.ribbon_order = base.ribbon_order.slice() | 
					
						
							|  |  |  | 			n.order = tail.order.splice(0, i) | 
					
						
							|  |  |  | 			for(var k in this.ribbons){ | 
					
						
							|  |  |  | 				n.ribbons[k] = tail.ribbons.splice(0, i) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			n.current = n.order.indexOf(tail.current) >= 0 ? tail.current : n.order[0] | 
					
						
							|  |  |  | 			 | 
					
						
							|  |  |  | 			res.push(n) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// update .current of the last element...
 | 
					
						
							|  |  |  | 		tail.current = tail.order.indexOf(tail.current) >= 0 ? tail.current : tail.order[0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		res.push(tail) | 
					
						
							|  |  |  | 		return res | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	// align can be:
 | 
					
						
							|  |  |  | 	// 	'gid'
 | 
					
						
							|  |  |  | 	// 	'base'
 | 
					
						
							|  |  |  | 	// 	'top'
 | 
					
						
							|  |  |  | 	// 	'bottom'
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: this will merge the items into the first list element...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX should this join to this or create a new data???
 | 
					
						
							| 
									
										
										
										
											2014-03-19 20:45:33 +04:00
										 |  |  | 	// XXX test
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	join: function(data, align){ | 
					
						
							|  |  |  | 		var res = data.pop() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		data.forEach(function(d){ | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		// XXX
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return res | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// NOTE: this may result in empty ribbons...
 | 
					
						
							|  |  |  | 	// NOTE: this will not crop the .order...
 | 
					
						
							|  |  |  | 	crop: function(list){ | 
					
						
							|  |  |  | 		var crop = this.clone() | 
					
						
							|  |  |  | 		list = crop.makeSparseImages(list) | 
					
						
							|  |  |  | 		for(var k in crop.ribbons){ | 
					
						
							|  |  |  | 			crop.ribbons[k] = crop.makeSparseImages(crop.ribbons[k].filter(function(_, i){ | 
					
						
							|  |  |  | 				return list[i] != null | 
					
						
							|  |  |  | 			})) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return crop | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	// Merge changes from crop into data...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: this may result in empty ribbons...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX should we be able to align a merged crop???
 | 
					
						
							| 
									
										
										
										
											2014-03-19 20:45:33 +04:00
										 |  |  | 	// XXX test
 | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 	mergeCrop: function(crop){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this.order = crop.order.slice() | 
					
						
							|  |  |  | 		// XXX sync these???
 | 
					
						
							|  |  |  | 		this.ribbon_order = crop.ribbon_order.slice() | 
					
						
							|  |  |  | 		this.sortRibbons() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// 
 | 
					
						
							|  |  |  | 		for(var k in crop.ribbons){ | 
					
						
							|  |  |  | 			var local = k in this.ribbons ? this.ribbons[k] : [] | 
					
						
							|  |  |  | 			var remote = crop.ribbons[k] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			this.ribbons[k] = local | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			remote.forEach(function(e){ | 
					
						
							|  |  |  | 				// add gid to local ribbon...
 | 
					
						
							|  |  |  | 				if(local.indexOf(e) < 0){ | 
					
						
							|  |  |  | 					this.shiftImage(e, k) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	clone: function(){ | 
					
						
							|  |  |  | 		var res = new Data() | 
					
						
							|  |  |  | 		res.base = this.base | 
					
						
							|  |  |  | 		res.current = this.current | 
					
						
							|  |  |  | 		res.order = this.order.slice() | 
					
						
							|  |  |  | 		res.ribbon_order = this.ribbon_order.slice() | 
					
						
							|  |  |  | 		res.ribbons = {} | 
					
						
							|  |  |  | 		// make ribbons sparse...
 | 
					
						
							|  |  |  | 		for(var k in this.ribbons){ | 
					
						
							|  |  |  | 			res.ribbons[k] = this.ribbons[k].slice() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return res | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// JSON serialization...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: this loads in-place, use .fromJSON(..) to create new data...
 | 
					
						
							|  |  |  | 	// XXX check if we need more version checking...
 | 
					
						
							|  |  |  | 	convertDataGen3: function(data){ | 
					
						
							|  |  |  | 		// XXX check for earlier versions...
 | 
					
						
							|  |  |  | 		// XXX do we need to do more here???
 | 
					
						
							|  |  |  | 		data = data.version == null ? convertDataGen1(data) : data | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		var res = {} | 
					
						
							|  |  |  | 		res.version = '3.0' | 
					
						
							|  |  |  | 		res.current = data.current | 
					
						
							|  |  |  | 		res.order = data.order.slice() | 
					
						
							|  |  |  | 		res.ribbon_order = [] | 
					
						
							|  |  |  | 		res.ribbons = {} | 
					
						
							|  |  |  | 		// generate gids...
 | 
					
						
							|  |  |  | 		data.ribbons.forEach(function(e){ | 
					
						
							|  |  |  | 			var gid = that.newGid('R') | 
					
						
							|  |  |  | 			res.ribbon_order.push(gid) | 
					
						
							|  |  |  | 			res.ribbons[gid] = e.slice() | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		// we set the base to the first ribbon...
 | 
					
						
							|  |  |  | 		res.base = res.ribbon_order[0] | 
					
						
							|  |  |  | 		return res | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	loadJSON: function(data){ | 
					
						
							|  |  |  | 		if(typeof(data) == typeof('str')){ | 
					
						
							|  |  |  | 			data = JSON.parse(data) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		data = data.version < '3.0' ? this.convertDataGen3(data) : data | 
					
						
							|  |  |  | 		this.base = data.base | 
					
						
							|  |  |  | 		this.current = data.current | 
					
						
							|  |  |  | 		this.order = data.order.slice() | 
					
						
							|  |  |  | 		this.ribbon_order = data.ribbon_order.slice() | 
					
						
							|  |  |  | 		this.ribbons = {} | 
					
						
							|  |  |  | 		// make ribbons sparse...
 | 
					
						
							|  |  |  | 		for(var k in data.ribbons){ | 
					
						
							|  |  |  | 			this.ribbons[k] = this.makeSparseImages(data.ribbons[k]) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	// NOTE: if mode is either 'str' or 'string' then this will stringify
 | 
					
						
							|  |  |  | 	// 		the result...
 | 
					
						
							|  |  |  | 	dumpJSON: function(mode){ | 
					
						
							|  |  |  | 		var res = { | 
					
						
							|  |  |  | 			varsion: '3.0', | 
					
						
							|  |  |  | 			base: this.base, | 
					
						
							|  |  |  | 			current: this.current, | 
					
						
							|  |  |  | 			order: this.order.slice(), | 
					
						
							|  |  |  | 			ribbon_order: this.ribbon_order.slice(), | 
					
						
							|  |  |  | 			ribbons: {}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// compact ribbons...
 | 
					
						
							|  |  |  | 		for(var k in this.ribbons){ | 
					
						
							|  |  |  | 			res.ribbons[k] = this.compactSparseList(this.ribbons[k]) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if(mode == 'string' || mode == 'str'){ | 
					
						
							|  |  |  | 			res = JSON.stringify(res) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return res | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-20 01:22:24 +04:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Main Data object...
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2014-03-19 20:07:40 +04:00
										 |  |  | function Data(json){ | 
					
						
							|  |  |  | 	if(this.constructor.name != 'Data'){ | 
					
						
							|  |  |  | 		obj = new Data | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		obj = this | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-19 20:07:40 +04:00
										 |  |  | 	// load initial state...
 | 
					
						
							|  |  |  | 	if(json != null){ | 
					
						
							|  |  |  | 		obj.loadJSON(json) | 
					
						
							| 
									
										
										
										
											2014-03-20 01:36:23 +04:00
										 |  |  | 	} else { | 
					
						
							|  |  |  | 		obj._reset() | 
					
						
							| 
									
										
										
										
											2014-03-19 20:07:40 +04:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return obj | 
					
						
							| 
									
										
										
										
											2014-03-19 19:55:42 +04:00
										 |  |  | } | 
					
						
							|  |  |  | Data.__proto__ = DataClassPrototype | 
					
						
							|  |  |  | Data.prototype = DataPrototype | 
					
						
							|  |  |  | Data.prototype.constructor = Data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /********************************************************************** | 
					
						
							|  |  |  | * vim:set ts=4 sw=4 :                                                */ |