PageRenderTime 71ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/src/org/openPyro/controls/listClasses/ListBase.as

http://github.com/arpit/openpyro
ActionScript | 599 lines | 402 code | 84 blank | 113 comment | 65 complexity | d58b99798337a68f00bac9e2cbee0cb8 MD5 | raw file
  1. package org.openPyro.controls.listClasses
  2. {
  3. import flash.display.DisplayObject;
  4. import flash.display.Sprite;
  5. import flash.events.Event;
  6. import flash.events.MouseEvent;
  7. import flash.utils.Dictionary;
  8. import flash.utils.Timer;
  9. import org.openPyro.collections.CollectionHelpers;
  10. import org.openPyro.collections.ICollection;
  11. import org.openPyro.collections.events.CollectionEvent;
  12. import org.openPyro.collections.events.CollectionEventKind;
  13. import org.openPyro.controls.events.ListEvent;
  14. import org.openPyro.controls.events.ListEventReason;
  15. import org.openPyro.core.ClassFactory;
  16. import org.openPyro.core.IDataRenderer;
  17. import org.openPyro.core.MeasurableControl;
  18. import org.openPyro.core.ObjectPool;
  19. import org.openPyro.core.UIContainer;
  20. import org.openPyro.effects.Effect;
  21. import org.openPyro.events.PyroEvent;
  22. import org.openPyro.layout.ILayout;
  23. import org.openPyro.layout.IVirtualizedLayout;
  24. import org.openPyro.painters.IPainter;
  25. import org.openPyro.painters.Stroke;
  26. import org.openPyro.painters.StrokePainter;
  27. import org.openPyro.utils.StringUtil;
  28. /**
  29. * The event dispatched when an item is clicked. This event is always broadcast
  30. * whether the selectedIndex has changed or not, unlike the change event.
  31. */
  32. [Event (name="itemClick", type="org.openPyro.controls.events.ListEvent")]
  33. /**
  34. * The event dispatched when the selectedIndex property changes.
  35. */
  36. [Event (name="change", type="org.openPyro.controls.events.ListEvent")]
  37. /**
  38. * The ListBase class is the base class for all classes that can use
  39. * renderer recycling algorithm for displaying a lot of data. ListBase
  40. * classes can have the IVirualizedLayout layouts applied to them.
  41. */
  42. public class ListBase extends UIContainer
  43. {
  44. public function ListBase()
  45. {
  46. super();
  47. }
  48. /**
  49. * Overrides the <code>UIContainer</code>'s default layout
  50. * setter to use IVirtualizedLayouts.
  51. *
  52. * Note: This will throw an Error if the layout being passed
  53. * in is not a <code>IVirtualizedLayout</code>
  54. *
  55. * @see org.openpyro.layouts.IVirtualizedLayout
  56. */
  57. override public function set layout(l:ILayout):void{
  58. if(!(l is IVirtualizedLayout)){
  59. throw new ArgumentError("ListBase can only accept IVirualizedLayout layouts");
  60. }
  61. super.layout = l;
  62. IVirtualizedLayout(l).listBase = this;
  63. }
  64. protected var _dataProvider:Object;
  65. protected var _rendererPool:ObjectPool;
  66. /**
  67. * A dictionary of renderers mapped with the data they represent
  68. * as keys.
  69. */
  70. public var visibleRenderersMap:Dictionary = new Dictionary();
  71. public function set dataProvider(src:Object):void{
  72. if(_dataProvider == src) return;
  73. _dataProvider = src;
  74. /*
  75. * Reset the scroll positions
  76. */
  77. verticalScrollPosition = 0;
  78. horizontalScrollPosition = 0;
  79. _selectedIndex = -1;
  80. _selectedItem = null;
  81. if(_dataProviderCollection){
  82. _dataProviderCollection.removeEventListener(CollectionEvent.COLLECTION_CHANGED, onSourceCollectionChanged);
  83. }
  84. convertDataToCollection(src);
  85. _dataProviderCollection.addEventListener(CollectionEvent.COLLECTION_CHANGED, onSourceCollectionChanged);
  86. removeAllRenderers();
  87. displayListInvalidated =true;
  88. needsReRendering = true;
  89. forceInvalidateDisplayList = true;
  90. invalidateSize();
  91. invalidateDisplayList();
  92. }
  93. public function get dataProvider():Object{
  94. return _dataProvider;
  95. }
  96. protected function removeAllRenderers():void{
  97. for (var uid:String in this.visibleRenderersMap){
  98. var renderer:DisplayObject = this.visibleRenderersMap[uid];
  99. delete(this.visibleRenderersMap[uid]);
  100. renderer.parent.removeChild(renderer);
  101. this.rendererPool.returnToPool(renderer);
  102. }
  103. }
  104. protected var animateRenderers:Boolean = false;
  105. /**
  106. * Function invoked when the source dataCollection changes by either having items added
  107. * or removed.
  108. */
  109. protected function onSourceCollectionChanged(event:CollectionEvent):void{
  110. //Effect.cancelAll();
  111. if(event.kind == CollectionEventKind.REMOVE){
  112. animateRenderers = true;
  113. handleItemsRemoved(event)
  114. }
  115. else if(event.kind == CollectionEventKind.ADD){
  116. animateRenderers = true;
  117. handleItemsAdded(event);
  118. }
  119. }
  120. protected var _labelFunction:Function
  121. public function set labelFunction(func:Function):void{
  122. _labelFunction = func;
  123. }
  124. public function get labelFunction():Function{
  125. return _labelFunction;
  126. }
  127. protected function handleItemsRemoved(event:CollectionEvent):void{
  128. var items:Array = event.delta;
  129. /*var needsEventDispatch:Boolean = false;
  130. if(event.location < _selectedIndex){
  131. _selectedIndex-=items.length;
  132. needsEventDispatch = true;
  133. }*/
  134. for each(var item:* in items){
  135. if(item == this._selectedItem){
  136. doSetSelectedIndex( -1, ListEventReason.DATAPROVIDER_UPDATED);
  137. // needsEventDispatch = false;
  138. }
  139. for (var uid:String in this.visibleRenderersMap){
  140. if(IListDataRenderer(this.visibleRenderersMap[uid]).data == item){
  141. var renderer:DisplayObject = this.visibleRenderersMap[uid];
  142. delete(this.visibleRenderersMap[uid]);
  143. renderer.parent.removeChild(renderer);
  144. this.rendererPool.returnToPool(renderer);
  145. }
  146. }
  147. }
  148. needsReRendering = true;
  149. forceInvalidateDisplayList = true;
  150. invalidateSize();
  151. invalidateDisplayList();
  152. /*if(needsEventDispatch){
  153. var listEvent:ListEvent = new ListEvent(ListEvent.CHANGE);
  154. dispatchEvent(listEvent);
  155. }
  156. */
  157. viewportInvalidated = true;
  158. }
  159. protected var viewportInvalidated:Boolean = false;
  160. protected function handleItemsAdded(event:CollectionEvent):void{
  161. if(event.location < _selectedIndex){
  162. doSetSelectedIndex( _selectedIndex+event.delta.length, ListEventReason.DATAPROVIDER_UPDATED);
  163. }
  164. forceInvalidateDisplayList = true;
  165. displayListInvalidated = true;
  166. needsReRendering = true;
  167. viewportInvalidated = true;
  168. invalidateSize();
  169. invalidateDisplayList();
  170. }
  171. protected var _dataProviderCollection:ICollection;
  172. /**
  173. * Converts an Array to ArrayCollection or xml to
  174. * XMLCollection. Written as a separate function so
  175. * that overriding classes may massage the data as
  176. * needed
  177. */
  178. protected function convertDataToCollection(dp:Object):void{
  179. this._dataProviderCollection = CollectionHelpers.sourceToCollection(dp);
  180. }
  181. public function get dataProviderCollection():ICollection{
  182. return _dataProviderCollection;
  183. }
  184. private var borderRect:Sprite;
  185. override protected function createChildren() : void{
  186. super.createChildren();
  187. if(_labelFunction == null){
  188. this._labelFunction = StringUtil.toStringLabel
  189. }
  190. borderRect = new Sprite();
  191. $addChild(borderRect);
  192. }
  193. protected var _itemRendererFactory:ClassFactory;
  194. public function set itemRenderer(factory:ClassFactory):void{
  195. // clear old stuff
  196. if(_rendererPool){
  197. _rendererPool.clear();
  198. }
  199. for each(var renderer:DisplayObject in visibleRenderersMap){
  200. renderer.parent.removeChild(renderer);
  201. renderer = null;
  202. }
  203. //add new stuff
  204. _rendererPool = new ObjectPool(factory);
  205. _itemRendererFactory = factory;
  206. needsReRendering = true
  207. }
  208. /**
  209. * Returns the ObjectPool being used to manage the
  210. * itemRenderers
  211. */
  212. public function get rendererPool():ObjectPool{
  213. return _rendererPool;
  214. }
  215. protected var _needsReRendering:Boolean = false;
  216. public function get needsReRendering():Boolean{
  217. return _needsReRendering;
  218. }
  219. public function set needsReRendering(b:Boolean):void{
  220. _needsReRendering = b;
  221. if(_needsReRendering){
  222. this.invalidateSize();
  223. }
  224. }
  225. override public function initialize() : void{
  226. super.initialize();
  227. if(_needsReRendering){
  228. needsReRendering=true;
  229. }
  230. }
  231. override public function set dimensionsChanged(val:Boolean) : void{
  232. needsReRendering=true;
  233. super.dimensionsChanged = val;
  234. }
  235. override public function queueValidateDisplayList(event:PyroEvent=null):void{
  236. super.queueValidateDisplayList(event);
  237. if(!_dataProvider || !_itemRendererFactory || !_needsReRendering || isNaN(height) || isNaN(width)){
  238. return;
  239. }
  240. _needsReRendering = false;
  241. renderListItems();
  242. ///dispatchEvent(new ListEvent(ListEvent.RENDERERS_REPOSITIONED));
  243. }
  244. /**
  245. * @private
  246. */
  247. public var positionAnchorRenderer:DisplayObject = null;
  248. /**
  249. * Array that maintains a list of the newlyCreatedRenderers.
  250. */
  251. protected var newlyCreatedRenderers:Array = [];
  252. protected function createNewRenderersAndMap(newRenderersUIDs:Array):void{
  253. var newRendererMap:Dictionary = new Dictionary();
  254. for(var uid:String in this.visibleRenderersMap){
  255. if(newRenderersUIDs.indexOf(uid) == -1){
  256. var unusedRenderer:DisplayObject = DisplayObject(visibleRenderersMap[uid]);
  257. unusedRenderer.parent.removeChild(unusedRenderer);
  258. rendererPool.returnToPool(unusedRenderer);
  259. }
  260. else{
  261. newRendererMap[uid] = visibleRenderersMap[uid];
  262. }
  263. }
  264. newlyCreatedRenderers = [];
  265. for(var i:int=0; i<newRenderersUIDs.length; i++){
  266. var newRendererUID:String = newRenderersUIDs[i];
  267. if(!newRendererMap[newRendererUID]){
  268. var newRenderer:DisplayObject = createNewRenderer(newRendererUID, i)
  269. newRendererMap[newRendererUID] = newRenderer;
  270. }
  271. }
  272. this.visibleRenderersMap = newRendererMap;
  273. }
  274. /**
  275. * Creates a new renderer for a given UID and rowIndex
  276. *
  277. * Todo: this could use a cleaner signiture for easier extensions
  278. */
  279. protected function createNewRenderer(newRendererUID:String,rowIndex:Number):DisplayObject{
  280. var newRenderer:DisplayObject = rendererPool.getObject() as DisplayObject;
  281. newlyCreatedRenderers.push(newRenderer);
  282. this.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
  283. newRenderer.addEventListener(MouseEvent.MOUSE_UP, handleMouseUp);
  284. contentPane.addChildAt(newRenderer,0);
  285. if(newRenderer is MeasurableControl){
  286. MeasurableControl(newRenderer).doOnAdded();
  287. }
  288. if(newRenderer is IListDataRenderer){
  289. var listRenderer:IListDataRenderer = newRenderer as IListDataRenderer;
  290. var baseListData:BaseListData = new BaseListData()
  291. baseListData.list = this;
  292. baseListData.rowIndex = rowIndex;
  293. listRenderer.baseListData = baseListData;
  294. if(_dataProviderCollection.getUIDForItemAtIndex(_selectedIndex) == newRendererUID){
  295. listRenderer.selected = true;
  296. }
  297. else{
  298. listRenderer.selected = false;
  299. }
  300. }
  301. if(newRenderer is IDataRenderer){
  302. IDataRenderer(newRenderer).data = _dataProviderCollection.getItemForUID(newRendererUID);
  303. }
  304. return newRenderer;
  305. }
  306. public function renderListItems():void{
  307. var visibleRendererData:Array = IVirtualizedLayout(this.layout).visibleRenderersData;
  308. createNewRenderersAndMap(visibleRendererData);
  309. IVirtualizedLayout(layout).positionRendererMap(this.visibleRenderersMap, newlyCreatedRenderers, animateRenderers);
  310. animateRenderers = false;
  311. displayListInvalidated = true;
  312. invalidateDisplayList();
  313. }
  314. protected var _rowHeight:Number = NaN;
  315. public function set rowHeight(value:Number):void{
  316. _rowHeight = value
  317. }
  318. /**
  319. * The height of each row. If the value is not explicitly set,
  320. * it is calculated a new itemRenderer is created and measured
  321. */
  322. public function get rowHeight():Number{
  323. if(isNaN(_rowHeight)){
  324. if(_rendererPool){
  325. var testWithRenderer:DisplayObject = DisplayObject(_rendererPool.getObject());
  326. _rowHeight = testWithRenderer.height;
  327. _rendererPool.returnToPool(testWithRenderer);
  328. }
  329. }
  330. return _rowHeight;
  331. }
  332. protected var _columnWidth:Number = NaN;
  333. public function get columnWidth():Number{
  334. if(isNaN(_columnWidth)){
  335. if(_columnWidth){
  336. var testWithRenderer:DisplayObject = DisplayObject(_rendererPool.getObject());
  337. _columnWidth = testWithRenderer.width;
  338. _rendererPool.returnToPool(testWithRenderer);
  339. }
  340. }
  341. return _columnWidth;
  342. }
  343. public function set columnWidth(v:Number):void{
  344. _columnWidth = v;
  345. }
  346. protected var _borderStrokePainter:IPainter = new StrokePainter(new Stroke(1,0xaaaaaa))
  347. public function set borderStrokePainter(painter:IPainter):void{
  348. _borderStrokePainter = painter;
  349. invalidateDisplayList();
  350. }
  351. override public function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number) : void{
  352. super.updateDisplayList(unscaledWidth, unscaledHeight);
  353. if(_borderStrokePainter){
  354. borderRect.graphics.clear();
  355. _borderStrokePainter.draw(borderRect.graphics, unscaledWidth, unscaledHeight);
  356. }
  357. }
  358. private var wasMouseDownWithinClickThreshold:Boolean = true;
  359. protected function handleMouseDown(event:MouseEvent):void{
  360. wasMouseDownWithinClickThreshold = true;
  361. trace("mousedown on list")
  362. }
  363. public function set scrolling(b:Boolean):void{
  364. if(b){
  365. wasMouseDownWithinClickThreshold = false;
  366. }
  367. this._scrollingVertically = b;
  368. }
  369. public function get scrolling():Boolean{
  370. return _scrollingVertically;
  371. }
  372. /**
  373. * Function invoked when an itemRenderer is clicked
  374. */
  375. protected function handleMouseUp(event:MouseEvent):void{
  376. if(!wasMouseDownWithinClickThreshold){
  377. return;
  378. }
  379. wasMouseDownWithinClickThreshold = true;
  380. _scrollingVertically = false;
  381. for (var uid:String in visibleRenderersMap){
  382. if(visibleRenderersMap[uid] == event.currentTarget){
  383. //_selectedItem = rendererData;
  384. var selectedRenderer:DisplayObject = visibleRenderersMap[uid];
  385. doSetSelectedIndex( _dataProviderCollection.getUIDIndex(uid), ListEventReason.USER_ACTION );
  386. if(selectedRenderer is IListDataRenderer){
  387. IListDataRenderer(selectedRenderer).selected = true;
  388. }
  389. break;
  390. }
  391. }
  392. var listEvent:ListEvent = new ListEvent(ListEvent.ITEM_CLICK);
  393. dispatchEvent(listEvent);
  394. }
  395. protected var _selectedIndex:int=-1;
  396. public function get selectedIndex():int{
  397. return _selectedIndex;
  398. }
  399. /**
  400. * Sets the selectedIndex of the ListBase based control.
  401. * If the selectedIndex is set before the control has been
  402. * initialized, the <code>change</code> event is not fired.
  403. * This hook lets us set initial values for the List and wait
  404. * for change events on it. For example, the <code>ComboBox</code>
  405. * set the List's value this way when the list is about to be
  406. * revealed.
  407. */
  408. public function set selectedIndex(val:int):void{
  409. doSetSelectedIndex(val, ListEventReason.OTHER);
  410. }
  411. protected function doSetSelectedIndex(val:int, reason:String):void{
  412. if(_selectedIndex == val) return;
  413. if(!initialized){
  414. _selectedIndex = val;
  415. if(_dataProviderCollection){
  416. _selectedItem = _dataProviderCollection.getItemAt(_selectedIndex);
  417. }
  418. return;
  419. }
  420. for(var uid:String in visibleRenderersMap){
  421. if(_selectedIndex == _dataProviderCollection.getUIDIndex(uid)){
  422. if(visibleRenderersMap[uid] is IListDataRenderer){
  423. IListDataRenderer(visibleRenderersMap[uid]).selected = false;
  424. break;
  425. }
  426. }
  427. }
  428. _selectedIndex = val;
  429. this.dataProviderCollection.iterator.cursorIndex = val;
  430. _selectedItem = _dataProviderCollection.getItemAt(_selectedIndex);
  431. var targetRenderer:IListDataRenderer = this.itemToItemRenderer(_selectedItem) as IListDataRenderer;
  432. if(targetRenderer){
  433. targetRenderer.selected = true;
  434. }
  435. var event:ListEvent = new ListEvent(ListEvent.CHANGE);
  436. event.reason = reason;
  437. dispatchEvent(event);
  438. }
  439. /**
  440. * Returns the itemRenderer for the associated with a particular
  441. * item in the dataProvider if one is created. Will return null
  442. * if the item is not being represented by a renderer at this particular
  443. * moment.
  444. */
  445. public function itemToItemRenderer(item:*):DisplayObject{
  446. for (var uid:String in this.visibleRenderersMap){
  447. if(this._dataProviderCollection.getItemForUID(uid) == item){
  448. return visibleRenderersMap[uid];
  449. }
  450. }
  451. return null;
  452. }
  453. protected var _selectedItem:*;
  454. public function get selectedItem():*{
  455. return _selectedItem;
  456. }
  457. public function set selectedItem(item:*):void{
  458. doSetSelectedIndex(_dataProviderCollection.getItemIndex(item), ListEventReason.OTHER);
  459. }
  460. /**
  461. * Unlike UIContainers, the contentHeight of List controls is
  462. * not measured using the number of children in the contentPane
  463. * but rather based on the _dataProviderCollection length.
  464. */
  465. override public function get contentHeight():Number{
  466. if(_dataProviderCollection){
  467. return _rowHeight*_dataProviderCollection.length
  468. }
  469. else{
  470. return 0;
  471. }
  472. }
  473. /**
  474. * Scrolls the list such that the item on the index specified
  475. * is the one visible as the first renderer or the list is at
  476. * maximum scroll.
  477. */
  478. public function scrollToItemAtIndex(index:int, animate:Boolean=true):void{
  479. /*
  480. Right now the presence or absence of a
  481. scrollbar is a metric of whether or not
  482. the list is scrollable. This needs to be fixed for
  483. cases for example when ScrollPolicy is set to None
  484. */
  485. if(! this._verticalScrollBar) return;
  486. var yval:Number = index*_rowHeight;
  487. var percent:Number = Math.min(yval/(_contentHeight-height), 1);
  488. if(!animate){
  489. _verticalScrollBar.value = percent;
  490. }
  491. else{
  492. Effect.on(_verticalScrollBar).animateProperty("value", percent, 1);
  493. }
  494. }
  495. public function scrollYBy(delta:Number):void{
  496. /*
  497. Right now the presence or absence of a
  498. scrollbar is a metric of whether or not
  499. the list is scrollable. This needs to be fixed for
  500. cases for example when ScrollPolicy is set to None
  501. */
  502. if(! this._verticalScrollBar) return;
  503. _verticalScrollBar
  504. var yval:Number = delta;
  505. var percent:Number = yval/(_contentHeight-height);
  506. percent += _verticalScrollBar.value;
  507. percent = Math.min(percent, 1);
  508. percent = Math.max(percent, 0);
  509. _verticalScrollBar.value = percent;
  510. _verticalScrollBar.visible = true;
  511. }
  512. public function setFocus():void{
  513. }
  514. }
  515. }