comparison lib/redmine/helpers/gantt.rb @ 441:cbce1fd3b1b7 redmine-1.2

Update to Redmine 1.2-stable branch (Redmine SVN rev 6000)
author Chris Cannam
date Mon, 06 Jun 2011 14:24:13 +0100
parents 051f544170fe
children cbb26bc654de
comparison
equal deleted inserted replaced
245:051f544170fe 441:cbce1fd3b1b7
1 # Redmine - project management software 1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 # 3 #
4 # This program is free software; you can redistribute it and/or 4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License 5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2 6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version. 7 # of the License, or (at your option) any later version.
8 # 8 #
9 # This program is distributed in the hope that it will be useful, 9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details. 12 # GNU General Public License for more details.
13 # 13 #
14 # You should have received a copy of the GNU General Public License 14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software 15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 module Redmine 18 module Redmine
36 36
37 attr_reader :year_from, :month_from, :date_from, :date_to, :zoom, :months, :truncated, :max_rows 37 attr_reader :year_from, :month_from, :date_from, :date_to, :zoom, :months, :truncated, :max_rows
38 attr_accessor :query 38 attr_accessor :query
39 attr_accessor :project 39 attr_accessor :project
40 attr_accessor :view 40 attr_accessor :view
41 41
42 def initialize(options={}) 42 def initialize(options={})
43 options = options.dup 43 options = options.dup
44 44
45 if options[:year] && options[:year].to_i >0 45 if options[:year] && options[:year].to_i >0
46 @year_from = options[:year].to_i 46 @year_from = options[:year].to_i
47 if options[:month] && options[:month].to_i >=1 && options[:month].to_i <= 12 47 if options[:month] && options[:month].to_i >=1 && options[:month].to_i <= 12
48 @month_from = options[:month].to_i 48 @month_from = options[:month].to_i
49 else 49 else
51 end 51 end
52 else 52 else
53 @month_from ||= Date.today.month 53 @month_from ||= Date.today.month
54 @year_from ||= Date.today.year 54 @year_from ||= Date.today.year
55 end 55 end
56 56
57 zoom = (options[:zoom] || User.current.pref[:gantt_zoom]).to_i 57 zoom = (options[:zoom] || User.current.pref[:gantt_zoom]).to_i
58 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2 58 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
59 months = (options[:months] || User.current.pref[:gantt_months]).to_i 59 months = (options[:months] || User.current.pref[:gantt_months]).to_i
60 @months = (months > 0 && months < 25) ? months : 6 60 @months = (months > 0 && months < 25) ? months : 6
61 61
62 # Save gantt parameters as user preference (zoom and months count) 62 # Save gantt parameters as user preference (zoom and months count)
63 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months])) 63 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
64 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months 64 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
65 User.current.preference.save 65 User.current.preference.save
66 end 66 end
67 67
68 @date_from = Date.civil(@year_from, @month_from, 1) 68 @date_from = Date.civil(@year_from, @month_from, 1)
69 @date_to = (@date_from >> @months) - 1 69 @date_to = (@date_from >> @months) - 1
70 70
71 @subjects = '' 71 @subjects = ''
72 @lines = '' 72 @lines = ''
73 @number_of_rows = nil 73 @number_of_rows = nil
74 74
75 @issue_ancestors = [] 75 @issue_ancestors = []
76 76
77 @truncated = false 77 @truncated = false
78 if options.has_key?(:max_rows) 78 if options.has_key?(:max_rows)
79 @max_rows = options[:max_rows] 79 @max_rows = options[:max_rows]
80 else 80 else
81 @max_rows = Setting.gantt_items_limit.blank? ? nil : Setting.gantt_items_limit.to_i 81 @max_rows = Setting.gantt_items_limit.blank? ? nil : Setting.gantt_items_limit.to_i
83 end 83 end
84 84
85 def common_params 85 def common_params
86 { :controller => 'gantts', :action => 'show', :project_id => @project } 86 { :controller => 'gantts', :action => 'show', :project_id => @project }
87 end 87 end
88 88
89 def params 89 def params
90 common_params.merge({ :zoom => zoom, :year => year_from, :month => month_from, :months => months }) 90 common_params.merge({ :zoom => zoom, :year => year_from, :month => month_from, :months => months })
91 end 91 end
92 92
93 def params_previous 93 def params_previous
94 common_params.merge({:year => (date_from << months).year, :month => (date_from << months).month, :zoom => zoom, :months => months }) 94 common_params.merge({:year => (date_from << months).year, :month => (date_from << months).month, :zoom => zoom, :months => months })
95 end 95 end
96 96
97 def params_next 97 def params_next
98 common_params.merge({:year => (date_from >> months).year, :month => (date_from >> months).month, :zoom => zoom, :months => months }) 98 common_params.merge({:year => (date_from >> months).year, :month => (date_from >> months).month, :zoom => zoom, :months => months })
99 end 99 end
100 100
101 ### Extracted from the HTML view/helpers
102 # Returns the number of rows that will be rendered on the Gantt chart 101 # Returns the number of rows that will be rendered on the Gantt chart
103 def number_of_rows 102 def number_of_rows
104 return @number_of_rows if @number_of_rows 103 return @number_of_rows if @number_of_rows
105 104
106 rows = if @project 105 rows = projects.inject(0) {|total, p| total += number_of_rows_on_project(p)}
107 number_of_rows_on_project(@project)
108 else
109 Project.roots.visible.has_module('issue_tracking').inject(0) do |total, project|
110 total += number_of_rows_on_project(project)
111 end
112 end
113
114 rows > @max_rows ? @max_rows : rows 106 rows > @max_rows ? @max_rows : rows
115 end 107 end
116 108
117 # Returns the number of rows that will be used to list a project on 109 # Returns the number of rows that will be used to list a project on
118 # the Gantt chart. This will recurse for each subproject. 110 # the Gantt chart. This will recurse for each subproject.
119 def number_of_rows_on_project(project) 111 def number_of_rows_on_project(project)
120 # Remove the project requirement for Versions because it will 112 return 0 unless projects.include?(project)
121 # restrict issues to only be on the current project. This 113
122 # ends up missing issues which are assigned to shared versions.
123 @query.project = nil if @query.project
124
125 # One Root project
126 count = 1 114 count = 1
127 # Issues without a Version 115 count += project_issues(project).size
128 count += project.issues.for_gantt.without_version.with_query(@query).count 116 count += project_versions(project).size
129
130 # Versions
131 count += project.versions.count
132
133 # Issues on the Versions
134 project.versions.each do |version|
135 count += version.fixed_issues.for_gantt.with_query(@query).count
136 end
137
138 # Subprojects
139 project.children.visible.has_module('issue_tracking').each do |subproject|
140 count += number_of_rows_on_project(subproject)
141 end
142
143 count 117 count
144 end 118 end
145 119
146 # Renders the subjects of the Gantt chart, the left side. 120 # Renders the subjects of the Gantt chart, the left side.
147 def subjects(options={}) 121 def subjects(options={})
152 # Renders the lines of the Gantt chart, the right side 126 # Renders the lines of the Gantt chart, the right side
153 def lines(options={}) 127 def lines(options={})
154 render(options.merge(:only => :lines)) unless @lines_rendered 128 render(options.merge(:only => :lines)) unless @lines_rendered
155 @lines 129 @lines
156 end 130 end
157 131
132 # Returns issues that will be rendered
133 def issues
134 @issues ||= @query.issues(
135 :include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
136 :order => "#{Project.table_name}.lft ASC, #{Issue.table_name}.id ASC",
137 :limit => @max_rows
138 )
139 end
140
141 # Return all the project nodes that will be displayed
142 def projects
143 return @projects if @projects
144
145 ids = issues.collect(&:project).uniq.collect(&:id)
146 if ids.any?
147 # All issues projects and their visible ancestors
148 @projects = Project.visible.all(
149 :joins => "LEFT JOIN #{Project.table_name} child ON #{Project.table_name}.lft <= child.lft AND #{Project.table_name}.rgt >= child.rgt",
150 :conditions => ["child.id IN (?)", ids],
151 :order => "#{Project.table_name}.lft ASC"
152 ).uniq
153 else
154 @projects = []
155 end
156 end
157
158 # Returns the issues that belong to +project+
159 def project_issues(project)
160 @issues_by_project ||= issues.group_by(&:project)
161 @issues_by_project[project] || []
162 end
163
164 # Returns the distinct versions of the issues that belong to +project+
165 def project_versions(project)
166 project_issues(project).collect(&:fixed_version).compact.uniq
167 end
168
169 # Returns the issues that belong to +project+ and are assigned to +version+
170 def version_issues(project, version)
171 project_issues(project).select {|issue| issue.fixed_version == version}
172 end
173
158 def render(options={}) 174 def render(options={})
159 options = {:indent => 4, :render => :subject, :format => :html}.merge(options) 175 options = {:top => 0, :top_increment => 20, :indent_increment => 20, :render => :subject, :format => :html}.merge(options)
160 176 indent = options[:indent] || 4
177
161 @subjects = '' unless options[:only] == :lines 178 @subjects = '' unless options[:only] == :lines
162 @lines = '' unless options[:only] == :subjects 179 @lines = '' unless options[:only] == :subjects
163 @number_of_rows = 0 180 @number_of_rows = 0
164 181
165 if @project 182 Project.project_tree(projects) do |project, level|
166 render_project(@project, options) 183 options[:indent] = indent + level * options[:indent_increment]
167 else 184 render_project(project, options)
168 Project.roots.visible.has_module('issue_tracking').each do |project| 185 break if abort?
169 render_project(project, options) 186 end
170 break if abort? 187
171 end
172 end
173
174 @subjects_rendered = true unless options[:only] == :lines 188 @subjects_rendered = true unless options[:only] == :lines
175 @lines_rendered = true unless options[:only] == :subjects 189 @lines_rendered = true unless options[:only] == :subjects
176 190
177 render_end(options) 191 render_end(options)
178 end 192 end
179 193
180 def render_project(project, options={}) 194 def render_project(project, options={})
181 options[:top] = 0 unless options.key? :top
182 options[:indent_increment] = 20 unless options.key? :indent_increment
183 options[:top_increment] = 20 unless options.key? :top_increment
184
185 subject_for_project(project, options) unless options[:only] == :lines 195 subject_for_project(project, options) unless options[:only] == :lines
186 line_for_project(project, options) unless options[:only] == :subjects 196 line_for_project(project, options) unless options[:only] == :subjects
187 197
188 options[:top] += options[:top_increment] 198 options[:top] += options[:top_increment]
189 options[:indent] += options[:indent_increment] 199 options[:indent] += options[:indent_increment]
190 @number_of_rows += 1 200 @number_of_rows += 1
191 return if abort? 201 return if abort?
192 202
193 # Second, Issues without a version 203 issues = project_issues(project).select {|i| i.fixed_version.nil?}
194 issues = project.issues.for_gantt.without_version.with_query(@query).all(:limit => current_limit)
195 sort_issues!(issues) 204 sort_issues!(issues)
196 if issues 205 if issues
197 render_issues(issues, options) 206 render_issues(issues, options)
198 return if abort? 207 return if abort?
199 end 208 end
200 209
201 # Third, Versions 210 versions = project_versions(project)
202 project.versions.sort.each do |version| 211 versions.each do |version|
203 render_version(version, options) 212 render_version(project, version, options)
204 return if abort? 213 end
205 end
206
207 # Fourth, subprojects
208 project.children.visible.has_module('issue_tracking').each do |project|
209 render_project(project, options)
210 return if abort?
211 end unless project.leaf?
212 214
213 # Remove indent to hit the next sibling 215 # Remove indent to hit the next sibling
214 options[:indent] -= options[:indent_increment] 216 options[:indent] -= options[:indent_increment]
215 end 217 end
216 218
217 def render_issues(issues, options={}) 219 def render_issues(issues, options={})
218 @issue_ancestors = [] 220 @issue_ancestors = []
219 221
220 issues.each do |i| 222 issues.each do |i|
221 subject_for_issue(i, options) unless options[:only] == :lines 223 subject_for_issue(i, options) unless options[:only] == :lines
222 line_for_issue(i, options) unless options[:only] == :subjects 224 line_for_issue(i, options) unless options[:only] == :subjects
223 225
224 options[:top] += options[:top_increment] 226 options[:top] += options[:top_increment]
225 @number_of_rows += 1 227 @number_of_rows += 1
226 break if abort? 228 break if abort?
227 end 229 end
228 230
229 options[:indent] -= (options[:indent_increment] * @issue_ancestors.size) 231 options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
230 end 232 end
231 233
232 def render_version(version, options={}) 234 def render_version(project, version, options={})
233 # Version header 235 # Version header
234 subject_for_version(version, options) unless options[:only] == :lines 236 subject_for_version(version, options) unless options[:only] == :lines
235 line_for_version(version, options) unless options[:only] == :subjects 237 line_for_version(version, options) unless options[:only] == :subjects
236 238
237 options[:top] += options[:top_increment] 239 options[:top] += options[:top_increment]
238 @number_of_rows += 1 240 @number_of_rows += 1
239 return if abort? 241 return if abort?
240 242
241 # Remove the project requirement for Versions because it will 243 issues = version_issues(project, version)
242 # restrict issues to only be on the current project. This
243 # ends up missing issues which are assigned to shared versions.
244 @query.project = nil if @query.project
245
246 issues = version.fixed_issues.for_gantt.with_query(@query).all(:limit => current_limit)
247 if issues 244 if issues
248 sort_issues!(issues) 245 sort_issues!(issues)
249 # Indent issues 246 # Indent issues
250 options[:indent] += options[:indent_increment] 247 options[:indent] += options[:indent_increment]
251 render_issues(issues, options) 248 render_issues(issues, options)
252 options[:indent] -= options[:indent_increment] 249 options[:indent] -= options[:indent_increment]
253 end 250 end
254 end 251 end
255 252
256 def render_end(options={}) 253 def render_end(options={})
257 case options[:format] 254 case options[:format]
258 when :pdf 255 when :pdf
259 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top]) 256 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
260 end 257 end
261 end 258 end
262 259
263 def subject_for_project(project, options) 260 def subject_for_project(project, options)
278 def line_for_project(project, options) 275 def line_for_project(project, options)
279 # Skip versions that don't have a start_date or due date 276 # Skip versions that don't have a start_date or due date
280 if project.is_a?(Project) && project.start_date && project.due_date 277 if project.is_a?(Project) && project.start_date && project.due_date
281 options[:zoom] ||= 1 278 options[:zoom] ||= 1
282 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom] 279 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
283 280
284 coords = coordinates(project.start_date, project.due_date, nil, options[:zoom]) 281 coords = coordinates(project.start_date, project.due_date, nil, options[:zoom])
285 label = h(project) 282 label = h(project)
286 283
287 case options[:format] 284 case options[:format]
288 when :html 285 when :html
289 html_task(options, coords, :css => "project task", :label => label, :markers => true) 286 html_task(options, coords, :css => "project task", :label => label, :markers => true)
290 when :image 287 when :image
291 image_task(options, coords, :label => label, :markers => true, :height => 3) 288 image_task(options, coords, :label => label, :markers => true, :height => 3)
316 def line_for_version(version, options) 313 def line_for_version(version, options)
317 # Skip versions that don't have a start_date 314 # Skip versions that don't have a start_date
318 if version.is_a?(Version) && version.start_date && version.due_date 315 if version.is_a?(Version) && version.start_date && version.due_date
319 options[:zoom] ||= 1 316 options[:zoom] ||= 1
320 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom] 317 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
321 318
322 coords = coordinates(version.start_date, version.due_date, version.completed_pourcent, options[:zoom]) 319 coords = coordinates(version.start_date, version.due_date, version.completed_pourcent, options[:zoom])
323 label = "#{h version } #{h version.completed_pourcent.to_i.to_s}%" 320 label = "#{h version } #{h version.completed_pourcent.to_i.to_s}%"
324 label = h("#{version.project} -") + label unless @project && @project == version.project 321 label = h("#{version.project} -") + label unless @project && @project == version.project
325 322
326 case options[:format] 323 case options[:format]
340 def subject_for_issue(issue, options) 337 def subject_for_issue(issue, options)
341 while @issue_ancestors.any? && !issue.is_descendant_of?(@issue_ancestors.last) 338 while @issue_ancestors.any? && !issue.is_descendant_of?(@issue_ancestors.last)
342 @issue_ancestors.pop 339 @issue_ancestors.pop
343 options[:indent] -= options[:indent_increment] 340 options[:indent] -= options[:indent_increment]
344 end 341 end
345 342
346 output = case options[:format] 343 output = case options[:format]
347 when :html 344 when :html
348 css_classes = '' 345 css_classes = ''
349 css_classes << ' issue-overdue' if issue.overdue? 346 css_classes << ' issue-overdue' if issue.overdue?
350 css_classes << ' issue-behind-schedule' if issue.behind_schedule? 347 css_classes << ' issue-behind-schedule' if issue.behind_schedule?
351 css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to 348 css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
352 349
353 subject = "<span class='#{css_classes}'>" 350 subject = "<span class='#{css_classes}'>"
354 if issue.assigned_to.present? 351 if issue.assigned_to.present?
355 assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name 352 assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
356 subject << view.avatar(issue.assigned_to, :class => 'gravatar icon-gravatar', :size => 10, :title => assigned_string).to_s 353 subject << view.avatar(issue.assigned_to, :class => 'gravatar icon-gravatar', :size => 10, :title => assigned_string).to_s
357 end 354 end
367 364
368 unless issue.leaf? 365 unless issue.leaf?
369 @issue_ancestors << issue 366 @issue_ancestors << issue
370 options[:indent] += options[:indent_increment] 367 options[:indent] += options[:indent_increment]
371 end 368 end
372 369
373 output 370 output
374 end 371 end
375 372
376 def line_for_issue(issue, options) 373 def line_for_issue(issue, options)
377 # Skip issues that don't have a due_before (due_date or version's due_date) 374 # Skip issues that don't have a due_before (due_date or version's due_date)
378 if issue.is_a?(Issue) && issue.due_before 375 if issue.is_a?(Issue) && issue.due_before
379 coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom]) 376 coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom])
380 label = "#{ issue.status.name } #{ issue.done_ratio }%" 377 label = "#{ issue.status.name } #{ issue.done_ratio }%"
381 378
382 case options[:format] 379 case options[:format]
383 when :html 380 when :html
384 html_task(options, coords, :css => "task " + (issue.leaf? ? 'leaf' : 'parent'), :label => label, :issue => issue, :markers => !issue.leaf?) 381 html_task(options, coords, :css => "task " + (issue.leaf? ? 'leaf' : 'parent'), :label => label, :issue => issue, :markers => !issue.leaf?)
385 when :image 382 when :image
386 image_task(options, coords, :label => label) 383 image_task(options, coords, :label => label)
394 end 391 end
395 392
396 # Generates a gantt image 393 # Generates a gantt image
397 # Only defined if RMagick is avalaible 394 # Only defined if RMagick is avalaible
398 def to_image(format='PNG') 395 def to_image(format='PNG')
399 date_to = (@date_from >> @months)-1 396 date_to = (@date_from >> @months)-1
400 show_weeks = @zoom > 1 397 show_weeks = @zoom > 1
401 show_days = @zoom > 2 398 show_days = @zoom > 2
402 399
403 subject_width = 400 400 subject_width = 400
404 header_heigth = 18 401 header_height = 18
405 # width of one day in pixels 402 # width of one day in pixels
406 zoom = @zoom*2 403 zoom = @zoom*2
407 g_width = (@date_to - @date_from + 1)*zoom 404 g_width = (@date_to - @date_from + 1)*zoom
408 g_height = 20 * number_of_rows + 30 405 g_height = 20 * number_of_rows + 30
409 headers_heigth = (show_weeks ? 2*header_heigth : header_heigth) 406 headers_height = (show_weeks ? 2*header_height : header_height)
410 height = g_height + headers_heigth 407 height = g_height + headers_height
411 408
412 imgl = Magick::ImageList.new 409 imgl = Magick::ImageList.new
413 imgl.new_image(subject_width+g_width+1, height) 410 imgl.new_image(subject_width+g_width+1, height)
414 gc = Magick::Draw.new 411 gc = Magick::Draw.new
415 412
416 # Subjects 413 # Subjects
417 gc.stroke('transparent') 414 gc.stroke('transparent')
418 subjects(:image => gc, :top => (headers_heigth + 20), :indent => 4, :format => :image) 415 subjects(:image => gc, :top => (headers_height + 20), :indent => 4, :format => :image)
419 416
420 # Months headers 417 # Months headers
421 month_f = @date_from 418 month_f = @date_from
422 left = subject_width 419 left = subject_width
423 @months.times do 420 @months.times do
424 width = ((month_f >> 1) - month_f) * zoom 421 width = ((month_f >> 1) - month_f) * zoom
425 gc.fill('white') 422 gc.fill('white')
426 gc.stroke('grey') 423 gc.stroke('grey')
427 gc.stroke_width(1) 424 gc.stroke_width(1)
428 gc.rectangle(left, 0, left + width, height) 425 gc.rectangle(left, 0, left + width, height)
431 gc.stroke_width(1) 428 gc.stroke_width(1)
432 gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}") 429 gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
433 left = left + width 430 left = left + width
434 month_f = month_f >> 1 431 month_f = month_f >> 1
435 end 432 end
436 433
437 # Weeks headers 434 # Weeks headers
438 if show_weeks 435 if show_weeks
439 left = subject_width 436 left = subject_width
440 height = header_heigth 437 height = header_height
441 if @date_from.cwday == 1 438 if @date_from.cwday == 1
442 # date_from is monday 439 # date_from is monday
443 week_f = date_from 440 week_f = date_from
444 else 441 else
445 # find next monday after date_from 442 # find next monday after date_from
446 week_f = @date_from + (7 - @date_from.cwday + 1) 443 week_f = @date_from + (7 - @date_from.cwday + 1)
447 width = (7 - @date_from.cwday + 1) * zoom 444 width = (7 - @date_from.cwday + 1) * zoom
448 gc.fill('white') 445 gc.fill('white')
449 gc.stroke('grey') 446 gc.stroke('grey')
450 gc.stroke_width(1) 447 gc.stroke_width(1)
451 gc.rectangle(left, header_heigth, left + width, 2*header_heigth + g_height-1) 448 gc.rectangle(left, header_height, left + width, 2*header_height + g_height-1)
452 left = left + width 449 left = left + width
453 end 450 end
454 while week_f <= date_to 451 while week_f <= date_to
455 width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom 452 width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
456 gc.fill('white') 453 gc.fill('white')
457 gc.stroke('grey') 454 gc.stroke('grey')
458 gc.stroke_width(1) 455 gc.stroke_width(1)
459 gc.rectangle(left.round, header_heigth, left.round + width, 2*header_heigth + g_height-1) 456 gc.rectangle(left.round, header_height, left.round + width, 2*header_height + g_height-1)
460 gc.fill('black') 457 gc.fill('black')
461 gc.stroke('transparent') 458 gc.stroke('transparent')
462 gc.stroke_width(1) 459 gc.stroke_width(1)
463 gc.text(left.round + 2, header_heigth + 14, week_f.cweek.to_s) 460 gc.text(left.round + 2, header_height + 14, week_f.cweek.to_s)
464 left = left + width 461 left = left + width
465 week_f = week_f+7 462 week_f = week_f+7
466 end 463 end
467 end 464 end
468 465
469 # Days details (week-end in grey) 466 # Days details (week-end in grey)
470 if show_days 467 if show_days
471 left = subject_width 468 left = subject_width
472 height = g_height + header_heigth - 1 469 height = g_height + header_height - 1
473 wday = @date_from.cwday 470 wday = @date_from.cwday
474 (date_to - @date_from + 1).to_i.times do 471 (date_to - @date_from + 1).to_i.times do
475 width = zoom 472 width = zoom
476 gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white') 473 gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
477 gc.stroke('#ddd') 474 gc.stroke('#ddd')
478 gc.stroke_width(1) 475 gc.stroke_width(1)
479 gc.rectangle(left, 2*header_heigth, left + width, 2*header_heigth + g_height-1) 476 gc.rectangle(left, 2*header_height, left + width, 2*header_height + g_height-1)
480 left = left + width 477 left = left + width
481 wday = wday + 1 478 wday = wday + 1
482 wday = 1 if wday > 7 479 wday = 1 if wday > 7
483 end 480 end
484 end 481 end
485 482
486 # border 483 # border
487 gc.fill('transparent') 484 gc.fill('transparent')
488 gc.stroke('grey') 485 gc.stroke('grey')
489 gc.stroke_width(1) 486 gc.stroke_width(1)
490 gc.rectangle(0, 0, subject_width+g_width, headers_heigth) 487 gc.rectangle(0, 0, subject_width+g_width, headers_height)
491 gc.stroke('black') 488 gc.stroke('black')
492 gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_heigth-1) 489 gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_height-1)
493 490
494 # content 491 # content
495 top = headers_heigth + 20 492 top = headers_height + 20
496 493
497 gc.stroke('transparent') 494 gc.stroke('transparent')
498 lines(:image => gc, :top => top, :zoom => zoom, :subject_width => subject_width, :format => :image) 495 lines(:image => gc, :top => top, :zoom => zoom, :subject_width => subject_width, :format => :image)
499 496
500 # today red line 497 # today red line
501 if Date.today >= @date_from and Date.today <= date_to 498 if Date.today >= @date_from and Date.today <= date_to
502 gc.stroke('red') 499 gc.stroke('red')
503 x = (Date.today-@date_from+1)*zoom + subject_width 500 x = (Date.today-@date_from+1)*zoom + subject_width
504 gc.line(x, headers_heigth, x, headers_heigth + g_height-1) 501 gc.line(x, headers_height, x, headers_height + g_height-1)
505 end 502 end
506 503
507 gc.draw(imgl) 504 gc.draw(imgl)
508 imgl.format = format 505 imgl.format = format
509 imgl.to_blob 506 imgl.to_blob
510 end if Object.const_defined?(:Magick) 507 end if Object.const_defined?(:Magick)
511 508
512 def to_pdf 509 def to_pdf
513 pdf = ::Redmine::Export::PDF::IFPDF.new(current_language) 510 pdf = ::Redmine::Export::PDF::ITCPDF.new(current_language)
514 pdf.SetTitle("#{l(:label_gantt)} #{project}") 511 pdf.SetTitle("#{l(:label_gantt)} #{project}")
515 pdf.AliasNbPages 512 pdf.alias_nb_pages
516 pdf.footer_date = format_date(Date.today) 513 pdf.footer_date = format_date(Date.today)
517 pdf.AddPage("L") 514 pdf.AddPage("L")
518 pdf.SetFontStyle('B',12) 515 pdf.SetFontStyle('B',12)
519 pdf.SetX(15) 516 pdf.SetX(15)
520 pdf.Cell(PDF::LeftPaneWidth, 20, project.to_s) 517 pdf.RDMCell(PDF::LeftPaneWidth, 20, project.to_s)
521 pdf.Ln 518 pdf.Ln
522 pdf.SetFontStyle('B',9) 519 pdf.SetFontStyle('B',9)
523 520
524 subject_width = PDF::LeftPaneWidth 521 subject_width = PDF::LeftPaneWidth
525 header_heigth = 5 522 header_height = 5
526 523
527 headers_heigth = header_heigth 524 headers_height = header_height
528 show_weeks = false 525 show_weeks = false
529 show_days = false 526 show_days = false
530 527
531 if self.months < 7 528 if self.months < 7
532 show_weeks = true 529 show_weeks = true
533 headers_heigth = 2*header_heigth 530 headers_height = 2*header_height
534 if self.months < 3 531 if self.months < 3
535 show_days = true 532 show_days = true
536 headers_heigth = 3*header_heigth 533 headers_height = 3*header_height
537 end 534 end
538 end 535 end
539 536
540 g_width = PDF.right_pane_width 537 g_width = PDF.right_pane_width
541 zoom = (g_width) / (self.date_to - self.date_from + 1) 538 zoom = (g_width) / (self.date_to - self.date_from + 1)
542 g_height = 120 539 g_height = 120
543 t_height = g_height + headers_heigth 540 t_height = g_height + headers_height
544 541
545 y_start = pdf.GetY 542 y_start = pdf.GetY
546 543
547 # Months headers 544 # Months headers
548 month_f = self.date_from 545 month_f = self.date_from
549 left = subject_width 546 left = subject_width
550 height = header_heigth 547 height = header_height
551 self.months.times do 548 self.months.times do
552 width = ((month_f >> 1) - month_f) * zoom 549 width = ((month_f >> 1) - month_f) * zoom
553 pdf.SetY(y_start) 550 pdf.SetY(y_start)
554 pdf.SetX(left) 551 pdf.SetX(left)
555 pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C") 552 pdf.RDMCell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
556 left = left + width 553 left = left + width
557 month_f = month_f >> 1 554 month_f = month_f >> 1
558 end 555 end
559 556
560 # Weeks headers 557 # Weeks headers
561 if show_weeks 558 if show_weeks
562 left = subject_width 559 left = subject_width
563 height = header_heigth 560 height = header_height
564 if self.date_from.cwday == 1 561 if self.date_from.cwday == 1
565 # self.date_from is monday 562 # self.date_from is monday
566 week_f = self.date_from 563 week_f = self.date_from
567 else 564 else
568 # find next monday after self.date_from 565 # find next monday after self.date_from
569 week_f = self.date_from + (7 - self.date_from.cwday + 1) 566 week_f = self.date_from + (7 - self.date_from.cwday + 1)
570 width = (7 - self.date_from.cwday + 1) * zoom-1 567 width = (7 - self.date_from.cwday + 1) * zoom-1
571 pdf.SetY(y_start + header_heigth) 568 pdf.SetY(y_start + header_height)
572 pdf.SetX(left) 569 pdf.SetX(left)
573 pdf.Cell(width + 1, height, "", "LTR") 570 pdf.RDMCell(width + 1, height, "", "LTR")
574 left = left + width+1 571 left = left + width+1
575 end 572 end
576 while week_f <= self.date_to 573 while week_f <= self.date_to
577 width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom 574 width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom
578 pdf.SetY(y_start + header_heigth) 575 pdf.SetY(y_start + header_height)
579 pdf.SetX(left) 576 pdf.SetX(left)
580 pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C") 577 pdf.RDMCell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
581 left = left + width 578 left = left + width
582 week_f = week_f+7 579 week_f = week_f+7
583 end 580 end
584 end 581 end
585 582
586 # Days headers 583 # Days headers
587 if show_days 584 if show_days
588 left = subject_width 585 left = subject_width
589 height = header_heigth 586 height = header_height
590 wday = self.date_from.cwday 587 wday = self.date_from.cwday
591 pdf.SetFontStyle('B',7) 588 pdf.SetFontStyle('B',7)
592 (self.date_to - self.date_from + 1).to_i.times do 589 (self.date_to - self.date_from + 1).to_i.times do
593 width = zoom 590 width = zoom
594 pdf.SetY(y_start + 2 * header_heigth) 591 pdf.SetY(y_start + 2 * header_height)
595 pdf.SetX(left) 592 pdf.SetX(left)
596 pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C") 593 pdf.RDMCell(width, height, day_name(wday).first, "LTR", 0, "C")
597 left = left + width 594 left = left + width
598 wday = wday + 1 595 wday = wday + 1
599 wday = 1 if wday > 7 596 wday = 1 if wday > 7
600 end 597 end
601 end 598 end
602 599
603 pdf.SetY(y_start) 600 pdf.SetY(y_start)
604 pdf.SetX(15) 601 pdf.SetX(15)
605 pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1) 602 pdf.RDMCell(subject_width+g_width-15, headers_height, "", 1)
606 603
607 # Tasks 604 # Tasks
608 top = headers_heigth + y_start 605 top = headers_height + y_start
609 options = { 606 options = {
610 :top => top, 607 :top => top,
611 :zoom => zoom, 608 :zoom => zoom,
612 :subject_width => subject_width, 609 :subject_width => subject_width,
613 :g_width => g_width, 610 :g_width => g_width,
618 :pdf => pdf 615 :pdf => pdf
619 } 616 }
620 render(options) 617 render(options)
621 pdf.Output 618 pdf.Output
622 end 619 end
623 620
624 private 621 private
625 622
626 def coordinates(start_date, end_date, progress, zoom=nil) 623 def coordinates(start_date, end_date, progress, zoom=nil)
627 zoom ||= @zoom 624 zoom ||= @zoom
628 625
629 coords = {} 626 coords = {}
630 if start_date && end_date && start_date < self.date_to && end_date > self.date_from 627 if start_date && end_date && start_date < self.date_to && end_date > self.date_from
631 if start_date > self.date_from 628 if start_date > self.date_from
632 coords[:start] = start_date - self.date_from 629 coords[:start] = start_date - self.date_from
633 coords[:bar_start] = start_date - self.date_from 630 coords[:bar_start] = start_date - self.date_from
638 coords[:end] = end_date - self.date_from 635 coords[:end] = end_date - self.date_from
639 coords[:bar_end] = end_date - self.date_from + 1 636 coords[:bar_end] = end_date - self.date_from + 1
640 else 637 else
641 coords[:bar_end] = self.date_to - self.date_from + 1 638 coords[:bar_end] = self.date_to - self.date_from + 1
642 end 639 end
643 640
644 if progress 641 if progress
645 progress_date = start_date + (end_date - start_date) * (progress / 100.0) 642 progress_date = start_date + (end_date - start_date + 1) * (progress / 100.0)
646 if progress_date > self.date_from && progress_date > start_date 643 if progress_date > self.date_from && progress_date > start_date
647 if progress_date < self.date_to 644 if progress_date < self.date_to
648 coords[:bar_progress_end] = progress_date - self.date_from + 1 645 coords[:bar_progress_end] = progress_date - self.date_from
649 else 646 else
650 coords[:bar_progress_end] = self.date_to - self.date_from + 1 647 coords[:bar_progress_end] = self.date_to - self.date_from + 1
651 end 648 end
652 end 649 end
653 650
654 if progress_date < Date.today 651 if progress_date < Date.today
655 late_date = [Date.today, end_date].min 652 late_date = [Date.today, end_date].min
656 if late_date > self.date_from && late_date > start_date 653 if late_date > self.date_from && late_date > start_date
657 if late_date < self.date_to 654 if late_date < self.date_to
658 coords[:bar_late_end] = late_date - self.date_from + 1 655 coords[:bar_late_end] = late_date - self.date_from + 1
661 end 658 end
662 end 659 end
663 end 660 end
664 end 661 end
665 end 662 end
666 663
667 # Transforms dates into pixels witdh 664 # Transforms dates into pixels witdh
668 coords.keys.each do |key| 665 coords.keys.each do |key|
669 coords[key] = (coords[key] * zoom).floor 666 coords[key] = (coords[key] * zoom).floor
670 end 667 end
671 coords 668 coords
673 670
674 # Sorts a collection of issues by start_date, due_date, id for gantt rendering 671 # Sorts a collection of issues by start_date, due_date, id for gantt rendering
675 def sort_issues!(issues) 672 def sort_issues!(issues)
676 issues.sort! { |a, b| gantt_issue_compare(a, b, issues) } 673 issues.sort! { |a, b| gantt_issue_compare(a, b, issues) }
677 end 674 end
678 675
679 # TODO: top level issues should be sorted by start date 676 # TODO: top level issues should be sorted by start date
680 def gantt_issue_compare(x, y, issues) 677 def gantt_issue_compare(x, y, issues)
681 if x.root_id == y.root_id 678 if x.root_id == y.root_id
682 x.lft <=> y.lft 679 x.lft <=> y.lft
683 else 680 else
684 x.root_id <=> y.root_id 681 x.root_id <=> y.root_id
685 end 682 end
686 end 683 end
687 684
688 def current_limit 685 def current_limit
689 if @max_rows 686 if @max_rows
690 @max_rows - @number_of_rows 687 @max_rows - @number_of_rows
691 else 688 else
692 nil 689 nil
693 end 690 end
694 end 691 end
695 692
696 def abort? 693 def abort?
697 if @max_rows && @number_of_rows >= @max_rows 694 if @max_rows && @number_of_rows >= @max_rows
698 @truncated = true 695 @truncated = true
699 end 696 end
700 end 697 end
701 698
702 def pdf_new_page?(options) 699 def pdf_new_page?(options)
703 if options[:top] > 180 700 if options[:top] > 180
704 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top]) 701 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
705 options[:pdf].AddPage("L") 702 options[:pdf].AddPage("L")
706 options[:top] = 15 703 options[:top] = 15
707 options[:pdf].Line(15, options[:top] - 0.1, PDF::TotalWidth, options[:top] - 0.1) 704 options[:pdf].Line(15, options[:top] - 0.1, PDF::TotalWidth, options[:top] - 0.1)
708 end 705 end
709 end 706 end
710 707
711 def html_subject(params, subject, options={}) 708 def html_subject(params, subject, options={})
712 style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;" 709 style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;"
713 style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width] 710 style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width]
714 711
715 output = view.content_tag 'div', subject, :class => options[:css], :style => style, :title => options[:title] 712 output = view.content_tag 'div', subject, :class => options[:css], :style => style, :title => options[:title]
716 @subjects << output 713 @subjects << output
717 output 714 output
718 end 715 end
719 716
720 def pdf_subject(params, subject, options={}) 717 def pdf_subject(params, subject, options={})
721 params[:pdf].SetY(params[:top]) 718 params[:pdf].SetY(params[:top])
722 params[:pdf].SetX(15) 719 params[:pdf].SetX(15)
723 720
724 char_limit = PDF::MaxCharactorsForSubject - params[:indent] 721 char_limit = PDF::MaxCharactorsForSubject - params[:indent]
725 params[:pdf].Cell(params[:subject_width]-15, 5, (" " * params[:indent]) + subject.to_s.sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR") 722 params[:pdf].RDMCell(params[:subject_width]-15, 5, (" " * params[:indent]) + subject.to_s.sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR")
726 723
727 params[:pdf].SetY(params[:top]) 724 params[:pdf].SetY(params[:top])
728 params[:pdf].SetX(params[:subject_width]) 725 params[:pdf].SetX(params[:subject_width])
729 params[:pdf].Cell(params[:g_width], 5, "", "LR") 726 params[:pdf].RDMCell(params[:g_width], 5, "", "LR")
730 end 727 end
731 728
732 def image_subject(params, subject, options={}) 729 def image_subject(params, subject, options={})
733 params[:image].fill('black') 730 params[:image].fill('black')
734 params[:image].stroke('transparent') 731 params[:image].stroke('transparent')
735 params[:image].stroke_width(1) 732 params[:image].stroke_width(1)
736 params[:image].text(params[:indent], params[:top] + 2, subject) 733 params[:image].text(params[:indent], params[:top] + 2, subject)
737 end 734 end
738 735
739 def html_task(params, coords, options={}) 736 def html_task(params, coords, options={})
740 output = '' 737 output = ''
741 # Renders the task bar, with progress and late 738 # Renders the task bar, with progress and late
742 if coords[:bar_start] && coords[:bar_end] 739 if coords[:bar_start] && coords[:bar_end]
743 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_todo'>&nbsp;</div>" 740 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_todo'>&nbsp;</div>"
744 741
745 if coords[:bar_late_end] 742 if coords[:bar_late_end]
746 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_late_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_late'>&nbsp;</div>" 743 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_late_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_late'>&nbsp;</div>"
747 end 744 end
748 if coords[:bar_progress_end] 745 if coords[:bar_progress_end]
749 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_progress_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_done'>&nbsp;</div>" 746 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_progress_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_done'>&nbsp;</div>"
772 output << "</span></div>" 769 output << "</span></div>"
773 end 770 end
774 @lines << output 771 @lines << output
775 output 772 output
776 end 773 end
777 774
778 def pdf_task(params, coords, options={}) 775 def pdf_task(params, coords, options={})
779 height = options[:height] || 2 776 height = options[:height] || 2
780 777
781 # Renders the task bar, with progress and late 778 # Renders the task bar, with progress and late
782 if coords[:bar_start] && coords[:bar_end] 779 if coords[:bar_start] && coords[:bar_end]
783 params[:pdf].SetY(params[:top]+1.5) 780 params[:pdf].SetY(params[:top]+1.5)
784 params[:pdf].SetX(params[:subject_width] + coords[:bar_start]) 781 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
785 params[:pdf].SetFillColor(200,200,200) 782 params[:pdf].SetFillColor(200,200,200)
786 params[:pdf].Cell(coords[:bar_end] - coords[:bar_start], height, "", 0, 0, "", 1) 783 params[:pdf].RDMCell(coords[:bar_end] - coords[:bar_start], height, "", 0, 0, "", 1)
787 784
788 if coords[:bar_late_end] 785 if coords[:bar_late_end]
789 params[:pdf].SetY(params[:top]+1.5) 786 params[:pdf].SetY(params[:top]+1.5)
790 params[:pdf].SetX(params[:subject_width] + coords[:bar_start]) 787 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
791 params[:pdf].SetFillColor(255,100,100) 788 params[:pdf].SetFillColor(255,100,100)
792 params[:pdf].Cell(coords[:bar_late_end] - coords[:bar_start], height, "", 0, 0, "", 1) 789 params[:pdf].RDMCell(coords[:bar_late_end] - coords[:bar_start], height, "", 0, 0, "", 1)
793 end 790 end
794 if coords[:bar_progress_end] 791 if coords[:bar_progress_end]
795 params[:pdf].SetY(params[:top]+1.5) 792 params[:pdf].SetY(params[:top]+1.5)
796 params[:pdf].SetX(params[:subject_width] + coords[:bar_start]) 793 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
797 params[:pdf].SetFillColor(90,200,90) 794 params[:pdf].SetFillColor(90,200,90)
798 params[:pdf].Cell(coords[:bar_progress_end] - coords[:bar_start], height, "", 0, 0, "", 1) 795 params[:pdf].RDMCell(coords[:bar_progress_end] - coords[:bar_start], height, "", 0, 0, "", 1)
799 end 796 end
800 end 797 end
801 # Renders the markers 798 # Renders the markers
802 if options[:markers] 799 if options[:markers]
803 if coords[:start] 800 if coords[:start]
804 params[:pdf].SetY(params[:top] + 1) 801 params[:pdf].SetY(params[:top] + 1)
805 params[:pdf].SetX(params[:subject_width] + coords[:start] - 1) 802 params[:pdf].SetX(params[:subject_width] + coords[:start] - 1)
806 params[:pdf].SetFillColor(50,50,200) 803 params[:pdf].SetFillColor(50,50,200)
807 params[:pdf].Cell(2, 2, "", 0, 0, "", 1) 804 params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1)
808 end 805 end
809 if coords[:end] 806 if coords[:end]
810 params[:pdf].SetY(params[:top] + 1) 807 params[:pdf].SetY(params[:top] + 1)
811 params[:pdf].SetX(params[:subject_width] + coords[:end] - 1) 808 params[:pdf].SetX(params[:subject_width] + coords[:end] - 1)
812 params[:pdf].SetFillColor(50,50,200) 809 params[:pdf].SetFillColor(50,50,200)
813 params[:pdf].Cell(2, 2, "", 0, 0, "", 1) 810 params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1)
814 end 811 end
815 end 812 end
816 # Renders the label on the right 813 # Renders the label on the right
817 if options[:label] 814 if options[:label]
818 params[:pdf].SetX(params[:subject_width] + (coords[:bar_end] || 0) + 5) 815 params[:pdf].SetX(params[:subject_width] + (coords[:bar_end] || 0) + 5)
819 params[:pdf].Cell(30, 2, options[:label]) 816 params[:pdf].RDMCell(30, 2, options[:label])
820 end 817 end
821 end 818 end
822 819
823 def image_task(params, coords, options={}) 820 def image_task(params, coords, options={})
824 height = options[:height] || 6 821 height = options[:height] || 6
825 822
826 # Renders the task bar, with progress and late 823 # Renders the task bar, with progress and late
827 if coords[:bar_start] && coords[:bar_end] 824 if coords[:bar_start] && coords[:bar_end]
828 params[:image].fill('#aaa') 825 params[:image].fill('#aaa')
829 params[:image].rectangle(params[:subject_width] + coords[:bar_start], params[:top], params[:subject_width] + coords[:bar_end], params[:top] - height) 826 params[:image].rectangle(params[:subject_width] + coords[:bar_start], params[:top], params[:subject_width] + coords[:bar_end], params[:top] - height)
830 827
831 if coords[:bar_late_end] 828 if coords[:bar_late_end]
832 params[:image].fill('#f66') 829 params[:image].fill('#f66')
833 params[:image].rectangle(params[:subject_width] + coords[:bar_start], params[:top], params[:subject_width] + coords[:bar_late_end], params[:top] - height) 830 params[:image].rectangle(params[:subject_width] + coords[:bar_start], params[:top], params[:subject_width] + coords[:bar_late_end], params[:top] - height)
834 end 831 end
835 if coords[:bar_progress_end] 832 if coords[:bar_progress_end]