diff --git a/lib/redmine/helpers/gantt.rb b/lib/redmine/helpers/gantt.rb index 4218c51efaf..40f1f33a511 100644 --- a/lib/redmine/helpers/gantt.rb +++ b/lib/redmine/helpers/gantt.rb @@ -165,17 +165,27 @@ def render_project(project, options={}) output = '' # Project Header - project_header = if options[:render] == :subject - subject_for_project(project, options) - else - # :line - line_for_project(project, options) - end - output << project_header if options[:format] == :html + if options[:format] == :pdf + subject_for_project(project, options) + line_for_project(project, options) + else + project_header = if options[:render] == :subject + subject_for_project(project, options) + else + # :line + line_for_project(project, options) + end + output << project_header if options[:format] == :html + end options[:top] += options[:top_increment] options[:indent] += options[:indent_increment] + # Insert a page break if we hit the bottom of the page + if options[:format] == :pdf and options[:top] > 180 + pdf_page_break(options[:pdf], options) + end + # Second, Issues without a version issues = project.issues.for_gantt.without_version.with_query(@query) sort_issues!(issues) @@ -205,14 +215,24 @@ def render_project(project, options={}) def render_issues(issues, options={}) output = '' issues.each do |i| - issue_rendering = if options[:render] == :subject - subject_for_issue(i, options) - else - # :line - line_for_issue(i, options) - end - output << issue_rendering if options[:format] == :html + if options[:format] == :pdf + subject_for_issue(i, options) + line_for_issue(i, options) + else + issue_rendering = if options[:render] == :subject + subject_for_issue(i, options) + else + # :line + line_for_issue(i, options) + end + output << issue_rendering if options[:format] == :html + end options[:top] += options[:top_increment] + + # Insert a page break if we hit the bottom of the page + if options[:format] == :pdf and options[:top] > 180 + pdf_page_break(options[:pdf], options) + end end output end @@ -220,17 +240,26 @@ def render_issues(issues, options={}) def render_version(version, options={}) output = '' # Version header - version_rendering = if options[:render] == :subject - subject_for_version(version, options) - else - # :line - line_for_version(version, options) - end + if options[:format] == :pdf + subject_for_version(version, options) + line_for_version(version, options) + else + version_rendering = if options[:render] == :subject + subject_for_version(version, options) + else + # :line + line_for_version(version, options) + end - output << version_rendering if options[:format] == :html - + output << version_rendering if options[:format] == :html + end options[:top] += options[:top_increment] + # Insert a page break if we hit the bottom of the page + if options[:format] == :pdf and options[:top] > 180 + pdf_page_break(options[:pdf], options) + end + # Remove the project requirement for Versions because it will # restrict issues to only be on the current project. This # ends up missing issues which are assigned to shared versions. @@ -266,7 +295,6 @@ def subject_for_project(project, options) output when :image - options[:image].fill('black') options[:image].stroke('transparent') options[:image].stroke_width(1) @@ -289,7 +317,6 @@ def line_for_project(project, options) if project.is_a?(Project) && project.start_date && project.due_date options[:zoom] ||= 1 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom] - case options[:format] when :html @@ -326,7 +353,6 @@ def line_for_project(project, options) if d_width > 0 && i_left <= options[:g_width] output<< "
 
" end - # Starting diamond if start_left <= options[:g_width] && start_left > 0 @@ -460,7 +486,6 @@ def line_for_version(version, options) if d_width > 0 && i_left <= options[:g_width] output<< "
 
" end - # Starting diamond if start_left <= options[:g_width] && start_left > 0 @@ -735,20 +760,20 @@ def to_image(format='PNG') show_days = @zoom > 2 subject_width = 400 - header_heigth = 18 + header_height = 18 # width of one day in pixels zoom = @zoom*2 g_width = (@date_to - @date_from + 1)*zoom g_height = 20 * number_of_rows + 30 - headers_heigth = (show_weeks ? 2*header_heigth : header_heigth) - height = g_height + headers_heigth + headers_height = (show_weeks ? 2*header_height : header_height) + height = g_height + headers_height imgl = Magick::ImageList.new imgl.new_image(subject_width+g_width+1, height) gc = Magick::Draw.new # Subjects - subjects(:image => gc, :top => (headers_heigth + 20), :indent => 4, :format => :image) + subjects(:image => gc, :top => (headers_height + 20), :indent => 4, :format => :image) # Months headers month_f = @date_from @@ -770,7 +795,7 @@ def to_image(format='PNG') # Weeks headers if show_weeks left = subject_width - height = header_heigth + height = header_height if @date_from.cwday == 1 # date_from is monday week_f = date_from @@ -781,7 +806,7 @@ def to_image(format='PNG') gc.fill('white') gc.stroke('grey') gc.stroke_width(1) - gc.rectangle(left, header_heigth, left + width, 2*header_heigth + g_height-1) + gc.rectangle(left, header_height, left + width, 2*header_height + g_height-1) left = left + width end while week_f <= date_to @@ -789,11 +814,11 @@ def to_image(format='PNG') gc.fill('white') gc.stroke('grey') gc.stroke_width(1) - gc.rectangle(left.round, header_heigth, left.round + width, 2*header_heigth + g_height-1) + gc.rectangle(left.round, header_height, left.round + width, 2*header_height + g_height-1) gc.fill('black') gc.stroke('transparent') gc.stroke_width(1) - gc.text(left.round + 2, header_heigth + 14, week_f.cweek.to_s) + gc.text(left.round + 2, header_height + 14, week_f.cweek.to_s) left = left + width week_f = week_f+7 end @@ -802,14 +827,14 @@ def to_image(format='PNG') # Days details (week-end in grey) if show_days left = subject_width - height = g_height + header_heigth - 1 + height = g_height + header_height - 1 wday = @date_from.cwday (date_to - @date_from + 1).to_i.times do width = zoom gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white') gc.stroke('grey') gc.stroke_width(1) - gc.rectangle(left, 2*header_heigth, left + width, 2*header_heigth + g_height-1) + gc.rectangle(left, 2*header_height, left + width, 2*header_height + g_height-1) left = left + width wday = wday + 1 wday = 1 if wday > 7 @@ -820,12 +845,12 @@ def to_image(format='PNG') gc.fill('transparent') gc.stroke('grey') gc.stroke_width(1) - gc.rectangle(0, 0, subject_width+g_width, headers_heigth) + gc.rectangle(0, 0, subject_width+g_width, headers_height) gc.stroke('black') - gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_heigth-1) + gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_height-1) # content - top = headers_heigth + 20 + top = headers_height + 20 lines(:image => gc, :top => top, :zoom => zoom, :subject_width => subject_width, :format => :image) @@ -833,7 +858,7 @@ def to_image(format='PNG') if Date.today >= @date_from and Date.today <= date_to gc.stroke('red') x = (Date.today-@date_from+1)*zoom + subject_width - gc.line(x, headers_heigth, x, headers_heigth + g_height-1) + gc.line(x, headers_height, x, headers_height + g_height-1) end gc.draw(imgl) @@ -852,37 +877,98 @@ def to_pdf pdf.Cell(PDF::LeftPaneWidth, 20, project.to_s) pdf.Ln pdf.SetFontStyle('B',9) + pdf.SetAutoPageBreak(FALSE) subject_width = PDF::LeftPaneWidth - header_heigth = 5 + header_height = 5 - headers_heigth = header_heigth + headers_height = header_height show_weeks = false show_days = false if self.months < 7 show_weeks = true - headers_heigth = 2*header_heigth + headers_height = 2*header_height if self.months < 3 show_days = true - headers_heigth = 3*header_heigth + headers_height = 3*header_height end end g_width = PDF.right_pane_width zoom = (g_width) / (self.date_to - self.date_from + 1) g_height = 120 - t_height = g_height + headers_heigth + t_height = g_height + headers_height y_start = pdf.GetY + options = { + :top => y_start, + :headers_top => y_start, + :header_height => header_height, + :headers_height => headers_height, + :subject_width => subject_width, + :show_weeks => show_weeks, + :show_days => show_days, + :zoom => zoom, + :g_width => g_width + } + + pdf_date_headers(pdf, options) + + # Tasks + top = headers_height + y_start + options[:top] = top + + pdf_subjects_and_lines(pdf, options) + + pdf.Line(15, options[:top]+2, subject_width+g_width, options[:top]+2) + pdf.Output + end + + private + + # Sorts a collection of issues by start_date, due_date, id for gantt rendering + def sort_issues!(issues) + issues.sort! do |a, b| + cmp = 0 + cmp = (a.start_date <=> b.start_date) if a.start_date? && b.start_date? + cmp = (a.due_date <=> b.due_date) if cmp == 0 && a.due_date? && b.due_date? + cmp = (a.id <=> b.id) if cmp == 0 + cmp + end + end + + # Goes to the next page so it doesn't overflow + def pdf_page_break(pdf, options = {}) + top = options[:top] + subject_width = options[:subject_width] + g_width = options[:g_width] + pdf.Line(15, top + 2, subject_width + g_width, top + 2) + pdf.AddPage('L') + options[:top] = options[:headers_top] + pdf_date_headers(pdf, options) + options[:top] += options[:headers_height] + end + + # Renders the date headers for the PDF format + def pdf_date_headers(pdf, options = {}) + top = options[:top] + g_width = options[:g_width] + header_height = options[:header_height] + headers_height = options[:headers_height] + subject_width = options[:subject_width] + show_weeks = options[:show_weeks] + show_days = options[:show_days] + zoom = options[:zoom] + # Months headers month_f = self.date_from left = subject_width - height = header_heigth + height = header_height self.months.times do width = ((month_f >> 1) - month_f) * zoom - pdf.SetY(y_start) + pdf.SetY(top) pdf.SetX(left) pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C") left = left + width @@ -892,7 +978,7 @@ def to_pdf # Weeks headers if show_weeks left = subject_width - height = header_heigth + height = header_height if self.date_from.cwday == 1 # self.date_from is monday week_f = self.date_from @@ -900,14 +986,14 @@ def to_pdf # find next monday after self.date_from week_f = self.date_from + (7 - self.date_from.cwday + 1) width = (7 - self.date_from.cwday + 1) * zoom-1 - pdf.SetY(y_start + header_heigth) + pdf.SetY(top + header_height) pdf.SetX(left) pdf.Cell(width + 1, height, "", "LTR") left = left + width+1 end while week_f <= self.date_to width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom - pdf.SetY(y_start + header_heigth) + pdf.SetY(top + header_height) pdf.SetX(left) pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C") left = left + width @@ -918,70 +1004,44 @@ def to_pdf # Days headers if show_days left = subject_width - height = header_heigth + height = header_height wday = self.date_from.cwday pdf.SetFontStyle('B',7) (self.date_to - self.date_from + 1).to_i.times do width = zoom - pdf.SetY(y_start + 2 * header_heigth) + pdf.SetY(top + 2 * header_height) pdf.SetX(left) pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C") left = left + width wday = wday + 1 wday = 1 if wday > 7 end - end - - pdf.SetY(y_start) - pdf.SetX(15) - pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1) - - # Tasks - top = headers_heigth + y_start - pdf_subjects_and_lines(pdf, { - :top => top, - :zoom => zoom, - :subject_width => subject_width, - :g_width => g_width - }) + end - - pdf.Line(15, top, subject_width+g_width, top) - pdf.Output - - - end - - private - - # Sorts a collection of issues by start_date, due_date, id for gantt rendering - def sort_issues!(issues) - issues.sort! do |a, b| - cmp = 0 - cmp = (a.start_date <=> b.start_date) if a.start_date? && b.start_date? - cmp = (a.due_date <=> b.due_date) if cmp == 0 && a.due_date? && b.due_date? - cmp = (a.id <=> b.id) if cmp == 0 - cmp - end + pdf.SetY(top) + pdf.SetX(15) + pdf.Cell(subject_width + g_width - 15, headers_height, "", 1) end # Renders both the subjects and lines of the Gantt chart for the # PDF format def pdf_subjects_and_lines(pdf, options = {}) - subject_options = {:indent => 0, :indent_increment => 5, :top_increment => 3, :render => :subject, :format => :pdf, :pdf => pdf}.merge(options) - line_options = {:indent => 0, :indent_increment => 5, :top_increment => 3, :render => :line, :format => :pdf, :pdf => pdf}.merge(options) + # Don't use Hash#merge to set defaults because the calling method + # wants to read the final value of options[:top] + options[:indent] ||= 0 + options[:indent_increment] ||= 5 + options[:top_increment] ||= 3 + options[:format] ||= :pdf + options[:pdf] ||= pdf if @project - render_project(@project, subject_options) - render_project(@project, line_options) + render_project(@project, options) else Project.roots.each do |project| - render_project(project, subject_options) - render_project(project, line_options) + render_project(project, options) end end end - end end end