sproutcore 0.9.4 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
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
+