Class Index [+]

Quicksearch

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

legend[R]

Public Class Methods

defaultColumnTitle(id) click to toggle source

Returns the default column title for the columns id.

     # File lib/taskjuggler/reports/TableReport.rb, line 135
135:     def TableReport::defaultColumnTitle(id)
136:       # Return an empty string for some special columns that don't have a fixed
137:       # title.
138:       specials = %( chart hourly daily weekly monthly quarterly yearly)
139:       return '' if specials.include?(id)
140: 
141:       # Return the title for build-in hardwired columns.
142:       @@propertiesById.include?(id) ? @@propertiesById[id][0] : nil
143:     end
new(report) click to toggle source

Generate a new TableReport object.

    # File lib/taskjuggler/reports/TableReport.rb, line 73
73:     def initialize(report)
74:       super
75:       @report.content = self
76: 
77:       # Reference to the intermediate representation.
78:       @table = nil
79:       @start = @end = nil
80: 
81:       @legend = ReportTableLegend.new
82: 
83:     end

Public Instance Methods

alignment(colId, propertyType) click to toggle source

Return the alignment of the column based on the colId or the propertyType.

     # File lib/taskjuggler/reports/TableReport.rb, line 159
159:     def alignment(colId, propertyType)
160:       if @@propertiesById.has_key?(colId)
161:         return @@propertiesById[colId][2]
162:       elsif @@propertiesByType.has_key?(propertyType)
163:         return @@propertiesByType[propertyType][1]
164:       else
165:         :center
166:       end
167:     end
calculated?(colId) click to toggle source

This function returns true if the values for the colId column need to be calculated.

     # File lib/taskjuggler/reports/TableReport.rb, line 171
171:     def calculated?(colId)
172:       return @@propertiesById.has_key?(colId)
173:     end
generateIntermediateFormat() click to toggle source
    # File lib/taskjuggler/reports/TableReport.rb, line 85
85:     def generateIntermediateFormat
86:       super
87:     end
indent(colId, propertyType) click to toggle source

Return if the column values should be indented based on the colId or the propertyType.

     # File lib/taskjuggler/reports/TableReport.rb, line 147
147:     def indent(colId, propertyType)
148:       if @@propertiesById.has_key?(colId)
149:         return @@propertiesById[colId][1]
150:       elsif @@propertiesByType.has_key?(propertyType)
151:         return @@propertiesByType[propertyType][0]
152:       else
153:         false
154:       end
155:     end
scenarioSpecific?(colId) click to toggle source

This functions returns true if the values for the col_id column are scenario specific.

     # File lib/taskjuggler/reports/TableReport.rb, line 177
177:     def scenarioSpecific?(colId)
178:       if @@propertiesById.has_key?(colId)
179:         return @@propertiesById[colId][3]
180:       end
181:       return false
182:     end
supportedColumns() click to toggle source
     # File lib/taskjuggler/reports/TableReport.rb, line 184
184:     def supportedColumns
185:       @@propertiesById.keys
186:     end
to_csv() click to toggle source

Convert the table into an Array of Arrays. It has one Array for each line. The nested Arrays have one String for each column.

     # File lib/taskjuggler/reports/TableReport.rb, line 130
130:     def to_csv
131:       @table.to_csv
132:     end
to_html() click to toggle source

Turn the TableReport into an equivalent HTML element tree.

     # File lib/taskjuggler/reports/TableReport.rb, line 90
 90:     def to_html
 91:       html = []
 92: 
 93:       html << XMLComment.new("Dynamic Report ID: " +
 94:                              "#{@report.project.reportContexts.last.
 95:                                 dynamicReportId}")
 96:       html << rt_to_html('header')
 97:       html << (tableFrame = generateHtmlTableFrame)
 98: 
 99:       # Now generate the actual table with the data.
100:       tableFrame << generateHtmlTableRow do
101:         td = XMLElement.new('td')
102:         td << @table.to_html
103:         td
104:       end
105: 
106:       # Embedd the caption as RichText into the table footer.
107:       if a('caption')
108:         tableFrame << generateHtmlTableRow do
109:           td = XMLElement.new('td')
110:           td << (div = XMLElement.new('div', 'class' => 'tj_table_caption'))
111:           a('caption').sectionNumbers = false
112:           div << a('caption').to_html
113:           td
114:         end
115:       end
116: 
117:       # The legend.
118:       tableFrame << generateHtmlTableRow do
119:         td = XMLElement.new('td')
120:         td << @legend.to_html
121:         td
122:       end
123: 
124:       html << rt_to_html('footer')
125:       html
126:     end

Protected Instance Methods

adjustReportPeriod(tasks, scenarios, columns) click to toggle source

In case the user has not specified the report period, we try to fit all the tasks in and add an extra 5% time at both ends. scenarios is a list of scenario indexes.

     # File lib/taskjuggler/reports/TableReport.rb, line 200
200:     def adjustReportPeriod(tasks, scenarios, columns)
201:       return if tasks.empty? ||
202:         a('start') != @project['start'] || a('end') != @project['end']
203: 
204:       @start = @end = nil
205:       scenarios.each do |scenarioIdx|
206:         tasks.each do |task|
207:           date = task['start', scenarioIdx] || @project['start']
208:           @start = date if @start.nil? || date < @start
209:           date = task['end', scenarioIdx] || @project['end']
210:           @end = date if @end.nil? || date > @end
211:         end
212:       end
213: 
214:       # We want to add at least 5% on both ends.
215:       margin = 0
216:       minWidth = @end - @start + 1
217:       columns.each do |column|
218:         case column.id
219:         when 'chart'
220:           # In case we have a 'chart' column, we enforce certain minimum width
221:           # The following table contains an entry for each scale. The entry
222:           # consists of the triple 'seconds per unit', 'minimum width units'
223:           # and 'margin units'. The minimum with does not include the margins
224:           # since they are always added.
225:           mwMap = {
226:             'hour' =>    [ 60 * 60,            18, 2 ],
227:             'day' =>     [ 60 * 60 * 24,       18, 2 ],
228:             'week' =>    [ 60 * 60 * 24 * 7,    6, 1 ],
229:             'month' =>   [ 60 * 60 * 24 * 31,  10, 1 ],
230:             'quarter' => [ 60 * 60 * 24 * 90,   6, 1 ],
231:             'year' =>    [ 60 * 60 * 24 * 365,  4, 1 ]
232:           }
233:           entry = mwMap[column.scale]
234:           raise "Unknown scale #{column.scale}" unless entry
235:           margin = entry[0] * entry[2]
236:           # If the with determined by start and end dates of the task is below
237:           # the minimum width, we increase the width to the value provided by
238:           # the table.
239:           minWidth = entry[0] * entry[1] if minWidth < entry[0] * entry[1]
240:           break
241:         when 'hourly', 'daily', 'weekly', 'monthly', 'quarterly', 'yearly'
242:           # For the calendar columns we use a similar approach as we use for
243:           # the 'chart' column.
244:           mwMap = {
245:             'hourly' =>    [ 60 * 60,            18, 2 ],
246:             'daily' =>     [ 60 * 60 * 24,       18, 2 ],
247:             'weekly' =>    [ 60 * 60 * 24 * 7,    6, 1 ],
248:             'monthly' =>   [ 60 * 60 * 24 * 31,  10, 1 ],
249:             'quarterly' => [ 60 * 60 * 24 * 90,   6, 1 ],
250:             'yearly' =>    [ 60 * 60 * 24 * 365,  4, 1 ]
251:           }
252:           entry = mwMap[column.id]
253:           raise "Unknown scale #{column.id}" unless entry
254:           margin = entry[0] * entry[2]
255:           minWidth = entry[0] * entry[1] if minWidth < entry[0] * entry[1]
256:           break
257:         end
258:       end
259: 
260:       if minWidth > (@end - @start + 1)
261:         margin += (minWidth - (@end - @start + 1)) / 2
262:       end
263:       @start -= margin
264:       @end += margin
265:     end
generateHeaderCell(columnDef) click to toggle source

Generates cells for the table header. columnDef is the TableColumnDefinition object that describes the column. Based on the id of the column different actions need to be taken to generate the header text.

     # File lib/taskjuggler/reports/TableReport.rb, line 270
270:     def generateHeaderCell(columnDef)
271:       case columnDef.id
272:       when 'chart'
273:         # For the 'chart' column we generate a GanttChart object. The sizes are
274:         # set so that the lines of the Gantt chart line up with the lines of the
275:         # table.
276:         gantt = GanttChart.new(a('now'),
277:                                a('weekStartsMonday'), self)
278:         gantt.generateByScale(@start, @end, columnDef.scale)
279:         # The header consists of 2 lines separated by a 1 pixel boundary.
280:         gantt.header.height = @table.headerLineHeight * 2 + 1
281:         # The maximum width of the chart. In case it needs more space, a
282:         # scrollbar is shown or the chart gets truncated depending on the output
283:         # format.
284:         gantt.viewWidth = columnDef.width ? columnDef.width : 450
285:         column = ReportTableColumn.new(@table, columnDef, '')
286:         column.cell1.special = gantt
287:         column.cell2.hidden = true
288:         column.scrollbar = gantt.hasScrollbar?
289:         @table.equiLines = true
290:       when 'hourly'
291:         genCalChartHeader(columnDef, @start.midnight, :sameTimeNextHour,
292:                           :weekdayAndDate, :hour)
293:       when 'daily'
294:         genCalChartHeader(columnDef, @start.midnight, :sameTimeNextDay,
295:                           :monthAndYear, :day)
296:       when 'weekly'
297:         genCalChartHeader(columnDef,
298:                           @start.beginOfWeek(a('weekStartsMonday')),
299:                           :sameTimeNextWeek, :monthAndYear, :day)
300:       when 'monthly'
301:         genCalChartHeader(columnDef, @start.beginOfMonth, :sameTimeNextMonth,
302:                           :year, :shortMonthName)
303:       when 'quarterly'
304:         genCalChartHeader(columnDef, @start.beginOfQuarter,
305:                           :sameTimeNextQuarter, :year, :quarterName)
306:       when 'yearly'
307:         genCalChartHeader(columnDef, @start.beginOfYear, :sameTimeNextYear,
308:                           nil, :year)
309:       else
310:         # This is the most common case. It does not need any special treatment.
311:         # We just set the pre-defined or user-defined column title in the first
312:         # row of the header. The 2nd row is not visible.
313:         column = ReportTableColumn.new(@table, columnDef, columnDef.title)
314:         column.cell1.rows = 2
315:         column.cell2.hidden = true
316:         column.cell1.width = columnDef.width if columnDef.width
317:       end
318:     end
generateResourceList(resourceList, taskList, scopeLine) click to toggle source

Generate a ReportTableLine for each of the resources in resourceList. In case taskList is not nil, it also generates the nested task lines for each task that the resource is assigned to. If scopeLine is defined, the generated resource lines will be within the scope this task line.

     # File lib/taskjuggler/reports/TableReport.rb, line 390
390:     def generateResourceList(resourceList, taskList, scopeLine)
391:       queryAttrs = { 'project' => @project,
392:                      'scopeProperty' => scopeLine ? scopeLine.property : nil,
393:                      'loadUnit' => a('loadUnit'),
394:                      'numberFormat' => a('numberFormat'),
395:                      'timeFormat' => a('timeFormat'),
396:                      'currencyFormat' => a('currencyFormat'),
397:                      'start' => @start, 'end' => @end,
398:                      'hideJournalEntry' => a('hideJournalEntry'),
399:                      'costAccount' => a('costAccount'),
400:                      'revenueAccount' => a('revenueAccount') }
401:       resourceList.query = Query.new(queryAttrs)
402:       resourceList.sort!
403: 
404:       # The primary line counter. Is not used for enclosed lines.
405:       no = 0
406:       # The scope line counter. It's reset for each new scope.
407:       lineNo = scopeLine ? scopeLine.lineNo : 0
408:       # Init the variable to get a larger scope
409:       line = nil
410:       resourceList.each do |resource|
411:         # Get the current Query from the report context and create a copy. We
412:         # are going to modify it.
413:         query = @project.reportContexts.last.query.dup
414:         query.property = resource
415:         query.scopeProperty = scopeLine ? scopeLine.property : nil
416: 
417:         no += 1
418:         Log.activity if lineNo % 10 == 0
419:         lineNo += 1
420:         a('scenarios').each do |scenarioIdx|
421:           query.scenarioIdx = scenarioIdx
422:           # Generate line for each resource.
423:           line = ReportTableLine.new(@table, resource, scopeLine)
424: 
425:           line.no = no unless scopeLine
426:           line.lineNo = lineNo
427:           line.subLineNo = @table.lines
428:           setIndent(line, a('resourceRoot'), resourceList.treeMode?)
429: 
430:           # Generate a cell for each column in this line.
431:           a('columns').each do |column|
432:             query.attributeId = column.id
433:             next unless generateTableCell(line, resource, column, query)
434:           end
435:         end
436: 
437:         if taskList
438:           # If we have a taskList we generate nested lines for each of the
439:           # tasks that the resource is assigned to and pass the user-defined
440:           # filter.
441:           taskList.setSorting(a('sortTasks'))
442:           assignedTaskList = filterTaskList(taskList, resource,
443:                                             a('hideTask'), a('rollupTask'),
444:                                             a('openNodes'))
445:           assignedTaskList.sort!
446:           lineNo = generateTaskList(assignedTaskList, nil, line)
447:         end
448:       end
449:       lineNo
450:     end
generateTaskList(taskList, resourceList, scopeLine) click to toggle source

Generate a ReportTableLine for each of the tasks in taskList. In case resourceList is not nil, it also generates the nested resource lines for each resource that is assigned to the particular task. If scopeLine is defined, the generated task lines will be within the scope this resource line.

     # File lib/taskjuggler/reports/TableReport.rb, line 325
325:     def generateTaskList(taskList, resourceList, scopeLine)
326:       queryAttrs = { 'project' => @project,
327:                      'scopeProperty' => scopeLine ? scopeLine.property : nil,
328:                      'loadUnit' => a('loadUnit'),
329:                      'numberFormat' => a('numberFormat'),
330:                      'timeFormat' => a('timeFormat'),
331:                      'currencyFormat' => a('currencyFormat'),
332:                      'start' => @start, 'end' => @end,
333:                      'hideJournalEntry' => a('hideJournalEntry'),
334:                      'costAccount' => a('costAccount'),
335:                      'revenueAccount' => a('revenueAccount') }
336:       taskList.query = Query.new(queryAttrs)
337:       taskList.sort!
338: 
339:       # The primary line counter. Is not used for enclosed lines.
340:       no = 0
341:       # The scope line counter. It's reset for each new scope.
342:       lineNo = scopeLine ? scopeLine.lineNo : 0
343:       # Init the variable to get a larger scope
344:       line = nil
345:       taskList.each do |task|
346:         # Get the current Query from the report context and create a copy. We
347:         # are going to modify it.
348:         query = @project.reportContexts.last.query.dup
349:         query.property = task
350:         query.scopeProperty = scopeLine ? scopeLine.property : nil
351: 
352:         no += 1
353:         Log.activity if lineNo % 10 == 0
354:         lineNo += 1
355:         a('scenarios').each do |scenarioIdx|
356:           query.scenarioIdx = scenarioIdx
357:           # Generate line for each task.
358:           line = ReportTableLine.new(@table, task, scopeLine)
359: 
360:           line.no = no unless scopeLine
361:           line.lineNo = lineNo
362:           line.subLineNo = @table.lines
363:           setIndent(line, a('taskRoot'), taskList.treeMode?)
364: 
365:           # Generate a cell for each column in this line.
366:           a('columns').each do |columnDef|
367:             query.attributeId = columnDef.id
368:             next unless generateTableCell(line, task, columnDef, query)
369:           end
370:         end
371: 
372:         if resourceList
373:           # If we have a resourceList we generate nested lines for each of the
374:           # resources that are assigned to this task and pass the user-defined
375:           # filter.
376:           resourceList.setSorting(a('sortResources'))
377:           assignedResourceList = filterResourceList(resourceList, task,
378:               a('hideResource'), a('rollupResource'), a('openNodes'))
379:           assignedResourceList.sort!
380:           lineNo = generateResourceList(assignedResourceList, nil, line)
381:         end
382:       end
383:       lineNo
384:     end
setReportPeriod() click to toggle source

These can’t be determined during initialization as they have have been changed afterwards.

     # File lib/taskjuggler/reports/TableReport.rb, line 192
192:     def setReportPeriod
193:       @start = a('start')
194:       @end = a('end')
195:     end

Private Instance Methods

checkCellText(cell) click to toggle source

Make sure we have a valid cell text. If not, this is the result of an error. This could happen after scheduling errors.

      # File lib/taskjuggler/reports/TableReport.rb, line 1018
1018:     def checkCellText(cell)
1019:       unless cell.text
1020:         cell.text = '<Error>'
1021:         cell.fontColor = '#FF0000'
1022:       end
1023:     end
genCalChartHeader(columnDef, t, sameTimeNextFunc, name1Func, name2Func) click to toggle source

Generate the header data for calendar tables. They consists of columns for each hour, day, week, etc. columnDef is the definition of the columns. t is the start time for the calendar. sameTimeNextFunc is a function that is called to advance t to the next table column interval. name1Func and name2Func are functions that return the upper and lower title of the particular column.

     # File lib/taskjuggler/reports/TableReport.rb, line 460
460:     def genCalChartHeader(columnDef, t, sameTimeNextFunc, name1Func, name2Func)
461:       tableColumn = ReportTableColumn.new(@table, columnDef, '')
462: 
463:       # Calendar chars only work when all lines have same height.
464:       @table.equiLines = true
465: 
466:       # Embedded tables have unpredictable width. So we always need to make room
467:       # for a potential scrollbar.
468:       tableColumn.scrollbar = true
469: 
470:       # Create the table that is embedded in this column.
471:       tableColumn.cell1.special = table = ColumnTable.new
472:       table.equiLines = true
473:       tableColumn.cell2.hidden = true
474:       table.viewWidth = columnDef.width ? columnDef.width : 450
475: 
476:       # Iterate over the report interval until we hit the end date. The
477:       # iteration is done with 2 nested loops. The outer loops generates the
478:       # intervals for the upper (larger) scale. The inner loop generates the
479:       # lower (smaller) scale.
480:       while t < @end
481:         cellsInInterval = 0
482:         # Label for upper scale. The yearly calendar only has a lower scale.
483:         currentInterval = t.send(name1Func) if name1Func
484:         firstColumn = nil
485:         # The innter loops terminates when the label for the upper scale has
486:         # changed to the next scale cell.
487:         while t < @end && (name1Func.nil? ||
488:                            t.send(name1Func) == currentInterval)
489:           # call TjTime::sameTimeNext... function to get the end of the column.
490:           nextT = t.send(sameTimeNextFunc)
491:           iv = Interval.new(t, nextT)
492:           # Create the new column object.
493:           column = ReportTableColumn.new(table, nil, '')
494:           # Store the date of the column in the original form.
495:           column.cell1.data = t.to_s(a('timeFormat'))
496:           # The upper scale cells will be merged into one large cell that spans
497:           # all lower scale cells that belong to this upper cell.
498:           if firstColumn.nil?
499:             firstColumn = column
500:             column.cell1.text = currentInterval.to_s
501:           else
502:             column.cell1.hidden = true
503:           end
504:           column.cell2.text = t.send(name2Func).to_s
505:           # TODO: The width should be taken from some data structure.
506:           column.cell2.width = 20
507:           # Off-duty cells will have a different color than working time cells.
508:           unless @project.isWorkingTime(iv)
509:             column.cell2.category = 'tabhead_offduty'
510:           end
511:           cellsInInterval += 1
512: 
513:           t = nextT
514:         end
515:         # The the first upper scale cell how many trailing hidden cells are
516:         # following.
517:         firstColumn.cell1.columns = cellsInInterval
518:       end
519:     end
genCalChartResourceCell(origQuery, line, columnDef, t, sameTimeNextFunc) click to toggle source

Generate the cells for the resource lines of a calendar column. These lines do not directly belong to the @table object but to an embedded ColumnTable object. Therefor a single @table column usually has many cells on each single line. scenarioIdx is the index of the scenario that is reported in this line. line is the @table line. t is the start date for the calendar. sameTimeNextFunc is the function that will move the date to the next cell.

     # File lib/taskjuggler/reports/TableReport.rb, line 808
808:     def genCalChartResourceCell(origQuery, line, columnDef, t,
809:                                 sameTimeNextFunc)
810:       # Find out if we have an enclosing task scope.
811:       if line.scopeLine && line.scopeLine.property.is_a?(Task)
812:         task = line.scopeLine.property
813:         # Get the interval of the task. In case a date is invalid due to a
814:         # scheduling problem, we use the full project interval.
815:         taskStart = task['start', origQuery.scenarioIdx]
816:         taskEnd = task['end', origQuery.scenarioIdx]
817:         taskIv = Interval.new(taskStart.nil? ?  @project['start'] : taskStart,
818:                               taskEnd.nil? ?  @project['end'] : taskEnd)
819:       else
820:         task = nil
821:       end
822: 
823:       firstCell = nil
824:       while t < @end
825:         # We modify the start and end dates to match the cell boundaries. So
826:         # we need to make sure we don't modify the original Query but our own
827:         # copies.
828:         query = origQuery.dup
829: 
830:         # Create a new cell
831:         cell = newCell(query, line)
832: 
833:         # call TjTime::sameTimeNext... function
834:         nextT = t.send(sameTimeNextFunc)
835:         cellIv = Interval.new(t, nextT)
836:         # Get work load for all tasks.
837:         query.scopeProperty = nil
838:         query.attributeId = 'effort'
839:         query.startIdx = @project.dateToIdx(t)
840:         query.endIdx = @project.dateToIdx(nextT) - 1
841:         query.process
842:         workLoad = query.to_num
843:         scaledWorkLoad = query.to_s
844:         if task
845:           # Get work load for the particular task.
846:           query.scopeProperty = task
847:           query.process
848:           workLoadTask = query.to_num
849:           scaledWorkLoad = query.to_s
850:         else
851:           workLoadTask = 0.0
852:         end
853:         # Get unassigned work load.
854:         query.attributeId = 'freework'
855:         query.process
856:         freeLoad = query.to_num
857:         case columnDef.content
858:         when 'empty'
859:           # We only generate cells will different background colors.
860:         when 'load'
861:           # Report the workload of the resource in this time interval.
862:           # To increase readability, we don't show 0.0 values.
863:           wLoad = task ? workLoadTask : workLoad
864:           if wLoad > 0.0
865:             cell.text = scaledWorkLoad
866:           end
867:         else
868:           raise "Unknown column content #{column.content}"
869:         end
870: 
871:         cdText = columnDef.cellText.getPattern(query)
872:         cell.text = cdText if cdText
873: 
874:         # Set the tooltip for the cell. We might delete it again.
875:         cell.tooltip = columnDef.tooltip.getPattern(query) || nil
876:         cell.showTooltipHint = false
877: 
878:         # Determine cell category (mostly the background color)
879:         cell.category = if task
880:                           if cellIv.overlaps?(taskIv)
881:                             if workLoadTask > 0.0 && freeLoad == 0.0
882:                               'busy'
883:                             elsif workLoad == 0.0 && freeLoad == 0.0
884:                               cell.tooltip = nil
885:                               'offduty'
886:                             else
887:                               'loaded'
888:                             end
889:                           else
890:                             if freeLoad > 0.0
891:                               'free'
892:                             elsif workLoad == 0.0 && freeLoad == 0.0
893:                               cell.tooltip = nil
894:                               'offduty'
895:                             else
896:                               cell.tooltip = nil
897:                               'resourcecell'
898:                             end
899:                           end
900:                         else
901:                           if workLoad > 0.0 && freeLoad == 0.0
902:                             'busy'
903:                           elsif workLoad > 0.0 && freeLoad > 0.0
904:                             'loaded'
905:                           elsif workLoad == 0.0 && freeLoad > 0.0
906:                             'free'
907:                           else
908:                             cell.tooltip = nil
909:                             'offduty'
910:                           end
911:                         end
912:         cell.category += line.property.get('index') % 2 == 1 ? '1' : '2'
913: 
914:         tryCellMerging(cell, line, firstCell)
915: 
916:         t = nextT
917:         firstCell = cell unless firstCell
918:       end
919: 
920:       legend.addCalendarItem('Resource is fully loaded', 'busy1')
921:       legend.addCalendarItem('Resource is partially loaded', 'loaded1')
922:       legend.addCalendarItem('Resource is available', 'free')
923:       legend.addCalendarItem('Off duty time', 'offduty')
924:     end
genCalChartTaskCell(origQuery, line, columnDef, t, sameTimeNextFunc) click to toggle source

Generate the cells for the task lines of a calendar column. These lines do not directly belong to the @table object but to an embedded ColumnTable object. Therefor a single @table column usually has many cells on each single line. scenarioIdx is the index of the scenario that is reported in this line. line is the @table line. t is the start date for the calendar. sameTimeNextFunc is the function that will move the date to the next cell.

     # File lib/taskjuggler/reports/TableReport.rb, line 726
726:     def genCalChartTaskCell(origQuery, line, columnDef, t, sameTimeNextFunc)
727:       task = line.property
728:       # Find out if we have an enclosing resource scope.
729:       if line.scopeLine && line.scopeLine.property.is_a?(Resource)
730:         resource = line.scopeLine.property
731:       else
732:         resource = nil
733:       end
734: 
735:       # Get the interval of the task. In case a date is invalid due to a
736:       # scheduling problem, we use the full project interval.
737:       taskStart = task['start', origQuery.scenarioIdx]
738:       taskEnd = task['end', origQuery.scenarioIdx]
739:       taskIv = Interval.new(taskStart.nil? ?  @project['start'] : taskStart,
740:                             taskEnd.nil? ?  @project['end'] : taskEnd)
741: 
742:       firstCell = nil
743:       while t < @end
744:         # We modify the start and end dates to match the cell boundaries. So
745:         # we need to make sure we don't modify the original Query but our own
746:         # copies.
747:         query = origQuery.dup
748:         # call TjTime::sameTimeNext... function
749:         nextT = t.send(sameTimeNextFunc)
750:         cellIv = Interval.new(t, nextT)
751:         case columnDef.content
752:         when 'empty'
753:           # Create a new cell
754:           cell = newCell(query, line)
755:           # We only generate cells will different background colors.
756:         when 'load'
757:           query.attributeId = 'effort'
758:           query.start = t
759:           query.end = nextT
760:           query.process
761: 
762:           # Create a new cell
763:           cell = newCell(query, line)
764: 
765:           # To increase readability, we don't show 0.0 values.
766:           cell.text = query.to_s if query.to_num != 0.0
767:         else
768:           raise "Unknown column content #{column.content}"
769:         end
770: 
771:         cdText = columnDef.cellText.getPattern(query)
772:         cell.text = cdText if cdText
773: 
774:         # Determine cell category (mostly the background color)
775:         if cellIv.overlaps?(taskIv)
776:           # The cell is either a container or leaf task
777:           cell.category = task.container? ? 'calconttask' : 'caltask'
778:           # If the user has requested a custom tooltip, add it to each task cell.
779:           cell.tooltip = columnDef.tooltip.getPattern(query) || nil
780:           cell.showTooltipHint = false
781:         elsif !@project.isWorkingTime(cellIv)
782:           # The cell is a vacation cell.
783:           cell.category = 'offduty'
784:         else
785:           # The cell is just filled with the background color.
786:           cell.category = 'taskcell'
787:         end
788:         cell.category += line.property.get('index') % 2  == 1 ? '1' : '2'
789: 
790:         tryCellMerging(cell, line, firstCell)
791: 
792:         t = nextT
793:         firstCell = cell unless firstCell
794:       end
795: 
796:       legend.addCalendarItem('Container Task', 'calconttask1')
797:       legend.addCalendarItem('Task', 'caltask1')
798:       legend.addCalendarItem('Off duty time', 'offduty')
799:     end
genCalculatedCell(query, line, columnDef, property) click to toggle source

Generate a ReportTableCell filled with a calculted value from the property or other sources of information. It returns true if the cell exists, false for a hidden cell. query is the Query to get the cell value. line is the ReportTableLine of the cell. columnDef is the TableColumnDefinition of the column. property is the PropertyTreeNode that is reported in this cell.

     # File lib/taskjuggler/reports/TableReport.rb, line 652
652:     def genCalculatedCell(query, line, columnDef, property)
653:       query = query.dup
654:       query.listType = columnDef.listType
655:       query.listItem = columnDef.listItem
656: 
657:       # Create a new cell
658:       cell = newCell(query, line)
659: 
660:       unless setScenarioSettings(cell, query.scenarioIdx,
661:                                  scenarioSpecific?(columnDef.id))
662:         return false
663:       end
664: 
665:       setStandardCellAttributes(cell, columnDef, nil, line)
666: 
667:       if query.process
668:         cell.text = (rti = query.to_rti) ? rti : query.to_s
669:       end
670: 
671:       # Some columns need some extra care.
672:       case columnDef.id
673:       when 'alert'
674:         id = @project.alertLevelId(query.to_num)
675:         cell.icon = "flag-#{id}"
676:         cell.fontColor = @project.alertLevelColor(query.to_sort)
677:       when 'alerttrend'
678:         icons = %( up flat down )
679:         cell.icon = "trend-#{icons[query.to_sort]}"
680:       when 'line'
681:         cell.text = line.lineNo.to_s
682:       when 'name'
683:         cell.icon =
684:           if property.is_a?(Task)
685:             if property.container?
686:               'taskgroup'
687:             else
688:               'task'
689:             end
690:           elsif property.is_a?(Resource)
691:             if property.container?
692:               'resourcegroup'
693:             else
694:               'resource'
695:             end
696:           else
697:             nil
698:           end
699:           cell.iconTooltip = RichText.new("'''ID:''' #{property.fullId}").
700:           generateIntermediateFormat
701:       when 'no'
702:         cell.text = line.no.to_s
703:       when 'bsi'
704:         cell.indent = 2 if line.scopeLine
705:       when 'scenario'
706:         cell.text = @project.scenario(query.scenarioIdx).name
707:       end
708: 
709:       # Replace the cell text if the user has requested a custom cell text.
710:       cdText = columnDef.cellText.getPattern(query)
711:       cell.text = cdText if cdText
712: 
713:       setCustomCellAttributes(cell, columnDef, query)
714:       checkCellText(cell)
715: 
716:       true
717:     end
genStandardCell(query, line, columnDef) click to toggle source

Generate a ReportTableCell filled the value of an attribute of the property that line is for. It returns true if the cell exists, false for a hidden cell.

     # File lib/taskjuggler/reports/TableReport.rb, line 606
606:     def genStandardCell(query, line, columnDef)
607:       query = query.dup
608:       query.listType = columnDef.listType
609:       query.listItem = columnDef.listItem
610: 
611:       # Find out, what type of PropertyTreeNode we are dealing with.
612:       property = line.property
613:       if property.is_a?(Task)
614:         propertyList = @project.tasks
615:       elsif property.is_a?(Resource)
616:         propertyList = @project.resources
617:       else
618:         raise "Unknown property type #{property.class}"
619:       end
620: 
621:       # Create a new cell
622:       cell = newCell(query, line)
623: 
624:       unless setScenarioSettings(cell, query.scenarioIdx,
625:                                  propertyList.scenarioSpecific?(columnDef.id))
626:         return false
627:       end
628: 
629:       setStandardCellAttributes(cell, columnDef,
630:                                 propertyList.attributeType(columnDef.id), line)
631: 
632:       # If the user has requested a custom cell text, this will be used
633:       # instead of the queried one.
634:       if (cdText = columnDef.cellText.getPattern(query))
635:         cell.text = cdText
636:       elsif query.process
637:         cell.text = (rti = query.to_rti) ? rti : query.to_s
638:       end
639: 
640:       setCustomCellAttributes(cell, columnDef, query)
641:       checkCellText(cell)
642: 
643:       true
644:     end
generateTableCell(line, property, columnDef, query) click to toggle source

Generate a cell of the table. line is the ReportTableLine that this cell should belong to. property is the PropertyTreeNode that is reported in this line. columnDef is the TableColumnDefinition of the column this cell should belong to. scenarioIdx is the index of the scenario that is reported in this line.

There are 4 kinds of cells. The most simple one is the standard cell. It literally reports the value of a property attribute. Calculated cells are more flexible. They contain computed values. The values are computed at cell generation time. The calendar columns consist of multiple sub columns. In such a case many cells are generated with a single call of this method. The last kind of cell is actually not a cell. It just generates the chart objects that belong to the property in this line.

     # File lib/taskjuggler/reports/TableReport.rb, line 534
534:     def generateTableCell(line, property, columnDef, query)
535:       if columnDef.start || columnDef.end
536:         # If the user has specified a new start or end time for this column,
537:         # we have to duplicate the query before we modify it.
538:         query = query.dup
539:         query.start = columnDef.start if columnDef.start
540:         query.end = columnDef.end if columnDef.end
541:       end
542: 
543:       case columnDef.id
544:       when 'chart'
545:         # Generate a hidden cell. The real meat is in the actual chart object,
546:         # not in this cell.
547:         cell = ReportTableCell.new(line, query, '')
548:         cell.hidden = true
549:         cell.text = nil
550:         # The GanttChart can be reached via the special variable of the column
551:         # header.
552:         chart = columnDef.column.cell1.special
553:         GanttLine.new(chart, query, (line.subLineNo - 1) * (line.height + 1),
554:                       line.height, a('selfcontained') ? nil : columnDef.tooltip)
555:         return true
556:       # The calendar cells can be all generated by the same function. But we
557:       # need to use different parameters.
558:       when 'hourly'
559:         start = @start.midnight
560:         sameTimeNextFunc = :sameTimeNextHour
561:       when 'daily'
562:         start = @start.midnight
563:         sameTimeNextFunc = :sameTimeNextDay
564:       when 'weekly'
565:         start = @start.beginOfWeek(a('weekStartsMonday'))
566:         sameTimeNextFunc = :sameTimeNextWeek
567:       when 'monthly'
568:         start = @start.beginOfMonth
569:         sameTimeNextFunc = :sameTimeNextMonth
570:       when 'quarterly'
571:         start = @start.beginOfQuarter
572:         sameTimeNextFunc = :sameTimeNextQuarter
573:       when 'yearly'
574:         start = @start.beginOfYear
575:         sameTimeNextFunc = :sameTimeNextYear
576:       else
577:         if calculated?(columnDef.id)
578:           return genCalculatedCell(query, line, columnDef, property)
579:         else
580:           return genStandardCell(query, line, columnDef)
581:         end
582:       end
583: 
584:       # The calendar cells don't live in this ReportTable but in an embedded
585:       # ReportTable that can be reached via the column header special variable.
586:       # For embedded column tables we need to create a new line.
587:       tcLine = ReportTableLine.new(columnDef.column.cell1.special,
588:                                    line.property, line.scopeLine)
589: 
590:       PlaceHolderCell.new(line, tcLine)
591:       # Depending on the property type we use different generator functions.
592:       if property.is_a?(Task)
593:         genCalChartTaskCell(query, tcLine, columnDef, start, sameTimeNextFunc)
594:       elsif property.is_a?(Resource)
595:         genCalChartResourceCell(query, tcLine, columnDef, start,
596:                                 sameTimeNextFunc)
597:       else
598:         raise "Unknown property type #{property.class}"
599:       end
600:       true
601:     end
newCell(query, line) click to toggle source

Create a new ReportTableCell object and initialize some common values.

      # File lib/taskjuggler/reports/TableReport.rb, line 992
 992:     def newCell(query, line)
 993:       property = line.property
 994:       cell = ReportTableCell.new(line, query)
 995: 
 996:       # Cells for containers should be using bold font face.
 997:       cell.bold = true if property.container? && line.bold
 998:       cell.selfcontained = a('selfcontained')
 999:       cell
1000:     end
setCustomCellAttributes(cell, columnDef, query) click to toggle source
     # File lib/taskjuggler/reports/TableReport.rb, line 950
950:     def setCustomCellAttributes(cell, columnDef, query)
951:       # Replace the cell background color if the user has requested a custom
952:       # color.
953:       cellColor = columnDef.cellColor.getPattern(query)
954:       cell.cellColor = cellColor if cellColor
955: 
956:       # Replace the font color setting if the user has requested a custom
957:       # color.
958:       fontColor = columnDef.fontColor.getPattern(query)
959:       cell.fontColor = fontColor if fontColor
960: 
961:       # Replace the default cell alignment if the user has requested a custom
962:       # alignment.
963:       hAlign = columnDef.hAlign.getPattern(query)
964:       cell.alignment = hAlign if hAlign
965: 
966:       # Register the custom tooltip if the user has requested one.
967:       cdTooltip = columnDef.tooltip.getPattern(query)
968:       cell.tooltip = cdTooltip if cdTooltip
969:     end
setIndent(line, propertyRoot, treeMode) click to toggle source

Determine the indentation for this line.

      # File lib/taskjuggler/reports/TableReport.rb, line 1003
1003:     def setIndent(line, propertyRoot, treeMode)
1004:       property = line.property
1005:       scopeLine = line.scopeLine
1006:       level = property.level - (propertyRoot ? propertyRoot.level : 0)
1007:       # We indent at least as much as the scopeline + 1, if we have a scope.
1008:       line.indentation = scopeLine.indentation + 1 if scopeLine
1009:       # In tree mode we indent according to the level.
1010:       if treeMode
1011:         line.indentation += level
1012:         line.bold = true
1013:       end
1014:     end
setScenarioSettings(cell, scenarioIdx, scenarioSpecific) click to toggle source
     # File lib/taskjuggler/reports/TableReport.rb, line 972
972:     def setScenarioSettings(cell, scenarioIdx, scenarioSpecific)
973:       # Check if we are dealing with multiple scenarios.
974:       if a('scenarios').length > 1
975:         # Check if the attribute is not scenario specific
976:         unless scenarioSpecific
977:           if scenarioIdx == a('scenarios').first
978:             #  Use a somewhat bigger font.
979:             cell.fontSize = 15
980:           else
981:             # And hide the cells for all but the first scenario.
982:             cell.hidden = true
983:             return false
984:           end
985:           cell.rows = a('scenarios').length
986:         end
987:       end
988:       true
989:     end
setStandardCellAttributes(cell, columnDef, attributeType, line) click to toggle source

This method takes care of often used cell attributes like indentation, alignment and background color.

     # File lib/taskjuggler/reports/TableReport.rb, line 928
928:     def setStandardCellAttributes(cell, columnDef, attributeType, line)
929:       # Determine whether it should be indented
930:       if indent(columnDef.id, attributeType)
931:         cell.indent = line.indentation
932:       end
933: 
934:       # Determine the cell alignment
935:       cell.alignment = alignment(columnDef.id, attributeType)
936: 
937:       # Set background color
938:       if line.property.is_a?(Task)
939:         cell.category = line.property.get('index') % 2 == 1 ?
940:           'taskcell1' : 'taskcell2'
941:       else
942:         cell.category = line.property.get('index') % 2 == 1 ?
943:           'resourcecell1' : 'resourcecell2'
944:       end
945: 
946:       # Set column width
947:       cell.width = columnDef.width if columnDef.width
948:     end
tryCellMerging(cell, line, firstCell) click to toggle source

Try to merge equal cells without text to multi-column cells.

      # File lib/taskjuggler/reports/TableReport.rb, line 1026
1026:     def tryCellMerging(cell, line, firstCell)
1027:       if cell.text == '' && firstCell && (c = line.last(1)) && c == cell
1028:         cell.hidden = true
1029:         c.columns += 1
1030:       end
1031:     end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.