This is base class for all types of tabular reports. All tabular reports are converted to an abstract (output independent) intermediate form first, before the are turned into the requested output format.
Returns the default column title for the columns id.
# File lib/taskjuggler/reports/TableReport.rb, line 135 135: def TableReport::defaultColumnTitle(id) 136: # Return an empty string for some special columns that don't have a fixed 137: # title. 138: specials = %( chart hourly daily weekly monthly quarterly yearly) 139: return '' if specials.include?(id) 140: 141: # Return the title for build-in hardwired columns. 142: @@propertiesById.include?(id) ? @@propertiesById[id][0] : nil 143: end
Generate a new TableReport object.
# File lib/taskjuggler/reports/TableReport.rb, line 73 73: def initialize(report) 74: super 75: @report.content = self 76: 77: # Reference to the intermediate representation. 78: @table = nil 79: @start = @end = nil 80: 81: @legend = ReportTableLegend.new 82: 83: end
Return the alignment of the column based on the colId or the propertyType.
# File lib/taskjuggler/reports/TableReport.rb, line 159 159: def alignment(colId, propertyType) 160: if @@propertiesById.has_key?(colId) 161: return @@propertiesById[colId][2] 162: elsif @@propertiesByType.has_key?(propertyType) 163: return @@propertiesByType[propertyType][1] 164: else 165: :center 166: end 167: end
This function returns true if the values for the colId column need to be calculated.
# File lib/taskjuggler/reports/TableReport.rb, line 171 171: def calculated?(colId) 172: return @@propertiesById.has_key?(colId) 173: end
# File lib/taskjuggler/reports/TableReport.rb, line 85 85: def generateIntermediateFormat 86: super 87: end
Return if the column values should be indented based on the colId or the propertyType.
# File lib/taskjuggler/reports/TableReport.rb, line 147 147: def indent(colId, propertyType) 148: if @@propertiesById.has_key?(colId) 149: return @@propertiesById[colId][1] 150: elsif @@propertiesByType.has_key?(propertyType) 151: return @@propertiesByType[propertyType][0] 152: else 153: false 154: end 155: end
This functions returns true if the values for the col_id column are scenario specific.
# File lib/taskjuggler/reports/TableReport.rb, line 177 177: def scenarioSpecific?(colId) 178: if @@propertiesById.has_key?(colId) 179: return @@propertiesById[colId][3] 180: end 181: return false 182: end
# File lib/taskjuggler/reports/TableReport.rb, line 184 184: def supportedColumns 185: @@propertiesById.keys 186: end
Convert the table into an Array of Arrays. It has one Array for each line. The nested Arrays have one String for each column.
# File lib/taskjuggler/reports/TableReport.rb, line 130 130: def to_csv 131: @table.to_csv 132: end
Turn the TableReport into an equivalent HTML element tree.
# File lib/taskjuggler/reports/TableReport.rb, line 90 90: def to_html 91: html = [] 92: 93: html << XMLComment.new("Dynamic Report ID: " + 94: "#{@report.project.reportContexts.last. 95: dynamicReportId}") 96: html << rt_to_html('header') 97: html << (tableFrame = generateHtmlTableFrame) 98: 99: # Now generate the actual table with the data. 100: tableFrame << generateHtmlTableRow do 101: td = XMLElement.new('td') 102: td << @table.to_html 103: td 104: end 105: 106: # Embedd the caption as RichText into the table footer. 107: if a('caption') 108: tableFrame << generateHtmlTableRow do 109: td = XMLElement.new('td') 110: td << (div = XMLElement.new('div', 'class' => 'tj_table_caption')) 111: a('caption').sectionNumbers = false 112: div << a('caption').to_html 113: td 114: end 115: end 116: 117: # The legend. 118: tableFrame << generateHtmlTableRow do 119: td = XMLElement.new('td') 120: td << @legend.to_html 121: td 122: end 123: 124: html << rt_to_html('footer') 125: html 126: end
In case the user has not specified the report period, we try to fit all the tasks in and add an extra 5% time at both ends. scenarios is a list of scenario indexes.
# File lib/taskjuggler/reports/TableReport.rb, line 200 200: def adjustReportPeriod(tasks, scenarios, columns) 201: return if tasks.empty? || 202: a('start') != @project['start'] || a('end') != @project['end'] 203: 204: @start = @end = nil 205: scenarios.each do |scenarioIdx| 206: tasks.each do |task| 207: date = task['start', scenarioIdx] || @project['start'] 208: @start = date if @start.nil? || date < @start 209: date = task['end', scenarioIdx] || @project['end'] 210: @end = date if @end.nil? || date > @end 211: end 212: end 213: 214: # We want to add at least 5% on both ends. 215: margin = 0 216: minWidth = @end - @start + 1 217: columns.each do |column| 218: case column.id 219: when 'chart' 220: # In case we have a 'chart' column, we enforce certain minimum width 221: # The following table contains an entry for each scale. The entry 222: # consists of the triple 'seconds per unit', 'minimum width units' 223: # and 'margin units'. The minimum with does not include the margins 224: # since they are always added. 225: mwMap = { 226: 'hour' => [ 60 * 60, 18, 2 ], 227: 'day' => [ 60 * 60 * 24, 18, 2 ], 228: 'week' => [ 60 * 60 * 24 * 7, 6, 1 ], 229: 'month' => [ 60 * 60 * 24 * 31, 10, 1 ], 230: 'quarter' => [ 60 * 60 * 24 * 90, 6, 1 ], 231: 'year' => [ 60 * 60 * 24 * 365, 4, 1 ] 232: } 233: entry = mwMap[column.scale] 234: raise "Unknown scale #{column.scale}" unless entry 235: margin = entry[0] * entry[2] 236: # If the with determined by start and end dates of the task is below 237: # the minimum width, we increase the width to the value provided by 238: # the table. 239: minWidth = entry[0] * entry[1] if minWidth < entry[0] * entry[1] 240: break 241: when 'hourly', 'daily', 'weekly', 'monthly', 'quarterly', 'yearly' 242: # For the calendar columns we use a similar approach as we use for 243: # the 'chart' column. 244: mwMap = { 245: 'hourly' => [ 60 * 60, 18, 2 ], 246: 'daily' => [ 60 * 60 * 24, 18, 2 ], 247: 'weekly' => [ 60 * 60 * 24 * 7, 6, 1 ], 248: 'monthly' => [ 60 * 60 * 24 * 31, 10, 1 ], 249: 'quarterly' => [ 60 * 60 * 24 * 90, 6, 1 ], 250: 'yearly' => [ 60 * 60 * 24 * 365, 4, 1 ] 251: } 252: entry = mwMap[column.id] 253: raise "Unknown scale #{column.id}" unless entry 254: margin = entry[0] * entry[2] 255: minWidth = entry[0] * entry[1] if minWidth < entry[0] * entry[1] 256: break 257: end 258: end 259: 260: if minWidth > (@end - @start + 1) 261: margin += (minWidth - (@end - @start + 1)) / 2 262: end 263: @start -= margin 264: @end += margin 265: end
Generates cells for the table header. columnDef is the TableColumnDefinition object that describes the column. Based on the id of the column different actions need to be taken to generate the header text.
# File lib/taskjuggler/reports/TableReport.rb, line 270 270: def generateHeaderCell(columnDef) 271: case columnDef.id 272: when 'chart' 273: # For the 'chart' column we generate a GanttChart object. The sizes are 274: # set so that the lines of the Gantt chart line up with the lines of the 275: # table. 276: gantt = GanttChart.new(a('now'), 277: a('weekStartsMonday'), self) 278: gantt.generateByScale(@start, @end, columnDef.scale) 279: # The header consists of 2 lines separated by a 1 pixel boundary. 280: gantt.header.height = @table.headerLineHeight * 2 + 1 281: # The maximum width of the chart. In case it needs more space, a 282: # scrollbar is shown or the chart gets truncated depending on the output 283: # format. 284: gantt.viewWidth = columnDef.width ? columnDef.width : 450 285: column = ReportTableColumn.new(@table, columnDef, '') 286: column.cell1.special = gantt 287: column.cell2.hidden = true 288: column.scrollbar = gantt.hasScrollbar? 289: @table.equiLines = true 290: when 'hourly' 291: genCalChartHeader(columnDef, @start.midnight, :sameTimeNextHour, 292: :weekdayAndDate, :hour) 293: when 'daily' 294: genCalChartHeader(columnDef, @start.midnight, :sameTimeNextDay, 295: :monthAndYear, :day) 296: when 'weekly' 297: genCalChartHeader(columnDef, 298: @start.beginOfWeek(a('weekStartsMonday')), 299: :sameTimeNextWeek, :monthAndYear, :day) 300: when 'monthly' 301: genCalChartHeader(columnDef, @start.beginOfMonth, :sameTimeNextMonth, 302: :year, :shortMonthName) 303: when 'quarterly' 304: genCalChartHeader(columnDef, @start.beginOfQuarter, 305: :sameTimeNextQuarter, :year, :quarterName) 306: when 'yearly' 307: genCalChartHeader(columnDef, @start.beginOfYear, :sameTimeNextYear, 308: nil, :year) 309: else 310: # This is the most common case. It does not need any special treatment. 311: # We just set the pre-defined or user-defined column title in the first 312: # row of the header. The 2nd row is not visible. 313: column = ReportTableColumn.new(@table, columnDef, columnDef.title) 314: column.cell1.rows = 2 315: column.cell2.hidden = true 316: column.cell1.width = columnDef.width if columnDef.width 317: end 318: end
Generate a ReportTableLine for each of the resources in resourceList. In case taskList is not nil, it also generates the nested task lines for each task that the resource is assigned to. If scopeLine is defined, the generated resource lines will be within the scope this task line.
# File lib/taskjuggler/reports/TableReport.rb, line 390 390: def generateResourceList(resourceList, taskList, scopeLine) 391: queryAttrs = { 'project' => @project, 392: 'scopeProperty' => scopeLine ? scopeLine.property : nil, 393: 'loadUnit' => a('loadUnit'), 394: 'numberFormat' => a('numberFormat'), 395: 'timeFormat' => a('timeFormat'), 396: 'currencyFormat' => a('currencyFormat'), 397: 'start' => @start, 'end' => @end, 398: 'hideJournalEntry' => a('hideJournalEntry'), 399: 'costAccount' => a('costAccount'), 400: 'revenueAccount' => a('revenueAccount') } 401: resourceList.query = Query.new(queryAttrs) 402: resourceList.sort! 403: 404: # The primary line counter. Is not used for enclosed lines. 405: no = 0 406: # The scope line counter. It's reset for each new scope. 407: lineNo = scopeLine ? scopeLine.lineNo : 0 408: # Init the variable to get a larger scope 409: line = nil 410: resourceList.each do |resource| 411: # Get the current Query from the report context and create a copy. We 412: # are going to modify it. 413: query = @project.reportContexts.last.query.dup 414: query.property = resource 415: query.scopeProperty = scopeLine ? scopeLine.property : nil 416: 417: no += 1 418: Log.activity if lineNo % 10 == 0 419: lineNo += 1 420: a('scenarios').each do |scenarioIdx| 421: query.scenarioIdx = scenarioIdx 422: # Generate line for each resource. 423: line = ReportTableLine.new(@table, resource, scopeLine) 424: 425: line.no = no unless scopeLine 426: line.lineNo = lineNo 427: line.subLineNo = @table.lines 428: setIndent(line, a('resourceRoot'), resourceList.treeMode?) 429: 430: # Generate a cell for each column in this line. 431: a('columns').each do |column| 432: query.attributeId = column.id 433: next unless generateTableCell(line, resource, column, query) 434: end 435: end 436: 437: if taskList 438: # If we have a taskList we generate nested lines for each of the 439: # tasks that the resource is assigned to and pass the user-defined 440: # filter. 441: taskList.setSorting(a('sortTasks')) 442: assignedTaskList = filterTaskList(taskList, resource, 443: a('hideTask'), a('rollupTask'), 444: a('openNodes')) 445: assignedTaskList.sort! 446: lineNo = generateTaskList(assignedTaskList, nil, line) 447: end 448: end 449: lineNo 450: end
Generate a ReportTableLine for each of the tasks in taskList. In case resourceList is not nil, it also generates the nested resource lines for each resource that is assigned to the particular task. If scopeLine is defined, the generated task lines will be within the scope this resource line.
# File lib/taskjuggler/reports/TableReport.rb, line 325 325: def generateTaskList(taskList, resourceList, scopeLine) 326: queryAttrs = { 'project' => @project, 327: 'scopeProperty' => scopeLine ? scopeLine.property : nil, 328: 'loadUnit' => a('loadUnit'), 329: 'numberFormat' => a('numberFormat'), 330: 'timeFormat' => a('timeFormat'), 331: 'currencyFormat' => a('currencyFormat'), 332: 'start' => @start, 'end' => @end, 333: 'hideJournalEntry' => a('hideJournalEntry'), 334: 'costAccount' => a('costAccount'), 335: 'revenueAccount' => a('revenueAccount') } 336: taskList.query = Query.new(queryAttrs) 337: taskList.sort! 338: 339: # The primary line counter. Is not used for enclosed lines. 340: no = 0 341: # The scope line counter. It's reset for each new scope. 342: lineNo = scopeLine ? scopeLine.lineNo : 0 343: # Init the variable to get a larger scope 344: line = nil 345: taskList.each do |task| 346: # Get the current Query from the report context and create a copy. We 347: # are going to modify it. 348: query = @project.reportContexts.last.query.dup 349: query.property = task 350: query.scopeProperty = scopeLine ? scopeLine.property : nil 351: 352: no += 1 353: Log.activity if lineNo % 10 == 0 354: lineNo += 1 355: a('scenarios').each do |scenarioIdx| 356: query.scenarioIdx = scenarioIdx 357: # Generate line for each task. 358: line = ReportTableLine.new(@table, task, scopeLine) 359: 360: line.no = no unless scopeLine 361: line.lineNo = lineNo 362: line.subLineNo = @table.lines 363: setIndent(line, a('taskRoot'), taskList.treeMode?) 364: 365: # Generate a cell for each column in this line. 366: a('columns').each do |columnDef| 367: query.attributeId = columnDef.id 368: next unless generateTableCell(line, task, columnDef, query) 369: end 370: end 371: 372: if resourceList 373: # If we have a resourceList we generate nested lines for each of the 374: # resources that are assigned to this task and pass the user-defined 375: # filter. 376: resourceList.setSorting(a('sortResources')) 377: assignedResourceList = filterResourceList(resourceList, task, 378: a('hideResource'), a('rollupResource'), a('openNodes')) 379: assignedResourceList.sort! 380: lineNo = generateResourceList(assignedResourceList, nil, line) 381: end 382: end 383: lineNo 384: end
Make sure we have a valid cell text. If not, this is the result of an error. This could happen after scheduling errors.
# File lib/taskjuggler/reports/TableReport.rb, line 1018 1018: def checkCellText(cell) 1019: unless cell.text 1020: cell.text = '<Error>' 1021: cell.fontColor = '#FF0000' 1022: end 1023: end
Generate the header data for calendar tables. They consists of columns for each hour, day, week, etc. columnDef is the definition of the columns. t is the start time for the calendar. sameTimeNextFunc is a function that is called to advance t to the next table column interval. name1Func and name2Func are functions that return the upper and lower title of the particular column.
# File lib/taskjuggler/reports/TableReport.rb, line 460 460: def genCalChartHeader(columnDef, t, sameTimeNextFunc, name1Func, name2Func) 461: tableColumn = ReportTableColumn.new(@table, columnDef, '') 462: 463: # Calendar chars only work when all lines have same height. 464: @table.equiLines = true 465: 466: # Embedded tables have unpredictable width. So we always need to make room 467: # for a potential scrollbar. 468: tableColumn.scrollbar = true 469: 470: # Create the table that is embedded in this column. 471: tableColumn.cell1.special = table = ColumnTable.new 472: table.equiLines = true 473: tableColumn.cell2.hidden = true 474: table.viewWidth = columnDef.width ? columnDef.width : 450 475: 476: # Iterate over the report interval until we hit the end date. The 477: # iteration is done with 2 nested loops. The outer loops generates the 478: # intervals for the upper (larger) scale. The inner loop generates the 479: # lower (smaller) scale. 480: while t < @end 481: cellsInInterval = 0 482: # Label for upper scale. The yearly calendar only has a lower scale. 483: currentInterval = t.send(name1Func) if name1Func 484: firstColumn = nil 485: # The innter loops terminates when the label for the upper scale has 486: # changed to the next scale cell. 487: while t < @end && (name1Func.nil? || 488: t.send(name1Func) == currentInterval) 489: # call TjTime::sameTimeNext... function to get the end of the column. 490: nextT = t.send(sameTimeNextFunc) 491: iv = Interval.new(t, nextT) 492: # Create the new column object. 493: column = ReportTableColumn.new(table, nil, '') 494: # Store the date of the column in the original form. 495: column.cell1.data = t.to_s(a('timeFormat')) 496: # The upper scale cells will be merged into one large cell that spans 497: # all lower scale cells that belong to this upper cell. 498: if firstColumn.nil? 499: firstColumn = column 500: column.cell1.text = currentInterval.to_s 501: else 502: column.cell1.hidden = true 503: end 504: column.cell2.text = t.send(name2Func).to_s 505: # TODO: The width should be taken from some data structure. 506: column.cell2.width = 20 507: # Off-duty cells will have a different color than working time cells. 508: unless @project.isWorkingTime(iv) 509: column.cell2.category = 'tabhead_offduty' 510: end 511: cellsInInterval += 1 512: 513: t = nextT 514: end 515: # The the first upper scale cell how many trailing hidden cells are 516: # following. 517: firstColumn.cell1.columns = cellsInInterval 518: end 519: end
Generate the cells for the resource lines of a calendar column. These lines do not directly belong to the @table object but to an embedded ColumnTable object. Therefor a single @table column usually has many cells on each single line. scenarioIdx is the index of the scenario that is reported in this line. line is the @table line. t is the start date for the calendar. sameTimeNextFunc is the function that will move the date to the next cell.
# File lib/taskjuggler/reports/TableReport.rb, line 808 808: def genCalChartResourceCell(origQuery, line, columnDef, t, 809: sameTimeNextFunc) 810: # Find out if we have an enclosing task scope. 811: if line.scopeLine && line.scopeLine.property.is_a?(Task) 812: task = line.scopeLine.property 813: # Get the interval of the task. In case a date is invalid due to a 814: # scheduling problem, we use the full project interval. 815: taskStart = task['start', origQuery.scenarioIdx] 816: taskEnd = task['end', origQuery.scenarioIdx] 817: taskIv = Interval.new(taskStart.nil? ? @project['start'] : taskStart, 818: taskEnd.nil? ? @project['end'] : taskEnd) 819: else 820: task = nil 821: end 822: 823: firstCell = nil 824: while t < @end 825: # We modify the start and end dates to match the cell boundaries. So 826: # we need to make sure we don't modify the original Query but our own 827: # copies. 828: query = origQuery.dup 829: 830: # Create a new cell 831: cell = newCell(query, line) 832: 833: # call TjTime::sameTimeNext... function 834: nextT = t.send(sameTimeNextFunc) 835: cellIv = Interval.new(t, nextT) 836: # Get work load for all tasks. 837: query.scopeProperty = nil 838: query.attributeId = 'effort' 839: query.startIdx = @project.dateToIdx(t) 840: query.endIdx = @project.dateToIdx(nextT) - 1 841: query.process 842: workLoad = query.to_num 843: scaledWorkLoad = query.to_s 844: if task 845: # Get work load for the particular task. 846: query.scopeProperty = task 847: query.process 848: workLoadTask = query.to_num 849: scaledWorkLoad = query.to_s 850: else 851: workLoadTask = 0.0 852: end 853: # Get unassigned work load. 854: query.attributeId = 'freework' 855: query.process 856: freeLoad = query.to_num 857: case columnDef.content 858: when 'empty' 859: # We only generate cells will different background colors. 860: when 'load' 861: # Report the workload of the resource in this time interval. 862: # To increase readability, we don't show 0.0 values. 863: wLoad = task ? workLoadTask : workLoad 864: if wLoad > 0.0 865: cell.text = scaledWorkLoad 866: end 867: else 868: raise "Unknown column content #{column.content}" 869: end 870: 871: cdText = columnDef.cellText.getPattern(query) 872: cell.text = cdText if cdText 873: 874: # Set the tooltip for the cell. We might delete it again. 875: cell.tooltip = columnDef.tooltip.getPattern(query) || nil 876: cell.showTooltipHint = false 877: 878: # Determine cell category (mostly the background color) 879: cell.category = if task 880: if cellIv.overlaps?(taskIv) 881: if workLoadTask > 0.0 && freeLoad == 0.0 882: 'busy' 883: elsif workLoad == 0.0 && freeLoad == 0.0 884: cell.tooltip = nil 885: 'offduty' 886: else 887: 'loaded' 888: end 889: else 890: if freeLoad > 0.0 891: 'free' 892: elsif workLoad == 0.0 && freeLoad == 0.0 893: cell.tooltip = nil 894: 'offduty' 895: else 896: cell.tooltip = nil 897: 'resourcecell' 898: end 899: end 900: else 901: if workLoad > 0.0 && freeLoad == 0.0 902: 'busy' 903: elsif workLoad > 0.0 && freeLoad > 0.0 904: 'loaded' 905: elsif workLoad == 0.0 && freeLoad > 0.0 906: 'free' 907: else 908: cell.tooltip = nil 909: 'offduty' 910: end 911: end 912: cell.category += line.property.get('index') % 2 == 1 ? '1' : '2' 913: 914: tryCellMerging(cell, line, firstCell) 915: 916: t = nextT 917: firstCell = cell unless firstCell 918: end 919: 920: legend.addCalendarItem('Resource is fully loaded', 'busy1') 921: legend.addCalendarItem('Resource is partially loaded', 'loaded1') 922: legend.addCalendarItem('Resource is available', 'free') 923: legend.addCalendarItem('Off duty time', 'offduty') 924: end
Generate the cells for the task lines of a calendar column. These lines do not directly belong to the @table object but to an embedded ColumnTable object. Therefor a single @table column usually has many cells on each single line. scenarioIdx is the index of the scenario that is reported in this line. line is the @table line. t is the start date for the calendar. sameTimeNextFunc is the function that will move the date to the next cell.
# File lib/taskjuggler/reports/TableReport.rb, line 726 726: def genCalChartTaskCell(origQuery, line, columnDef, t, sameTimeNextFunc) 727: task = line.property 728: # Find out if we have an enclosing resource scope. 729: if line.scopeLine && line.scopeLine.property.is_a?(Resource) 730: resource = line.scopeLine.property 731: else 732: resource = nil 733: end 734: 735: # Get the interval of the task. In case a date is invalid due to a 736: # scheduling problem, we use the full project interval. 737: taskStart = task['start', origQuery.scenarioIdx] 738: taskEnd = task['end', origQuery.scenarioIdx] 739: taskIv = Interval.new(taskStart.nil? ? @project['start'] : taskStart, 740: taskEnd.nil? ? @project['end'] : taskEnd) 741: 742: firstCell = nil 743: while t < @end 744: # We modify the start and end dates to match the cell boundaries. So 745: # we need to make sure we don't modify the original Query but our own 746: # copies. 747: query = origQuery.dup 748: # call TjTime::sameTimeNext... function 749: nextT = t.send(sameTimeNextFunc) 750: cellIv = Interval.new(t, nextT) 751: case columnDef.content 752: when 'empty' 753: # Create a new cell 754: cell = newCell(query, line) 755: # We only generate cells will different background colors. 756: when 'load' 757: query.attributeId = 'effort' 758: query.start = t 759: query.end = nextT 760: query.process 761: 762: # Create a new cell 763: cell = newCell(query, line) 764: 765: # To increase readability, we don't show 0.0 values. 766: cell.text = query.to_s if query.to_num != 0.0 767: else 768: raise "Unknown column content #{column.content}" 769: end 770: 771: cdText = columnDef.cellText.getPattern(query) 772: cell.text = cdText if cdText 773: 774: # Determine cell category (mostly the background color) 775: if cellIv.overlaps?(taskIv) 776: # The cell is either a container or leaf task 777: cell.category = task.container? ? 'calconttask' : 'caltask' 778: # If the user has requested a custom tooltip, add it to each task cell. 779: cell.tooltip = columnDef.tooltip.getPattern(query) || nil 780: cell.showTooltipHint = false 781: elsif !@project.isWorkingTime(cellIv) 782: # The cell is a vacation cell. 783: cell.category = 'offduty' 784: else 785: # The cell is just filled with the background color. 786: cell.category = 'taskcell' 787: end 788: cell.category += line.property.get('index') % 2 == 1 ? '1' : '2' 789: 790: tryCellMerging(cell, line, firstCell) 791: 792: t = nextT 793: firstCell = cell unless firstCell 794: end 795: 796: legend.addCalendarItem('Container Task', 'calconttask1') 797: legend.addCalendarItem('Task', 'caltask1') 798: legend.addCalendarItem('Off duty time', 'offduty') 799: end
Generate a ReportTableCell filled with a calculted value from the property or other sources of information. It returns true if the cell exists, false for a hidden cell. query is the Query to get the cell value. line is the ReportTableLine of the cell. columnDef is the TableColumnDefinition of the column. property is the PropertyTreeNode that is reported in this cell.
# File lib/taskjuggler/reports/TableReport.rb, line 652 652: def genCalculatedCell(query, line, columnDef, property) 653: query = query.dup 654: query.listType = columnDef.listType 655: query.listItem = columnDef.listItem 656: 657: # Create a new cell 658: cell = newCell(query, line) 659: 660: unless setScenarioSettings(cell, query.scenarioIdx, 661: scenarioSpecific?(columnDef.id)) 662: return false 663: end 664: 665: setStandardCellAttributes(cell, columnDef, nil, line) 666: 667: if query.process 668: cell.text = (rti = query.to_rti) ? rti : query.to_s 669: end 670: 671: # Some columns need some extra care. 672: case columnDef.id 673: when 'alert' 674: id = @project.alertLevelId(query.to_num) 675: cell.icon = "flag-#{id}" 676: cell.fontColor = @project.alertLevelColor(query.to_sort) 677: when 'alerttrend' 678: icons = %( up flat down ) 679: cell.icon = "trend-#{icons[query.to_sort]}" 680: when 'line' 681: cell.text = line.lineNo.to_s 682: when 'name' 683: cell.icon = 684: if property.is_a?(Task) 685: if property.container? 686: 'taskgroup' 687: else 688: 'task' 689: end 690: elsif property.is_a?(Resource) 691: if property.container? 692: 'resourcegroup' 693: else 694: 'resource' 695: end 696: else 697: nil 698: end 699: cell.iconTooltip = RichText.new("'''ID:''' #{property.fullId}"). 700: generateIntermediateFormat 701: when 'no' 702: cell.text = line.no.to_s 703: when 'bsi' 704: cell.indent = 2 if line.scopeLine 705: when 'scenario' 706: cell.text = @project.scenario(query.scenarioIdx).name 707: end 708: 709: # Replace the cell text if the user has requested a custom cell text. 710: cdText = columnDef.cellText.getPattern(query) 711: cell.text = cdText if cdText 712: 713: setCustomCellAttributes(cell, columnDef, query) 714: checkCellText(cell) 715: 716: true 717: end
Generate a ReportTableCell filled the value of an attribute of the property that line is for. It returns true if the cell exists, false for a hidden cell.
# File lib/taskjuggler/reports/TableReport.rb, line 606 606: def genStandardCell(query, line, columnDef) 607: query = query.dup 608: query.listType = columnDef.listType 609: query.listItem = columnDef.listItem 610: 611: # Find out, what type of PropertyTreeNode we are dealing with. 612: property = line.property 613: if property.is_a?(Task) 614: propertyList = @project.tasks 615: elsif property.is_a?(Resource) 616: propertyList = @project.resources 617: else 618: raise "Unknown property type #{property.class}" 619: end 620: 621: # Create a new cell 622: cell = newCell(query, line) 623: 624: unless setScenarioSettings(cell, query.scenarioIdx, 625: propertyList.scenarioSpecific?(columnDef.id)) 626: return false 627: end 628: 629: setStandardCellAttributes(cell, columnDef, 630: propertyList.attributeType(columnDef.id), line) 631: 632: # If the user has requested a custom cell text, this will be used 633: # instead of the queried one. 634: if (cdText = columnDef.cellText.getPattern(query)) 635: cell.text = cdText 636: elsif query.process 637: cell.text = (rti = query.to_rti) ? rti : query.to_s 638: end 639: 640: setCustomCellAttributes(cell, columnDef, query) 641: checkCellText(cell) 642: 643: true 644: end
Generate a cell of the table. line is the ReportTableLine that this cell should belong to. property is the PropertyTreeNode that is reported in this line. columnDef is the TableColumnDefinition of the column this cell should belong to. scenarioIdx is the index of the scenario that is reported in this line.
There are 4 kinds of cells. The most simple one is the standard cell. It literally reports the value of a property attribute. Calculated cells are more flexible. They contain computed values. The values are computed at cell generation time. The calendar columns consist of multiple sub columns. In such a case many cells are generated with a single call of this method. The last kind of cell is actually not a cell. It just generates the chart objects that belong to the property in this line.
# File lib/taskjuggler/reports/TableReport.rb, line 534 534: def generateTableCell(line, property, columnDef, query) 535: if columnDef.start || columnDef.end 536: # If the user has specified a new start or end time for this column, 537: # we have to duplicate the query before we modify it. 538: query = query.dup 539: query.start = columnDef.start if columnDef.start 540: query.end = columnDef.end if columnDef.end 541: end 542: 543: case columnDef.id 544: when 'chart' 545: # Generate a hidden cell. The real meat is in the actual chart object, 546: # not in this cell. 547: cell = ReportTableCell.new(line, query, '') 548: cell.hidden = true 549: cell.text = nil 550: # The GanttChart can be reached via the special variable of the column 551: # header. 552: chart = columnDef.column.cell1.special 553: GanttLine.new(chart, query, (line.subLineNo - 1) * (line.height + 1), 554: line.height, a('selfcontained') ? nil : columnDef.tooltip) 555: return true 556: # The calendar cells can be all generated by the same function. But we 557: # need to use different parameters. 558: when 'hourly' 559: start = @start.midnight 560: sameTimeNextFunc = :sameTimeNextHour 561: when 'daily' 562: start = @start.midnight 563: sameTimeNextFunc = :sameTimeNextDay 564: when 'weekly' 565: start = @start.beginOfWeek(a('weekStartsMonday')) 566: sameTimeNextFunc = :sameTimeNextWeek 567: when 'monthly' 568: start = @start.beginOfMonth 569: sameTimeNextFunc = :sameTimeNextMonth 570: when 'quarterly' 571: start = @start.beginOfQuarter 572: sameTimeNextFunc = :sameTimeNextQuarter 573: when 'yearly' 574: start = @start.beginOfYear 575: sameTimeNextFunc = :sameTimeNextYear 576: else 577: if calculated?(columnDef.id) 578: return genCalculatedCell(query, line, columnDef, property) 579: else 580: return genStandardCell(query, line, columnDef) 581: end 582: end 583: 584: # The calendar cells don't live in this ReportTable but in an embedded 585: # ReportTable that can be reached via the column header special variable. 586: # For embedded column tables we need to create a new line. 587: tcLine = ReportTableLine.new(columnDef.column.cell1.special, 588: line.property, line.scopeLine) 589: 590: PlaceHolderCell.new(line, tcLine) 591: # Depending on the property type we use different generator functions. 592: if property.is_a?(Task) 593: genCalChartTaskCell(query, tcLine, columnDef, start, sameTimeNextFunc) 594: elsif property.is_a?(Resource) 595: genCalChartResourceCell(query, tcLine, columnDef, start, 596: sameTimeNextFunc) 597: else 598: raise "Unknown property type #{property.class}" 599: end 600: true 601: end
Create a new ReportTableCell object and initialize some common values.
# File lib/taskjuggler/reports/TableReport.rb, line 992 992: def newCell(query, line) 993: property = line.property 994: cell = ReportTableCell.new(line, query) 995: 996: # Cells for containers should be using bold font face. 997: cell.bold = true if property.container? && line.bold 998: cell.selfcontained = a('selfcontained') 999: cell 1000: end
# File lib/taskjuggler/reports/TableReport.rb, line 950 950: def setCustomCellAttributes(cell, columnDef, query) 951: # Replace the cell background color if the user has requested a custom 952: # color. 953: cellColor = columnDef.cellColor.getPattern(query) 954: cell.cellColor = cellColor if cellColor 955: 956: # Replace the font color setting if the user has requested a custom 957: # color. 958: fontColor = columnDef.fontColor.getPattern(query) 959: cell.fontColor = fontColor if fontColor 960: 961: # Replace the default cell alignment if the user has requested a custom 962: # alignment. 963: hAlign = columnDef.hAlign.getPattern(query) 964: cell.alignment = hAlign if hAlign 965: 966: # Register the custom tooltip if the user has requested one. 967: cdTooltip = columnDef.tooltip.getPattern(query) 968: cell.tooltip = cdTooltip if cdTooltip 969: end
Determine the indentation for this line.
# File lib/taskjuggler/reports/TableReport.rb, line 1003 1003: def setIndent(line, propertyRoot, treeMode) 1004: property = line.property 1005: scopeLine = line.scopeLine 1006: level = property.level - (propertyRoot ? propertyRoot.level : 0) 1007: # We indent at least as much as the scopeline + 1, if we have a scope. 1008: line.indentation = scopeLine.indentation + 1 if scopeLine 1009: # In tree mode we indent according to the level. 1010: if treeMode 1011: line.indentation += level 1012: line.bold = true 1013: end 1014: end
# File lib/taskjuggler/reports/TableReport.rb, line 972 972: def setScenarioSettings(cell, scenarioIdx, scenarioSpecific) 973: # Check if we are dealing with multiple scenarios. 974: if a('scenarios').length > 1 975: # Check if the attribute is not scenario specific 976: unless scenarioSpecific 977: if scenarioIdx == a('scenarios').first 978: # Use a somewhat bigger font. 979: cell.fontSize = 15 980: else 981: # And hide the cells for all but the first scenario. 982: cell.hidden = true 983: return false 984: end 985: cell.rows = a('scenarios').length 986: end 987: end 988: true 989: end
This method takes care of often used cell attributes like indentation, alignment and background color.
# File lib/taskjuggler/reports/TableReport.rb, line 928 928: def setStandardCellAttributes(cell, columnDef, attributeType, line) 929: # Determine whether it should be indented 930: if indent(columnDef.id, attributeType) 931: cell.indent = line.indentation 932: end 933: 934: # Determine the cell alignment 935: cell.alignment = alignment(columnDef.id, attributeType) 936: 937: # Set background color 938: if line.property.is_a?(Task) 939: cell.category = line.property.get('index') % 2 == 1 ? 940: 'taskcell1' : 'taskcell2' 941: else 942: cell.category = line.property.get('index') % 2 == 1 ? 943: 'resourcecell1' : 'resourcecell2' 944: end 945: 946: # Set column width 947: cell.width = columnDef.width if columnDef.width 948: end
Try to merge equal cells without text to multi-column cells.
# File lib/taskjuggler/reports/TableReport.rb, line 1026 1026: def tryCellMerging(cell, line, firstCell) 1027: if cell.text == '' && firstCell && (c = line.last(1)) && c == cell 1028: cell.hidden = true 1029: c.columns += 1 1030: end 1031: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.