##// END OF EJS Templates
gantt: use content_tag instead of html tag at the tooltip...
Toshi MARUYAMA -
r10179:097d9661c92c
parent child
Show More
@@ -1,873 +1,881
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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
19 module Helpers
19 module Helpers
20 # Simple class to handle gantt chart data
20 # Simple class to handle gantt chart data
21 class Gantt
21 class Gantt
22 include ERB::Util
22 include ERB::Util
23 include Redmine::I18n
23 include Redmine::I18n
24
24
25 # :nodoc:
25 # :nodoc:
26 # Some utility methods for the PDF export
26 # Some utility methods for the PDF export
27 class PDF
27 class PDF
28 MaxCharactorsForSubject = 45
28 MaxCharactorsForSubject = 45
29 TotalWidth = 280
29 TotalWidth = 280
30 LeftPaneWidth = 100
30 LeftPaneWidth = 100
31
31
32 def self.right_pane_width
32 def self.right_pane_width
33 TotalWidth - LeftPaneWidth
33 TotalWidth - LeftPaneWidth
34 end
34 end
35 end
35 end
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 if options[:year] && options[:year].to_i >0
44 if options[:year] && options[:year].to_i >0
45 @year_from = options[:year].to_i
45 @year_from = options[:year].to_i
46 if options[:month] && options[:month].to_i >=1 && options[:month].to_i <= 12
46 if options[:month] && options[:month].to_i >=1 && options[:month].to_i <= 12
47 @month_from = options[:month].to_i
47 @month_from = options[:month].to_i
48 else
48 else
49 @month_from = 1
49 @month_from = 1
50 end
50 end
51 else
51 else
52 @month_from ||= Date.today.month
52 @month_from ||= Date.today.month
53 @year_from ||= Date.today.year
53 @year_from ||= Date.today.year
54 end
54 end
55 zoom = (options[:zoom] || User.current.pref[:gantt_zoom]).to_i
55 zoom = (options[:zoom] || User.current.pref[:gantt_zoom]).to_i
56 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
56 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
57 months = (options[:months] || User.current.pref[:gantt_months]).to_i
57 months = (options[:months] || User.current.pref[:gantt_months]).to_i
58 @months = (months > 0 && months < 25) ? months : 6
58 @months = (months > 0 && months < 25) ? months : 6
59 # Save gantt parameters as user preference (zoom and months count)
59 # Save gantt parameters as user preference (zoom and months count)
60 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] ||
60 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] ||
61 @months != User.current.pref[:gantt_months]))
61 @months != User.current.pref[:gantt_months]))
62 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
62 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
63 User.current.preference.save
63 User.current.preference.save
64 end
64 end
65 @date_from = Date.civil(@year_from, @month_from, 1)
65 @date_from = Date.civil(@year_from, @month_from, 1)
66 @date_to = (@date_from >> @months) - 1
66 @date_to = (@date_from >> @months) - 1
67 @subjects = ''
67 @subjects = ''
68 @lines = ''
68 @lines = ''
69 @number_of_rows = nil
69 @number_of_rows = nil
70 @issue_ancestors = []
70 @issue_ancestors = []
71 @truncated = false
71 @truncated = false
72 if options.has_key?(:max_rows)
72 if options.has_key?(:max_rows)
73 @max_rows = options[:max_rows]
73 @max_rows = options[:max_rows]
74 else
74 else
75 @max_rows = Setting.gantt_items_limit.blank? ? nil : Setting.gantt_items_limit.to_i
75 @max_rows = Setting.gantt_items_limit.blank? ? nil : Setting.gantt_items_limit.to_i
76 end
76 end
77 end
77 end
78
78
79 def common_params
79 def common_params
80 { :controller => 'gantts', :action => 'show', :project_id => @project }
80 { :controller => 'gantts', :action => 'show', :project_id => @project }
81 end
81 end
82
82
83 def params
83 def params
84 common_params.merge({:zoom => zoom, :year => year_from,
84 common_params.merge({:zoom => zoom, :year => year_from,
85 :month => month_from, :months => months})
85 :month => month_from, :months => months})
86 end
86 end
87
87
88 def params_previous
88 def params_previous
89 common_params.merge({:year => (date_from << months).year,
89 common_params.merge({:year => (date_from << months).year,
90 :month => (date_from << months).month,
90 :month => (date_from << months).month,
91 :zoom => zoom, :months => months})
91 :zoom => zoom, :months => months})
92 end
92 end
93
93
94 def params_next
94 def params_next
95 common_params.merge({:year => (date_from >> months).year,
95 common_params.merge({:year => (date_from >> months).year,
96 :month => (date_from >> months).month,
96 :month => (date_from >> months).month,
97 :zoom => zoom, :months => months})
97 :zoom => zoom, :months => months})
98 end
98 end
99
99
100 # Returns the number of rows that will be rendered on the Gantt chart
100 # Returns the number of rows that will be rendered on the Gantt chart
101 def number_of_rows
101 def number_of_rows
102 return @number_of_rows if @number_of_rows
102 return @number_of_rows if @number_of_rows
103 rows = projects.inject(0) {|total, p| total += number_of_rows_on_project(p)}
103 rows = projects.inject(0) {|total, p| total += number_of_rows_on_project(p)}
104 rows > @max_rows ? @max_rows : rows
104 rows > @max_rows ? @max_rows : rows
105 end
105 end
106
106
107 # Returns the number of rows that will be used to list a project on
107 # Returns the number of rows that will be used to list a project on
108 # the Gantt chart. This will recurse for each subproject.
108 # the Gantt chart. This will recurse for each subproject.
109 def number_of_rows_on_project(project)
109 def number_of_rows_on_project(project)
110 return 0 unless projects.include?(project)
110 return 0 unless projects.include?(project)
111 count = 1
111 count = 1
112 count += project_issues(project).size
112 count += project_issues(project).size
113 count += project_versions(project).size
113 count += project_versions(project).size
114 count
114 count
115 end
115 end
116
116
117 # Renders the subjects of the Gantt chart, the left side.
117 # Renders the subjects of the Gantt chart, the left side.
118 def subjects(options={})
118 def subjects(options={})
119 render(options.merge(:only => :subjects)) unless @subjects_rendered
119 render(options.merge(:only => :subjects)) unless @subjects_rendered
120 @subjects
120 @subjects
121 end
121 end
122
122
123 # Renders the lines of the Gantt chart, the right side
123 # Renders the lines of the Gantt chart, the right side
124 def lines(options={})
124 def lines(options={})
125 render(options.merge(:only => :lines)) unless @lines_rendered
125 render(options.merge(:only => :lines)) unless @lines_rendered
126 @lines
126 @lines
127 end
127 end
128
128
129 # Returns issues that will be rendered
129 # Returns issues that will be rendered
130 def issues
130 def issues
131 @issues ||= @query.issues(
131 @issues ||= @query.issues(
132 :include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
132 :include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
133 :order => "#{Project.table_name}.lft ASC, #{Issue.table_name}.id ASC",
133 :order => "#{Project.table_name}.lft ASC, #{Issue.table_name}.id ASC",
134 :limit => @max_rows
134 :limit => @max_rows
135 )
135 )
136 end
136 end
137
137
138 # Return all the project nodes that will be displayed
138 # Return all the project nodes that will be displayed
139 def projects
139 def projects
140 return @projects if @projects
140 return @projects if @projects
141 ids = issues.collect(&:project).uniq.collect(&:id)
141 ids = issues.collect(&:project).uniq.collect(&:id)
142 if ids.any?
142 if ids.any?
143 # All issues projects and their visible ancestors
143 # All issues projects and their visible ancestors
144 @projects = Project.visible.all(
144 @projects = Project.visible.all(
145 :joins => "LEFT JOIN #{Project.table_name} child ON #{Project.table_name}.lft <= child.lft AND #{Project.table_name}.rgt >= child.rgt",
145 :joins => "LEFT JOIN #{Project.table_name} child ON #{Project.table_name}.lft <= child.lft AND #{Project.table_name}.rgt >= child.rgt",
146 :conditions => ["child.id IN (?)", ids],
146 :conditions => ["child.id IN (?)", ids],
147 :order => "#{Project.table_name}.lft ASC"
147 :order => "#{Project.table_name}.lft ASC"
148 ).uniq
148 ).uniq
149 else
149 else
150 @projects = []
150 @projects = []
151 end
151 end
152 end
152 end
153
153
154 # Returns the issues that belong to +project+
154 # Returns the issues that belong to +project+
155 def project_issues(project)
155 def project_issues(project)
156 @issues_by_project ||= issues.group_by(&:project)
156 @issues_by_project ||= issues.group_by(&:project)
157 @issues_by_project[project] || []
157 @issues_by_project[project] || []
158 end
158 end
159
159
160 # Returns the distinct versions of the issues that belong to +project+
160 # Returns the distinct versions of the issues that belong to +project+
161 def project_versions(project)
161 def project_versions(project)
162 project_issues(project).collect(&:fixed_version).compact.uniq
162 project_issues(project).collect(&:fixed_version).compact.uniq
163 end
163 end
164
164
165 # Returns the issues that belong to +project+ and are assigned to +version+
165 # Returns the issues that belong to +project+ and are assigned to +version+
166 def version_issues(project, version)
166 def version_issues(project, version)
167 project_issues(project).select {|issue| issue.fixed_version == version}
167 project_issues(project).select {|issue| issue.fixed_version == version}
168 end
168 end
169
169
170 def render(options={})
170 def render(options={})
171 options = {:top => 0, :top_increment => 20,
171 options = {:top => 0, :top_increment => 20,
172 :indent_increment => 20, :render => :subject,
172 :indent_increment => 20, :render => :subject,
173 :format => :html}.merge(options)
173 :format => :html}.merge(options)
174 indent = options[:indent] || 4
174 indent = options[:indent] || 4
175 @subjects = '' unless options[:only] == :lines
175 @subjects = '' unless options[:only] == :lines
176 @lines = '' unless options[:only] == :subjects
176 @lines = '' unless options[:only] == :subjects
177 @number_of_rows = 0
177 @number_of_rows = 0
178 Project.project_tree(projects) do |project, level|
178 Project.project_tree(projects) do |project, level|
179 options[:indent] = indent + level * options[:indent_increment]
179 options[:indent] = indent + level * options[:indent_increment]
180 render_project(project, options)
180 render_project(project, options)
181 break if abort?
181 break if abort?
182 end
182 end
183 @subjects_rendered = true unless options[:only] == :lines
183 @subjects_rendered = true unless options[:only] == :lines
184 @lines_rendered = true unless options[:only] == :subjects
184 @lines_rendered = true unless options[:only] == :subjects
185 render_end(options)
185 render_end(options)
186 end
186 end
187
187
188 def render_project(project, options={})
188 def render_project(project, options={})
189 subject_for_project(project, options) unless options[:only] == :lines
189 subject_for_project(project, options) unless options[:only] == :lines
190 line_for_project(project, options) unless options[:only] == :subjects
190 line_for_project(project, options) unless options[:only] == :subjects
191 options[:top] += options[:top_increment]
191 options[:top] += options[:top_increment]
192 options[:indent] += options[:indent_increment]
192 options[:indent] += options[:indent_increment]
193 @number_of_rows += 1
193 @number_of_rows += 1
194 return if abort?
194 return if abort?
195 issues = project_issues(project).select {|i| i.fixed_version.nil?}
195 issues = project_issues(project).select {|i| i.fixed_version.nil?}
196 sort_issues!(issues)
196 sort_issues!(issues)
197 if issues
197 if issues
198 render_issues(issues, options)
198 render_issues(issues, options)
199 return if abort?
199 return if abort?
200 end
200 end
201 versions = project_versions(project)
201 versions = project_versions(project)
202 versions.each do |version|
202 versions.each do |version|
203 render_version(project, version, options)
203 render_version(project, version, options)
204 end
204 end
205 # Remove indent to hit the next sibling
205 # Remove indent to hit the next sibling
206 options[:indent] -= options[:indent_increment]
206 options[:indent] -= options[:indent_increment]
207 end
207 end
208
208
209 def render_issues(issues, options={})
209 def render_issues(issues, options={})
210 @issue_ancestors = []
210 @issue_ancestors = []
211 issues.each do |i|
211 issues.each do |i|
212 subject_for_issue(i, options) unless options[:only] == :lines
212 subject_for_issue(i, options) unless options[:only] == :lines
213 line_for_issue(i, options) unless options[:only] == :subjects
213 line_for_issue(i, options) unless options[:only] == :subjects
214 options[:top] += options[:top_increment]
214 options[:top] += options[:top_increment]
215 @number_of_rows += 1
215 @number_of_rows += 1
216 break if abort?
216 break if abort?
217 end
217 end
218 options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
218 options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
219 end
219 end
220
220
221 def render_version(project, version, options={})
221 def render_version(project, version, options={})
222 # Version header
222 # Version header
223 subject_for_version(version, options) unless options[:only] == :lines
223 subject_for_version(version, options) unless options[:only] == :lines
224 line_for_version(version, options) unless options[:only] == :subjects
224 line_for_version(version, options) unless options[:only] == :subjects
225 options[:top] += options[:top_increment]
225 options[:top] += options[:top_increment]
226 @number_of_rows += 1
226 @number_of_rows += 1
227 return if abort?
227 return if abort?
228 issues = version_issues(project, version)
228 issues = version_issues(project, version)
229 if issues
229 if issues
230 sort_issues!(issues)
230 sort_issues!(issues)
231 # Indent issues
231 # Indent issues
232 options[:indent] += options[:indent_increment]
232 options[:indent] += options[:indent_increment]
233 render_issues(issues, options)
233 render_issues(issues, options)
234 options[:indent] -= options[:indent_increment]
234 options[:indent] -= options[:indent_increment]
235 end
235 end
236 end
236 end
237
237
238 def render_end(options={})
238 def render_end(options={})
239 case options[:format]
239 case options[:format]
240 when :pdf
240 when :pdf
241 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
241 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
242 end
242 end
243 end
243 end
244
244
245 def subject_for_project(project, options)
245 def subject_for_project(project, options)
246 case options[:format]
246 case options[:format]
247 when :html
247 when :html
248 html_class = ""
248 html_class = ""
249 html_class << 'icon icon-projects '
249 html_class << 'icon icon-projects '
250 html_class << (project.overdue? ? 'project-overdue' : '')
250 html_class << (project.overdue? ? 'project-overdue' : '')
251 s = view.link_to_project(project).html_safe
251 s = view.link_to_project(project).html_safe
252 subject = view.content_tag(:span, s,
252 subject = view.content_tag(:span, s,
253 :class => html_class).html_safe
253 :class => html_class).html_safe
254 html_subject(options, subject, :css => "project-name")
254 html_subject(options, subject, :css => "project-name")
255 when :image
255 when :image
256 image_subject(options, project.name)
256 image_subject(options, project.name)
257 when :pdf
257 when :pdf
258 pdf_new_page?(options)
258 pdf_new_page?(options)
259 pdf_subject(options, project.name)
259 pdf_subject(options, project.name)
260 end
260 end
261 end
261 end
262
262
263 def line_for_project(project, options)
263 def line_for_project(project, options)
264 # Skip versions that don't have a start_date or due date
264 # Skip versions that don't have a start_date or due date
265 if project.is_a?(Project) && project.start_date && project.due_date
265 if project.is_a?(Project) && project.start_date && project.due_date
266 options[:zoom] ||= 1
266 options[:zoom] ||= 1
267 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
267 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
268 coords = coordinates(project.start_date, project.due_date, nil, options[:zoom])
268 coords = coordinates(project.start_date, project.due_date, nil, options[:zoom])
269 label = h(project)
269 label = h(project)
270 case options[:format]
270 case options[:format]
271 when :html
271 when :html
272 html_task(options, coords, :css => "project task", :label => label, :markers => true)
272 html_task(options, coords, :css => "project task", :label => label, :markers => true)
273 when :image
273 when :image
274 image_task(options, coords, :label => label, :markers => true, :height => 3)
274 image_task(options, coords, :label => label, :markers => true, :height => 3)
275 when :pdf
275 when :pdf
276 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8)
276 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8)
277 end
277 end
278 else
278 else
279 ActiveRecord::Base.logger.debug "Gantt#line_for_project was not given a project with a start_date"
279 ActiveRecord::Base.logger.debug "Gantt#line_for_project was not given a project with a start_date"
280 ''
280 ''
281 end
281 end
282 end
282 end
283
283
284 def subject_for_version(version, options)
284 def subject_for_version(version, options)
285 case options[:format]
285 case options[:format]
286 when :html
286 when :html
287 html_class = ""
287 html_class = ""
288 html_class << 'icon icon-package '
288 html_class << 'icon icon-package '
289 html_class << (version.behind_schedule? ? 'version-behind-schedule' : '') << " "
289 html_class << (version.behind_schedule? ? 'version-behind-schedule' : '') << " "
290 html_class << (version.overdue? ? 'version-overdue' : '')
290 html_class << (version.overdue? ? 'version-overdue' : '')
291 s = view.link_to_version(version).html_safe
291 s = view.link_to_version(version).html_safe
292 subject = view.content_tag(:span, s,
292 subject = view.content_tag(:span, s,
293 :class => html_class).html_safe
293 :class => html_class).html_safe
294 html_subject(options, subject, :css => "version-name")
294 html_subject(options, subject, :css => "version-name")
295 when :image
295 when :image
296 image_subject(options, version.to_s_with_project)
296 image_subject(options, version.to_s_with_project)
297 when :pdf
297 when :pdf
298 pdf_new_page?(options)
298 pdf_new_page?(options)
299 pdf_subject(options, version.to_s_with_project)
299 pdf_subject(options, version.to_s_with_project)
300 end
300 end
301 end
301 end
302
302
303 def line_for_version(version, options)
303 def line_for_version(version, options)
304 # Skip versions that don't have a start_date
304 # Skip versions that don't have a start_date
305 if version.is_a?(Version) && version.start_date && version.due_date
305 if version.is_a?(Version) && version.start_date && version.due_date
306 options[:zoom] ||= 1
306 options[:zoom] ||= 1
307 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
307 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
308 coords = coordinates(version.start_date,
308 coords = coordinates(version.start_date,
309 version.due_date, version.completed_pourcent,
309 version.due_date, version.completed_pourcent,
310 options[:zoom])
310 options[:zoom])
311 label = "#{h version} #{h version.completed_pourcent.to_i.to_s}%"
311 label = "#{h version} #{h version.completed_pourcent.to_i.to_s}%"
312 label = h("#{version.project} -") + label unless @project && @project == version.project
312 label = h("#{version.project} -") + label unless @project && @project == version.project
313 case options[:format]
313 case options[:format]
314 when :html
314 when :html
315 html_task(options, coords, :css => "version task", :label => label, :markers => true)
315 html_task(options, coords, :css => "version task", :label => label, :markers => true)
316 when :image
316 when :image
317 image_task(options, coords, :label => label, :markers => true, :height => 3)
317 image_task(options, coords, :label => label, :markers => true, :height => 3)
318 when :pdf
318 when :pdf
319 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8)
319 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8)
320 end
320 end
321 else
321 else
322 ActiveRecord::Base.logger.debug "Gantt#line_for_version was not given a version with a start_date"
322 ActiveRecord::Base.logger.debug "Gantt#line_for_version was not given a version with a start_date"
323 ''
323 ''
324 end
324 end
325 end
325 end
326
326
327 def subject_for_issue(issue, options)
327 def subject_for_issue(issue, options)
328 while @issue_ancestors.any? && !issue.is_descendant_of?(@issue_ancestors.last)
328 while @issue_ancestors.any? && !issue.is_descendant_of?(@issue_ancestors.last)
329 @issue_ancestors.pop
329 @issue_ancestors.pop
330 options[:indent] -= options[:indent_increment]
330 options[:indent] -= options[:indent_increment]
331 end
331 end
332 output = case options[:format]
332 output = case options[:format]
333 when :html
333 when :html
334 css_classes = ''
334 css_classes = ''
335 css_classes << ' issue-overdue' if issue.overdue?
335 css_classes << ' issue-overdue' if issue.overdue?
336 css_classes << ' issue-behind-schedule' if issue.behind_schedule?
336 css_classes << ' issue-behind-schedule' if issue.behind_schedule?
337 css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
337 css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
338 s = "".html_safe
338 s = "".html_safe
339 if issue.assigned_to.present?
339 if issue.assigned_to.present?
340 assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
340 assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
341 s << view.avatar(issue.assigned_to,
341 s << view.avatar(issue.assigned_to,
342 :class => 'gravatar icon-gravatar',
342 :class => 'gravatar icon-gravatar',
343 :size => 10,
343 :size => 10,
344 :title => assigned_string).to_s.html_safe
344 :title => assigned_string).to_s.html_safe
345 end
345 end
346 s << view.link_to_issue(issue).html_safe
346 s << view.link_to_issue(issue).html_safe
347 subject = view.content_tag(:span, s, :class => css_classes).html_safe
347 subject = view.content_tag(:span, s, :class => css_classes).html_safe
348 html_subject(options, subject, :css => "issue-subject",
348 html_subject(options, subject, :css => "issue-subject",
349 :title => issue.subject) + "\n"
349 :title => issue.subject) + "\n"
350 when :image
350 when :image
351 image_subject(options, issue.subject)
351 image_subject(options, issue.subject)
352 when :pdf
352 when :pdf
353 pdf_new_page?(options)
353 pdf_new_page?(options)
354 pdf_subject(options, issue.subject)
354 pdf_subject(options, issue.subject)
355 end
355 end
356 unless issue.leaf?
356 unless issue.leaf?
357 @issue_ancestors << issue
357 @issue_ancestors << issue
358 options[:indent] += options[:indent_increment]
358 options[:indent] += options[:indent_increment]
359 end
359 end
360 output
360 output
361 end
361 end
362
362
363 def line_for_issue(issue, options)
363 def line_for_issue(issue, options)
364 # Skip issues that don't have a due_before (due_date or version's due_date)
364 # Skip issues that don't have a due_before (due_date or version's due_date)
365 if issue.is_a?(Issue) && issue.due_before
365 if issue.is_a?(Issue) && issue.due_before
366 coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom])
366 coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom])
367 label = "#{issue.status.name} #{issue.done_ratio}%"
367 label = "#{issue.status.name} #{issue.done_ratio}%"
368 case options[:format]
368 case options[:format]
369 when :html
369 when :html
370 html_task(options, coords,
370 html_task(options, coords,
371 :css => "task " + (issue.leaf? ? 'leaf' : 'parent'),
371 :css => "task " + (issue.leaf? ? 'leaf' : 'parent'),
372 :label => label, :issue => issue,
372 :label => label, :issue => issue,
373 :markers => !issue.leaf?)
373 :markers => !issue.leaf?)
374 when :image
374 when :image
375 image_task(options, coords, :label => label)
375 image_task(options, coords, :label => label)
376 when :pdf
376 when :pdf
377 pdf_task(options, coords, :label => label)
377 pdf_task(options, coords, :label => label)
378 end
378 end
379 else
379 else
380 ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
380 ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
381 ''
381 ''
382 end
382 end
383 end
383 end
384
384
385 # Generates a gantt image
385 # Generates a gantt image
386 # Only defined if RMagick is avalaible
386 # Only defined if RMagick is avalaible
387 def to_image(format='PNG')
387 def to_image(format='PNG')
388 date_to = (@date_from >> @months) - 1
388 date_to = (@date_from >> @months) - 1
389 show_weeks = @zoom > 1
389 show_weeks = @zoom > 1
390 show_days = @zoom > 2
390 show_days = @zoom > 2
391 subject_width = 400
391 subject_width = 400
392 header_height = 18
392 header_height = 18
393 # width of one day in pixels
393 # width of one day in pixels
394 zoom = @zoom * 2
394 zoom = @zoom * 2
395 g_width = (@date_to - @date_from + 1) * zoom
395 g_width = (@date_to - @date_from + 1) * zoom
396 g_height = 20 * number_of_rows + 30
396 g_height = 20 * number_of_rows + 30
397 headers_height = (show_weeks ? 2 * header_height : header_height)
397 headers_height = (show_weeks ? 2 * header_height : header_height)
398 height = g_height + headers_height
398 height = g_height + headers_height
399 imgl = Magick::ImageList.new
399 imgl = Magick::ImageList.new
400 imgl.new_image(subject_width + g_width + 1, height)
400 imgl.new_image(subject_width + g_width + 1, height)
401 gc = Magick::Draw.new
401 gc = Magick::Draw.new
402 # Subjects
402 # Subjects
403 gc.stroke('transparent')
403 gc.stroke('transparent')
404 subjects(:image => gc, :top => (headers_height + 20), :indent => 4, :format => :image)
404 subjects(:image => gc, :top => (headers_height + 20), :indent => 4, :format => :image)
405 # Months headers
405 # Months headers
406 month_f = @date_from
406 month_f = @date_from
407 left = subject_width
407 left = subject_width
408 @months.times do
408 @months.times do
409 width = ((month_f >> 1) - month_f) * zoom
409 width = ((month_f >> 1) - month_f) * zoom
410 gc.fill('white')
410 gc.fill('white')
411 gc.stroke('grey')
411 gc.stroke('grey')
412 gc.stroke_width(1)
412 gc.stroke_width(1)
413 gc.rectangle(left, 0, left + width, height)
413 gc.rectangle(left, 0, left + width, height)
414 gc.fill('black')
414 gc.fill('black')
415 gc.stroke('transparent')
415 gc.stroke('transparent')
416 gc.stroke_width(1)
416 gc.stroke_width(1)
417 gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
417 gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
418 left = left + width
418 left = left + width
419 month_f = month_f >> 1
419 month_f = month_f >> 1
420 end
420 end
421 # Weeks headers
421 # Weeks headers
422 if show_weeks
422 if show_weeks
423 left = subject_width
423 left = subject_width
424 height = header_height
424 height = header_height
425 if @date_from.cwday == 1
425 if @date_from.cwday == 1
426 # date_from is monday
426 # date_from is monday
427 week_f = date_from
427 week_f = date_from
428 else
428 else
429 # find next monday after date_from
429 # find next monday after date_from
430 week_f = @date_from + (7 - @date_from.cwday + 1)
430 week_f = @date_from + (7 - @date_from.cwday + 1)
431 width = (7 - @date_from.cwday + 1) * zoom
431 width = (7 - @date_from.cwday + 1) * zoom
432 gc.fill('white')
432 gc.fill('white')
433 gc.stroke('grey')
433 gc.stroke('grey')
434 gc.stroke_width(1)
434 gc.stroke_width(1)
435 gc.rectangle(left, header_height, left + width, 2 * header_height + g_height - 1)
435 gc.rectangle(left, header_height, left + width, 2 * header_height + g_height - 1)
436 left = left + width
436 left = left + width
437 end
437 end
438 while week_f <= date_to
438 while week_f <= date_to
439 width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
439 width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
440 gc.fill('white')
440 gc.fill('white')
441 gc.stroke('grey')
441 gc.stroke('grey')
442 gc.stroke_width(1)
442 gc.stroke_width(1)
443 gc.rectangle(left.round, header_height, left.round + width, 2 * header_height + g_height - 1)
443 gc.rectangle(left.round, header_height, left.round + width, 2 * header_height + g_height - 1)
444 gc.fill('black')
444 gc.fill('black')
445 gc.stroke('transparent')
445 gc.stroke('transparent')
446 gc.stroke_width(1)
446 gc.stroke_width(1)
447 gc.text(left.round + 2, header_height + 14, week_f.cweek.to_s)
447 gc.text(left.round + 2, header_height + 14, week_f.cweek.to_s)
448 left = left + width
448 left = left + width
449 week_f = week_f + 7
449 week_f = week_f + 7
450 end
450 end
451 end
451 end
452 # Days details (week-end in grey)
452 # Days details (week-end in grey)
453 if show_days
453 if show_days
454 left = subject_width
454 left = subject_width
455 height = g_height + header_height - 1
455 height = g_height + header_height - 1
456 wday = @date_from.cwday
456 wday = @date_from.cwday
457 (date_to - @date_from + 1).to_i.times do
457 (date_to - @date_from + 1).to_i.times do
458 width = zoom
458 width = zoom
459 gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
459 gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
460 gc.stroke('#ddd')
460 gc.stroke('#ddd')
461 gc.stroke_width(1)
461 gc.stroke_width(1)
462 gc.rectangle(left, 2 * header_height, left + width, 2 * header_height + g_height - 1)
462 gc.rectangle(left, 2 * header_height, left + width, 2 * header_height + g_height - 1)
463 left = left + width
463 left = left + width
464 wday = wday + 1
464 wday = wday + 1
465 wday = 1 if wday > 7
465 wday = 1 if wday > 7
466 end
466 end
467 end
467 end
468 # border
468 # border
469 gc.fill('transparent')
469 gc.fill('transparent')
470 gc.stroke('grey')
470 gc.stroke('grey')
471 gc.stroke_width(1)
471 gc.stroke_width(1)
472 gc.rectangle(0, 0, subject_width + g_width, headers_height)
472 gc.rectangle(0, 0, subject_width + g_width, headers_height)
473 gc.stroke('black')
473 gc.stroke('black')
474 gc.rectangle(0, 0, subject_width + g_width, g_height + headers_height - 1)
474 gc.rectangle(0, 0, subject_width + g_width, g_height + headers_height - 1)
475 # content
475 # content
476 top = headers_height + 20
476 top = headers_height + 20
477 gc.stroke('transparent')
477 gc.stroke('transparent')
478 lines(:image => gc, :top => top, :zoom => zoom,
478 lines(:image => gc, :top => top, :zoom => zoom,
479 :subject_width => subject_width, :format => :image)
479 :subject_width => subject_width, :format => :image)
480 # today red line
480 # today red line
481 if Date.today >= @date_from and Date.today <= date_to
481 if Date.today >= @date_from and Date.today <= date_to
482 gc.stroke('red')
482 gc.stroke('red')
483 x = (Date.today - @date_from + 1) * zoom + subject_width
483 x = (Date.today - @date_from + 1) * zoom + subject_width
484 gc.line(x, headers_height, x, headers_height + g_height - 1)
484 gc.line(x, headers_height, x, headers_height + g_height - 1)
485 end
485 end
486 gc.draw(imgl)
486 gc.draw(imgl)
487 imgl.format = format
487 imgl.format = format
488 imgl.to_blob
488 imgl.to_blob
489 end if Object.const_defined?(:Magick)
489 end if Object.const_defined?(:Magick)
490
490
491 def to_pdf
491 def to_pdf
492 pdf = ::Redmine::Export::PDF::ITCPDF.new(current_language)
492 pdf = ::Redmine::Export::PDF::ITCPDF.new(current_language)
493 pdf.SetTitle("#{l(:label_gantt)} #{project}")
493 pdf.SetTitle("#{l(:label_gantt)} #{project}")
494 pdf.alias_nb_pages
494 pdf.alias_nb_pages
495 pdf.footer_date = format_date(Date.today)
495 pdf.footer_date = format_date(Date.today)
496 pdf.AddPage("L")
496 pdf.AddPage("L")
497 pdf.SetFontStyle('B', 12)
497 pdf.SetFontStyle('B', 12)
498 pdf.SetX(15)
498 pdf.SetX(15)
499 pdf.RDMCell(PDF::LeftPaneWidth, 20, project.to_s)
499 pdf.RDMCell(PDF::LeftPaneWidth, 20, project.to_s)
500 pdf.Ln
500 pdf.Ln
501 pdf.SetFontStyle('B', 9)
501 pdf.SetFontStyle('B', 9)
502 subject_width = PDF::LeftPaneWidth
502 subject_width = PDF::LeftPaneWidth
503 header_height = 5
503 header_height = 5
504 headers_height = header_height
504 headers_height = header_height
505 show_weeks = false
505 show_weeks = false
506 show_days = false
506 show_days = false
507 if self.months < 7
507 if self.months < 7
508 show_weeks = true
508 show_weeks = true
509 headers_height = 2 * header_height
509 headers_height = 2 * header_height
510 if self.months < 3
510 if self.months < 3
511 show_days = true
511 show_days = true
512 headers_height = 3 * header_height
512 headers_height = 3 * header_height
513 end
513 end
514 end
514 end
515 g_width = PDF.right_pane_width
515 g_width = PDF.right_pane_width
516 zoom = (g_width) / (self.date_to - self.date_from + 1)
516 zoom = (g_width) / (self.date_to - self.date_from + 1)
517 g_height = 120
517 g_height = 120
518 t_height = g_height + headers_height
518 t_height = g_height + headers_height
519 y_start = pdf.GetY
519 y_start = pdf.GetY
520 # Months headers
520 # Months headers
521 month_f = self.date_from
521 month_f = self.date_from
522 left = subject_width
522 left = subject_width
523 height = header_height
523 height = header_height
524 self.months.times do
524 self.months.times do
525 width = ((month_f >> 1) - month_f) * zoom
525 width = ((month_f >> 1) - month_f) * zoom
526 pdf.SetY(y_start)
526 pdf.SetY(y_start)
527 pdf.SetX(left)
527 pdf.SetX(left)
528 pdf.RDMCell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
528 pdf.RDMCell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
529 left = left + width
529 left = left + width
530 month_f = month_f >> 1
530 month_f = month_f >> 1
531 end
531 end
532 # Weeks headers
532 # Weeks headers
533 if show_weeks
533 if show_weeks
534 left = subject_width
534 left = subject_width
535 height = header_height
535 height = header_height
536 if self.date_from.cwday == 1
536 if self.date_from.cwday == 1
537 # self.date_from is monday
537 # self.date_from is monday
538 week_f = self.date_from
538 week_f = self.date_from
539 else
539 else
540 # find next monday after self.date_from
540 # find next monday after self.date_from
541 week_f = self.date_from + (7 - self.date_from.cwday + 1)
541 week_f = self.date_from + (7 - self.date_from.cwday + 1)
542 width = (7 - self.date_from.cwday + 1) * zoom-1
542 width = (7 - self.date_from.cwday + 1) * zoom-1
543 pdf.SetY(y_start + header_height)
543 pdf.SetY(y_start + header_height)
544 pdf.SetX(left)
544 pdf.SetX(left)
545 pdf.RDMCell(width + 1, height, "", "LTR")
545 pdf.RDMCell(width + 1, height, "", "LTR")
546 left = left + width + 1
546 left = left + width + 1
547 end
547 end
548 while week_f <= self.date_to
548 while week_f <= self.date_to
549 width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom
549 width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom
550 pdf.SetY(y_start + header_height)
550 pdf.SetY(y_start + header_height)
551 pdf.SetX(left)
551 pdf.SetX(left)
552 pdf.RDMCell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
552 pdf.RDMCell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
553 left = left + width
553 left = left + width
554 week_f = week_f + 7
554 week_f = week_f + 7
555 end
555 end
556 end
556 end
557 # Days headers
557 # Days headers
558 if show_days
558 if show_days
559 left = subject_width
559 left = subject_width
560 height = header_height
560 height = header_height
561 wday = self.date_from.cwday
561 wday = self.date_from.cwday
562 pdf.SetFontStyle('B', 7)
562 pdf.SetFontStyle('B', 7)
563 (self.date_to - self.date_from + 1).to_i.times do
563 (self.date_to - self.date_from + 1).to_i.times do
564 width = zoom
564 width = zoom
565 pdf.SetY(y_start + 2 * header_height)
565 pdf.SetY(y_start + 2 * header_height)
566 pdf.SetX(left)
566 pdf.SetX(left)
567 pdf.RDMCell(width, height, day_name(wday).first, "LTR", 0, "C")
567 pdf.RDMCell(width, height, day_name(wday).first, "LTR", 0, "C")
568 left = left + width
568 left = left + width
569 wday = wday + 1
569 wday = wday + 1
570 wday = 1 if wday > 7
570 wday = 1 if wday > 7
571 end
571 end
572 end
572 end
573 pdf.SetY(y_start)
573 pdf.SetY(y_start)
574 pdf.SetX(15)
574 pdf.SetX(15)
575 pdf.RDMCell(subject_width + g_width - 15, headers_height, "", 1)
575 pdf.RDMCell(subject_width + g_width - 15, headers_height, "", 1)
576 # Tasks
576 # Tasks
577 top = headers_height + y_start
577 top = headers_height + y_start
578 options = {
578 options = {
579 :top => top,
579 :top => top,
580 :zoom => zoom,
580 :zoom => zoom,
581 :subject_width => subject_width,
581 :subject_width => subject_width,
582 :g_width => g_width,
582 :g_width => g_width,
583 :indent => 0,
583 :indent => 0,
584 :indent_increment => 5,
584 :indent_increment => 5,
585 :top_increment => 5,
585 :top_increment => 5,
586 :format => :pdf,
586 :format => :pdf,
587 :pdf => pdf
587 :pdf => pdf
588 }
588 }
589 render(options)
589 render(options)
590 pdf.Output
590 pdf.Output
591 end
591 end
592
592
593 private
593 private
594
594
595 def coordinates(start_date, end_date, progress, zoom=nil)
595 def coordinates(start_date, end_date, progress, zoom=nil)
596 zoom ||= @zoom
596 zoom ||= @zoom
597 coords = {}
597 coords = {}
598 if start_date && end_date && start_date < self.date_to && end_date > self.date_from
598 if start_date && end_date && start_date < self.date_to && end_date > self.date_from
599 if start_date > self.date_from
599 if start_date > self.date_from
600 coords[:start] = start_date - self.date_from
600 coords[:start] = start_date - self.date_from
601 coords[:bar_start] = start_date - self.date_from
601 coords[:bar_start] = start_date - self.date_from
602 else
602 else
603 coords[:bar_start] = 0
603 coords[:bar_start] = 0
604 end
604 end
605 if end_date < self.date_to
605 if end_date < self.date_to
606 coords[:end] = end_date - self.date_from
606 coords[:end] = end_date - self.date_from
607 coords[:bar_end] = end_date - self.date_from + 1
607 coords[:bar_end] = end_date - self.date_from + 1
608 else
608 else
609 coords[:bar_end] = self.date_to - self.date_from + 1
609 coords[:bar_end] = self.date_to - self.date_from + 1
610 end
610 end
611 if progress
611 if progress
612 progress_date = start_date + (end_date - start_date + 1) * (progress / 100.0)
612 progress_date = start_date + (end_date - start_date + 1) * (progress / 100.0)
613 if progress_date > self.date_from && progress_date > start_date
613 if progress_date > self.date_from && progress_date > start_date
614 if progress_date < self.date_to
614 if progress_date < self.date_to
615 coords[:bar_progress_end] = progress_date - self.date_from
615 coords[:bar_progress_end] = progress_date - self.date_from
616 else
616 else
617 coords[:bar_progress_end] = self.date_to - self.date_from + 1
617 coords[:bar_progress_end] = self.date_to - self.date_from + 1
618 end
618 end
619 end
619 end
620 if progress_date < Date.today
620 if progress_date < Date.today
621 late_date = [Date.today, end_date].min
621 late_date = [Date.today, end_date].min
622 if late_date > self.date_from && late_date > start_date
622 if late_date > self.date_from && late_date > start_date
623 if late_date < self.date_to
623 if late_date < self.date_to
624 coords[:bar_late_end] = late_date - self.date_from + 1
624 coords[:bar_late_end] = late_date - self.date_from + 1
625 else
625 else
626 coords[:bar_late_end] = self.date_to - self.date_from + 1
626 coords[:bar_late_end] = self.date_to - self.date_from + 1
627 end
627 end
628 end
628 end
629 end
629 end
630 end
630 end
631 end
631 end
632 # Transforms dates into pixels witdh
632 # Transforms dates into pixels witdh
633 coords.keys.each do |key|
633 coords.keys.each do |key|
634 coords[key] = (coords[key] * zoom).floor
634 coords[key] = (coords[key] * zoom).floor
635 end
635 end
636 coords
636 coords
637 end
637 end
638
638
639 # Sorts a collection of issues by start_date, due_date, id for gantt rendering
639 # Sorts a collection of issues by start_date, due_date, id for gantt rendering
640 def sort_issues!(issues)
640 def sort_issues!(issues)
641 issues.sort! { |a, b| gantt_issue_compare(a, b, issues) }
641 issues.sort! { |a, b| gantt_issue_compare(a, b, issues) }
642 end
642 end
643
643
644 # TODO: top level issues should be sorted by start date
644 # TODO: top level issues should be sorted by start date
645 def gantt_issue_compare(x, y, issues)
645 def gantt_issue_compare(x, y, issues)
646 if x.root_id == y.root_id
646 if x.root_id == y.root_id
647 x.lft <=> y.lft
647 x.lft <=> y.lft
648 else
648 else
649 x.root_id <=> y.root_id
649 x.root_id <=> y.root_id
650 end
650 end
651 end
651 end
652
652
653 def current_limit
653 def current_limit
654 if @max_rows
654 if @max_rows
655 @max_rows - @number_of_rows
655 @max_rows - @number_of_rows
656 else
656 else
657 nil
657 nil
658 end
658 end
659 end
659 end
660
660
661 def abort?
661 def abort?
662 if @max_rows && @number_of_rows >= @max_rows
662 if @max_rows && @number_of_rows >= @max_rows
663 @truncated = true
663 @truncated = true
664 end
664 end
665 end
665 end
666
666
667 def pdf_new_page?(options)
667 def pdf_new_page?(options)
668 if options[:top] > 180
668 if options[:top] > 180
669 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
669 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
670 options[:pdf].AddPage("L")
670 options[:pdf].AddPage("L")
671 options[:top] = 15
671 options[:top] = 15
672 options[:pdf].Line(15, options[:top] - 0.1, PDF::TotalWidth, options[:top] - 0.1)
672 options[:pdf].Line(15, options[:top] - 0.1, PDF::TotalWidth, options[:top] - 0.1)
673 end
673 end
674 end
674 end
675
675
676 def html_subject(params, subject, options={})
676 def html_subject(params, subject, options={})
677 style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;"
677 style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;"
678 style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width]
678 style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width]
679 output = view.content_tag('div', subject,
679 output = view.content_tag('div', subject,
680 :class => options[:css], :style => style,
680 :class => options[:css], :style => style,
681 :title => options[:title])
681 :title => options[:title])
682 @subjects << output
682 @subjects << output
683 output
683 output
684 end
684 end
685
685
686 def pdf_subject(params, subject, options={})
686 def pdf_subject(params, subject, options={})
687 params[:pdf].SetY(params[:top])
687 params[:pdf].SetY(params[:top])
688 params[:pdf].SetX(15)
688 params[:pdf].SetX(15)
689 char_limit = PDF::MaxCharactorsForSubject - params[:indent]
689 char_limit = PDF::MaxCharactorsForSubject - params[:indent]
690 params[:pdf].RDMCell(params[:subject_width] - 15, 5,
690 params[:pdf].RDMCell(params[:subject_width] - 15, 5,
691 (" " * params[:indent]) +
691 (" " * params[:indent]) +
692 subject.to_s.sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'),
692 subject.to_s.sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'),
693 "LR")
693 "LR")
694 params[:pdf].SetY(params[:top])
694 params[:pdf].SetY(params[:top])
695 params[:pdf].SetX(params[:subject_width])
695 params[:pdf].SetX(params[:subject_width])
696 params[:pdf].RDMCell(params[:g_width], 5, "", "LR")
696 params[:pdf].RDMCell(params[:g_width], 5, "", "LR")
697 end
697 end
698
698
699 def image_subject(params, subject, options={})
699 def image_subject(params, subject, options={})
700 params[:image].fill('black')
700 params[:image].fill('black')
701 params[:image].stroke('transparent')
701 params[:image].stroke('transparent')
702 params[:image].stroke_width(1)
702 params[:image].stroke_width(1)
703 params[:image].text(params[:indent], params[:top] + 2, subject)
703 params[:image].text(params[:indent], params[:top] + 2, subject)
704 end
704 end
705
705
706 def html_task(params, coords, options={})
706 def html_task(params, coords, options={})
707 output = ''
707 output = ''
708 # Renders the task bar, with progress and late
708 # Renders the task bar, with progress and late
709 if coords[:bar_start] && coords[:bar_end]
709 if coords[:bar_start] && coords[:bar_end]
710 width = coords[:bar_end] - coords[:bar_start] - 2
710 width = coords[:bar_end] - coords[:bar_start] - 2
711 style = ""
711 style = ""
712 style << "top:#{params[:top]}px;"
712 style << "top:#{params[:top]}px;"
713 style << "left:#{coords[:bar_start]}px;"
713 style << "left:#{coords[:bar_start]}px;"
714 style << "width:#{width}px;"
714 style << "width:#{width}px;"
715 output << view.content_tag(:div, '&nbsp;'.html_safe,
715 output << view.content_tag(:div, '&nbsp;'.html_safe,
716 :style => style,
716 :style => style,
717 :class => "#{options[:css]} task_todo")
717 :class => "#{options[:css]} task_todo")
718 if coords[:bar_late_end]
718 if coords[:bar_late_end]
719 width = coords[:bar_late_end] - coords[:bar_start] - 2
719 width = coords[:bar_late_end] - coords[:bar_start] - 2
720 style = ""
720 style = ""
721 style << "top:#{params[:top]}px;"
721 style << "top:#{params[:top]}px;"
722 style << "left:#{coords[:bar_start]}px;"
722 style << "left:#{coords[:bar_start]}px;"
723 style << "width:#{width}px;"
723 style << "width:#{width}px;"
724 output << view.content_tag(:div, '&nbsp;'.html_safe,
724 output << view.content_tag(:div, '&nbsp;'.html_safe,
725 :style => style,
725 :style => style,
726 :class => "#{options[:css]} task_late")
726 :class => "#{options[:css]} task_late")
727 end
727 end
728 if coords[:bar_progress_end]
728 if coords[:bar_progress_end]
729 width = coords[:bar_progress_end] - coords[:bar_start] - 2
729 width = coords[:bar_progress_end] - coords[:bar_start] - 2
730 style = ""
730 style = ""
731 style << "top:#{params[:top]}px;"
731 style << "top:#{params[:top]}px;"
732 style << "left:#{coords[:bar_start]}px;"
732 style << "left:#{coords[:bar_start]}px;"
733 style << "width:#{width}px;"
733 style << "width:#{width}px;"
734 output << view.content_tag(:div, '&nbsp;'.html_safe,
734 output << view.content_tag(:div, '&nbsp;'.html_safe,
735 :style => style,
735 :style => style,
736 :class => "#{options[:css]} task_done")
736 :class => "#{options[:css]} task_done")
737 end
737 end
738 end
738 end
739 # Renders the markers
739 # Renders the markers
740 if options[:markers]
740 if options[:markers]
741 if coords[:start]
741 if coords[:start]
742 style = ""
742 style = ""
743 style << "top:#{params[:top]}px;"
743 style << "top:#{params[:top]}px;"
744 style << "left:#{coords[:start]}px;"
744 style << "left:#{coords[:start]}px;"
745 style << "width:15px;"
745 style << "width:15px;"
746 output << view.content_tag(:div, '&nbsp;'.html_safe,
746 output << view.content_tag(:div, '&nbsp;'.html_safe,
747 :style => style,
747 :style => style,
748 :class => "#{options[:css]} marker starting")
748 :class => "#{options[:css]} marker starting")
749 end
749 end
750 if coords[:end]
750 if coords[:end]
751 style = ""
751 style = ""
752 style << "top:#{params[:top]}px;"
752 style << "top:#{params[:top]}px;"
753 style << "left:#{coords[:end] + params[:zoom]}px;"
753 style << "left:#{coords[:end] + params[:zoom]}px;"
754 style << "width:15px;"
754 style << "width:15px;"
755 output << view.content_tag(:div, '&nbsp;'.html_safe,
755 output << view.content_tag(:div, '&nbsp;'.html_safe,
756 :style => style,
756 :style => style,
757 :class => "#{options[:css]} marker ending")
757 :class => "#{options[:css]} marker ending")
758 end
758 end
759 end
759 end
760 # Renders the label on the right
760 # Renders the label on the right
761 if options[:label]
761 if options[:label]
762 style = ""
762 style = ""
763 style << "top:#{params[:top]}px;"
763 style << "top:#{params[:top]}px;"
764 style << "left:#{(coords[:bar_end] || 0) + 8}px;"
764 style << "left:#{(coords[:bar_end] || 0) + 8}px;"
765 style << "width:15px;"
765 style << "width:15px;"
766 output << view.content_tag(:div, options[:label],
766 output << view.content_tag(:div, options[:label],
767 :style => style,
767 :style => style,
768 :class => "#{options[:css]} label")
768 :class => "#{options[:css]} label")
769 end
769 end
770 # Renders the tooltip
770 # Renders the tooltip
771 if options[:issue] && coords[:bar_start] && coords[:bar_end]
771 if options[:issue] && coords[:bar_start] && coords[:bar_end]
772 output << "<div class='tooltip' style='position: absolute;top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_end] - coords[:bar_start] }px;height:12px;'>".html_safe
772 s = view.content_tag(:span,
773 output << '<span class="tip">'.html_safe
773 view.render_issue_tooltip(options[:issue]).html_safe,
774 output << view.render_issue_tooltip(options[:issue]).html_safe
774 :class => "tip")
775 output << "</span></div>".html_safe
775 style = ""
776 style << "position: absolute;"
777 style << "top:#{params[:top]}px;"
778 style << "left:#{coords[:bar_start]}px;"
779 style << "width:#{coords[:bar_end] - coords[:bar_start]}px;"
780 style << "height:12px;"
781 output << view.content_tag(:div, s.html_safe,
782 :style => style,
783 :class => "tooltip")
776 end
784 end
777 @lines << output
785 @lines << output
778 output
786 output
779 end
787 end
780
788
781 def pdf_task(params, coords, options={})
789 def pdf_task(params, coords, options={})
782 height = options[:height] || 2
790 height = options[:height] || 2
783 # Renders the task bar, with progress and late
791 # Renders the task bar, with progress and late
784 if coords[:bar_start] && coords[:bar_end]
792 if coords[:bar_start] && coords[:bar_end]
785 params[:pdf].SetY(params[:top] + 1.5)
793 params[:pdf].SetY(params[:top] + 1.5)
786 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
794 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
787 params[:pdf].SetFillColor(200, 200, 200)
795 params[:pdf].SetFillColor(200, 200, 200)
788 params[:pdf].RDMCell(coords[:bar_end] - coords[:bar_start], height, "", 0, 0, "", 1)
796 params[:pdf].RDMCell(coords[:bar_end] - coords[:bar_start], height, "", 0, 0, "", 1)
789 if coords[:bar_late_end]
797 if coords[:bar_late_end]
790 params[:pdf].SetY(params[:top] + 1.5)
798 params[:pdf].SetY(params[:top] + 1.5)
791 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
799 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
792 params[:pdf].SetFillColor(255, 100, 100)
800 params[:pdf].SetFillColor(255, 100, 100)
793 params[:pdf].RDMCell(coords[:bar_late_end] - coords[:bar_start], height, "", 0, 0, "", 1)
801 params[:pdf].RDMCell(coords[:bar_late_end] - coords[:bar_start], height, "", 0, 0, "", 1)
794 end
802 end
795 if coords[:bar_progress_end]
803 if coords[:bar_progress_end]
796 params[:pdf].SetY(params[:top] + 1.5)
804 params[:pdf].SetY(params[:top] + 1.5)
797 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
805 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
798 params[:pdf].SetFillColor(90, 200, 90)
806 params[:pdf].SetFillColor(90, 200, 90)
799 params[:pdf].RDMCell(coords[:bar_progress_end] - coords[:bar_start], height, "", 0, 0, "", 1)
807 params[:pdf].RDMCell(coords[:bar_progress_end] - coords[:bar_start], height, "", 0, 0, "", 1)
800 end
808 end
801 end
809 end
802 # Renders the markers
810 # Renders the markers
803 if options[:markers]
811 if options[:markers]
804 if coords[:start]
812 if coords[:start]
805 params[:pdf].SetY(params[:top] + 1)
813 params[:pdf].SetY(params[:top] + 1)
806 params[:pdf].SetX(params[:subject_width] + coords[:start] - 1)
814 params[:pdf].SetX(params[:subject_width] + coords[:start] - 1)
807 params[:pdf].SetFillColor(50, 50, 200)
815 params[:pdf].SetFillColor(50, 50, 200)
808 params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1)
816 params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1)
809 end
817 end
810 if coords[:end]
818 if coords[:end]
811 params[:pdf].SetY(params[:top] + 1)
819 params[:pdf].SetY(params[:top] + 1)
812 params[:pdf].SetX(params[:subject_width] + coords[:end] - 1)
820 params[:pdf].SetX(params[:subject_width] + coords[:end] - 1)
813 params[:pdf].SetFillColor(50, 50, 200)
821 params[:pdf].SetFillColor(50, 50, 200)
814 params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1)
822 params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1)
815 end
823 end
816 end
824 end
817 # Renders the label on the right
825 # Renders the label on the right
818 if options[:label]
826 if options[:label]
819 params[:pdf].SetX(params[:subject_width] + (coords[:bar_end] || 0) + 5)
827 params[:pdf].SetX(params[:subject_width] + (coords[:bar_end] || 0) + 5)
820 params[:pdf].RDMCell(30, 2, options[:label])
828 params[:pdf].RDMCell(30, 2, options[:label])
821 end
829 end
822 end
830 end
823
831
824 def image_task(params, coords, options={})
832 def image_task(params, coords, options={})
825 height = options[:height] || 6
833 height = options[:height] || 6
826 # Renders the task bar, with progress and late
834 # Renders the task bar, with progress and late
827 if coords[:bar_start] && coords[:bar_end]
835 if coords[:bar_start] && coords[:bar_end]
828 params[:image].fill('#aaa')
836 params[:image].fill('#aaa')
829 params[:image].rectangle(params[:subject_width] + coords[:bar_start],
837 params[:image].rectangle(params[:subject_width] + coords[:bar_start],
830 params[:top],
838 params[:top],
831 params[:subject_width] + coords[:bar_end],
839 params[:subject_width] + coords[:bar_end],
832 params[:top] - height)
840 params[:top] - height)
833 if coords[:bar_late_end]
841 if coords[:bar_late_end]
834 params[:image].fill('#f66')
842 params[:image].fill('#f66')
835 params[:image].rectangle(params[:subject_width] + coords[:bar_start],
843 params[:image].rectangle(params[:subject_width] + coords[:bar_start],
836 params[:top],
844 params[:top],
837 params[:subject_width] + coords[:bar_late_end],
845 params[:subject_width] + coords[:bar_late_end],
838 params[:top] - height)
846 params[:top] - height)
839 end
847 end
840 if coords[:bar_progress_end]
848 if coords[:bar_progress_end]
841 params[:image].fill('#00c600')
849 params[:image].fill('#00c600')
842 params[:image].rectangle(params[:subject_width] + coords[:bar_start],
850 params[:image].rectangle(params[:subject_width] + coords[:bar_start],
843 params[:top],
851 params[:top],
844 params[:subject_width] + coords[:bar_progress_end],
852 params[:subject_width] + coords[:bar_progress_end],
845 params[:top] - height)
853 params[:top] - height)
846 end
854 end
847 end
855 end
848 # Renders the markers
856 # Renders the markers
849 if options[:markers]
857 if options[:markers]
850 if coords[:start]
858 if coords[:start]
851 x = params[:subject_width] + coords[:start]
859 x = params[:subject_width] + coords[:start]
852 y = params[:top] - height / 2
860 y = params[:top] - height / 2
853 params[:image].fill('blue')
861 params[:image].fill('blue')
854 params[:image].polygon(x - 4, y, x, y - 4, x + 4, y, x, y + 4)
862 params[:image].polygon(x - 4, y, x, y - 4, x + 4, y, x, y + 4)
855 end
863 end
856 if coords[:end]
864 if coords[:end]
857 x = params[:subject_width] + coords[:end] + params[:zoom]
865 x = params[:subject_width] + coords[:end] + params[:zoom]
858 y = params[:top] - height / 2
866 y = params[:top] - height / 2
859 params[:image].fill('blue')
867 params[:image].fill('blue')
860 params[:image].polygon(x - 4, y, x, y - 4, x + 4, y, x, y + 4)
868 params[:image].polygon(x - 4, y, x, y - 4, x + 4, y, x, y + 4)
861 end
869 end
862 end
870 end
863 # Renders the label on the right
871 # Renders the label on the right
864 if options[:label]
872 if options[:label]
865 params[:image].fill('black')
873 params[:image].fill('black')
866 params[:image].text(params[:subject_width] + (coords[:bar_end] || 0) + 5,
874 params[:image].text(params[:subject_width] + (coords[:bar_end] || 0) + 5,
867 params[:top] + 1,
875 params[:top] + 1,
868 options[:label])
876 options[:label])
869 end
877 end
870 end
878 end
871 end
879 end
872 end
880 end
873 end
881 end
General Comments 0
You need to be logged in to leave comments. Login now