The PropertyList is a utility class that can be used to hold a list of properties. It’s derived from an Array, so it can hold the properties in a well defined order. The order can be determined by an arbitrary number of sorting levels. A sorting level specifies an attribute who’s value should be used for sorting, a scenario index if necessary and the sorting direction (up/down). All nodes in the PropertyList must belong to the same PropertySet.
A PropertyList is always bound to a certain PropertySet. All properties in the list must be of that set.
# File lib/taskjuggler/PropertyList.rb, line 31 31: def initialize(arg, copyItems = true) 32: @items = copyItems ? arg.to_ary : [] 33: if arg.is_a?(PropertySet) 34: # Create a PropertyList from the given PropertySet. 35: @propertySet = arg 36: # To keep the list sorted, we may have to access Property attributes. 37: # Pre-scheduling, we can only use static attributes. Post-scheduling, 38: # we can include dynamic attributes as well. This query template will 39: # be used to query attributes when it has been set. Otherwise the list 40: # can only be sorted by static attributes. 41: @query = nil 42: resetSorting 43: addSortingCriteria('seqno', true, 1) 44: sort! 45: else 46: # Create a PropertyList from a given other PropertyList. 47: @propertySet = arg.propertySet 48: @query = arg.query ? arg.query.dup : nil 49: @sortingLevels = arg.sortingLevels 50: @sortingCriteria = arg.sortingCriteria.dup 51: @sortingUp = arg.sortingUp.dup 52: @scenarioIdx = arg.scenarioIdx.dup 53: end 54: end
Append another Array of PropertyTreeNodes or a PropertyList to this. The list will be sorted again.
# File lib/taskjuggler/PropertyList.rb, line 85 85: def append(list) 86: if $DEBUG 87: list.each do |node| 88: unless node.propertySet == @propertySet 89: raise "Fatal Error: All nodes must belong to the same PropertySet." 90: end 91: end 92: end 93: 94: @items.concat(list) 95: sort! 96: end
This function sets the index attribute of all the properties in the list. The index starts with 0 and increases for each property.
# File lib/taskjuggler/PropertyList.rb, line 147 147: def index 148: i = 0 149: @items.each do |p| 150: p.set('index', i += 1) 151: end 152: end
Return the Array index of item or nil.
# File lib/taskjuggler/PropertyList.rb, line 141 141: def itemIndex(item) 142: @items.index(item) 143: end
This class should be a derived class of Array. But since it re-defines sort!() and still needs to call Array::sort!() I took a different route. All missing methods will be propagated to the @items Array.
# File lib/taskjuggler/PropertyList.rb, line 59 59: def method_missing(func, *args, &block) 60: @items.method(func).call(*args, &block) 61: end
Clear all sorting levels.
# File lib/taskjuggler/PropertyList.rb, line 76 76: def resetSorting 77: @sortingLevels = 0 78: @sortingCriteria = [] 79: @sortingUp = [] 80: @scenarioIdx = [] 81: end
Set all sorting levels as Array of triplets.
# File lib/taskjuggler/PropertyList.rb, line 68 68: def setSorting(modes) 69: resetSorting 70: modes.each do |mode| 71: addSortingCriteria(*mode) 72: end 73: end
Sort the properties according to the currently defined sorting criteria.
# File lib/taskjuggler/PropertyList.rb, line 106 106: def sort! 107: if treeMode? 108: # Tree sorting is somewhat complex. It will be based on the 'tree' 109: # attribute of the PropertyTreeNodes but we have to update them first 110: # based on the other sorting criteria. 111: 112: # Remove the tree sorting mode first. 113: sc = @sortingCriteria.delete_at(0) 114: su = @sortingUp.delete_at(0) 115: si = @scenarioIdx.delete_at(0) 116: @sortingLevels -= 1 117: 118: # Sort the list based on the rest of the modes. 119: sortInternal 120: # The update the 'index' attributes of the PropertyTreeNodes. 121: index 122: # An then the 'tree' attributes. 123: indexTree 124: 125: # Restore the 'tree' sorting mode again. 126: @sortingCriteria.insert(0, sc) 127: @sortingUp.insert(0, su) 128: @scenarioIdx.insert(0, si) 129: @sortingLevels += 1 130: 131: # Sort again, now based on the updated 'tree' attributes. 132: sortInternal 133: else 134: sortInternal 135: end 136: # Update indexes. 137: index 138: end
# File lib/taskjuggler/PropertyList.rb, line 63 63: def to_ary 64: @items.dup 65: end
If the first sorting level is ‘tree’ the breakdown structure of the list is preserved. This is a somewhat special mode and this function returns true if the mode is set.
# File lib/taskjuggler/PropertyList.rb, line 101 101: def treeMode? 102: @sortingLevels > 0 && @sortingCriteria[0] == 'tree' 103: end
Append a new sorting level to the existing levels.
# File lib/taskjuggler/PropertyList.rb, line 169 169: def addSortingCriteria(criteria, up, scIdx) 170: unless @propertySet.knownAttribute?(criteria) || 171: @propertySet.hasQuery?(criteria, scIdx) 172: raise TjException.new, 173: "Unknown attribute #{criteria} used for sorting criterium" 174: end 175: if scIdx == 1 176: if @propertySet.scenarioSpecific?(criteria) 177: raise TjException.new, 178: "Attribute #{criteria} is scenario specific." + 179: "You must specify a scenario id." 180: end 181: else 182: if @propertySet.project.scenario(scIdx).nil? 183: raise TjException.new, "Unknown scenario index #{scIdx} used." 184: end 185: if !@propertySet.scenarioSpecific?(criteria) 186: raise TjException.new, "Attribute #{criteria} is not scenario specific" 187: end 188: end 189: @sortingCriteria.push(criteria) 190: @sortingUp.push(up) 191: @scenarioIdx.push(scIdx) 192: @sortingLevels += 1 193: end
Update the ‘tree’ indicies that are needed for the ‘tree’ sorting mode.
# File lib/taskjuggler/PropertyList.rb, line 196 196: def indexTree 197: @items.each do |property| 198: # The indicies are an Array if the 'index' attributes for this 199: # property and all its parents. 200: treeIdcs = property.getIndicies 201: # Now convert them to a String. 202: tree = '' 203: treeIdcs.each do |idx| 204: # Prefix the level index with zeros so that we always have a 6 205: # digit long String. 6 digits should be large enough for all 206: # real-world projects. 207: tree += idx.to_s.rjust(6, '0') 208: end 209: property.set('tree', tree) 210: end 211: end
# File lib/taskjuggler/PropertyList.rb, line 213 213: def sortInternal 214: @items.sort! do |a, b| 215: res = 0 216: @sortingLevels.times do |i| 217: if @query 218: # In case we have a Query reference, we get the two values with this 219: # query. 220: @query.scenarioIdx = @scenarioIdx[i] < 0 ? nil : @scenarioIdx[i] 221: @query.attributeId = @sortingCriteria[i] 222: 223: @query.property = a 224: @query.process 225: aVal = @query.to_sort 226: 227: @query.property = b 228: @query.process 229: bVal = @query.to_sort 230: else 231: # In case we don't have a query, we use the static mechanism. 232: # If the scenario index is negative we have a non-scenario-specific 233: # attribute. 234: if @scenarioIdx[i] < 0 235: if @sortingCriteria[i] == 'id' 236: aVal = a.fullId 237: bVal = b.fullId 238: else 239: aVal = a.get(@sortingCriteria[i]) 240: bVal = b.get(@sortingCriteria[i]) 241: end 242: else 243: aVal = a[@sortingCriteria[i], @scenarioIdx[i]] 244: bVal = b[@sortingCriteria[i], @scenarioIdx[i]] 245: end 246: end 247: res = aVal <=> bVal 248: # Invert the result if we have to sort in decreasing order. 249: res = -res unless @sortingUp[i] 250: # If the two elements are equal on this compare level we try the next 251: # level. 252: break if res != 0 253: end 254: res 255: end 256: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.