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