lib/reports/TableReport.rb in taskjuggler-0.0.3 vs lib/reports/TableReport.rb in taskjuggler-0.0.4
- old
+ new
@@ -1,11 +1,11 @@
#!/usr/bin/env ruby -w
# encoding: UTF-8
#
# = TableReport.rb -- The TaskJuggler III Project Management Software
#
-# Copyright (c) 2006, 2007, 2008, 2009 by Chris Schlaeger <cs@kde.org>
+# Copyright (c) 2006, 2007, 2008, 2009, 2010 by Chris Schlaeger <cs@kde.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
@@ -24,24 +24,34 @@
class TableReport < ReportBase
attr_reader :legend
@@propertiesById = {
- # ID Header Indent Align Calced. Scen Spec.
- 'complete' => [ 'Completion', false, :right, true, true ],
- 'cost' => [ 'Cost', true, :right, true, true ],
- 'duration' => [ 'Duration', true, :right, true, true ],
- 'effort' => [ 'Effort', true, :right, true, true ],
- 'id' => [ 'Id', false, :left, true, false ],
- 'line' => [ 'Line No.', false, :right, true, false ],
- 'name' => [ 'Name', true, :left, false, false ],
- 'no' => [ 'No.', false, :right, true, false ],
- 'rate' => [ 'Rate', true, :right, true, true ],
- 'resources' => [ 'Resources', false, :left, true, true ],
- 'revenue' => [ 'Revenue', true, :right, true, true ],
- 'scenario' => [ 'Scenario', false, :left, true, true ],
- 'wbs' => [ 'WBS', false, :left, true, false ]
+ # ID Header Indent Align Scen Spec.
+ 'alert' => [ 'Alert', true, :left, false ],
+ 'alertmessage' => [ 'Alert Message', false, :left, false ],
+ 'alertsummary' => [ 'Alert Summary', false, :left, false ],
+ 'alerttrend' => [ 'Alert Trend', false, :left, false ],
+ 'complete' => [ 'Completion', false, :right, true ],
+ 'cost' => [ 'Cost', true, :right, true ],
+ 'duration' => [ 'Duration', true, :right, true ],
+ 'effort' => [ 'Effort', true, :right, true ],
+ 'effortdone' => [ 'Effort Done', true, :right, true ],
+ 'effortleft' => [ 'Effort Left', true, :right, true ],
+ 'freetime' => [ 'Free Time', true, :right, true ],
+ 'id' => [ 'Id', false, :left, false ],
+ 'line' => [ 'Line No.', false, :right, false ],
+ 'name' => [ 'Name', true, :left, false ],
+ 'no' => [ 'No.', false, :right, false ],
+ 'rate' => [ 'Rate', true, :right, true ],
+ 'resources' => [ 'Resources', false, :left, true ],
+ 'responsible' => [ 'Responsible', false, :left, true ],
+ 'revenue' => [ 'Revenue', true, :right, true ],
+ 'scenario' => [ 'Scenario', false, :left, true ],
+ 'status' => [ 'Status', false, :left, true ],
+ 'targets' => [ 'Targets', false, :left, true ],
+ 'wbs' => [ 'WBS', false, :left, false ]
}
@@propertiesByType = {
# Type Indent Align
DateAttribute => [ false, :left ],
FixnumAttribute => [ false, :right ],
@@ -60,20 +70,25 @@
@legend = ReportTableLegend.new
end
+ def generateIntermediateFormat
+ super
+ end
+
+
# Turn the TableReport into an equivalent HTML element tree.
def to_html
html = []
html << rt_to_html('header')
html << (table = XMLElement.new('table', 'summary' => 'Report Table',
'cellspacing' => '2', 'border' => '0',
'cellpadding' => '0', 'align' => 'center',
- 'class' => 'tabback'))
+ 'class' => 'tjtable'))
# The headline is put in a sub-table to appear bigger.
if a('headline')
table << (thead = XMLElement.new('thead'))
thead << (tr = XMLElement.new('tr'))
@@ -83,13 +98,14 @@
'cellpadding' => '5',
'align' => 'center', 'width' => '100%'))
table1 << (tr1 = XMLElement.new('tr'))
tr1 << (td1 = XMLElement.new('td', 'align' => 'center',
'style' => 'font-size:16px; ' +
- 'font-weight:bold',
+ 'font-weight:bold; ' +
+ 'padding:5px',
'class' => 'tabfront'))
- td1 << XMLNamedText.new(a('headline'), 'p')
+ td1 << a('headline').to_html
end
# Now generate the actual table with the data.
table << (tbody = XMLElement.new('tbody'))
tbody << (tr = XMLElement.new('tr'))
@@ -110,77 +126,31 @@
tbody << (tr = XMLElement.new('tr', 'style' => 'font-size:10px;'))
tr << (td = XMLElement.new('td', 'style' =>
'padding-left:1px; padding-right:1px;'))
td << @legend.to_html
- # The footer with some administrative information.
- tbody << (tr = XMLElement.new('tr', 'style' => 'font-size:9px'))
- tr << (td = XMLElement.new('td', 'class' => 'tabfooter'))
- td << XMLText.new(@project['copyright'] + " - ") if @project['copyright']
- td << XMLText.new("Project: #{@project['name']} " +
- "Version: #{@project['version']} - " +
- "Created on #{TjTime.now.to_s("%Y-%m-%d %H:%M:%S")} " +
- "with ")
- td << XMLNamedText.new("#{AppConfig.packageName}", 'a',
- 'href' => "#{AppConfig.contact}")
- td << XMLText.new(" v#{AppConfig.version}")
-
html << rt_to_html('footer')
html
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.
def to_csv
@table.to_csv
end
- # This is the default attribute value to text converter. It is used
- # whenever we need no special treatment.
- def cellText(value, type)
- begin
- if value.nil?
- if type == DateAttribute
- nil
- else
- ''
- end
- else
- # Certain attribute types need special treatment.
- if type == DateAttribute
- value.value.to_s(a('timeFormat'))
- elsif type == RichTextAttribute
- value.value
- elsif type == ReferenceAttribute
- value.label
- else
- value.to_s
- end
- end
- rescue TjException
- ''
- end
- end
+ # Returns the default column title for the columns _id_.
+ 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)
- # This function returns true if the values for the _colId_ column need to be
- # calculated.
- def calculated?(colId)
- if @@propertiesById.has_key?(colId)
- return @@propertiesById[colId][3]
- end
- return false
+ # Return the title for build-in hardwired columns.
+ @@propertiesById.include?(id) ? @@propertiesById[id][0] : nil
end
- # This functions returns true if the values for the _col_id_ column are
- # scenario specific.
- def scenarioSpecific?(colId)
- if @@propertiesById.has_key?(colId)
- return @@propertiesById[colId][4]
- end
- return false
- end
-
# Return if the column values should be indented based on the _colId_ or the
# _propertyType_.
def indent(colId, propertyType)
if @@propertiesById.has_key?(colId)
return @@propertiesById[colId][1]
@@ -201,19 +171,23 @@
else
:center
end
end
- # Returns the default column title for the columns _id_.
- 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)
+ # This function returns true if the values for the _colId_ column need to be
+ # calculated.
+ def calculated?(colId)
+ return @@propertiesById.has_key?(colId)
+ end
- # Return the title for build-in hardwired columns.
- @@propertiesById.include?(id) ? @@propertiesById[id][0] : nil
+ # This functions returns true if the values for the _col_id_ column are
+ # scenario specific.
+ def scenarioSpecific?(colId)
+ if @@propertiesById.has_key?(colId)
+ return @@propertiesById[colId][3]
+ end
+ return false
end
def supportedColumns
@@propertiesById.keys
end
@@ -248,11 +222,10 @@
minWidth = @end - @start + 1
columns.each do |column|
case column.id
when 'chart'
# In case we have a 'chart' column, we enforce certain minimum width
- # of the chart. The width depends on the selected scale.
# 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 = {
@@ -312,11 +285,11 @@
# 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
+ 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
@@ -353,13 +326,15 @@
# _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.
def generateTaskList(taskList, resourceList, scopeLine)
- queryAttrs = { 'scopeProperty' => scopeLine ? scopeLine.property : nil,
+ queryAttrs = { 'project' => @project,
+ 'scopeProperty' => scopeLine ? scopeLine.property : nil,
'loadUnit' => a('loadUnit'),
'numberFormat' => a('numberFormat'),
+ 'timeFormat' => a('timeFormat'),
'currencyFormat' => a('currencyFormat'),
'start' => @start, 'end' => @end,
'costAccount' => a('costAccount'),
'revenueAccount' => a('revenueAccount') }
taskList.query = Query.new(queryAttrs)
@@ -370,25 +345,33 @@
# 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 = @project.reportContext.query.dup
+ 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, task, columnDef, scenarioIdx)
+ query.attributeId = columnDef.id
+ next unless generateTableCell(line, task, columnDef, query)
end
end
if resourceList
# If we have a resourceList we generate nested lines for each of the
@@ -407,13 +390,15 @@
# 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.
def generateResourceList(resourceList, taskList, scopeLine)
- queryAttrs = { 'scopeProperty' => scopeLine ? scopeLine.property : nil,
+ queryAttrs = { 'project' => @project,
+ 'scopeProperty' => scopeLine ? scopeLine.property : nil,
'loadUnit' => a('loadUnit'),
'numberFormat' => a('numberFormat'),
+ 'timeFormat' => a('timeFormat'),
'currencyFormat' => a('currencyFormat'),
'start' => @start, 'end' => @end,
'costAccount' => a('costAccount'),
'revenueAccount' => a('revenueAccount') }
resourceList.query = Query.new(queryAttrs)
@@ -424,25 +409,33 @@
# 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 = @project.reportContext.query.dup
+ 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, resource, column, scenarioIdx)
+ query.attributeId = column.id
+ next unless generateTableCell(line, resource, column, query)
end
end
if taskList
# If we have a taskList we generate nested lines for each of the
@@ -479,11 +472,11 @@
# Create the table that is embedded in this column.
tableColumn.cell1.special = table = ColumnTable.new
table.equiLines = true
tableColumn.cell2.hidden = true
- table.maxWidth = columnDef.width
+ 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.
@@ -539,24 +532,32 @@
# 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.
- def generateTableCell(line, property, columnDef, scenarioIdx)
+ def generateTableCell(line, property, columnDef, query)
+ if columnDef.start || columnDef.end
+ # If the user has specified a new start or end time for this column,
+ # we have to duplicate the query before we modify it.
+ query = query.dup
+ query.start = columnDef.start if columnDef.start
+ query.end = columnDef.end if columnDef.end
+ end
+
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)
+ 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, property,
- line.scopeLine ? line.scopeLine.property : nil,
- scenarioIdx, (line.subLineNo - 1) * (line.height + 1),
+ GanttLine.new(chart, property, line.scopeProperty,
+ query.scenarioIdx,
+ (line.subLineNo - 1) * (line.height + 1),
line.height)
return true
# The calendar cells can be all generated by the same function. But we
# need to use different parameters.
when 'hourly'
@@ -577,14 +578,13 @@
when 'yearly'
start = @start.beginOfYear
sameTimeNextFunc = :sameTimeNextYear
else
if calculated?(columnDef.id)
- genCalculatedCell(scenarioIdx, line, columnDef, property)
- return true
+ return genCalculatedCell(query, line, columnDef, property)
else
- return genStandardCell(scenarioIdx, line, columnDef)
+ 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.
@@ -592,184 +592,178 @@
tcLine = ReportTableLine.new(columnDef.column.cell1.special,
line.property, line.scopeLine)
# Depending on the property type we use different generator functions.
if property.is_a?(Task)
- genCalChartTaskCell(scenarioIdx, tcLine, columnDef, start,
- sameTimeNextFunc)
+ genCalChartTaskCell(query, tcLine, columnDef, start, sameTimeNextFunc)
elsif property.is_a?(Resource)
- genCalChartResourceCell(scenarioIdx, tcLine, columnDef, start,
+ genCalChartResourceCell(query, tcLine, columnDef, start,
sameTimeNextFunc)
else
raise "Unknown property type #{property.class}"
end
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.
- def genStandardCell(scenarioIdx, line, columnDef)
- property = line.property
-
+ 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
else
raise "Unknown property type #{property.class}"
end
- colId = columnDef.id
- # Get the value no matter if it's scenario specific or not.
- if propertyList.scenarioSpecific?(colId)
- value = property.getAttr(colId, scenarioIdx)
- else
- value = property.getAttr(colId)
- end
- # Get the type of the property
- type = propertyList.attributeType(colId)
# Create a new cell
- cell = newCell(line, cellText(value, type))
- if type == ReferenceAttribute
- cell.url = value.url
- end
+ cell = newCell(query, line)
- # Check if we are dealing with multiple scenarios.
- if a('scenarios').length > 1
- # Check if the attribute is not scenario specific
- unless propertyList.scenarioSpecific?(columnDef.id)
- 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
+ unless setScenarioSettings(cell, query.scenarioIdx,
+ propertyList.scenarioSpecific?(columnDef.id))
+ return false
end
setStandardCellAttributes(cell, columnDef,
propertyList.attributeType(columnDef.id), line)
- scopeProperty = line.scopeLine ? line.scopeLine.property : nil
- query = Query.new('property' => property,
- 'scopeProperty' => scopeProperty,
- 'attributeId' => columnDef.id,
- 'scenarioIdx' => scenarioIdx,
- 'loadUnit' => a('loadUnit'),
- 'numberFormat' => a('numberFormat'),
- 'currencyFormat' => a('currencyFormat'),
- 'start' => @start, 'end' => @end,
- 'costAccount' => a('costAccount'),
- 'revenueAccount' => a('revenueAccount'))
- if cell.text
- if columnDef.cellText
- cell.text = expandMacros(columnDef.cellText, cell.text, query)
- end
- else
- cell.text = '<Error>'
- cell.fontColor = 0xFF0000
+ # 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
- setCellURL(cell, columnDef, query)
+ # 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 cell background color if the user has requested a custom
+ # color.
+ cellColor = columnDef.cellColor.getPattern(query)
+ cell.cellColor = cellColor if cellColor
+
+ # If the user has requested a custom tooltip, add it to the cell.
+ cell.tooltip = columnDef.tooltip.getPattern(query) || nil
+
+ 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. _scenarioIdx_ is the index of the reported scenario.
- # _line_ is the ReportTableLine of the cell. _columnDef_ is the
+ # 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.
- def genCalculatedCell(scenarioIdx, line, columnDef, property)
+ def genCalculatedCell(query, line, columnDef, property)
# Create a new cell
- cell = newCell(line)
+ cell = newCell(query, line)
- unless scenarioSpecific?(columnDef.id)
- if scenarioIdx != a('scenarios').first
- cell.hidden = true
- return false
- end
- cell.rows = a('scenarios').length
+ unless setScenarioSettings(cell, query.scenarioIdx,
+ scenarioSpecific?(columnDef.id))
+ return false
end
setStandardCellAttributes(cell, columnDef, nil, line)
- scopeProperty = line.scopeLine ? line.scopeLine.property : nil
- return if columnDef.hideCellText &&
- columnDef.hideCellText.eval(property, scopeProperty)
+ if query.process
+ cell.text = (rti = query.to_rti) ? rti : query.to_s
+ end
- query = Query.new('property' => property,
- 'scopeProperty' => scopeProperty,
- 'attributeId' => columnDef.id,
- 'scenarioIdx' => scenarioIdx,
- 'loadUnit' => a('loadUnit'),
- 'numberFormat' => a('numberFormat'),
- 'currencyFormat' => a('currencyFormat'),
- 'start' => @start, 'end' => @end,
- 'costAccount' => a('costAccount'),
- 'revenueAccount' => a('revenueAccount'))
- query.process
- cell.text = query.result
-
# Some columns need some extra care.
case columnDef.id
+ when 'alert'
+ id = @project.alertLevelId(query.to_num)
+ cell.icon = "flag-#{id}"
+ cell.fontColor = @project.alertLevelColor(query.to_sort)
+ when 'alerttrend'
+ icons = %w( up flat down )
+ cell.icon = "trend-#{icons[query.to_sort]}"
when 'line'
cell.text = line.lineNo.to_s
+ when 'name'
+ 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
when 'no'
cell.text = line.no.to_s
when 'wbs'
cell.indent = 2 if line.scopeLine
when 'scenario'
- cell.text = @project.scenario(scenarioIdx).name
+ cell.text = @project.scenario(query.scenarioIdx).name
end
- if columnDef.cellText
- cell.text = expandMacros(columnDef.cellText, cell.text, query)
- end
- setCellURL(cell, columnDef, query)
+ # Replace the cell text if the user has requested a custom cell text.
+ cdText = columnDef.cellText.getPattern(query)
+ cell.text = cdText if cdText
+
+ # 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
+
+ # Register the custom tooltip if the user has requested one.
+ cdTooltip = columnDef.tooltip.getPattern(query)
+ cell.tooltip = cdTooltip if cdTooltip
+
+ checkCellText(cell)
+
+ true
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.
- def genCalChartTaskCell(scenarioIdx, line, columnDef, t, sameTimeNextFunc)
+ 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.
- taskIv = Interval.new(task['start', scenarioIdx].nil? ?
- @project['start'] : task['start', scenarioIdx],
- task['end', scenarioIdx].nil? ?
- @project['end'] : task['end', scenarioIdx])
+ taskIv = Interval.new(task['start', query.scenarioIdx].nil? ?
+ @project['start'] : task['start', query.scenarioIdx],
+ task['end', query.scenarioIdx].nil? ?
+ @project['end'] : task['end', query.scenarioIdx])
- query = Query.new('property' => task, 'scopeProperty' => resource,
- 'scenarioIdx' => scenarioIdx,
- 'loadUnit' => a('loadUnit'),
- 'numberFormat' => a('numberFormat'),
- 'currencyFormat' => a('currencyFormat'),
- 'costAccount' => a('costAccount'),
- 'revenueAccount' => a('revenueAccount'))
-
firstCell = nil
while t < @end
# Create a new cell
- cell = newCell(line)
+ cell = newCell(query, line)
# call TjTime::sameTimeNext... function
nextT = t.send(sameTimeNextFunc)
cellIv = Interval.new(t, nextT)
case columnDef.content
@@ -779,11 +773,11 @@
query.attributeId = 'effort'
query.startIdx = t
query.endIdx = nextT
query.process
# To increase readability, we don't show 0.0 values.
- cell.text = query.result if query.numericalResult > 0.0
+ cell.text = query.to_s if query.to_num != 0.0
else
raise "Unknown column content #{column.content}"
end
# Determine cell category (mostly the background color)
@@ -812,63 +806,56 @@
# 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.
- def genCalChartResourceCell(scenarioIdx, line, columnDef, t,
+ def genCalChartResourceCell(query, line, columnDef, t,
sameTimeNextFunc)
resource = line.property
# 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.
- taskIv = Interval.new(task['start', scenarioIdx].nil? ?
- @project['start'] : task['start', scenarioIdx],
- task['end', scenarioIdx].nil? ?
- @project['end'] : task['end', scenarioIdx])
+ taskIv = Interval.new(task['start', query.scenarioIdx].nil? ?
+ @project['start'] :
+ task['start', query.scenarioIdx],
+ task['end', query.scenarioIdx].nil? ?
+ @project['end'] : task['end', query.scenarioIdx])
else
task = nil
end
- query = Query.new('property' => resource,
- 'scenarioIdx' => scenarioIdx,
- 'loadUnit' => a('loadUnit'),
- 'numberFormat' => a('numberFormat'),
- 'currencyFormat' => a('currencyFormat'),
- 'costAccount' => a('costAccount'),
- 'revenueAccount' => a('revenueAccount'))
-
firstCell = nil
while t < @end
# Create a new cell
- cell = newCell(line)
+ cell = newCell(query, line)
# call TjTime::sameTimeNext... function
nextT = t.send(sameTimeNextFunc)
cellIv = Interval.new(t, nextT)
# Get work load for all tasks.
query.scopeProperty = nil
query.attributeId = 'effort'
query.startIdx = @project.dateToIdx(t, true)
query.endIdx = @project.dateToIdx(nextT, true) - 1
query.process
- workLoad = query.numericalResult
- scaledWorkLoad = query.result
+ workLoad = query.to_num
+ scaledWorkLoad = query.to_s
if task
# Get work load for the particular task.
query.scopeProperty = task
query.process
- workLoadTask = query.numericalResult
- scaledWorkLoad = query.result
+ workLoadTask = query.to_num
+ scaledWorkLoad = query.to_s
else
workLoadTask = 0.0
end
# Get unassigned work load.
query.attributeId = 'freework'
query.process
- freeLoad = query.numericalResult
+ 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.
@@ -923,10 +910,12 @@
legend.addCalendarItem('Resource is partially loaded', 'loaded1')
legend.addCalendarItem('Resource is available', 'free')
legend.addCalendarItem('Off duty time', 'offduty')
end
+ # This method takes care of often used cell attributes like indentation,
+ # alignment and background color.
def setStandardCellAttributes(cell, columnDef, attributeType, line)
# Determine whether it should be indented
if indent(columnDef.id, attributeType)
cell.indent = line.indentation
end
@@ -940,20 +929,42 @@
'taskcell1' : 'taskcell2'
else
cell.category = line.property.get('index') % 2 == 1 ?
'resourcecell1' : 'resourcecell2'
end
+
+ # Set column width
+ cell.width = columnDef.width if columnDef.width
end
+ 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
+
# Create a new ReportTableCell object and initialize some common values.
- def newCell(line, text = '')
+ def newCell(query, line)
property = line.property
- cell = ReportTableCell.new(line, text)
+ cell = ReportTableCell.new(line, query)
# Cells for containers should be using bold font face.
- cell.bold = true if property.container?
-
+ cell.bold = true if property.container? && line.bold
+ cell.selfcontained = a('selfcontained')
cell
end
# Determine the indentation for this line.
def setIndent(line, propertyRoot, treeMode)
@@ -961,92 +972,31 @@
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.
- line.indentation += level if treeMode
+ if treeMode
+ line.indentation += level
+ line.bold = true
+ end
end
- # Set the URL associated with the cell text. _cell_ is the ReportTableCell.
- # _columnDef_ is the user specified definition for the cell content and
- # look. _query_ is the query used to resolve dynamic macros in the cellURL.
- def setCellURL(cell, columnDef, query)
- return unless columnDef.cellURL
-
- if columnDef.hideCellURL &&
- columnDef.hideCellURL.eval(query.property, query.scopeProperty)
- url = nil
- else
- url = expandMacros(columnDef.cellURL, cell.text, query)
+ # Make sure we have a valid cell text. If not, this is the result of an
+ # error. This could happen after scheduling errors.
+ def checkCellText(cell)
+ unless cell.text
+ cell.text = '<Error>'
+ cell.fontColor = '#FF0000'
end
- cell.url = url unless url.nil? || url.empty?
end
# Try to merge equal cells without text to multi-column cells.
def tryCellMerging(cell, line, firstCell)
if cell.text == '' && firstCell && (c = line.last(1)) && c == cell
cell.hidden = true
c.columns += 1
end
end
- # Expand the run-time macros in _pattern_. ${0} is a special case and will
- # be replaced with the _originalText_. For all other macros the _query_ will
- # be used with the macro name used for the attributeId of the query. The
- # method returns the expanded pattern.
- def expandMacros(pattern, originalText, query)
- return pattern unless pattern.include?('${')
-
- out = ''
- # Scan the pattern for macros ${...}
- i = 0
- while i < pattern.length
- c = pattern[i]
- if c == ?$
- # This could be a macro
- if pattern[i + 1] != ?{
- # It's not. Just append the '$'
- out << c
- else
- # It is a macro.
- i += 2
- macro = ''
- # Scan for the end '}' and get the macro name.
- while i < pattern.length && pattern[i] != ?}
- macro << pattern[i]
- i += 1
- end
- if macro == '0'
- # This turns RichText into plain ASCII!
- out += originalText
- else
- # resolve by query
- # If the macro is prefixed by a '?' it may be undefined.
- if macro[0] == ??
- macro = macro[1..-1]
- ignoreErrors = true
- else
- ignoreErrors = false
- end
- query.attributeId = macro
- query.process
- unless query.ok || ignoreErrors
- raise TjException.new, query.errorMessage
- end
- # This turns RichText into plain ASCII!
- out += query.result
- end
- end
- else
- # Just append the character to the output.
- out << c
- end
- i += 1
- end
-
- out
- end
-
end
end
-