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 152 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
Generate a new TableReport object.
# File lib/taskjuggler/reports/TableReport.rb, line 87 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
Return the alignment of the column based on the colId or the attributeType.
# File lib/taskjuggler/reports/TableReport.rb, line 176 def 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
This function returns true if the values for the colId column need to be calculated.
# File lib/taskjuggler/reports/TableReport.rb, line 188 def calculated?(colId) return @@propertiesById.has_key?(colId) end
# File lib/taskjuggler/reports/TableReport.rb, line 102 def generateIntermediateFormat super end
Return if the column values should be indented based on the colId or the propertyType.
# File lib/taskjuggler/reports/TableReport.rb, line 164 def 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
This functions returns true if the values for the col_id column are scenario specific.
# File lib/taskjuggler/reports/TableReport.rb, line 194 def scenarioSpecific?(colId) if @@propertiesById.has_key?(colId) return @@propertiesById[colId][3] end return false end
# File lib/taskjuggler/reports/TableReport.rb, line 201 def supportedColumns @@propertiesById.keys 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 147 def to_csv @table.to_csv end
Turn the TableReport into an equivalent HTML element tree.
# File lib/taskjuggler/reports/TableReport.rb, line 107 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
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.
# File lib/taskjuggler/reports/TableReport.rb, line 212 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
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.
# File lib/taskjuggler/reports/TableReport.rb, line 388 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| query.attributeId = columnDef.id next unless generateTableCell(line, columnDef, query) end end end lineNo 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 331 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) 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
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 488 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| query.attributeId = column.id 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 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 431 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| query.attributeId = columnDef.id 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
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 1181 def checkCellText(cell) unless cell.text cell.text = '<Error>' cell.fontColor = '#FF0000' 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.
# File lib/taskjuggler/reports/TableReport.rb, line 822 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 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.
# File lib/taskjuggler/reports/TableReport.rb, line 550 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 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 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 'free' else cell.tooltip = nil 'offduty' end end cell.category += line.property.get('index') % 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 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 865 def genCalChartTaskCell(query, line, columnDef, t, sameTimeNextFunc) task = line.property # Find out if we have an enclosing resource scope. if line.scopeLine && line.scopeLine.property.is_a?(Resource) resource = line.scopeLine.property else resource = nil end # 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.property.get('index') % 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 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.
# File lib/taskjuggler/reports/TableReport.rb, line 751 def genCalculatedCell(query, line, columnDef) # Create a new cell cell = newCell(query, line) unless setScenarioSettings(cell, query.scenarioIdx, 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 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 708 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 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 633 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.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, 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 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) # 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
Create a new ReportTableCell object and initialize some common values.
# 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
# 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.property.get('index') % 2 == 1 ? '1' : '2') end
# 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
Determine the indentation for this line.
# 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
# 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
This method takes care of often used cell attributes like indentation, alignment and background color.
# File lib/taskjuggler/reports/TableReport.rb, line 1073 def setStandardCellAttributes(query, cell, columnDef, attributeType, line) # Determine whether it should be indented if indent(columnDef.id, attributeType) cell.indent = line.indentation end # Determine the cell alignment cell.alignment = alignment(columnDef.id, attributeType) # Set background color if line.property.is_a?(Task) cell.category = 'taskcell' cell.category += line.property.get('index') % 2 == 1 ? '1' : '2' elsif line.property.is_a?(Resource) cell.category = 'resourcecell' cell.category += line.property.get('index') % 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
Try to merge equal cells without text to multi-column cells.
# 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