Revision 442:753f1380d6bc lib/redmine/helpers

View differences:

lib/redmine/helpers/.svn/all-wcprops
1 1
K 25
2 2
svn:wc:ra_dav:version-url
3
V 44
4
/svn/!svn/ver/4968/trunk/lib/redmine/helpers
3
V 58
4
/svn/!svn/ver/5878/branches/1.2-stable/lib/redmine/helpers
5 5
END
6 6
gantt.rb
7 7
K 25
8 8
svn:wc:ra_dav:version-url
9
V 53
10
/svn/!svn/ver/4968/trunk/lib/redmine/helpers/gantt.rb
11
END
12
diff.rb
13
K 25
14
svn:wc:ra_dav:version-url
15
V 52
16
/svn/!svn/ver/4953/trunk/lib/redmine/helpers/diff.rb
9
V 67
10
/svn/!svn/ver/5878/branches/1.2-stable/lib/redmine/helpers/gantt.rb
17 11
END
18 12
calendar.rb
19 13
K 25
20 14
svn:wc:ra_dav:version-url
21
V 56
22
/svn/!svn/ver/3166/trunk/lib/redmine/helpers/calendar.rb
15
V 70
16
/svn/!svn/ver/5878/branches/1.2-stable/lib/redmine/helpers/calendar.rb
23 17
END
lib/redmine/helpers/.svn/entries
1 1
10
2 2

  
3 3
dir
4
4993
5
http://redmine.rubyforge.org/svn/trunk/lib/redmine/helpers
4
6000
5
http://redmine.rubyforge.org/svn/branches/1.2-stable/lib/redmine/helpers
6 6
http://redmine.rubyforge.org/svn
7 7

  
8 8

  
9 9

  
10
2011-02-28T20:23:28.847227Z
11
4968
12
jplang
10
2011-05-09T11:12:27.265905Z
11
5720
12
tmaruyama
13 13

  
14 14

  
15 15

  
......
32 32

  
33 33

  
34 34

  
35
2011-03-03T11:40:18.000000Z
36
b06b1d003a56093679e3004b619b0e04
37
2011-02-28T20:23:28.847227Z
38
4968
39
jplang
35
2011-06-06T13:20:53.000000Z
36
111af23362cad335d5d23c03cc7668d1
37
2011-05-09T11:12:27.265905Z
38
5720
39
tmaruyama
40 40
has-props
41 41

  
42 42

  
......
58 58

  
59 59

  
60 60

  
61
32380
61
31709
62 62

63 63
diff.rb
64 64
file
......
66 66

  
67 67

  
68 68

  
69
2011-03-03T11:40:18.000000Z
69
2011-06-06T13:15:00.000000Z
70 70
a3487dfca2baab10aacea141b7c0fc5f
71 71
2011-02-27T12:50:47.369941Z
72 72
4953
......
100 100

  
101 101

  
102 102

  
103
2011-03-03T11:05:14.000000Z
104
0a7d81755cf7c1519d4b4c1d84712139
105
2009-12-13T04:06:55.726600Z
106
3166
107
edavis10
103
2011-06-06T13:20:53.000000Z
104
4e48abe08914d8b6e345e9752257a603
105
2011-03-27T15:43:26.269165Z
106
5228
107
jplang
108 108
has-props
109 109

  
110 110

  
......
126 126

  
127 127

  
128 128

  
129
2733
129
2787
130 130

lib/redmine/helpers/.svn/text-base/calendar.rb.svn-base
68 68
        case Setting.start_of_week.to_i
69 69
        when 1
70 70
          @first_dow ||= (1 - 1)%7 + 1
71
        when 6
72
          @first_dow ||= (6 - 1)%7 + 1
71 73
        when 7
72 74
          @first_dow ||= (7 - 1)%7 + 1
73 75
        else
lib/redmine/helpers/.svn/text-base/gantt.rb.svn-base
1 1
# Redmine - project management software
2
# Copyright (C) 2006-2008  Jean-Philippe Lang
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.
......
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
......
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
        @zoom = (zoom > 0 && zoom < 5) ? zoom : 2    
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]
......
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
            ### Extracted from the HTML view/helpers
102 101
      # Returns the number of rows that will be rendered on the Gantt chart
103 102
      def number_of_rows
104 103
        return @number_of_rows if @number_of_rows
105
        
106
        rows = if @project
107
          number_of_rows_on_project(@project)
108
        else
109
          Project.roots.visible.has_module('issue_tracking').inject(0) do |total, project|
110
            total += number_of_rows_on_project(project)
111
          end
112
        end
113
        
104

  
105
        rows = projects.inject(0) {|total, p| total += number_of_rows_on_project(p)}
114 106
        rows > @max_rows ? @max_rows : rows
115 107
      end
116 108

  
117 109
      # Returns the number of rows that will be used to list a project on
118 110
      # the Gantt chart.  This will recurse for each subproject.
119 111
      def number_of_rows_on_project(project)
120
        # Remove the project requirement for Versions because it will
121
        # restrict issues to only be on the current project.  This
122
        # ends up missing issues which are assigned to shared versions.
123
        @query.project = nil if @query.project
112
        return 0 unless projects.include?(project)
124 113

  
125
        # One Root project
126 114
        count = 1
127
        # Issues without a Version
128
        count += project.issues.for_gantt.without_version.with_query(@query).count
129

  
130
        # Versions
131
        count += project.versions.count
132

  
133
        # Issues on the Versions
134
        project.versions.each do |version|
135
          count += version.fixed_issues.for_gantt.with_query(@query).count
136
        end
137

  
138
        # Subprojects
139
        project.children.visible.has_module('issue_tracking').each do |subproject|
140
          count += number_of_rows_on_project(subproject)
141
        end
142

  
115
        count += project_issues(project).size
116
        count += project_versions(project).size
143 117
        count
144 118
      end
145 119

  
......
154 128
        render(options.merge(:only => :lines)) unless @lines_rendered
155 129
        @lines
156 130
      end
157
      
131

  
132
      # Returns issues that will be rendered
133
      def issues
134
        @issues ||= @query.issues(
135
          :include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
136
          :order => "#{Project.table_name}.lft ASC, #{Issue.table_name}.id ASC",
137
          :limit => @max_rows
138
        )
139
      end
140

  
141
      # Return all the project nodes that will be displayed
142
      def projects
143
        return @projects if @projects
144

  
145
        ids = issues.collect(&:project).uniq.collect(&:id)
146
        if ids.any?
147
          # All issues projects and their visible ancestors
148
          @projects = Project.visible.all(
149
            :joins => "LEFT JOIN #{Project.table_name} child ON #{Project.table_name}.lft <= child.lft AND #{Project.table_name}.rgt >= child.rgt",
150
            :conditions => ["child.id IN (?)", ids],
151
            :order => "#{Project.table_name}.lft ASC"
152
          ).uniq
153
        else
154
          @projects = []
155
        end
156
      end
157

  
158
      # Returns the issues that belong to +project+
159
      def project_issues(project)
160
        @issues_by_project ||= issues.group_by(&:project)
161
        @issues_by_project[project] || []
162
      end
163

  
164
      # Returns the distinct versions of the issues that belong to +project+
165
      def project_versions(project)
166
        project_issues(project).collect(&:fixed_version).compact.uniq
167
      end
168

  
169
      # Returns the issues that belong to +project+ and are assigned to +version+
170
      def version_issues(project, version)
171
        project_issues(project).select {|issue| issue.fixed_version == version}
172
      end
173

  
158 174
      def render(options={})
159
        options = {:indent => 4, :render => :subject, :format => :html}.merge(options)
160
        
175
        options = {:top => 0, :top_increment => 20, :indent_increment => 20, :render => :subject, :format => :html}.merge(options)
176
        indent = options[:indent] || 4
177

  
161 178
        @subjects = '' unless options[:only] == :lines
162 179
        @lines = '' unless options[:only] == :subjects
163 180
        @number_of_rows = 0
164
        
165
        if @project
166
          render_project(@project, options)
167
        else
168
          Project.roots.visible.has_module('issue_tracking').each do |project|
169
            render_project(project, options)
170
            break if abort?
171
          end
181

  
182
        Project.project_tree(projects) do |project, level|
183
          options[:indent] = indent + level * options[:indent_increment]
184
          render_project(project, options)
185
          break if abort?
172 186
        end
173
        
187

  
174 188
        @subjects_rendered = true unless options[:only] == :lines
175 189
        @lines_rendered = true unless options[:only] == :subjects
176
        
190

  
177 191
        render_end(options)
178 192
      end
179 193

  
180 194
      def render_project(project, options={})
181
        options[:top] = 0 unless options.key? :top
182
        options[:indent_increment] = 20 unless options.key? :indent_increment
183
        options[:top_increment] = 20 unless options.key? :top_increment
184

  
185 195
        subject_for_project(project, options) unless options[:only] == :lines
186 196
        line_for_project(project, options) unless options[:only] == :subjects
187
        
197

  
188 198
        options[:top] += options[:top_increment]
189 199
        options[:indent] += options[:indent_increment]
190 200
        @number_of_rows += 1
191 201
        return if abort?
192
        
193
        # Second, Issues without a version
194
        issues = project.issues.for_gantt.without_version.with_query(@query).all(:limit => current_limit)
202

  
203
        issues = project_issues(project).select {|i| i.fixed_version.nil?}
195 204
        sort_issues!(issues)
196 205
        if issues
197 206
          render_issues(issues, options)
198 207
          return if abort?
199 208
        end
200 209

  
201
        # Third, Versions
202
        project.versions.sort.each do |version|
203
          render_version(version, options)
204
          return if abort?
210
        versions = project_versions(project)
211
        versions.each do |version|
212
          render_version(project, version, options)
205 213
        end
206 214

  
207
        # Fourth, subprojects
208
        project.children.visible.has_module('issue_tracking').each do |project|
209
          render_project(project, options)
210
          return if abort?
211
        end unless project.leaf?
212

  
213 215
        # Remove indent to hit the next sibling
214 216
        options[:indent] -= options[:indent_increment]
215 217
      end
216 218

  
217 219
      def render_issues(issues, options={})
218 220
        @issue_ancestors = []
219
        
221

  
220 222
        issues.each do |i|
221 223
          subject_for_issue(i, options) unless options[:only] == :lines
222 224
          line_for_issue(i, options) unless options[:only] == :subjects
223
          
225

  
224 226
          options[:top] += options[:top_increment]
225 227
          @number_of_rows += 1
226 228
          break if abort?
227 229
        end
228
        
230

  
229 231
        options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
230 232
      end
231 233

  
232
      def render_version(version, options={})
234
      def render_version(project, version, options={})
233 235
        # Version header
234 236
        subject_for_version(version, options) unless options[:only] == :lines
235 237
        line_for_version(version, options) unless options[:only] == :subjects
236
        
238

  
237 239
        options[:top] += options[:top_increment]
238 240
        @number_of_rows += 1
239 241
        return if abort?
240
        
241
        # Remove the project requirement for Versions because it will
242
        # restrict issues to only be on the current project.  This
243
        # ends up missing issues which are assigned to shared versions.
244
        @query.project = nil if @query.project
245
        
246
        issues = version.fixed_issues.for_gantt.with_query(@query).all(:limit => current_limit)
242

  
243
        issues = version_issues(project, version)
247 244
        if issues
248 245
          sort_issues!(issues)
249 246
          # Indent issues
......
252 249
          options[:indent] -= options[:indent_increment]
253 250
        end
254 251
      end
255
      
252

  
256 253
      def render_end(options={})
257 254
        case options[:format]
258
        when :pdf        
255
        when :pdf
259 256
          options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
260 257
        end
261 258
      end
......
280 277
        if project.is_a?(Project) && project.start_date && project.due_date
281 278
          options[:zoom] ||= 1
282 279
          options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
283
            
280

  
284 281
          coords = coordinates(project.start_date, project.due_date, nil, options[:zoom])
285 282
          label = h(project)
286
          
283

  
287 284
          case options[:format]
288 285
          when :html
289 286
            html_task(options, coords, :css => "project task", :label => label, :markers => true)
......
318 315
        if version.is_a?(Version) && version.start_date && version.due_date
319 316
          options[:zoom] ||= 1
320 317
          options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
321
          
318

  
322 319
          coords = coordinates(version.start_date, version.due_date, version.completed_pourcent, options[:zoom])
323 320
          label = "#{h version } #{h version.completed_pourcent.to_i.to_s}%"
324 321
          label = h("#{version.project} -") + label unless @project && @project == version.project
......
342 339
          @issue_ancestors.pop
343 340
          options[:indent] -= options[:indent_increment]
344 341
        end
345
          
342

  
346 343
        output = case options[:format]
347 344
        when :html
348 345
          css_classes = ''
349 346
          css_classes << ' issue-overdue' if issue.overdue?
350 347
          css_classes << ' issue-behind-schedule' if issue.behind_schedule?
351 348
          css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
352
          
349

  
353 350
          subject = "<span class='#{css_classes}'>"
354 351
          if issue.assigned_to.present?
355 352
            assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
......
369 366
          @issue_ancestors << issue
370 367
          options[:indent] += options[:indent_increment]
371 368
        end
372
        
369

  
373 370
        output
374 371
      end
375 372

  
......
378 375
        if issue.is_a?(Issue) && issue.due_before
379 376
          coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom])
380 377
          label = "#{ issue.status.name } #{ issue.done_ratio }%"
381
          
378

  
382 379
          case options[:format]
383 380
          when :html
384 381
            html_task(options, coords, :css => "task " + (issue.leaf? ? 'leaf' : 'parent'), :label => label, :issue => issue, :markers => !issue.leaf?)
......
396 393
      # Generates a gantt image
397 394
      # Only defined if RMagick is avalaible
398 395
      def to_image(format='PNG')
399
        date_to = (@date_from >> @months)-1    
396
        date_to = (@date_from >> @months)-1
400 397
        show_weeks = @zoom > 1
401 398
        show_days = @zoom > 2
402
        
399

  
403 400
        subject_width = 400
404
        header_heigth = 18
401
        header_height = 18
405 402
        # width of one day in pixels
406 403
        zoom = @zoom*2
407 404
        g_width = (@date_to - @date_from + 1)*zoom
408 405
        g_height = 20 * number_of_rows + 30
409
        headers_heigth = (show_weeks ? 2*header_heigth : header_heigth)
410
        height = g_height + headers_heigth
411
            
406
        headers_height = (show_weeks ? 2*header_height : header_height)
407
        height = g_height + headers_height
408

  
412 409
        imgl = Magick::ImageList.new
413 410
        imgl.new_image(subject_width+g_width+1, height)
414 411
        gc = Magick::Draw.new
415
        
412

  
416 413
        # Subjects
417 414
        gc.stroke('transparent')
418
        subjects(:image => gc, :top => (headers_heigth + 20), :indent => 4, :format => :image)
419
    
415
        subjects(:image => gc, :top => (headers_height + 20), :indent => 4, :format => :image)
416

  
420 417
        # Months headers
421 418
        month_f = @date_from
422 419
        left = subject_width
423
        @months.times do 
420
        @months.times do
424 421
          width = ((month_f >> 1) - month_f) * zoom
425 422
          gc.fill('white')
426 423
          gc.stroke('grey')
......
433 430
          left = left + width
434 431
          month_f = month_f >> 1
435 432
        end
436
        
433

  
437 434
        # Weeks headers
438 435
        if show_weeks
439 436
        	left = subject_width
440
        	height = header_heigth
437
        	height = header_height
441 438
        	if @date_from.cwday == 1
442 439
        	    # date_from is monday
443 440
                week_f = date_from
......
448 445
                gc.fill('white')
449 446
                gc.stroke('grey')
450 447
                gc.stroke_width(1)
451
                gc.rectangle(left, header_heigth, left + width, 2*header_heigth + g_height-1)
448
                gc.rectangle(left, header_height, left + width, 2*header_height + g_height-1)
452 449
        		left = left + width
453 450
        	end
454 451
        	while week_f <= date_to
......
456 453
                gc.fill('white')
457 454
                gc.stroke('grey')
458 455
                gc.stroke_width(1)
459
                gc.rectangle(left.round, header_heigth, left.round + width, 2*header_heigth + g_height-1)
456
                gc.rectangle(left.round, header_height, left.round + width, 2*header_height + g_height-1)
460 457
                gc.fill('black')
461 458
                gc.stroke('transparent')
462 459
                gc.stroke_width(1)
463
                gc.text(left.round + 2, header_heigth + 14, week_f.cweek.to_s)
460
                gc.text(left.round + 2, header_height + 14, week_f.cweek.to_s)
464 461
        		left = left + width
465 462
        		week_f = week_f+7
466 463
        	end
467 464
        end
468
        
465

  
469 466
        # Days details (week-end in grey)
470 467
        if show_days
471 468
        	left = subject_width
472
        	height = g_height + header_heigth - 1
469
        	height = g_height + header_height - 1
473 470
        	wday = @date_from.cwday
474
        	(date_to - @date_from + 1).to_i.times do 
471
        	(date_to - @date_from + 1).to_i.times do
475 472
              width =  zoom
476 473
              gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
477 474
              gc.stroke('#ddd')
478 475
              gc.stroke_width(1)
479
              gc.rectangle(left, 2*header_heigth, left + width, 2*header_heigth + g_height-1)
476
              gc.rectangle(left, 2*header_height, left + width, 2*header_height + g_height-1)
480 477
              left = left + width
481 478
              wday = wday + 1
482 479
              wday = 1 if wday > 7
483 480
        	end
484 481
        end
485
    
482

  
486 483
        # border
487 484
        gc.fill('transparent')
488 485
        gc.stroke('grey')
489 486
        gc.stroke_width(1)
490
        gc.rectangle(0, 0, subject_width+g_width, headers_heigth)
487
        gc.rectangle(0, 0, subject_width+g_width, headers_height)
491 488
        gc.stroke('black')
492
        gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_heigth-1)
493
            
489
        gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_height-1)
490

  
494 491
        # content
495
        top = headers_heigth + 20
492
        top = headers_height + 20
496 493

  
497 494
        gc.stroke('transparent')
498 495
        lines(:image => gc, :top => top, :zoom => zoom, :subject_width => subject_width, :format => :image)
499
        
496

  
500 497
        # today red line
501 498
        if Date.today >= @date_from and Date.today <= date_to
502 499
          gc.stroke('red')
503 500
          x = (Date.today-@date_from+1)*zoom + subject_width
504
          gc.line(x, headers_heigth, x, headers_heigth + g_height-1)      
505
        end    
506
        
501
          gc.line(x, headers_height, x, headers_height + g_height-1)
502
        end
503

  
507 504
        gc.draw(imgl)
508 505
        imgl.format = format
509 506
        imgl.to_blob
510 507
      end if Object.const_defined?(:Magick)
511 508

  
512 509
      def to_pdf
513
        pdf = ::Redmine::Export::PDF::IFPDF.new(current_language)
510
        pdf = ::Redmine::Export::PDF::ITCPDF.new(current_language)
514 511
        pdf.SetTitle("#{l(:label_gantt)} #{project}")
515
        pdf.AliasNbPages
512
        pdf.alias_nb_pages
516 513
        pdf.footer_date = format_date(Date.today)
517 514
        pdf.AddPage("L")
518 515
        pdf.SetFontStyle('B',12)
519 516
        pdf.SetX(15)
520
        pdf.Cell(PDF::LeftPaneWidth, 20, project.to_s)
517
        pdf.RDMCell(PDF::LeftPaneWidth, 20, project.to_s)
521 518
        pdf.Ln
522 519
        pdf.SetFontStyle('B',9)
523
        
520

  
524 521
        subject_width = PDF::LeftPaneWidth
525
        header_heigth = 5
526
        
527
        headers_heigth = header_heigth
522
        header_height = 5
523

  
524
        headers_height = header_height
528 525
        show_weeks = false
529 526
        show_days = false
530
        
527

  
531 528
        if self.months < 7
532 529
          show_weeks = true
533
          headers_heigth = 2*header_heigth
530
          headers_height = 2*header_height
534 531
          if self.months < 3
535 532
            show_days = true
536
            headers_heigth = 3*header_heigth
533
            headers_height = 3*header_height
537 534
          end
538 535
        end
539
        
536

  
540 537
        g_width = PDF.right_pane_width
541 538
        zoom = (g_width) / (self.date_to - self.date_from + 1)
542 539
        g_height = 120
543
        t_height = g_height + headers_heigth
544
        
540
        t_height = g_height + headers_height
541

  
545 542
        y_start = pdf.GetY
546
        
543

  
547 544
        # Months headers
548 545
        month_f = self.date_from
549 546
        left = subject_width
550
        height = header_heigth
551
        self.months.times do 
552
          width = ((month_f >> 1) - month_f) * zoom 
547
        height = header_height
548
        self.months.times do
549
          width = ((month_f >> 1) - month_f) * zoom
553 550
          pdf.SetY(y_start)
554 551
          pdf.SetX(left)
555
          pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
552
          pdf.RDMCell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
556 553
          left = left + width
557 554
          month_f = month_f >> 1
558
        end  
559
        
555
        end
556

  
560 557
        # Weeks headers
561 558
        if show_weeks
562 559
          left = subject_width
563
          height = header_heigth
560
          height = header_height
564 561
          if self.date_from.cwday == 1
565 562
            # self.date_from is monday
566 563
            week_f = self.date_from
......
568 565
            # find next monday after self.date_from
569 566
            week_f = self.date_from + (7 - self.date_from.cwday + 1)
570 567
            width = (7 - self.date_from.cwday + 1) * zoom-1
571
            pdf.SetY(y_start + header_heigth)
568
            pdf.SetY(y_start + header_height)
572 569
            pdf.SetX(left)
573
            pdf.Cell(width + 1, height, "", "LTR")
570
            pdf.RDMCell(width + 1, height, "", "LTR")
574 571
            left = left + width+1
575 572
          end
576 573
          while week_f <= self.date_to
577 574
            width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom
578
            pdf.SetY(y_start + header_heigth)
575
            pdf.SetY(y_start + header_height)
579 576
            pdf.SetX(left)
580
            pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
577
            pdf.RDMCell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
581 578
            left = left + width
582 579
            week_f = week_f+7
583 580
          end
584 581
        end
585
        
582

  
586 583
        # Days headers
587 584
        if show_days
588 585
          left = subject_width
589
          height = header_heigth
586
          height = header_height
590 587
          wday = self.date_from.cwday
591 588
          pdf.SetFontStyle('B',7)
592
          (self.date_to - self.date_from + 1).to_i.times do 
589
          (self.date_to - self.date_from + 1).to_i.times do
593 590
            width = zoom
594
            pdf.SetY(y_start + 2 * header_heigth)
591
            pdf.SetY(y_start + 2 * header_height)
595 592
            pdf.SetX(left)
596
            pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C")
593
            pdf.RDMCell(width, height, day_name(wday).first, "LTR", 0, "C")
597 594
            left = left + width
598 595
            wday = wday + 1
599 596
            wday = 1 if wday > 7
600 597
          end
601 598
        end
602
        
599

  
603 600
        pdf.SetY(y_start)
604 601
        pdf.SetX(15)
605
        pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1)
606
        
602
        pdf.RDMCell(subject_width+g_width-15, headers_height, "", 1)
603

  
607 604
        # Tasks
608
        top = headers_heigth + y_start
605
        top = headers_height + y_start
609 606
        options = {
610 607
          :top => top,
611 608
          :zoom => zoom,
......
620 617
        render(options)
621 618
        pdf.Output
622 619
      end
623
      
620

  
624 621
      private
625
      
622

  
626 623
      def coordinates(start_date, end_date, progress, zoom=nil)
627 624
        zoom ||= @zoom
628
        
625

  
629 626
        coords = {}
630 627
        if start_date && end_date && start_date < self.date_to && end_date > self.date_from
631 628
          if start_date > self.date_from
......
640 637
          else
641 638
            coords[:bar_end] = self.date_to - self.date_from + 1
642 639
          end
643
        
640

  
644 641
          if progress
645
            progress_date = start_date + (end_date - start_date) * (progress / 100.0)
642
            progress_date = start_date + (end_date - start_date + 1) * (progress / 100.0)
646 643
            if progress_date > self.date_from && progress_date > start_date
647 644
              if progress_date < self.date_to
648
                coords[:bar_progress_end] = progress_date - self.date_from + 1
645
                coords[:bar_progress_end] = progress_date - self.date_from
649 646
              else
650 647
                coords[:bar_progress_end] = self.date_to - self.date_from + 1
651 648
              end
652 649
            end
653
            
650

  
654 651
            if progress_date < Date.today
655 652
              late_date = [Date.today, end_date].min
656 653
              if late_date > self.date_from && late_date > start_date
......
663 660
            end
664 661
          end
665 662
        end
666
        
663

  
667 664
        # Transforms dates into pixels witdh
668 665
        coords.keys.each do |key|
669 666
          coords[key] = (coords[key] * zoom).floor
......
675 672
      def sort_issues!(issues)
676 673
        issues.sort! { |a, b| gantt_issue_compare(a, b, issues) }
677 674
      end
678
  
675

  
679 676
      # TODO: top level issues should be sorted by start date
680 677
      def gantt_issue_compare(x, y, issues)
681 678
        if x.root_id == y.root_id
......
684 681
          x.root_id <=> y.root_id
685 682
        end
686 683
      end
687
      
684

  
688 685
      def current_limit
689 686
        if @max_rows
690 687
          @max_rows - @number_of_rows
......
692 689
          nil
693 690
        end
694 691
      end
695
      
692

  
696 693
      def abort?
697 694
        if @max_rows && @number_of_rows >= @max_rows
698 695
          @truncated = true
699 696
        end
700 697
      end
701
      
698

  
702 699
      def pdf_new_page?(options)
703 700
        if options[:top] > 180
704 701
          options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
......
707 704
          options[:pdf].Line(15, options[:top] - 0.1, PDF::TotalWidth, options[:top] - 0.1)
708 705
        end
709 706
      end
710
      
707

  
711 708
      def html_subject(params, subject, options={})
712 709
        style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;"
713 710
        style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width]
714
        
711

  
715 712
        output = view.content_tag 'div', subject, :class => options[:css], :style => style, :title => options[:title]
716 713
        @subjects << output
717 714
        output
718 715
      end
719
      
716

  
720 717
      def pdf_subject(params, subject, options={})
721 718
        params[:pdf].SetY(params[:top])
722 719
        params[:pdf].SetX(15)
723
        
720

  
724 721
        char_limit = PDF::MaxCharactorsForSubject - params[:indent]
725
        params[:pdf].Cell(params[:subject_width]-15, 5, (" " * params[:indent]) +  subject.to_s.sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR")
726
      
722
        params[:pdf].RDMCell(params[:subject_width]-15, 5, (" " * params[:indent]) +  subject.to_s.sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR")
723

  
727 724
        params[:pdf].SetY(params[:top])
728 725
        params[:pdf].SetX(params[:subject_width])
729
        params[:pdf].Cell(params[:g_width], 5, "", "LR")
726
        params[:pdf].RDMCell(params[:g_width], 5, "", "LR")
730 727
      end
731
      
728

  
732 729
      def image_subject(params, subject, options={})
733 730
        params[:image].fill('black')
734 731
        params[:image].stroke('transparent')
735 732
        params[:image].stroke_width(1)
736 733
        params[:image].text(params[:indent], params[:top] + 2, subject)
737 734
      end
738
      
735

  
739 736
      def html_task(params, coords, options={})
740 737
        output = ''
741 738
        # Renders the task bar, with progress and late
742 739
        if coords[:bar_start] && coords[:bar_end]
743 740
          output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_todo'>&nbsp;</div>"
744
          
741

  
745 742
          if coords[:bar_late_end]
746 743
            output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_late_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_late'>&nbsp;</div>"
747 744
          end
......
774 771
        @lines << output
775 772
        output
776 773
      end
777
      
774

  
778 775
      def pdf_task(params, coords, options={})
779 776
        height = options[:height] || 2
780
        
777

  
781 778
        # Renders the task bar, with progress and late
782 779
        if coords[:bar_start] && coords[:bar_end]
783 780
          params[:pdf].SetY(params[:top]+1.5)
784 781
          params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
785 782
          params[:pdf].SetFillColor(200,200,200)
786
          params[:pdf].Cell(coords[:bar_end] - coords[:bar_start], height, "", 0, 0, "", 1)
787
            
783
          params[:pdf].RDMCell(coords[:bar_end] - coords[:bar_start], height, "", 0, 0, "", 1)
784

  
788 785
          if coords[:bar_late_end]
789 786
            params[:pdf].SetY(params[:top]+1.5)
790 787
            params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
791 788
            params[:pdf].SetFillColor(255,100,100)
792
            params[:pdf].Cell(coords[:bar_late_end] - coords[:bar_start], height, "", 0, 0, "", 1)
789
            params[:pdf].RDMCell(coords[:bar_late_end] - coords[:bar_start], height, "", 0, 0, "", 1)
793 790
          end
794 791
          if coords[:bar_progress_end]
795 792
            params[:pdf].SetY(params[:top]+1.5)
796 793
            params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
797 794
            params[:pdf].SetFillColor(90,200,90)
798
            params[:pdf].Cell(coords[:bar_progress_end] - coords[:bar_start], height, "", 0, 0, "", 1)
795
            params[:pdf].RDMCell(coords[:bar_progress_end] - coords[:bar_start], height, "", 0, 0, "", 1)
799 796
          end
800 797
        end
801 798
        # Renders the markers
......
804 801
            params[:pdf].SetY(params[:top] + 1)
805 802
            params[:pdf].SetX(params[:subject_width] + coords[:start] - 1)
806 803
            params[:pdf].SetFillColor(50,50,200)
807
            params[:pdf].Cell(2, 2, "", 0, 0, "", 1) 
804
            params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1)
808 805
          end
809 806
          if coords[:end]
810 807
            params[:pdf].SetY(params[:top] + 1)
811 808
            params[:pdf].SetX(params[:subject_width] + coords[:end] - 1)
812 809
            params[:pdf].SetFillColor(50,50,200)
813
            params[:pdf].Cell(2, 2, "", 0, 0, "", 1) 
810
            params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1)
814 811
          end
815 812
        end
816 813
        # Renders the label on the right
817 814
        if options[:label]
818 815
          params[:pdf].SetX(params[:subject_width] + (coords[:bar_end] || 0) + 5)
819
          params[:pdf].Cell(30, 2, options[:label])
816
          params[:pdf].RDMCell(30, 2, options[:label])
820 817
        end
821 818
      end
822 819

  
823 820
      def image_task(params, coords, options={})
824 821
        height = options[:height] || 6
825
        
822

  
826 823
        # Renders the task bar, with progress and late
827 824
        if coords[:bar_start] && coords[:bar_end]
828 825
          params[:image].fill('#aaa')
829 826
          params[:image].rectangle(params[:subject_width] + coords[:bar_start], params[:top], params[:subject_width] + coords[:bar_end], params[:top] - height)
830
 
827

  
831 828
          if coords[:bar_late_end]
832 829
            params[:image].fill('#f66')
833 830
            params[:image].rectangle(params[:subject_width] + coords[:bar_start], params[:top], params[:subject_width] + coords[:bar_late_end], params[:top] - height)
lib/redmine/helpers/calendar.rb
68 68
        case Setting.start_of_week.to_i
69 69
        when 1
70 70
          @first_dow ||= (1 - 1)%7 + 1
71
        when 6
72
          @first_dow ||= (6 - 1)%7 + 1
71 73
        when 7
72 74
          @first_dow ||= (7 - 1)%7 + 1
73 75
        else
lib/redmine/helpers/gantt.rb
1 1
# Redmine - project management software
2
# Copyright (C) 2006-2008  Jean-Philippe Lang
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.
......
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
......
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
        @zoom = (zoom > 0 && zoom < 5) ? zoom : 2    
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]
......
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
            ### Extracted from the HTML view/helpers
102 101
      # Returns the number of rows that will be rendered on the Gantt chart
103 102
      def number_of_rows
104 103
        return @number_of_rows if @number_of_rows
105
        
106
        rows = if @project
107
          number_of_rows_on_project(@project)
108
        else
109
          Project.roots.visible.has_module('issue_tracking').inject(0) do |total, project|
110
            total += number_of_rows_on_project(project)
111
          end
112
        end
113
        
104

  
105
        rows = projects.inject(0) {|total, p| total += number_of_rows_on_project(p)}
114 106
        rows > @max_rows ? @max_rows : rows
115 107
      end
116 108

  
117 109
      # Returns the number of rows that will be used to list a project on
118 110
      # the Gantt chart.  This will recurse for each subproject.
119 111
      def number_of_rows_on_project(project)
120
        # Remove the project requirement for Versions because it will
121
        # restrict issues to only be on the current project.  This
122
        # ends up missing issues which are assigned to shared versions.
123
        @query.project = nil if @query.project
112
        return 0 unless projects.include?(project)
124 113

  
125
        # One Root project
126 114
        count = 1
127
        # Issues without a Version
128
        count += project.issues.for_gantt.without_version.with_query(@query).count
129

  
130
        # Versions
131
        count += project.versions.count
132

  
133
        # Issues on the Versions
134
        project.versions.each do |version|
135
          count += version.fixed_issues.for_gantt.with_query(@query).count
136
        end
137

  
138
        # Subprojects
139
        project.children.visible.has_module('issue_tracking').each do |subproject|
140
          count += number_of_rows_on_project(subproject)
141
        end
142

  
115
        count += project_issues(project).size
116
        count += project_versions(project).size
143 117
        count
144 118
      end
145 119

  
......
154 128
        render(options.merge(:only => :lines)) unless @lines_rendered
155 129
        @lines
156 130
      end
157
      
131

  
132
      # Returns issues that will be rendered
133
      def issues
134
        @issues ||= @query.issues(
135
          :include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
136
          :order => "#{Project.table_name}.lft ASC, #{Issue.table_name}.id ASC",
137
          :limit => @max_rows
138
        )
139
      end
140

  
141
      # Return all the project nodes that will be displayed
142
      def projects
143
        return @projects if @projects
144

  
145
        ids = issues.collect(&:project).uniq.collect(&:id)
146
        if ids.any?
147
          # All issues projects and their visible ancestors
148
          @projects = Project.visible.all(
149
            :joins => "LEFT JOIN #{Project.table_name} child ON #{Project.table_name}.lft <= child.lft AND #{Project.table_name}.rgt >= child.rgt",
150
            :conditions => ["child.id IN (?)", ids],
151
            :order => "#{Project.table_name}.lft ASC"
152
          ).uniq
153
        else
154
          @projects = []
155
        end
156
      end
157

  
158
      # Returns the issues that belong to +project+
159
      def project_issues(project)
160
        @issues_by_project ||= issues.group_by(&:project)
161
        @issues_by_project[project] || []
162
      end
163

  
164
      # Returns the distinct versions of the issues that belong to +project+
165
      def project_versions(project)
166
        project_issues(project).collect(&:fixed_version).compact.uniq
167
      end
168

  
169
      # Returns the issues that belong to +project+ and are assigned to +version+
170
      def version_issues(project, version)
171
        project_issues(project).select {|issue| issue.fixed_version == version}
172
      end
173

  
158 174
      def render(options={})
159
        options = {:indent => 4, :render => :subject, :format => :html}.merge(options)
160
        
175
        options = {:top => 0, :top_increment => 20, :indent_increment => 20, :render => :subject, :format => :html}.merge(options)
176
        indent = options[:indent] || 4
177

  
161 178
        @subjects = '' unless options[:only] == :lines
162 179
        @lines = '' unless options[:only] == :subjects
163 180
        @number_of_rows = 0
164
        
165
        if @project
166
          render_project(@project, options)
167
        else
168
          Project.roots.visible.has_module('issue_tracking').each do |project|
169
            render_project(project, options)
170
            break if abort?
171
          end
181

  
182
        Project.project_tree(projects) do |project, level|
183
          options[:indent] = indent + level * options[:indent_increment]
184
          render_project(project, options)
185
          break if abort?
172 186
        end
173
        
187

  
174 188
        @subjects_rendered = true unless options[:only] == :lines
175 189
        @lines_rendered = true unless options[:only] == :subjects
176
        
190

  
177 191
        render_end(options)
178 192
      end
179 193

  
180 194
      def render_project(project, options={})
181
        options[:top] = 0 unless options.key? :top
182
        options[:indent_increment] = 20 unless options.key? :indent_increment
183
        options[:top_increment] = 20 unless options.key? :top_increment
184

  
185 195
        subject_for_project(project, options) unless options[:only] == :lines
186 196
        line_for_project(project, options) unless options[:only] == :subjects
187
        
197

  
188 198
        options[:top] += options[:top_increment]
189 199
        options[:indent] += options[:indent_increment]
190 200
        @number_of_rows += 1
191 201
        return if abort?
192
        
193
        # Second, Issues without a version
194
        issues = project.issues.for_gantt.without_version.with_query(@query).all(:limit => current_limit)
202

  
203
        issues = project_issues(project).select {|i| i.fixed_version.nil?}
195 204
        sort_issues!(issues)
196 205
        if issues
197 206
          render_issues(issues, options)
198 207
          return if abort?
199 208
        end
200 209

  
201
        # Third, Versions
202
        project.versions.sort.each do |version|
203
          render_version(version, options)
204
          return if abort?
210
        versions = project_versions(project)
211
        versions.each do |version|
212
          render_version(project, version, options)
205 213
        end
206 214

  
207
        # Fourth, subprojects
208
        project.children.visible.has_module('issue_tracking').each do |project|
209
          render_project(project, options)
210
          return if abort?
211
        end unless project.leaf?
212

  
213 215
        # Remove indent to hit the next sibling
214 216
        options[:indent] -= options[:indent_increment]
215 217
      end
216 218

  
217 219
      def render_issues(issues, options={})
218 220
        @issue_ancestors = []
219
        
221

  
220 222
        issues.each do |i|
221 223
          subject_for_issue(i, options) unless options[:only] == :lines
222 224
          line_for_issue(i, options) unless options[:only] == :subjects
223
          
225

  
224 226
          options[:top] += options[:top_increment]
225 227
          @number_of_rows += 1
226 228
          break if abort?
227 229
        end
228
        
230

  
229 231
        options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
230 232
      end
231 233

  
232
      def render_version(version, options={})
234
      def render_version(project, version, options={})
233 235
        # Version header
234 236
        subject_for_version(version, options) unless options[:only] == :lines
235 237
        line_for_version(version, options) unless options[:only] == :subjects
236
        
238

  
237 239
        options[:top] += options[:top_increment]
238 240
        @number_of_rows += 1
239 241
        return if abort?
240
        
241
        # Remove the project requirement for Versions because it will
242
        # restrict issues to only be on the current project.  This
243
        # ends up missing issues which are assigned to shared versions.
244
        @query.project = nil if @query.project
245
        
246
        issues = version.fixed_issues.for_gantt.with_query(@query).all(:limit => current_limit)
242

  
243
        issues = version_issues(project, version)
247 244
        if issues
248 245
          sort_issues!(issues)
249 246
          # Indent issues
......
252 249
          options[:indent] -= options[:indent_increment]
253 250
        end
254 251
      end
255
      
252

  
256 253
      def render_end(options={})
257 254
        case options[:format]
258
        when :pdf        
255
        when :pdf
259 256
          options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
260 257
        end
261 258
      end
......
280 277
        if project.is_a?(Project) && project.start_date && project.due_date
281 278
          options[:zoom] ||= 1
282 279
          options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
283
            
280

  
284 281
          coords = coordinates(project.start_date, project.due_date, nil, options[:zoom])
285 282
          label = h(project)
286
          
283

  
287 284
          case options[:format]
288 285
          when :html
289 286
            html_task(options, coords, :css => "project task", :label => label, :markers => true)
......
318 315
        if version.is_a?(Version) && version.start_date && version.due_date
319 316
          options[:zoom] ||= 1
320 317
          options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
321
          
318

  
322 319
          coords = coordinates(version.start_date, version.due_date, version.completed_pourcent, options[:zoom])
323 320
          label = "#{h version } #{h version.completed_pourcent.to_i.to_s}%"
324 321
          label = h("#{version.project} -") + label unless @project && @project == version.project
......
342 339
          @issue_ancestors.pop
343 340
          options[:indent] -= options[:indent_increment]
344 341
        end
345
          
342

  
346 343
        output = case options[:format]
347 344
        when :html
348 345
          css_classes = ''
349 346
          css_classes << ' issue-overdue' if issue.overdue?
350 347
          css_classes << ' issue-behind-schedule' if issue.behind_schedule?
351 348
          css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
352
          
349

  
353 350
          subject = "<span class='#{css_classes}'>"
354 351
          if issue.assigned_to.present?
355 352
            assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
......
369 366
          @issue_ancestors << issue
370 367
          options[:indent] += options[:indent_increment]
371 368
        end
372
        
369

  
373 370
        output
374 371
      end
375 372

  
......
378 375
        if issue.is_a?(Issue) && issue.due_before
379 376
          coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom])
380 377
          label = "#{ issue.status.name } #{ issue.done_ratio }%"
381
          
378

  
382 379
          case options[:format]
383 380
          when :html
384 381
            html_task(options, coords, :css => "task " + (issue.leaf? ? 'leaf' : 'parent'), :label => label, :issue => issue, :markers => !issue.leaf?)
......
396 393
      # Generates a gantt image
397 394
      # Only defined if RMagick is avalaible
398 395
      def to_image(format='PNG')
399
        date_to = (@date_from >> @months)-1    
396
        date_to = (@date_from >> @months)-1
400 397
        show_weeks = @zoom > 1
401 398
        show_days = @zoom > 2
402
        
399

  
403 400
        subject_width = 400
404
        header_heigth = 18
401
        header_height = 18
405 402
        # width of one day in pixels
406 403
        zoom = @zoom*2
407 404
        g_width = (@date_to - @date_from + 1)*zoom
408 405
        g_height = 20 * number_of_rows + 30
409
        headers_heigth = (show_weeks ? 2*header_heigth : header_heigth)
410
        height = g_height + headers_heigth
411
            
406
        headers_height = (show_weeks ? 2*header_height : header_height)
407
        height = g_height + headers_height
408

  
412 409
        imgl = Magick::ImageList.new
413 410
        imgl.new_image(subject_width+g_width+1, height)
414 411
        gc = Magick::Draw.new
415
        
412

  
416 413
        # Subjects
417 414
        gc.stroke('transparent')
418
        subjects(:image => gc, :top => (headers_heigth + 20), :indent => 4, :format => :image)
419
    
415
        subjects(:image => gc, :top => (headers_height + 20), :indent => 4, :format => :image)
416

  
420 417
        # Months headers
421 418
        month_f = @date_from
422 419
        left = subject_width
423
        @months.times do 
420
        @months.times do
424 421
          width = ((month_f >> 1) - month_f) * zoom
425 422
          gc.fill('white')
426 423
          gc.stroke('grey')
......
433 430
          left = left + width
434 431
          month_f = month_f >> 1
435 432
        end
436
        
433

  
437 434
        # Weeks headers
438 435
        if show_weeks
439 436
        	left = subject_width
440
        	height = header_heigth
437
        	height = header_height
441 438
        	if @date_from.cwday == 1
442 439
        	    # date_from is monday
443 440
                week_f = date_from
......
448 445
                gc.fill('white')
449 446
                gc.stroke('grey')
450 447
                gc.stroke_width(1)
451
                gc.rectangle(left, header_heigth, left + width, 2*header_heigth + g_height-1)
448
                gc.rectangle(left, header_height, left + width, 2*header_height + g_height-1)
452 449
        		left = left + width
453 450
        	end
454 451
        	while week_f <= date_to
......
456 453
                gc.fill('white')
457 454
                gc.stroke('grey')
458 455
                gc.stroke_width(1)
459
                gc.rectangle(left.round, header_heigth, left.round + width, 2*header_heigth + g_height-1)
456
                gc.rectangle(left.round, header_height, left.round + width, 2*header_height + g_height-1)
460 457
                gc.fill('black')
461 458
                gc.stroke('transparent')
462 459
                gc.stroke_width(1)
463
                gc.text(left.round + 2, header_heigth + 14, week_f.cweek.to_s)
460
                gc.text(left.round + 2, header_height + 14, week_f.cweek.to_s)
464 461
        		left = left + width
465 462
        		week_f = week_f+7
466 463
        	end
467 464
        end
468
        
465

  
469 466
        # Days details (week-end in grey)
470 467
        if show_days
471 468
        	left = subject_width
472
        	height = g_height + header_heigth - 1
469
        	height = g_height + header_height - 1
473 470
        	wday = @date_from.cwday
474
        	(date_to - @date_from + 1).to_i.times do 
471
        	(date_to - @date_from + 1).to_i.times do
475 472
              width =  zoom
476 473
              gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
477 474
              gc.stroke('#ddd')
478 475
              gc.stroke_width(1)
479
              gc.rectangle(left, 2*header_heigth, left + width, 2*header_heigth + g_height-1)
476
              gc.rectangle(left, 2*header_height, left + width, 2*header_height + g_height-1)
480 477
              left = left + width
481 478
              wday = wday + 1
482 479
              wday = 1 if wday > 7
483 480
        	end
484 481
        end
485
    
482

  
486 483
        # border
487 484
        gc.fill('transparent')
488 485
        gc.stroke('grey')
489 486
        gc.stroke_width(1)
490
        gc.rectangle(0, 0, subject_width+g_width, headers_heigth)
487
        gc.rectangle(0, 0, subject_width+g_width, headers_height)
491 488
        gc.stroke('black')
492
        gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_heigth-1)
493
            
489
        gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_height-1)
490

  
494 491
        # content
495
        top = headers_heigth + 20
492
        top = headers_height + 20
496 493

  
497 494
        gc.stroke('transparent')
498 495
        lines(:image => gc, :top => top, :zoom => zoom, :subject_width => subject_width, :format => :image)
499
        
496

  
500 497
        # today red line
501 498
        if Date.today >= @date_from and Date.today <= date_to
502 499
          gc.stroke('red')
503 500
          x = (Date.today-@date_from+1)*zoom + subject_width
504
          gc.line(x, headers_heigth, x, headers_heigth + g_height-1)      
505
        end    
506
        
501
          gc.line(x, headers_height, x, headers_height + g_height-1)
502
        end
503

  
507 504
        gc.draw(imgl)
508 505
        imgl.format = format
509 506
        imgl.to_blob
510 507
      end if Object.const_defined?(:Magick)
511 508

  
512 509
      def to_pdf
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff