class TaskJuggler::TableReport
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.
Attributes
Public Class Methods
Source
# File lib/taskjuggler/reports/TableReport.rb, line 180 def TableReport::alignment(colId, attributeType) if @@propertiesById.has_key?(colId) return @@propertiesById[colId][2] elsif @@propertiesByType.has_key?(attributeType) return @@propertiesByType[attributeType][1] else :center end end
Return the alignment of the column based on the colId or the attributeType.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 192 def TableReport::calculated?(colId) return @@propertiesById.has_key?(colId) end
This function returns true if the values for the colId column need to be calculated.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 156 def TableReport::defaultColumnTitle(id) # Return an empty string for some special columns that don't have a fixed # title. specials = %w( chart hourly daily weekly monthly quarterly yearly) return '' if specials.include?(id) # Return the title for build-in hardwired columns. @@propertiesById.include?(id) ? @@propertiesById[id][0] : nil end
Returns the default column title for the columns id.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 168 def TableReport::indent(colId, propertyType) if @@propertiesById.has_key?(colId) return @@propertiesById[colId][1] elsif @@propertiesByType.has_key?(propertyType) return @@propertiesByType[propertyType][0] else false end end
Return if the column values should be indented based on the colId or the propertyType.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 91 def initialize(report) super @report.content = self # Reference to the intermediate representation. @table = nil # The table is generated row after row. We need to hold some computed # values that are specific to certain columns. For that we use a Hash of # ReportTableColumn objects. @columns = { } @legend = ReportTableLegend.new end
Generate a new TableReport
object.
TaskJuggler::ReportBase::new
Source
# File lib/taskjuggler/reports/TableReport.rb, line 198 def TableReport::scenarioSpecific?(colId) if @@propertiesById.has_key?(colId) return @@propertiesById[colId][3] end return false end
This functions returns true if the values for the col_id column are scenario specific.
Public Instance Methods
Source
# File lib/taskjuggler/reports/TableReport.rb, line 106 def generateIntermediateFormat super end
TaskJuggler::ReportBase#generateIntermediateFormat
Source
# File lib/taskjuggler/reports/TableReport.rb, line 151 def to_csv @table.to_csv 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.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 111 def to_html html = [] html << XMLComment.new("Dynamic Report ID: " + "#{@report.project.reportContexts.last. dynamicReportId}") html << rt_to_html('header') html << (tableFrame = generateHtmlTableFrame) # Now generate the actual table with the data. tableFrame << generateHtmlTableRow do td = XMLElement.new('td') td << @table.to_html td end # Embedd the caption as RichText into the table footer. if a('caption') tableFrame << generateHtmlTableRow do td = XMLElement.new('td') td << (div = XMLElement.new('div', 'class' => 'tj_table_caption')) a('caption').sectionNumbers = false div << a('caption').to_html td end end # The legend. tableFrame << generateHtmlTableRow do td = XMLElement.new('td') td << @legend.to_html td end html << rt_to_html('footer') html end
Turn the TableReport
into an equivalent HTML element tree.
Protected Instance Methods
Source
# File lib/taskjuggler/reports/TableReport.rb, line 216 def adjustColumnPeriod(columnDef, tasks = [], scenarios = []) # If we have user specified dates for the report period or the column # period, we don't adjust the period. This flag is used to mark if we # have user-provided values. doNotAdjustStart = false doNotAdjustEnd = false # Determine the start date for the column. if columnDef.start # We have a user-specified, column specific start date. rStart = columnDef.start doNotAdjustStart = true else # Use the report start date. rStart = a('start') doNotAdjustStart = true if rStart != @project['start'] end if columnDef.end rEnd = columnDef.end doNotAdjustEnd = true else rEnd = a('end') doNotAdjustEnd = true if rEnd != @project['end'] end origStart = rStart origEnd = rEnd # Save the unadjusted dates to the columns Hash. @columns[columnDef] = TableReportColumn.new(rStart, rEnd) # If the task list is empty or the user has provided a custom start or # end date, we don't touch the report period. return if tasks.empty? || scenarios.empty? || (doNotAdjustStart && doNotAdjustEnd) # Find the start date of the earliest tasks included in the report and # the end date of the last included tasks. rStart = rEnd = nil scenarios.each do |scenarioIdx| tasks.each do |task| date = task['start', scenarioIdx] || @project['start'] rStart = date if rStart.nil? || date < rStart date = task['end', scenarioIdx] || @project['end'] rEnd = date if rEnd.nil? || date > rEnd end end # We want to add at least 5% on both ends. margin = 0 minWidth = rEnd - rStart + 1 case columnDef.id when 'chart' # In case we have a 'chart' column, we enforce certain minimum width # The following table contains an entry for each scale. The entry # consists of the triple 'seconds per unit', 'minimum width units' # and 'margin units'. The minimum with does not include the margins # since they are always added. mwMap = { 'hour' => [ 60 * 60, 18, 2 ], 'day' => [ 60 * 60 * 24, 18, 2 ], 'week' => [ 60 * 60 * 24 * 7, 6, 1 ], 'month' => [ 60 * 60 * 24 * 31, 10, 1 ], 'quarter' => [ 60 * 60 * 24 * 90, 6, 1 ], 'year' => [ 60 * 60 * 24 * 365, 4, 1 ] } entry = mwMap[columnDef.scale] raise "Unknown scale #{columnDef.scale}" unless entry margin = entry[0] * entry[2] # If the with determined by start and end dates of the task is below # the minimum width, we increase the width to the value provided by # the table. minWidth = entry[0] * entry[1] if minWidth < entry[0] * entry[1] when 'hourly', 'daily', 'weekly', 'monthly', 'quarterly', 'yearly' # For the calendar columns we use a similar approach as we use for # the 'chart' column. mwMap = { 'hourly' => [ 60 * 60, 18, 2 ], 'daily' => [ 60 * 60 * 24, 18, 2 ], 'weekly' => [ 60 * 60 * 24 * 7, 6, 1 ], 'monthly' => [ 60 * 60 * 24 * 31, 10, 1 ], 'quarterly' => [ 60 * 60 * 24 * 90, 6, 1 ], 'yearly' => [ 60 * 60 * 24 * 365, 4, 1 ] } entry = mwMap[columnDef.id] raise "Unknown scale #{columnDef.id}" unless entry margin = entry[0] * entry[2] minWidth = entry[0] * entry[1] if minWidth < entry[0] * entry[1] else doNotAdjustStart = doNotAdjustEnd = true end unless doNotAdjustStart && doNotAdjustEnd if minWidth > (rEnd - rStart + 1) margin = (minWidth - (rEnd - rStart + 1)) / 2 end rStart -= margin rEnd += margin # This could cause rStart to be larger than rEnd. rStart = origStart if doNotAdjustStart rEnd = origEnd if doNotAdjustEnd # Ensure that we have a valid interval. If not, go back to the # original interval dates. if rStart >= rEnd rStart = origStart rEnd = origEnd end # Save the adjusted dates to the columns Hash. @columns[columnDef] = TableReportColumn.new(rStart, rEnd) end 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 for some specific type of columns. scenarios is a list of scenario indexes. columnDef is a reference to the TableColumnDefinition
object describing the current column.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 392 def generateAccountList(accountList, lineOffset, mode) # Get the current Query from the report context and create a copy. We # are going to modify it. accountList.query = query = @project.reportContexts.last.query.dup accountList.sort! # The primary line counter. Is not used for enclosed lines. no = lineOffset # The scope line counter. It's reset for each new scope. lineNo = lineOffset # Init the variable to get a larger scope line = nil accountList.each do |account| query.property = account no += 1 Log.activity if lineNo % 10 == 0 lineNo += 1 a('scenarios').each do |scenarioIdx| query.scenarioIdx = scenarioIdx # Generate line for each account. line = ReportTableLine.new(@table, account, nil) line.no = no line.lineNo = lineNo line.subLineNo = @table.lines setIndent(line, a('accountroot'), accountList.treeMode?) # Generate a cell for each column in this line. a('columns').each do |columnDef| next unless generateTableCell(line, columnDef, query) end end end lineNo end
Generate a ReportTableLine
for each of the accounts in accountList. If scopeLine is defined, the generated account lines will be within the scope this resource line.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 335 def generateHeaderCell(columnDef) rStart = @columns[columnDef].start rEnd = @columns[columnDef].end case columnDef.id when 'chart' # For the 'chart' column we generate a GanttChart object. The sizes are # set so that the lines of the Gantt chart line up with the lines of the # table gantt = GanttChart.new(a('now'), a('weekStartsMonday'), columnDef, self, a('markdate')) gantt.generateByScale(rStart, rEnd, columnDef.scale) # The header consists of 2 lines separated by a 1 pixel boundary. gantt.header.height = @table.headerLineHeight * 2 + 1 # The maximum width of the chart. In case it needs more space, a # scrollbar is shown or the chart gets truncated depending on the output # format. gantt.viewWidth = columnDef.width ? columnDef.width : 450 column = ReportTableColumn.new(@table, columnDef, '') column.cell1.special = gantt column.cell2.hidden = true column.scrollbar = gantt.hasScrollbar? @table.equiLines = true when 'hourly' genCalChartHeader(columnDef, rStart.midnight, rEnd, :sameTimeNextHour, '%A %Y-%m-%d', '%H') when 'daily' genCalChartHeader(columnDef, rStart.midnight, rEnd, :sameTimeNextDay, '%b %Y', '%d') when 'weekly' genCalChartHeader(columnDef, rStart.beginOfWeek(a('weekStartsMonday')), rEnd, :sameTimeNextWeek, '%b %Y', '%d') when 'monthly' genCalChartHeader(columnDef, rStart.beginOfMonth, rEnd, :sameTimeNextMonth, '%Y', '%b') when 'quarterly' genCalChartHeader(columnDef, rStart.beginOfQuarter, rEnd, :sameTimeNextQuarter, '%Y', 'Q%Q') when 'yearly' genCalChartHeader(columnDef, rStart.beginOfYear, rEnd, :sameTimeNextYear, nil, '%Y') else # This is the most common case. It does not need any special treatment. # We just set the pre-defined or user-defined column title in the first # row of the header. The 2nd row is not visible. column = ReportTableColumn.new(@table, columnDef, columnDef.title) column.cell1.rows = 2 column.cell2.hidden = true column.cell1.width = columnDef.width if columnDef.width end 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.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 490 def generateResourceList(resourceList, taskList, scopeLine) # Get the current Query from the report context and create a copy. We # are going to modify it. resourceList.query = query = @project.reportContexts.last.query.dup query.scopeProperty = scopeLine ? scopeLine.property : nil resourceList.sort! # The primary line counter. Is not used for enclosed lines. no = 0 # The scope line counter. It's reset for each new scope. lineNo = scopeLine ? scopeLine.lineNo : 0 # Init the variable to get a larger scope line = nil resourceList.each do |resource| # Get the current Query from the report context and create a copy. We # are going to modify it. query.property = resource query.scopeProperty = scopeLine ? scopeLine.property : nil no += 1 Log.activity if lineNo % 10 == 0 lineNo += 1 a('scenarios').each do |scenarioIdx| query.scenarioIdx = scenarioIdx # Generate line for each resource. line = ReportTableLine.new(@table, resource, scopeLine) line.no = no unless scopeLine line.lineNo = lineNo line.subLineNo = @table.lines setIndent(line, a('resourceroot'), resourceList.treeMode?) # Generate a cell for each column in this line. a('columns').each do |column| next unless generateTableCell(line, column, query) end end if taskList # If we have a taskList we generate nested lines for each of the # tasks that the resource is assigned to and pass the user-defined # filter. taskList.setSorting(a('sortTasks')) assignedTaskList = filterTaskList(taskList, resource, a('hideTask'), a('rollupTask'), a('openNodes')) assignedTaskList.sort! lineNo = generateTaskList(assignedTaskList, nil, line) end end lineNo 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.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 434 def generateTaskList(taskList, resourceList, scopeLine) # Get the current Query from the report context and create a copy. We # are going to modify it. taskList.query = query = @project.reportContexts.last.query.dup query.scopeProperty = scopeLine ? scopeLine.property : nil taskList.sort! # The primary line counter. Is not used for enclosed lines. no = 0 # The scope line counter. It's reset for each new scope. lineNo = scopeLine ? scopeLine.lineNo : 0 # Init the variable to get a larger scope line = nil taskList.each do |task| # Get the current Query from the report context and create a copy. We # are going to modify it. query.property = task query.scopeProperty = scopeLine ? scopeLine.property : nil no += 1 Log.activity if lineNo % 10 == 0 lineNo += 1 a('scenarios').each do |scenarioIdx| query.scenarioIdx = scenarioIdx # Generate line for each task. line = ReportTableLine.new(@table, task, scopeLine) line.no = no unless scopeLine line.lineNo = lineNo line.subLineNo = @table.lines setIndent(line, a('taskroot'), taskList.treeMode?) # Generate a cell for each column in this line. a('columns').each do |columnDef| next unless generateTableCell(line, columnDef, query) end end if resourceList # If we have a resourceList we generate nested lines for each of the # resources that are assigned to this task and pass the user-defined # filter. resourceList.setSorting(a('sortResources')) assignedResourceList = filterResourceList(resourceList, task, a('hideResource'), a('rollupResource'), a('openNodes')) assignedResourceList.sort! lineNo = generateResourceList(assignedResourceList, nil, line) end end lineNo 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.
Private Instance Methods
Source
# File lib/taskjuggler/reports/TableReport.rb, line 1181 def checkCellText(cell) unless cell.text cell.text = '<Error>' cell.fontColor = '#FF0000' end end
Make sure we have a valid cell text. If not, this is the result of an error. This could happen after scheduling errors.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 828 def genCalChartAccountCell(query, line, columnDef, t, sameTimeNextFunc) # We modify the start and end dates to match the cell boundaries. So # we need to make sure we don't modify the original Query but our own # copies. query = query.dup firstCell = nil endDate = query.end while t < endDate # call TjTime::sameTimeNext... function nextT = t.send(sameTimeNextFunc) query.attributeId = 'balance' query.start = t query.end = nextT query.process # Create a new cell cell = newCell(query, line) cell.text = query.to_s cdText = columnDef.cellText.getPattern(query) cell.text = cdText if cdText cell.showTooltipHint = false setAccountCellBgColor(query, line, cell) setCustomCellAttributes(cell, columnDef, query) tryCellMerging(cell, line, firstCell) t = nextT firstCell = cell unless firstCell end end
Generate the cells for the account 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.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 551 def genCalChartHeader(columnDef, t, rEnd, sameTimeNextFunc, timeformat1, timeformat2) tableColumn = ReportTableColumn.new(@table, columnDef, '') # Overwrite the built-in time formats if the user specified a different # one. timeformat1 = columnDef.timeformat1 if columnDef.timeformat1 timeformat2 = columnDef.timeformat2 if columnDef.timeformat2 # Calendar chars only work when all lines have same height. @table.equiLines = true # Embedded tables have unpredictable width. So we always need to make room # for a potential scrollbar. tableColumn.scrollbar = true # Create the table that is embedded in this column. tableColumn.cell1.special = table = ColumnTable.new table.equiLines = true table.selfcontained = a('selfcontained') tableColumn.cell2.hidden = true table.viewWidth = columnDef.width ? columnDef.width : 450 # Iterate over the report interval until we hit the end date. The # iteration is done with 2 nested loops. The outer loops generates the # intervals for the upper (larger) scale. The inner loop generates the # lower (smaller) scale. while t < rEnd cellsInInterval = 0 # Label for upper scale. The yearly calendar only has a lower scale. currentInterval = t.to_s(timeformat1) if timeformat1 firstColumn = nil # The innter loops terminates when the label for the upper scale has # changed to the next scale cell. while t < rEnd && (timeformat1.nil? || t.to_s(timeformat1) == currentInterval) # call TjTime::sameTimeNext... function to get the end of the column. nextT = t.send(sameTimeNextFunc) iv = TimeInterval.new(t, nextT) # Create the new column object. column = ReportTableColumn.new(table, nil, '') # Store the date of the column in the original form. column.cell1.data = t.to_s(a('timeFormat')) # The upper scale cells will be merged into one large cell that spans # all lower scale cells that belong to this upper cell. if firstColumn.nil? firstColumn = column column.cell1.text = currentInterval else column.cell1.hidden = true end column.cell2.text = t.to_s(timeformat2) # We assume an average of 7 pixel per character width = 8 + 7 * column.cell2.text.length # Ensure a minimum with of 28 to have good looking tables even with # small column headers (like day of months numbers). column.cell2.width = width <= 28 ? 28 : width # Off-duty cells will have a different color than working time cells. unless @project.hasWorkingTime(iv) column.cell2.category = 'tabhead_offduty' end cellsInInterval += 1 t = nextT end # The the first upper scale cell how many trailing hidden cells are # following. firstColumn.cell1.columns = cellsInInterval end 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. timeformat1 and timeformat2 are strftime format Strings that are used to generate the upper and lower title of the particular column.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 948 def genCalChartResourceCell(query, line, columnDef, t, sameTimeNextFunc) # Find out if we have an enclosing task scope. if line.scopeLine && line.scopeLine.property.is_a?(Task) task = line.scopeLine.property # Get the interval of the task. In case a date is invalid due to a # scheduling problem, we use the full project interval. taskStart = task['start', query.scenarioIdx] taskEnd = task['end', query.scenarioIdx] taskIv = TimeInterval.new(taskStart.nil? ? @project['start'] : taskStart, taskEnd.nil? ? @project['end'] : taskEnd) else task = nil end # We modify the start and end dates to match the cell boundaries. So # we need to make sure we don't modify the original Query but our own # copies. query = query.dup firstCell = nil endDate = query.end while t < endDate # Create a new cell cell = newCell(query, line) # call TjTime::sameTimeNext... function nextT = t.send(sameTimeNextFunc) cellIv = TimeInterval.new(t, nextT) # Get work load for all tasks. query.scopeProperty = nil query.attributeId = 'effort' query.startIdx = @project.dateToIdx(t) query.endIdx = @project.dateToIdx(nextT) query.process workLoad = query.to_num scaledWorkLoad = query.to_s if task # Get work load for the particular task. query.scopeProperty = task query.process workLoadTask = query.to_num scaledWorkLoad = query.to_s else workLoadTask = 0.0 end # Get unassigned work load. query.attributeId = 'freework' query.process freeLoad = query.to_num case columnDef.content when 'empty' # We only generate cells will different background colors. when 'load' # Report the workload of the resource in this time interval. # To increase readability, we don't show 0.0 values. wLoad = task ? workLoadTask : workLoad if wLoad > 0.0 cell.text = scaledWorkLoad end else raise "Unknown column content #{column.content}" end cdText = columnDef.cellText.getPattern(query) cell.text = cdText if cdText # Set the tooltip for the cell. We might delete it again. cell.tooltip = columnDef.tooltip.getPattern(query) || nil cell.showTooltipHint = false # Determine cell category (mostly the background color) cell.category = if task if cellIv.overlaps?(taskIv) if workLoadTask > 0.0 && freeLoad == 0.0 'busy' elsif workLoad == 0.0 && freeLoad == 0.0 cell.tooltip = nil 'offduty' else 'loaded' end else if freeLoad > 0.0 'free' elsif workLoad == 0.0 && freeLoad == 0.0 cell.tooltip = nil 'offduty' else cell.tooltip = nil 'resourcecell' end end else if workLoad > 0.0 && freeLoad == 0.0 'busy' elsif workLoad > 0.0 && freeLoad > 0.0 'loaded' elsif workLoad == 0.0 && freeLoad == 0.0 cell.tooltip = nil 'offduty' else 'free' end end cell.category += line.subLineNo % 2 == 1 ? '1' : '2' setCustomCellAttributes(cell, columnDef, query) tryCellMerging(cell, line, firstCell) t = nextT firstCell = cell unless firstCell end legend.addCalendarItem('Resource is fully loaded', 'busy1') legend.addCalendarItem('Resource is partially loaded', 'loaded1') legend.addCalendarItem('Resource is available', 'free') legend.addCalendarItem('Off duty time', 'offduty') 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.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 871 def genCalChartTaskCell(query, line, columnDef, t, sameTimeNextFunc) task = line.property # Get the interval of the task. In case a date is invalid due to a # scheduling problem, we use the full project interval. taskStart = task['start', query.scenarioIdx] taskEnd = task['end', query.scenarioIdx] taskIv = TimeInterval.new(taskStart.nil? ? @project['start'] : taskStart, taskEnd.nil? ? @project['end'] : taskEnd) # We modify the start and end dates to match the cell boundaries. So # we need to make sure we don't modify the original Query but our own # copies. query = query.dup firstCell = nil endDate = query.end while t < endDate # call TjTime::sameTimeNext... function nextT = t.send(sameTimeNextFunc) cellIv = TimeInterval.new(t, nextT) case columnDef.content when 'empty' # Create a new cell cell = newCell(query, line) # We only generate cells will different background colors. when 'load' query.attributeId = 'effort' query.start = t query.end = nextT query.process # Create a new cell cell = newCell(query, line) # To increase readability show empty cells instead of 0.0 values. cell.text = query.to_s if query.to_num != 0.0 else raise "Unknown column content #{column.content}" end cdText = columnDef.cellText.getPattern(query) cell.text = cdText if cdText cell.showTooltipHint = false # Determine cell category (mostly the background color) if cellIv.overlaps?(taskIv) # The cell is either a container or leaf task cell.category = task.container? ? 'calconttask' : 'caltask' elsif !@project.isWorkingTime(cellIv) # The cell is a vacation cell. cell.category = 'offduty' else # The cell is just filled with the background color. cell.category = 'taskcell' end cell.category += line.subLineNo % 2 == 1 ? '1' : '2' setCustomCellAttributes(cell, columnDef, query) tryCellMerging(cell, line, firstCell) t = nextT firstCell = cell unless firstCell end legend.addCalendarItem('Container Task', 'calconttask1') legend.addCalendarItem('Task', 'caltask1') legend.addCalendarItem('Off duty time', 'offduty') 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.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 756 def genCalculatedCell(query, line, columnDef) # Create a new cell cell = newCell(query, line) cell.force_string = true if columnDef.id == 'bsi' unless setScenarioSettings(cell, query.scenarioIdx, TableReport.scenarioSpecific?(columnDef.id)) return false end setStandardCellAttributes(query, cell, columnDef, nil, line) if query.process cell.text = (rti = query.to_rti) ? rti : query.to_s end # Some columns need some extra care. case columnDef.id when 'alert' id = @project['alertLevels'][query.to_sort].id cell.icon = "flag-#{id}" cell.fontColor = @project['alertLevels'][query.to_sort].color when 'alerttrend' icons = %w( up flat down ) cell.icon = "trend-#{icons[query.to_sort]}" when 'line' cell.text = line.lineNo.to_s when 'name' property = query.property cell.icon = if property.is_a?(Task) if property.container? 'taskgroup' else 'task' end elsif property.is_a?(Resource) if property.container? 'resourcegroup' else 'resource' end else nil end cell.iconTooltip = RichText.new("'''ID:''' #{property.fullId}"). generateIntermediateFormat when 'no' cell.text = line.no.to_s when 'bsi' cell.indent = 2 if line.scopeLine when 'scenario' cell.text = @project.scenario(query.scenarioIdx).name end # Replace the cell text if the user has requested a custom cell text. cdText = columnDef.cellText.getPattern(query) cell.text = cdText if cdText setCustomCellAttributes(cell, columnDef, query) checkCellText(cell) true 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.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 713 def genStandardCell(query, line, columnDef) # Find out, what type of PropertyTreeNode we are dealing with. property = line.property if property.is_a?(Task) propertyList = @project.tasks elsif property.is_a?(Resource) propertyList = @project.resources elsif property.is_a?(Account) propertyList = @project.accounts else raise "Unknown property type #{property.class}" end # Create a new cell cell = newCell(query, line) unless setScenarioSettings(cell, query.scenarioIdx, propertyList.scenarioSpecific?(columnDef.id)) return false end setStandardCellAttributes(query, cell, columnDef, propertyList.attributeType(columnDef.id), line) # If the user has requested a custom cell text, this will be used # instead of the queried one. if (cdText = columnDef.cellText.getPattern(query)) cell.text = cdText elsif query.process cell.text = (rti = query.to_rti) ? rti : query.to_s end setCustomCellAttributes(cell, columnDef, query) checkCellText(cell) true 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.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 634 def generateTableCell(line, columnDef, query) # Adjust the Query to use column specific settings. We create a copy of # the Query to avoid spoiling the original query with column specific # settings. query = query.dup query.attributeId = columnDef.id query.start = @columns[columnDef].start query.end = @columns[columnDef].end query.listType = columnDef.listType query.listItem = columnDef.listItem case columnDef.id when 'chart' # Generate a hidden cell. The real meat is in the actual chart object, # not in this cell. cell = ReportTableCell.new(line, query, '') cell.hidden = true cell.text = nil # The GanttChart can be reached via the special variable of the column # header. chart = columnDef.column.cell1.special GanttLine.new(chart, query, (line.subLineNo - 1) * (line.height + 1), line.height, line.subLineNo, a('selfcontained') ? nil : columnDef.tooltip) return true # The calendar cells can be all generated by the same function. But we # need to use different parameters. when 'hourly' start = query.start.midnight sameTimeNextFunc = :sameTimeNextHour when 'daily' start = query.start.midnight sameTimeNextFunc = :sameTimeNextDay when 'weekly' start = query.start.beginOfWeek(a('weekStartsMonday')) sameTimeNextFunc = :sameTimeNextWeek when 'monthly' start = query.start.beginOfMonth sameTimeNextFunc = :sameTimeNextMonth when 'quarterly' start = query.start.beginOfQuarter sameTimeNextFunc = :sameTimeNextQuarter when 'yearly' start = query.start.beginOfYear sameTimeNextFunc = :sameTimeNextYear else if TableReport.calculated?(columnDef.id) return genCalculatedCell(query, line, columnDef) else return genStandardCell(query, line, columnDef) end end # The calendar cells don't live in this ReportTable but in an embedded # ReportTable that can be reached via the column header special variable. # For embedded column tables we need to create a new line. tcLine = ReportTableLine.new(columnDef.column.cell1.special, line.property, line.scopeLine) PlaceHolderCell.new(line, tcLine) tcLine.subLineNo = line.subLineNo # Depending on the property type we use different generator functions. if query.property.is_a?(Task) genCalChartTaskCell(query, tcLine, columnDef, start, sameTimeNextFunc) elsif query.property.is_a?(Resource) genCalChartResourceCell(query, tcLine, columnDef, start, sameTimeNextFunc) elsif query.property.is_a?(Account) genCalChartAccountCell(query, tcLine, columnDef, start, sameTimeNextFunc) else raise "Unknown property type #{query.property.class}" end true 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.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 1139 def newCell(query, line) property = line.property cell = ReportTableCell.new(line, query) # Cells for containers should be using bold font face. cell.bold = true if property.container? && line.bold cell end
Create a new ReportTableCell
object and initialize some common values.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 1162 def setAccountCellBgColor(query, line, cell) if query.costAccount && (query.property.isChildOf?(query.costAccount) || query.costAccount == query.property) prefix = 'cost' elsif query.revenueAccount && (query.property.isChildOf?(query.revenueAccount) || query.revenueAccount == query.property) prefix = 'revenue' else prefix = +'' end cell.category = prefix + 'accountcell' + (line.subLineNo % 2 == 1 ? '1' : '2') end
Source
# File lib/taskjuggler/reports/TableReport.rb, line 1097 def setCustomCellAttributes(cell, columnDef, query) # Replace the cell background color if the user has requested a custom # color. cellColor = columnDef.cellColor.getPattern(query) cell.cellColor = cellColor if cellColor # Replace the font color setting if the user has requested a custom # color. fontColor = columnDef.fontColor.getPattern(query) cell.fontColor = fontColor if fontColor # Replace the default cell alignment if the user has requested a custom # alignment. hAlign = columnDef.hAlign.getPattern(query) cell.alignment = hAlign if hAlign # Register the custom tooltip if the user has requested one. cdTooltip = columnDef.tooltip.getPattern(query) cell.tooltip = cdTooltip if cdTooltip end
Source
# File lib/taskjuggler/reports/TableReport.rb, line 1149 def setIndent(line, propertyRoot, treeMode) property = line.property scopeLine = line.scopeLine level = property.level - (propertyRoot ? propertyRoot.level : 0) # We indent at least as much as the scopeline + 1, if we have a scope. line.indentation = scopeLine.indentation + 1 if scopeLine # In tree mode we indent according to the level. if treeMode line.indentation += level line.bold = true end end
Determine the indentation for this line.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 1119 def setScenarioSettings(cell, scenarioIdx, scenarioSpecific) # Check if we are dealing with multiple scenarios. if a('scenarios').length > 1 # Check if the attribute is not scenario specific unless scenarioSpecific if scenarioIdx == a('scenarios').first # Use a somewhat bigger font. cell.fontSize = 15 else # And hide the cells for all but the first scenario. cell.hidden = true return false end cell.rows = a('scenarios').length end end true end
Source
# File lib/taskjuggler/reports/TableReport.rb, line 1073 def setStandardCellAttributes(query, cell, columnDef, attributeType, line) # Determine whether it should be indented if TableReport.indent(columnDef.id, attributeType) cell.indent = line.indentation end # Determine the cell alignment cell.alignment = TableReport.alignment(columnDef.id, attributeType) # Set background color if line.property.is_a?(Task) cell.category = 'taskcell' cell.category += line.subLineNo % 2 == 1 ? '1' : '2' elsif line.property.is_a?(Resource) cell.category = 'resourcecell' cell.category += line.subLineNo % 2 == 1 ? '1' : '2' elsif line.property.is_a?(Account) setAccountCellBgColor(query, line, cell) end # Set column width cell.width = columnDef.width if columnDef.width end
This method takes care of often used cell attributes like indentation, alignment and background color.
Source
# File lib/taskjuggler/reports/TableReport.rb, line 1189 def tryCellMerging(cell, line, firstCell) if cell.text == '' && firstCell && (c = line.last(1)) && c == cell cell.hidden = true c.columns += 1 end end
Try to merge equal cells without text to multi-column cells.