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