sproutcore 0.9.4 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. data/History.txt +70 -2
  2. data/Manifest.txt +3 -15
  3. data/config/hoe.rb +1 -1
  4. data/frameworks/sproutcore/animation/animation.js +411 -0
  5. data/frameworks/sproutcore/controllers/array.js +68 -21
  6. data/frameworks/sproutcore/controllers/object.js +21 -2
  7. data/frameworks/sproutcore/drag/drag.js +13 -4
  8. data/frameworks/sproutcore/drag/drop_target.js +26 -19
  9. data/frameworks/sproutcore/english.lproj/core.css +4 -0
  10. data/frameworks/sproutcore/english.lproj/strings.js +5 -0
  11. data/frameworks/sproutcore/english.lproj/theme.css +5 -0
  12. data/frameworks/sproutcore/foundation/application.js +1 -2
  13. data/frameworks/sproutcore/foundation/set.js +31 -12
  14. data/frameworks/sproutcore/foundation/sorted_set.js +590 -0
  15. data/frameworks/sproutcore/foundation/string.js +43 -9
  16. data/frameworks/sproutcore/globals/window.js +34 -9
  17. data/frameworks/sproutcore/lib/button_views.rb +1 -0
  18. data/frameworks/sproutcore/lib/collection_view.rb +1 -0
  19. data/frameworks/sproutcore/lib/core_views.rb +3 -0
  20. data/frameworks/sproutcore/lib/index.rhtml +1 -1
  21. data/frameworks/sproutcore/mixins/collection_view_delegate.js +201 -0
  22. data/frameworks/sproutcore/mixins/observable.js +2 -7
  23. data/frameworks/sproutcore/models/record.js +1 -1
  24. data/frameworks/sproutcore/models/store.js +81 -28
  25. data/frameworks/sproutcore/tests/views/view/clippingFrame.rhtml +9 -6
  26. data/frameworks/sproutcore/views/collection/collection.js +649 -211
  27. data/frameworks/sproutcore/views/collection/grid.js +62 -26
  28. data/frameworks/sproutcore/views/collection/list.js +57 -21
  29. data/frameworks/sproutcore/views/collection/source_list.js +61 -13
  30. data/frameworks/sproutcore/views/image.js +7 -0
  31. data/frameworks/sproutcore/views/inline_text_field.js +4 -5
  32. data/frameworks/sproutcore/views/slider.js +2 -0
  33. data/frameworks/sproutcore/views/view.js +2 -2
  34. data/lib/sproutcore/build_tools/html_builder.rb +4 -6
  35. data/lib/sproutcore/build_tools/resource_builder.rb +32 -20
  36. data/lib/sproutcore/bundle.rb +130 -32
  37. data/lib/sproutcore/bundle_manifest.rb +24 -21
  38. data/lib/sproutcore/helpers/static_helper.rb +22 -9
  39. data/lib/sproutcore/merb/bundle_controller.rb +4 -3
  40. data/lib/sproutcore/version.rb +1 -1
  41. metadata +14 -17
  42. data/clients/view_builder/builders/builder.js +0 -339
  43. data/clients/view_builder/builders/button.js +0 -81
  44. data/clients/view_builder/controllers/document.js +0 -21
  45. data/clients/view_builder/core.js +0 -19
  46. data/clients/view_builder/english.lproj/body.css +0 -77
  47. data/clients/view_builder/english.lproj/body.rhtml +0 -39
  48. data/clients/view_builder/english.lproj/controls.css +0 -0
  49. data/clients/view_builder/english.lproj/strings.js +0 -14
  50. data/clients/view_builder/main.js +0 -38
  51. data/clients/view_builder/mixins/design_mode.js +0 -92
  52. data/clients/view_builder/tests/controllers/document.rhtml +0 -20
  53. data/clients/view_builder/tests/views/builder.rhtml +0 -20
  54. data/clients/view_builder/tests/views/palette.rhtml +0 -21
  55. data/clients/view_builder/views/builder.js +0 -26
  56. data/clients/view_builder/views/palette.js +0 -30
@@ -17,11 +17,25 @@ until you call commitChanges().
17
17
 
18
18
  @extends SC.Controller
19
19
  @extends SC.Array
20
+ @extends SC.SelectionSupport
21
+ @since SproutCore 1.0
20
22
 
21
23
  */
22
24
  SC.ArrayController = SC.Controller.extend(SC.Array, SC.SelectionSupport,
23
25
  /** @scope SC.ArrayController.prototype */
24
26
  {
27
+ /**
28
+ If YES the will return controllers for content objects.
29
+
30
+ If you want to use an array controller to edit an array contents directly
31
+ but you do not want to wrap the values of the array in controller objects
32
+ then you should set this property to NO.
33
+
34
+ @field
35
+ @type {Boolean}
36
+ */
37
+ useControllersForContent: NO,
38
+
25
39
  /**
26
40
  Provides compatibility with CollectionControllers.
27
41
  @field
@@ -45,7 +59,7 @@ SC.ArrayController = SC.Controller.extend(SC.Array, SC.SelectionSupport,
45
59
  @field
46
60
  @type {Boolean}
47
61
  */
48
- destroyOnRemoval: false,
62
+ destroyOnRemoval: NO,
49
63
 
50
64
  /**
51
65
  Watches changes to the content property updates the contentClone.
@@ -53,17 +67,46 @@ SC.ArrayController = SC.Controller.extend(SC.Array, SC.SelectionSupport,
53
67
  @observes content
54
68
  */
55
69
  _contentObserver: function() {
56
- this.beginPropertyChanges();
57
- this.contentCloneReset();
58
- this.arrayContentDidChange() ;
59
- this.notifyPropertyChange('length') ;
60
- this.updateSelectionAfterContentChange();
61
- this.endPropertyChanges() ;
70
+ var content = this.get('content') ;
71
+ if (SC.isEqual(content, this._content)) return ; // nothing to do
72
+
73
+ if (!this._boundContentPropertyObserver) {
74
+ this._boundContentPropertyObserver = this._contentPropertyObserver.bind(this) ;
75
+ }
76
+ var func = this._boundContentPropertyObserver ;
77
+
78
+ // remove old observer, add new observer, and trigger content property change
79
+ if (this._content && this._content.removeObserver) this._content.removeObserver('[]', func) ;
80
+ if (content && content.addObserver) content.addObserver('[]', func) ;
81
+ this._content = content; //cache
82
+ this._contentPropertyRevision = null ;
83
+
84
+ var rev = (content) ? content.propertyRevision : -1 ;
85
+ this._contentPropertyObserver(this, '[]', content, rev) ;
62
86
  }.observes('content'),
63
87
 
88
+ _contentPropertyObserver: function(target, key, value, rev) {
89
+
90
+ if (!this._updatingContent && (!rev || (rev != this._contentPropertyRevision))) {
91
+ this._contentPropertyRevision = rev ;
92
+
93
+ this._updatingContent = true ;
94
+
95
+ this.beginPropertyChanges();
96
+ this.contentCloneReset();
97
+ this.arrayContentDidChange() ;
98
+ this.notifyPropertyChange('length') ;
99
+ this.updateSelectionAfterContentChange();
100
+ this.endPropertyChanges() ;
101
+
102
+ this._updatingContent = false ;
103
+
104
+ }
105
+ },
106
+
64
107
  /**
65
- The array content that (when committed) will be merged back into the content property.
66
- All array methods will take place on this object.
108
+ The array content that (when committed) will be merged back into the
109
+ content property. All array methods will take place on this object.
67
110
 
68
111
  @field
69
112
  @type {SC.Array}
@@ -71,9 +114,9 @@ SC.ArrayController = SC.Controller.extend(SC.Array, SC.SelectionSupport,
71
114
  contentClone: null,
72
115
 
73
116
  /**
74
- * Clones the content property into the contentClone property.
75
- * @private
76
- **/
117
+ Clones the content property into the contentClone property.
118
+ @private
119
+ */
77
120
  contentCloneReset: function() {
78
121
  this._changelog = [];
79
122
  this.set('contentClone', null);
@@ -82,17 +125,14 @@ SC.ArrayController = SC.Controller.extend(SC.Array, SC.SelectionSupport,
82
125
  /**
83
126
  SC.Array interface implimentation.
84
127
 
85
- @param {Number} idx
86
- Starting index in the array to replace. If idx >= length, then append to
87
- the end of the array.
128
+ @param idx {Number} Starting index in the array to replace. If idx >=
129
+ length, then append to the end of the array.
88
130
 
89
- @param {Number} amt
90
- Number of elements that should be removed from the array, starting at
91
- *idx*.
131
+ @param amt {Number} Number of elements that should be removed from the
132
+ array, starting at *idx*.
92
133
 
93
- @param {Array} objects
94
- An array of zero or more objects that should be inserted into the array at
95
- *idx*
134
+ @param objects {Array} An array of zero or more objects that should be
135
+ inserted into the array at *idx*
96
136
  */
97
137
  replace: function(idx, amt, objects) {
98
138
 
@@ -229,6 +269,8 @@ SC.ArrayController = SC.Controller.extend(SC.Array, SC.SelectionSupport,
229
269
  Returns the object controller for a source value.
230
270
  */
231
271
  _objectControllerFor: function(obj) {
272
+ if (!this.useControllersForContent) return obj;
273
+
232
274
  var controllers = this._objControllers = this._objControllers || {} ;
233
275
  var guid = SC.getGUID(obj) ;
234
276
  var ret = controllers[guid] ;
@@ -246,6 +288,11 @@ SC.ArrayController = SC.Controller.extend(SC.Array, SC.SelectionSupport,
246
288
  */
247
289
  _sourceObjectFor: function(obj) {
248
290
  return (obj && obj.kindOf && obj.kindOf(SC.Controller)) ? obj.get('content') : obj ;
291
+ },
292
+
293
+ init: function() {
294
+ arguments.callee.base.apply(this, arguments) ;
295
+ if (this.get('content')) this._contentObserver() ;
249
296
  }
250
297
 
251
298
  });
@@ -6,8 +6,15 @@
6
6
  require('controllers/controller') ;
7
7
 
8
8
  /** @class
9
- An ObjectController gives you a simple way to manage one or more objects
10
- as a single object.
9
+
10
+ An ObjectController gives you a simple way to manage the editing state of
11
+ an object. You can use an ObjectController instance as a "proxy" for your
12
+ model objects.
13
+
14
+ Any properties you get or set on the object controller, will be passed
15
+ through to its content object. This allows you to setup bindings to your
16
+ object controller one time for all of your views and then swap out the
17
+ content as needed.
11
18
 
12
19
  @extends SC.Controller
13
20
  */
@@ -58,6 +65,18 @@ SC.ObjectController = SC.Controller.extend(
58
65
  */
59
66
  allowsMultipleContent: true,
60
67
 
68
+ /**
69
+ Override this method to destroy the selected object.
70
+
71
+ The default just passes this call onto the content object if it supports
72
+ it, and then sets the content to null.
73
+ */
74
+ destroy: function() {
75
+ var content = this.get('content') ;
76
+ if (content && $type(content.destroy) === T_FUNCTION) content.destroy();
77
+ this.set('content', null) ;
78
+ },
79
+
61
80
  // ...............................
62
81
  // INTERNAL SUPPORT
63
82
  //
@@ -149,6 +149,17 @@ SC.Drag = SC.Object.extend(
149
149
  return [] ;
150
150
  }.property(),
151
151
 
152
+ /**
153
+ Checks for a named data type in the drag.
154
+
155
+ @param dataType {String} the data type
156
+ @returns {Boolean} YES if data type is present in dataTypes array.
157
+ */
158
+ hasDataType: function(dataType) {
159
+ var dataTypes = this.get('dataTypes') || [] ;
160
+ return (dataTypes.indexOf(dataType) >= 0) ;
161
+ },
162
+
152
163
  /**
153
164
  Retrieve the data for the specified dataType from the drag source.
154
165
 
@@ -286,8 +297,6 @@ SC.Drag = SC.Object.extend(
286
297
  op = op & target.dragEntered(this, evt) ;
287
298
  } else op = SC.DRAG_NONE ;
288
299
 
289
- console.log(SC.Drag.inspectOperation(op)) ;
290
-
291
300
  // if DRAG_NONE, then look for the next parent that is a drop zone.
292
301
  if (op == SC.DRAG_NONE) target = this._findNextDropTarget(target) ;
293
302
  }
@@ -742,9 +751,9 @@ SC.Drag.mixin(
742
751
  */
743
752
  inspectOperation: function(op) {
744
753
  var ret = [] ;
745
- if (op == SC.DRAG_NONE) {
754
+ if (op === SC.DRAG_NONE) {
746
755
  ret = ['DRAG_NONE'];
747
- } else if (op == SC.DRAG_ANY) {
756
+ } else if (op === SC.DRAG_ANY) {
748
757
  ret = ['DRAG_ANY'] ;
749
758
  } else {
750
759
  if (op & SC.DRAG_LINK) {
@@ -25,31 +25,37 @@ SC.DropTarget = {
25
25
  */
26
26
  isDropTarget: true,
27
27
 
28
- /** Called when the drag enters the droppable area.
28
+ /**
29
+ Called when the drag enters the droppable area.
29
30
 
30
- Override this method to return an OR'd mask of the allowed drag operations.
31
- If the user drags over a droppable area within another droppable area,
32
- the drag will latch onto the deepest view that returns one or more available
33
- operations.
31
+ Override this method to return an OR'd mask of the allowed drag
32
+ operations. If the user drags over a droppable area within another
33
+ droppable area, the drag will latch onto the deepest view that returns one
34
+ or more available operations.
34
35
 
35
36
  You can also use this method to perform any one-time changes to your view
36
- when a drop enters the area. If you return anything other than DRAG_NONE on
37
- this method, the dragUpdated() method will also be called immediately.
38
-
39
- Note that dragEntered may be called frequently during a drag, not just when the
40
- drag first enters your view. In particular, the Drag object may use this method
41
- to determine which nested drop target should receive a drop.
37
+ when a drop enters the area. If you return anything other than DRAG_NONE
38
+ on this method, the dragUpdated() method will also be called immediately.
39
+
40
+ Note that dragEntered may be called frequently during a drag, not just
41
+ when the drag first enters your view. In particular, the Drag object may
42
+ use this method to determine which nested drop target should receive a
43
+ drop. You should implement this method to determine as quickly as
44
+ possible all of the possible operations that might be allowed by this
45
+ drop target. You can use dragUpdated to determine the specific operation
46
+ allowed by the user's current mouse location.
42
47
 
43
- You should implement your dragEntered method to always return the correct drag
44
- operation, but only to perform any one-time setup the first time dragEntered
45
- is called after a dragExited.
48
+ You should implement your dragEntered method to always return the correct
49
+ drag operation, but only to perform any one-time setup the first time
50
+ dragEntered is called after a dragExited.
46
51
 
47
52
  The default implementation returns SC.DRAG_NONE
48
53
 
49
- @param {SC.Drag} drag The current drag object
50
- @param {Event} evt The most recent mouse move event. Use to get location
51
-
52
- @return {DragOps} A mask of all the drag operations allowed or SC.DRAG_NONE
54
+ @param drag {SC.Drag} The current drag object
55
+ @param evt {Event} The most recent mouse move event. Use to get
56
+ location
57
+ @returns {DragOps} A mask of all the drag operations allowed or
58
+ SC.DRAG_NONE
53
59
  */
54
60
  dragEntered: function(drag, evt) { return SC.DRAG_NONE; },
55
61
 
@@ -67,7 +73,8 @@ SC.DropTarget = {
67
73
  The default implementation does nothing.
68
74
 
69
75
  @param {SC.Drag} drag The current drag object
70
- @param {Event} evt The most recent mouse move event. Use to get location
76
+ @param {Event} evt The most recent mouse move event. Use to get
77
+ location
71
78
  */
72
79
  dragUpdated: function(drag, evt) {},
73
80
 
@@ -10,6 +10,10 @@
10
10
  These styles are required by various views. You should keep these in your views even if you write your own theme.
11
11
  */
12
12
 
13
+ .sc-hide-overflow * {
14
+ overflow: hidden !important;
15
+ }
16
+
13
17
  /* @group SC.Drag */
14
18
 
15
19
  .sc-ghost-view {
@@ -10,6 +10,11 @@ Object.extend(String.English,{
10
10
  "Invalid.Email(%@)": "%@ is not a valid email address",
11
11
  "Invalid.NotEmpty(%@)": "%@ must not be empty",
12
12
  "Invalid.Password": "Your passwords do not match. Please try typing them again.",
13
+ // "CommentedOut": "String",
14
+ /*
15
+ "A bunch of": "Commented out",
16
+ "Strings": "Do not include",
17
+ */
13
18
  "Invalid.General(%@)": "%@ is invalid. Please try again.",
14
19
  "Invalid.Number(%@)": "%@ is not a number."
15
20
  }) ;
@@ -221,6 +221,11 @@ body.sc-theme {
221
221
  background: no-repeat static_url('images/sc-theme-sprite.png') right -69px;
222
222
  }
223
223
 
224
+ .sc-theme .sc-source-list-view .drop-target {
225
+ outline: 2px purple solid;
226
+ }
227
+
228
+
224
229
  /* @end */
225
230
 
226
231
  /* @group SC.SplitView */
@@ -196,8 +196,6 @@ SC.Application = SC.Responder.extend(
196
196
  // keystring is a method name representing the keys pressed (i.e 'alt_shift_escape')
197
197
  var keystring = SC.Responder.inputManager.codesForEvent(evt).first();
198
198
 
199
- //console.log( '[SC.Application#_attemptKeyEquivalent] keystring: %s, evt: %o', keystring, evt );
200
-
201
199
  // inputManager couldn't build a keystring for this key event... nothing to do...
202
200
  if (!keystring) return false;
203
201
 
@@ -208,6 +206,7 @@ SC.Application = SC.Responder.extend(
208
206
  if (mainPane && (mainPane != keyPane) && mainPane.performKeyEquivalent(keystring, evt)) return true;
209
207
  return this.performKeyEquivalent(keystring, evt);
210
208
  },
209
+
211
210
  _attemptKeyInterfaceControl: function( evt )
212
211
  {
213
212
  // keystring is a method name representing the keys pressed (i.e 'alt_shift_escape')
@@ -36,34 +36,53 @@ SC.Set = SC.Object.extend(SC.Array,
36
36
  Call this method to test for membership.
37
37
  */
38
38
  contains: function(obj) {
39
- if (obj == null) return false ;
40
- return [this._guidFor(obj)] == obj ;
39
+ if (obj === null) return false ;
40
+ return this[this._guidFor(obj)] === obj ;
41
41
  },
42
42
 
43
43
  /**
44
44
  Call this method to add an object. performs a basic add.
45
+
46
+ If the object is already in the set it will not be added again.
47
+
48
+ @param obj {Object} the object to add
49
+ @returns {Boolean} YES if the object as added.
45
50
  */
46
51
  add: function(obj) {
47
- if (obj == null) return; // cannot add null to a set.
48
- this[this._guidFor(obj)] = obj ;
49
- this.incrementProperty('length') ;
50
- this.incrementProperty('revision') ;
52
+ if (obj == null) return NO; // cannot add null to a set.
53
+
54
+ var guid = this._guidFor(obj) ;
55
+ if (this[guid] == null) {
56
+ this[this._guidFor(obj)] = obj ;
57
+ this.incrementProperty('length') ;
58
+ this.incrementProperty('revision') ;
59
+ return YES ;
60
+ } else return NO ;
51
61
  },
52
62
 
53
63
  /**
54
- Performs a basic remove
64
+ Removes the object from the set if it is found.
65
+
66
+ If the object is not in the set, nothing will be changed.
67
+
68
+ @param obj {Object} the object to remove
69
+ @returns {Boolean} YES if the object was removed.
55
70
  */
56
71
  remove: function(obj) {
57
- if (obj == null) return ;
58
- delete this[this._guidFor(obj)] ;
59
- this.decrementProperty('length') ;
60
- this.incrementProperty('revision') ;
72
+ if (obj == null) return NO ;
73
+ var guid = this._guidFor(obj);
74
+ if (this[guid] === obj) {
75
+ delete this[this._guidFor(obj)] ;
76
+ this.decrementProperty('length') ;
77
+ this.incrementProperty('revision') ;
78
+ return YES ;
79
+ } else return NO;
61
80
  },
62
81
 
63
82
  // .......................................
64
83
  // PRIVATE
65
84
  _guidFor: function(obj) {
66
- return '@' + SC.getGUID(obj);
85
+ return '@' + SC.guidFor(obj);
67
86
  },
68
87
 
69
88
  _each: function(iterator) {
@@ -0,0 +1,590 @@
1
+ // ========================================================================
2
+ // SproutCore
3
+ // copyright 2006-2008 Sprout Systems, Inc.
4
+ // ========================================================================
5
+
6
+ require('Core') ;
7
+ require('foundation/object') ;
8
+
9
+ /**
10
+ @class
11
+
12
+ A sorted set stores objects in a sorted order based on a combination of
13
+ sort keys.
14
+
15
+ h2. Usage
16
+
17
+ @extends SC.Object
18
+ @extends SC.Array
19
+ @since SproutCore 1.0
20
+ */
21
+ SC.SortedSet = SC.Object.extend(
22
+ /** @scope SC.SortedSet.prototype */ {
23
+
24
+ /**
25
+ An array of property keys to use when sorting objects. This should be
26
+ set when the set is created and not change during the lifetime of the
27
+ object.
28
+ */
29
+ sortBy: [],
30
+
31
+ compareObjects: function(v1, v2) {
32
+ return (v1 < v2) ? -1 : (v1 > v2) ? 1 : 0 ;
33
+ },
34
+
35
+ init: function() {
36
+ arguments.callee.base.call(this) ;
37
+ this._root = null;
38
+ this._cursor = null;
39
+ this._ancestors = [];
40
+ },
41
+
42
+ /** _findNode */
43
+ _findNode: function(value, saveAncestors) {
44
+ if ( saveAncestors == null ) saveAncestors = false;
45
+
46
+ var result = this._root;
47
+
48
+ if ( saveAncestors ) {
49
+ this._ancestors = [];
50
+ }
51
+
52
+ while ( result != null ) {
53
+ var relation = this.compareObjects(value, result._value);
54
+
55
+ if ( relation != 0 ) {
56
+ if ( saveAncestors ) {
57
+ this._ancestors.push(result);
58
+ }
59
+ if ( relation < 0 ) {
60
+ result = result._left;
61
+ } else {
62
+ result = result._right;
63
+ }
64
+ } else {
65
+ break;
66
+ }
67
+ }
68
+
69
+ return result;
70
+ },
71
+
72
+ /** _maxNode */
73
+ _maxNode: function(node, saveAncestors) {
74
+ if ( node == null ) node = this._root;
75
+ if ( saveAncestors == null ) saveAncestors = false;
76
+
77
+ if ( node != null ) {
78
+ while ( node._right != null ) {
79
+ if ( saveAncestors ) {
80
+ this._ancestors.push(node);
81
+ }
82
+ node = node._right;
83
+ }
84
+ }
85
+
86
+ return node;
87
+ },
88
+
89
+ /** _minNode */
90
+ _minNode: function(node, saveAncestors) {
91
+ if ( node == null ) node = this._root;
92
+ if ( saveAncestors == null ) saveAncestors = false;
93
+
94
+ if ( node != null ) {
95
+ while ( node._left != null ) {
96
+ if ( saveAncestors ) {
97
+ this._ancestors.push(node);
98
+ }
99
+ node = node._left;
100
+ }
101
+ }
102
+
103
+ return node;
104
+ },
105
+
106
+ /** _nextNode */
107
+ _nextNode: function(node) {
108
+ if ( node != null ) {
109
+ if ( node._right != null ) {
110
+ this._ancestors.push(node);
111
+ node = this._minNode(node._right, true);
112
+ } else {
113
+ var ancestors = this._ancestors;
114
+ parent = ancestors.pop();
115
+
116
+ while ( parent != null && parent._right === node ) {
117
+ node = parent;
118
+ parent = ancestors.pop();
119
+ }
120
+
121
+ node = parent;
122
+ }
123
+ } else {
124
+ this._ancestors = [];
125
+ node = this._minNode(this._root, true);
126
+ }
127
+
128
+ return node;
129
+ },
130
+
131
+ /** _previousNode */
132
+ _previousNode: function(node) {
133
+ if ( node != null ) {
134
+ if ( node._left != null ) {
135
+ this._ancestors.push(node);
136
+ node = this._maxNode(node._left, true);
137
+ } else {
138
+ var ancestors = this._ancestors;
139
+ parent = ancestors.pop();
140
+
141
+ while ( parent != null && parent._left === node ) {
142
+ node = parent;
143
+ parent = ancestors.pop();
144
+ }
145
+
146
+ node = parent;
147
+ }
148
+ } else {
149
+ this._ancestors = [];
150
+ node = this._maxNode(this._root, true);
151
+ }
152
+
153
+ return node;
154
+ },
155
+
156
+ /**
157
+ Adds a new object to the tree. The object must respond to the
158
+ compareMethod.
159
+ */
160
+ add: function(value) {
161
+ var result;
162
+
163
+ if ( this._root == null ) {
164
+ result = this._root = new SC.RedBlackNode(value, this);
165
+ } else {
166
+ var addResult = this._root.add(value);
167
+
168
+ this._root = addResult[0];
169
+ result = addResult[1];
170
+ }
171
+
172
+ return result;
173
+ },
174
+
175
+
176
+ find: function(value) {
177
+ var node = this._findNode(value);
178
+
179
+ return ( node != null ) ? node._value : null;
180
+ },
181
+
182
+ findNext: function(value) {
183
+ var current = this._findNode(value, true);
184
+
185
+ current = this._nextNode(current);
186
+
187
+ return (current != null ) ? current._value : null;
188
+ },
189
+
190
+ findPrevious: function(value) {
191
+ var current = this._findNode(value, true);
192
+
193
+ current = this._previousNode(current);
194
+
195
+ return (current != null ) ? current._value : null;
196
+ },
197
+
198
+ max: function() {
199
+ var result = this._maxNode();
200
+
201
+ return ( result != null ) ? result._value : null;
202
+ },
203
+
204
+ min: function() {
205
+ var result = this._minNode();
206
+
207
+ return ( result != null ) ? result._value : null;
208
+ },
209
+
210
+ next: function() {
211
+ this._cursor = this._nextNode(this._cursor);
212
+
213
+ return ( this._cursor ) ? this._cursor._value : null;
214
+ },
215
+
216
+ previous: function() {
217
+ this._cursor = this._previousNode(this._cursor);
218
+
219
+ return ( this._cursor ) ? this._cursor._value : null;
220
+ },
221
+
222
+ remove: function(value) {
223
+ var result;
224
+
225
+ if ( this._root != null ) {
226
+ var remResult = this._root.remove(value);
227
+
228
+ this._root = remResult[0];
229
+ result = remResult[1];
230
+ } else {
231
+ result = null;
232
+ }
233
+
234
+ return result;
235
+ },
236
+
237
+ traverse: function(func) {
238
+ if ( this._root != null ) {
239
+ this._root.traverse(func);
240
+ }
241
+ },
242
+
243
+ toString: function() {
244
+ var lines = [];
245
+
246
+ if ( this._root != null ) {
247
+ var indentText = " ";
248
+ var stack = [[this._root, 0, "^"]];
249
+
250
+ while ( stack.length > 0 ) {
251
+ var current = stack.pop();
252
+ var node = current[0];
253
+ var indent = current[1];
254
+ var line = "";
255
+
256
+ for ( var i = 0; i < indent; i++ ) {
257
+ line += indentText;
258
+ }
259
+
260
+ line += current[2] + "(" + node.toString() + ")";
261
+ lines.push(line);
262
+
263
+ if ( node._right != null ) stack.push([node._right, indent+1, "R"]);
264
+ if ( node._left != null ) stack.push([node._left, indent+1, "L"]);
265
+ }
266
+ }
267
+
268
+ return lines.join("\n");
269
+ }
270
+
271
+ });
272
+
273
+ /*****
274
+ *
275
+ * SC.RedBlackNode.js
276
+ *
277
+ * copyright 2004, Kevin Lindsey
278
+ * licensing info available at: http://www.kevlindev.com/license.txt
279
+ *
280
+ *****/
281
+
282
+ /*****
283
+ *
284
+ * constructor
285
+ *
286
+ *****/
287
+ SC.RedBlackNode = function(value, owner) {
288
+ this._owner = owner ;
289
+ this._left = null;
290
+ this._right = null;
291
+ this._value = value;
292
+ this._height = 1;
293
+ this._count = 1 ;
294
+ } ;
295
+
296
+ SC.RedBlackNode.prototype.count = function() {
297
+ if (!this._count) {
298
+ this._count = 1 ;
299
+ this._count += (this._left) ? this._left.count() : 0 ;
300
+ this._count += (this._right) ? this._right.count() : 0;
301
+ }
302
+ return this._count ;
303
+ };
304
+
305
+ /**
306
+ Returns the node at the specified index. Uses counts.
307
+
308
+ Remember: left branch is before the right branch.
309
+ */
310
+ SC.RedBlackNode.prototype.nodeAt = function(idx) {
311
+ var leftCount = (this._left) ? this._left.count() : 0 ;
312
+ if (idx === leftCount) return this; // its me!
313
+
314
+ // look lower
315
+ if (idx < leftCount) {
316
+ return (this._left) ? this._left.nodeAt(idx) : null ;
317
+
318
+ // look higher
319
+ } else {
320
+ return (this._right) ? this._right.nodeAt(idx - leftCount) : null;
321
+ }
322
+ };
323
+
324
+ /*****
325
+ *
326
+ * add
327
+ *
328
+ *****/
329
+ SC.RedBlackNode.prototype.add = function(value) {
330
+ var relation = this._owner.compareObjects(value, this._value);
331
+ var addResult;
332
+ var result;
333
+ var newNode;
334
+
335
+ if ( relation != 0 ) {
336
+ if ( relation < 0 ) {
337
+ if ( this._left != null ) {
338
+ addResult = this._left.add(value);
339
+ this._left = addResult[0];
340
+ newNode = addResult[1];
341
+ } else {
342
+ newNode = this._left = new SC.RedBlackNode(value, this._owner);
343
+ }
344
+ } else if ( relation > 0 ) {
345
+ if ( this._right != null ) {
346
+ addResult = this._right.add(value);
347
+ this._right = addResult[0];
348
+ newNode = addResult[1];
349
+ } else {
350
+ newNode = this._right = new SC.RedBlackNode(value, this._owner);
351
+ }
352
+ }
353
+ result = [this.balanceTree(), newNode];
354
+ } else {
355
+ result = [this, this];
356
+ }
357
+
358
+ // add to the return
359
+ if (result[0]._count != null) result[0]._count = null ;
360
+ return result;
361
+ };
362
+
363
+
364
+ /*****
365
+ *
366
+ * balanceTree
367
+ *
368
+ *****/
369
+ SC.RedBlackNode.prototype.balanceTree = function() {
370
+ var leftHeight = (this._left != null) ? this._left._height : 0;
371
+ var rightHeight = (this._right != null) ? this._right._height : 0;
372
+ var result;
373
+
374
+ if ( leftHeight > rightHeight + 1 ) {
375
+ result = this.swingRight();
376
+ } else if ( rightHeight > leftHeight + 1 ) {
377
+ result = this.swingLeft();
378
+ } else {
379
+ this.setHeight();
380
+ result = this;
381
+ }
382
+
383
+ return result;
384
+ };
385
+
386
+
387
+ /*****
388
+ *
389
+ * join
390
+ *
391
+ *****/
392
+ SC.RedBlackNode.prototype.join = function(that) {
393
+ var result;
394
+
395
+ if ( that == null ) {
396
+ result = this;
397
+ } else {
398
+ var top;
399
+
400
+ if ( this._height > that._height ) {
401
+ top = this;
402
+ top._right = that.join(top._right);
403
+ } else {
404
+ top = that;
405
+ top._left = this.join(top._left);
406
+ }
407
+
408
+ result = top.balanceTree();
409
+ }
410
+
411
+ return result;
412
+ };
413
+
414
+
415
+ /*****
416
+ *
417
+ * moveLeft
418
+ *
419
+ *****/
420
+ SC.RedBlackNode.prototype.moveLeft = function() {
421
+ var right = this._right;
422
+ var rightLeft = right._left;
423
+
424
+ this._right = rightLeft;
425
+ right._left = this;
426
+ this.setHeight();
427
+ right.setHeight();
428
+
429
+ return right;
430
+ };
431
+
432
+
433
+ /*****
434
+ *
435
+ * moveRight
436
+ *
437
+ *****/
438
+ SC.RedBlackNode.prototype.moveRight = function() {
439
+ var left = this._left;
440
+ var leftRight = left._right;
441
+
442
+ this._left = leftRight;
443
+ left._right = this;
444
+ this.setHeight();
445
+ left.setHeight();
446
+
447
+ return left;
448
+ };
449
+
450
+
451
+ /*****
452
+ *
453
+ * remove
454
+ *
455
+ *****/
456
+ SC.RedBlackNode.prototype.remove = function(value) {
457
+ var relation = this._owner.compareObjects(value, this._value);
458
+ var remResult;
459
+ var result;
460
+ var remNode;
461
+
462
+ if ( relation != 0 ) {
463
+ if ( relation < 0 ) {
464
+ if ( this._left != null ) {
465
+ remResult = this._left.remove(value);
466
+ this._left = remResult[0];
467
+ remNode = remResult[1];
468
+ } else {
469
+ remNode = null;
470
+ }
471
+ } else {
472
+ if ( this._right != null ) {
473
+ remResult = this._right.remove(value);
474
+ this._right = remResult[0];
475
+ remNode = remResult[1];
476
+ } else {
477
+ remNode = null;
478
+ }
479
+ }
480
+
481
+ result = this;
482
+ } else {
483
+ remNode = this;
484
+
485
+ if ( this._left == null ) {
486
+ result = this._right;
487
+ } else if ( this._right == null ) {
488
+ result = this._left;
489
+ } else {
490
+ result = this._left.join(this._right);
491
+ this._left = null;
492
+ this._right = null;
493
+ }
494
+ }
495
+
496
+ if ( remNode != null ) {
497
+ if ( result != null ) {
498
+ result = [result.balanceTree(), remNode];
499
+ } else {
500
+ result = [result, remNode];
501
+ }
502
+ } else {
503
+ result = [this, null];
504
+ }
505
+
506
+ if (result[0]._count != null) result[0]._count = null ;
507
+ return result ;
508
+ };
509
+
510
+
511
+ /*****
512
+ *
513
+ * setHeight
514
+ *
515
+ *****/
516
+ SC.RedBlackNode.prototype.setHeight = function() {
517
+ var leftHeight = (this._left != null) ? this._left._height : 0;
518
+ var rightHeight = (this._right != null) ? this._right._height : 0;
519
+
520
+ this._height = (leftHeight < rightHeight) ? rightHeight + 1 : leftHeight + 1;
521
+ };
522
+
523
+
524
+ /*****
525
+ *
526
+ * swingLeft
527
+ *
528
+ *****/
529
+ SC.RedBlackNode.prototype.swingLeft = function() {
530
+ var right = this._right;
531
+ var rightLeft = right._left;
532
+ var rightRight = right._right;
533
+ var left = this._left;
534
+
535
+ var leftHeight = (left != null ) ? left._height : 0;
536
+ var rightLeftHeight = (rightLeft != null ) ? rightLeft._height : 0;
537
+ var rightRightHeight = (rightRight != null ) ? rightRight._height : 0;
538
+
539
+ if ( rightLeftHeight > rightRightHeight ) {
540
+ this._right = right.moveRight();
541
+ }
542
+
543
+ return this.moveLeft();
544
+ };
545
+
546
+
547
+ /*****
548
+ *
549
+ * swingRight
550
+ *
551
+ *****/
552
+ SC.RedBlackNode.prototype.swingRight = function() {
553
+ var left = this._left;
554
+ var leftRight = left._right;
555
+ var leftLeft = left._left;
556
+ var right = this._right;
557
+
558
+ var rightHeight = (right != null ) ? right._height : 0;
559
+ var leftRightHeight = (leftRight != null ) ? leftRight._height : 0;
560
+ var leftLeftHeight = (leftLeft != null ) ? leftLeft._height : 0;
561
+
562
+ if ( leftRightHeight > leftLeftHeight ) {
563
+ this._left = left.moveLeft();
564
+ }
565
+
566
+ return this.moveRight();
567
+ };
568
+
569
+
570
+ /*****
571
+ *
572
+ * traverse
573
+ *
574
+ *****/
575
+ SC.RedBlackNode.prototype.traverse = function(func) {
576
+ if ( this._left != null ) this._left.traverse(func);
577
+ func(this);
578
+ if ( this._right != null ) this._right.traverse(func);
579
+ };
580
+
581
+
582
+ /*****
583
+ *
584
+ * toString
585
+ *
586
+ *****/
587
+ SC.RedBlackNode.prototype.toString = function() {
588
+ return this._value.toString();
589
+ };
590
+