Index: app/helpers/issues_helper.rb =================================================================== --- app/helpers/issues_helper.rb (revision 2819) +++ app/helpers/issues_helper.rb (working copy) @@ -15,6 +15,10 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# Matthew Keniston & Alex Mendes +# BackOffice Associates +# 7/7/09 fixes for: 1) export to PDF/CSV ignoring custom queries; 2) query display problems + require 'csv' module IssuesHelper @@ -129,19 +133,179 @@ end end - def issues_to_csv(issues, project = nil) + def retrieveHeaders(query, custom_fields) + headers = Array.new(query.column_names.length()) + 0.upto(query.column_names.length() - 1) do |counter| + headerName = nil + columnName = query.column_names[counter].to_s() + case columnName + when 'status' + headerName = l(:field_status) + when 'project' + headerName = l(:field_project) + when 'tracker' + headerName = l(:field_tracker) + when 'priority' + headerName = l(:field_priority) + when 'subject' + headerName = l(:field_subject) + when 'assigned_to' + headerName = l(:field_assigned_to) + when 'category' + headerName = l(:field_category) + when 'fixed_version' + headerName = l(:field_fixed_version) + when 'author' + headerName = l(:field_author) + when 'start_date' + headerName = l(:field_start_date) + when 'due_date' + headerName = l(:field_due_date) + when 'done_ratio' + headerName = l(:field_done_ratio) + when 'estimated_hours' + headerName = l(:field_estimated_hours) + when 'created_on' + headerName = l(:field_created_on) + when 'updated_on' + headerName = l(:field_updated_on) + else + #custom field case... + + #parse the custom column id # from the column header + id = getIDFromCustomColName(columnName) + + #match it with a custom field id to extract the correct header and field data + custom_fields.each do |custom_field| #cycle through all possible custom fields + if id == custom_field.id.to_s() + headerName = custom_field.name.to_s() + break + end + end + end + headers[counter] = headerName != nil ? headerName : '' + end + return headers + end + + def retrieveFieldData(issues, query, decimal_separator,custom_fields) + fields = Array.new(query.column_names.length()){[]} + #cycle through each issue (row) in the table.. + issues.each do |issue| + #cycle through each column in the row... + #loading the column names from the passed query... + #in order to load the correct column data for each issue --> fields2[column][issue] + #this method also loads the headers for each column --> headers2[column] + 0.upto(query.column_names.length() - 1) do |counter| + columnName = query.column_names[counter].to_s() + case columnName + when 'status' + fieldData = issue.status.name + when 'project' + fieldData = issue.project.name + when 'tracker' + fieldData = issue.tracker.name + when 'priority' + fieldData = issue.priority.name + when 'subject' + fieldData= issue.subject + when 'assigned_to' + fieldData = issue.assigned_to + when 'category' + fieldData = issue.category + when 'fixed_version' + fieldData = issue.fixed_version + when 'author' + fieldData = issue.author.name + when 'start_date' + fieldData = format_date(issue.start_date) + when 'due_date' + fieldData = format_date(issue.due_date) + when 'done_ratio' + fieldData = issue.done_ratio + when 'estimated_hours' + fieldData = issue.estimated_hours.to_s.gsub('.', decimal_separator) + when 'created_on' + fieldData = format_time(issue.created_on) + when 'updated_on' + fieldData = format_time(issue.updated_on) + else + #custom field case... + + #parse the custom column id # from the column header + id = getIDFromCustomColName(columnName) + + #match it with a custom field id to extract the correct header and field data + custom_fields.each do |custom_field| #cycle through all possible custom fields + if id == custom_field.id.to_s() + fieldData = show_value(issue.custom_value_for(custom_field)) + break + end + end + end #end case + fields[counter][issue.id] = fieldData + end #end upto + end #end issues.each + return fields + end + + #Method changed 7/7/09 + def issues_to_csv(issues, query, project = nil) ic = Iconv.new(l(:general_csv_encoding), 'UTF-8') decimal_separator = l(:general_csv_decimal_separator) export = StringIO.new CSV::Writer.generate(export, l(:general_csv_separator)) do |csv| - # csv header fields - headers = [ "#", + # csv header fields + + #if the user has specified columns in the query... + if(@query.column_names != nil) + headers = Array.new(query.column_names.length()) #array for column headers + fields = Array.new(query.column_names.length()){[]} #array for column fields + custom_fields = project.nil? ? IssueCustomField.for_all : project.all_issue_custom_fields + + # - _ - Retrieve the header and column data. + + fields = retrieveFieldData(issues,query,decimal_separator,custom_fields) + headers = retrieveHeaders(query,custom_fields) + #fields[col][row] is a multi-dimensional array which holds all the field data for the csv table. + #...The first index is the column, and the second is the issue or row. + # + #headers[col] is an array which holds data for the column headers + + # - _ - Now, Print the headers and fields + + #Add bug number to the front of column headers list, via new array headersTemp + #Make '#' the first entry, then append with the 'headers' array + headersTemp = Array.new(1) + headersTemp[0] = "#" + 0.upto(headers.length()-1) do |counter| + headersTemp << headers[counter] + end + + #print headers, via headersTemp + csv << headersTemp.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end } + + #add bug number to the field arrays, via a new array called together + together = Array.new(issues.length()) + issues.each do |issue| #for each issue... + together[issue.id] = [issue.id.to_s()] #add the issue id + #for each column in the issue... + 0.upto(@query.column_names.length() - 1) do |counter| + # construct the issue rows from the columns. + together[issue.id] << fields[counter][issue.id] + end + #print the fields to the screen, issue at a time, via together + csv << together[issue.id].collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end } + end + else # if no fields specified --> then display default fields in csv + # csv default header fields + headers = [ "#", l(:field_status), l(:field_project), - l(:field_tracker), + l(:field_tracker), l(:field_priority), l(:field_subject), - l(:field_assigned_to), + l(:field_assigned_to), l(:field_category), l(:field_fixed_version), l(:field_author), @@ -156,32 +320,33 @@ # otherwise export custom fields marked as "For all projects" custom_fields = project.nil? ? IssueCustomField.for_all : project.all_issue_custom_fields custom_fields.each {|f| headers << f.name} - # Description in the last column + # Description in the last column headers << l(:field_description) - csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end } - # csv lines - issues.each do |issue| - fields = [issue.id, - issue.status.name, - issue.project.name, - issue.tracker.name, - issue.priority.name, - issue.subject, - issue.assigned_to, - issue.category, - issue.fixed_version, - issue.author.name, - format_date(issue.start_date), - format_date(issue.due_date), - issue.done_ratio, - issue.estimated_hours.to_s.gsub('.', decimal_separator), - format_time(issue.created_on), - format_time(issue.updated_on) - ] + csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end } + # csv lines + issues.each do |issue| + fields = [issue.id, + issue.status.name, + issue.project.name, + issue.tracker.name, + issue.priority.name, + issue.subject, + issue.assigned_to, + issue.category, + issue.fixed_version, + issue.author.name, + format_date(issue.start_date), + format_date(issue.due_date), + issue.done_ratio, + issue.estimated_hours.to_s.gsub('.', decimal_separator), + format_time(issue.created_on), + format_time(issue.updated_on) + ] custom_fields.each {|f| fields << show_value(issue.custom_value_for(f)) } fields << issue.description - csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end } - end + csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end } + end + end end export.rewind export Index: app/helpers/custom_fields_helper.rb =================================================================== --- app/helpers/custom_fields_helper.rb (revision 2819) +++ app/helpers/custom_fields_helper.rb (working copy) @@ -15,6 +15,10 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# Matthew Keniston & Alex Mendes +# BackOffice Associates +# 7/7/09 fixes for: 1) export to PDF/CSV ignoring custom queries; 2) query display problems + module CustomFieldsHelper def custom_fields_tabs @@ -85,4 +89,100 @@ def custom_field_formats_for_select CustomField::FIELD_FORMATS.sort {|a,b| a[1][:order]<=>b[1][:order]}.collect { |k| [ l(k[1][:name]), k[0] ] } end + + #Method Added 7/7/09 + #Extracts the id from a custom column name + #E.g. "cf_6" becomes "6", which is the id for the custom column + def getIDFromCustomColName(colName) + return colName.gsub(/.*_(.*)/, '\1') + end + +#Method Added 7/7/09 + #Capitalizes each word in a title + def capitalizeEachWord(title) + spaceFlag = nil + newTitle = "" + 0.upto(title.length() - 1) do |cntr| + char = title.slice(cntr..cntr) + if(cntr == 0) + char.capitalize! + elsif(char == " ") + spaceFlag = true + elsif(spaceFlag) + char.capitalize! + spaceFlag = nil + end + newTitle = newTitle + char + end + return newTitle + end + +#Method Added 7/7/09 + #Returns the type of the custom field or the column name for default fields + def getType(issue,pos) + fieldType = "" + + if (@query.column_names != nil) # make sure we have user query + + col = @query.column_names[pos].to_s() + + if (issue.respond_to?(col)) + fieldType = col.to_s() + + else # custom field + custom_fields = @project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields + columnName = @query.column_names[pos].to_s() + id = getIDFromCustomColName(columnName) + custom_fields.each do |custom_field| + if id == custom_field.id.to_s() + fieldType = custom_field.field_format.to_s() + break + end + end + end + end + return fieldType + end + +#Method Added 7/2/09 + #Given an issue and a column this returns the correct data + def getValue(issue, pos) + fieldValue = "" + col = @query.column_names[pos].to_s() + + #Default field + if(issue.respond_to?(col)) + type = (issue.send(col)).type.to_s() + case type + when "Time" + fieldValue = format_time(issue.send(col)) + when "Date" + fieldValue = format_date(issue.send(col)) + when "Float", "String", "int", "Fixnum", "" + fieldValue = issue.send(col).to_s() + when "Text", "Tracker","IssueStatus", "IssueCategory", "Enumeration", "Version", "User", "Project" + fieldValue = issue.send(col).name + end + + #Custom Field + else + custom_fields = @project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields + columnName = @query.column_names[pos].to_s() + id = getIDFromCustomColName(columnName) + custom_fields.each do |custom_field| + if id == custom_field.id.to_s() + type = custom_field.field_format.to_s() + fieldValue = show_value(issue.custom_value_for(custom_field)) + break + end + end + + #If type is a boolean value + if type == "bool" + fieldValue = (fieldValue == "1") ? "YES" : "NO" + end + end + + return fieldValue + end end Index: app/controllers/issues_controller.rb =================================================================== --- app/controllers/issues_controller.rb (revision 2819) +++ app/controllers/issues_controller.rb (working copy) @@ -15,6 +15,10 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# Matthew Keniston & Alex Mendes +# BackOffice Associates +# 7/7/09 fixes for: 1) export to PDF/CSV ignoring custom queries; 2) query display problems + class IssuesController < ApplicationController menu_item :new_issue, :only => :new @@ -77,8 +81,12 @@ render :template => 'issues/index.rhtml', :layout => !request.xhr? } format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") } - format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') } - format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') } + #Changed 7/7/09 - call smarter csv and pdf functions + format.csv { send_data(issues_to_csv(@issues, @query, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') } +# format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') } + format.pdf { send_data(issues_to_pdf(@issues, @query, @project), :type => 'application/pdf', :filename => 'export.pdf') } +# format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') } + end else # Send html if the query is not valid Index: app/views/issues/_list.rhtml =================================================================== --- app/views/issues/_list.rhtml (revision 2819) +++ app/views/issues/_list.rhtml (working copy) @@ -25,7 +25,19 @@ <%= check_box_tag("ids[]", issue.id, false, :id => nil) %> <%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %> - <% query.columns.each do |column| %><%= content_tag 'td', column_content(column, issue), :class => column.name %><% end %> + + + <% pos = 0 %> + <% query.columns.each do |column| %> + <% value = column.name %> + + <% if query.column_names != nil %> + <% value = getType(issue, pos) %> + <% pos = pos + 1 %> + <% end %> + + <%= content_tag 'td', column_content(column, issue), :class => value %> + <% end -%> <% end -%> Index: lib/redmine/export/pdf.rb =================================================================== --- lib/redmine/export/pdf.rb (revision 2819) +++ lib/redmine/export/pdf.rb (working copy) @@ -15,6 +15,10 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# Matthew Keniston & Alex Mendes +# BackOffice Associates +# 7/7/09 fixes for: 1) export to PDF/CSV ignoring custom queries; 2) query display problems + require 'iconv' require 'rfpdf/fpdf' require 'rfpdf/chinese' @@ -22,6 +26,11 @@ module Redmine module Export module PDF +#Added 7/2/09 +#***************************************************************************** + MAXCOLSIZE = 60 #Maximum column size before wrappping (string length) + MAXPDFWIDTH = 250 #Approximate width of pdf document +#***************************************************************************** include ActionView::Helpers::TextHelper include ActionView::Helpers::NumberHelper @@ -95,8 +104,12 @@ end || '' super w,h,txt,border,ln,align,fill,link end - + + #Method Changed 7/2/09 def Footer + tempSize = getFontSize() + tempFamily = getFontFamily() + tempStyle = getFontStyle() SetFont(@font_for_footer, 'I', 8) SetY(-15) SetX(15) @@ -104,13 +117,213 @@ SetY(-15) SetX(-30) Cell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C') + SetFont(tempFamily, tempStyle, tempSize) end end - + + #Method Added 7/2/09 + def printColumnHeadersPDF(query, issues, colWidth, row_height, fontSize, pdf, maxCustomContent) + # headers + pdf.SetFontStyle('B',fontSize) + pdf.SetFillColor(230, 230, 230) + + #Print the column header + pdf.Cell(20, row_height, "#", 0, 0, 'L', 1) + + 0.upto(@query.column_names.length - 1) do |cntr| + colName = query.column_names[cntr].to_s() + re = /^cf.*/ #identifier for the column names of custom fields + + # custom field + if (re.match(colName)) + + custom_fields = @project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields + id = getIDFromCustomColName(colName) + custom_fields.each do |custom_field| #find the name + if id == custom_field.id.to_s() + colName = custom_field.name.to_s() + break + end + end + end + + colName = colName.gsub(/[_]/, ' ').to_s() # convert underscores to spaces + colName = capitalizeEachWord(colName) + pdf.Cell(colWidth[cntr], row_height, colName, 0, 0, 'L', 1) + end + + end + + #Method Added 7/2/09 + def printFieldsPDF(query, issues, colWidth, row_height, fontSize, pdf, rowHeightMultiCell, maxMultiColWidth) + + pdf.Line(10, pdf.GetY, 287, pdf.GetY) + pdf.Ln + pdf.Line(10, pdf.GetY, 287, pdf.GetY) + pdf.SetY(pdf.GetY() + 1) + pdf.SetFontStyle('',fontSize) + pdf.SetFillColor(255, 255, 255) + + #Print the rows + issues.each do |issue| + flag = 0 #How many lines occur during a text wrapping + flagPageBreak = 0 #Check if a page break has occurred + pageBreakPos = 0 #Position after the page break used for restoring + + pdf.Cell(20, row_height, issue.id.to_s, 0, 0, 'L', 1) + + 0.upto(query.column_names.length - 1) do |cntr| + + if(getValue(issue,cntr).length < MAXCOLSIZE) #No text wrapping + pdf.SetFontStyle('',fontSize) + pdf.Cell(colWidth[cntr], row_height, getValue(issue,cntr)) + else #For text wrapping + beforeX = pdf.GetX() #Before X position + beforeY = pdf.GetY() #Before Y position + pad = 1 #Extra Height of muli-cells + + pdf.SetY(pdf.GetY() + pad) + pdf.SetX(beforeX) + pdf.MultiCell(colWidth[cntr], rowHeightMultiCell, getValue(issue,cntr)) + + afterX = pdf.GetX() + pdf.SetY(pdf.GetY() + pad) + pdf.SetX(afterX) + + currentPage = pdf.getPage() + afterY = pdf.GetY() #After Y position + dif = afterY - beforeY #Difference after text wrapping + if(afterY < beforeY) + pdf.setPage(pdf.getPage - 1) + flagPageBreak = 1 + if(pageBreakPos < afterY) + pageBreakPos = afterY + end + end + + #Count the number of lines skipped + if(dif/rowHeightMultiCell > flag) + flag = 0 + while (dif > 0) + dif = dif - rowHeightMultiCell + flag = flag + 1 + end + end + + pdf.SetY(beforeY) + pdf.SetX(beforeX + maxMultiColWidth) + + end + end + if(flagPageBreak == 1) #Restore Page after break + pdf.setPage(pdf.getPage() + 1) + pdf.SetY(pageBreakPos - row_height) + flagPageBreak = 0 + else + pdf.Line(10, pdf.GetY, 287, pdf.GetY) + end + #Restore after wrapping + if(flag > 0) + pdf.SetY(pdf.GetY() + rowHeightMultiCell * flag) + else + pdf.SetY(pdf.GetY() + 10) + end + end + end + +#Method Added 7/7/09 + def printUserQuery(pdf, row_height) + + rowHeightMultiCell = 4 #Default height for multi-cell + fontSize = 10 #Font size before shinking + maxMultiColWidth = 120 #Maximium column width before wrapping + maxCustomContent = 0 #Issue that contains the most custom fields + + arrMaxColLen = Array.new(@query.column_names.length) #Column Width in terms of characters based on the longest string + colWidth = Array.new(@query.column_names.length) #Contains the width of each column in pdf units + + + #Create an array containing the lengths of the largest fields in each column + 0.upto(@query.column_names.length - 1) do |cntr| + currentMax = 0; + @issues.each do |issue| + valueLength = getValue(issue,cntr).length() + if(valueLength > currentMax) + currentMax = valueLength + end + arrMaxColLen[cntr] = currentMax + end + end + + #See if any of the headers are longer than the largest value + 0.upto(@query.column_names.length - 1) do |cntr| + len = 0 #Length of the column name header + re = /^cf.*/ #Identify column fields by their column headers + + #Finds the length of the column name header + if (!re.match(@query.column_names[cntr].to_s())) + len = @query.column_names[cntr].to_s().length() + else #Custom Field + max = 0 + 0.upto(@issues.length - 1) do |issue| + if(@issues[issue].custom_values.length() > max) + max = @issues[issue].custom_values.length() + maxCustomContent = issue #Issue that contains the most custom fields + end + end + columnNameId = getIDFromCustomColName(@query.column_names[cntr].to_s()) + 0.upto(@issues[maxCustomContent].custom_values.length - 1) do |custom_value| + customField = @issues[maxCustomContent].custom_values[custom_value].custom_field + + if (customField.id.to_s() == columnNameId) + len = customField.name.length() #Length of the header of the custom fields + end + end + end + + #Check if column name headers are larger than the fields + if(arrMaxColLen[cntr] < len) + arrMaxColLen[cntr] = len + end + end + + #Set each column width given the value lengths + 0.upto(@query.column_names.length - 1) do |cntr| + ourColumnWidth = arrMaxColLen[cntr] * 1.5 + 10 + + colWidth[cntr] = (ourColumnWidth < maxMultiColWidth) ? ourColumnWidth : maxMultiColWidth + end + + #Total column length of the whole page + total = 0 + 0.upto(colWidth.length - 1) do |cntr| + total = total + colWidth[cntr] + end + + #Scales the font sizes based on column data width + percent = 1.0 + if(total > MAXPDFWIDTH) + percent = MAXPDFWIDTH.to_f()/total.to_f() + maxMultiColWidth = percent * maxMultiColWidth + 0.upto(@query.column_names.length - 1) do |cntr| + colWidth[cntr] = colWidth[cntr] * percent + end + fontSize = fontSize * 0.75 * percent + pdf.SetFontStyle('B',fontSize) + end + + printColumnHeadersPDF(@query, @issues, colWidth, row_height, fontSize, pdf, maxCustomContent) + printFieldsPDF (@query, @issues, colWidth, row_height, fontSize, pdf, rowHeightMultiCell, maxMultiColWidth) + end + # Returns a PDF string of a list of issues - def issues_to_pdf(issues, project, query) + def issues_to_pdf(issues, query, project) pdf = IFPDF.new(current_language) - title = project ? "#{project} - #{l(:label_issue_plural)}" : "#{l(:label_issue_plural)}" + if(query.name != "_") + title = project ? "#{project} - #{@query.name.to_s()}" : " #{@query.name.to_s()}" + else + title = project ? "#{project} - #{l(:label_issue_plural)}" : "#{l(:label_issue_plural)}" + end pdf.SetTitle(title) pdf.AliasNbPages pdf.footer_date = format_date(Date.today) @@ -122,46 +335,51 @@ pdf.Cell(190,10, title) pdf.Ln - # headers - pdf.SetFontStyle('B',10) - pdf.SetFillColor(230, 230, 230) - pdf.Cell(15, row_height, "#", 0, 0, 'L', 1) - pdf.Cell(30, row_height, l(:field_tracker), 0, 0, 'L', 1) - pdf.Cell(30, row_height, l(:field_status), 0, 0, 'L', 1) - pdf.Cell(30, row_height, l(:field_priority), 0, 0, 'L', 1) - pdf.Cell(40, row_height, l(:field_assigned_to), 0, 0, 'L', 1) - pdf.Cell(25, row_height, l(:field_updated_on), 0, 0, 'L', 1) - pdf.Cell(0, row_height, l(:field_subject), 0, 0, 'L', 1) - pdf.Line(10, pdf.GetY, 287, pdf.GetY) - pdf.Ln - pdf.Line(10, pdf.GetY, 287, pdf.GetY) - pdf.SetY(pdf.GetY() + 1) + # headers + #If there are column names + if(@query.column_names != nil) + printUserQuery(pdf, row_height) + else #If there are no columns + pdf.SetFontStyle('B',10) + pdf.SetFillColor(230, 230, 230) + pdf.Cell(15, row_height, "#", 0, 0, 'L', 1) + pdf.Cell(30, row_height, l(:field_tracker), 0, 0, 'L', 1) + pdf.Cell(30, row_height, l(:field_status), 0, 0, 'L', 1) + pdf.Cell(30, row_height, l(:field_priority), 0, 0, 'L', 1) + pdf.Cell(40, row_height, l(:field_assigned_to), 0, 0, 'L', 1) + pdf.Cell(25, row_height, l(:field_updated_on), 0, 0, 'L', 1) + pdf.Cell(0, row_height, l(:field_subject), 0, 0, 'L', 1) + pdf.Line(10, pdf.GetY, 287, pdf.GetY) + pdf.Ln + pdf.Line(10, pdf.GetY, 287, pdf.GetY) + pdf.SetY(pdf.GetY() + 1) - # rows - pdf.SetFontStyle('',9) - pdf.SetFillColor(255, 255, 255) - group = false - issues.each do |issue| - if query.grouped? && issue.send(query.group_by) != group - group = issue.send(query.group_by) - pdf.SetFontStyle('B',10) - pdf.Cell(0, row_height, "#{group.blank? ? 'None' : group.to_s}", 0, 1, 'L') + # rows + pdf.SetFontStyle('',9) + pdf.SetFillColor(255, 255, 255) + group = false + issues.each do |issue| + if query.grouped? && issue.send(query.group_by) != group + group = issue.send(query.group_by) + pdf.SetFontStyle('B',10) + pdf.Cell(0, row_height, "#{group.blank? ? 'None' : group.to_s}", 0, 1, 'L') + pdf.Line(10, pdf.GetY, 287, pdf.GetY) + pdf.SetY(pdf.GetY() + 0.5) + pdf.Line(10, pdf.GetY, 287, pdf.GetY) + pdf.SetY(pdf.GetY() + 1) + pdf.SetFontStyle('',9) + end + pdf.Cell(15, row_height, issue.id.to_s, 0, 0, 'L', 1) + pdf.Cell(30, row_height, issue.tracker.name, 0, 0, 'L', 1) + pdf.Cell(30, row_height, issue.status.name, 0, 0, 'L', 1) + pdf.Cell(30, row_height, issue.priority.name, 0, 0, 'L', 1) + pdf.Cell(40, row_height, issue.assigned_to ? issue.assigned_to.to_s : '', 0, 0, 'L', 1) + pdf.Cell(25, row_height, format_date(issue.updated_on), 0, 0, 'L', 1) + pdf.MultiCell(0, row_height, (project == issue.project ? issue.subject : "#{issue.project} - #{issue.subject}")) pdf.Line(10, pdf.GetY, 287, pdf.GetY) - pdf.SetY(pdf.GetY() + 0.5) - pdf.Line(10, pdf.GetY, 287, pdf.GetY) pdf.SetY(pdf.GetY() + 1) - pdf.SetFontStyle('',9) end - pdf.Cell(15, row_height, issue.id.to_s, 0, 0, 'L', 1) - pdf.Cell(30, row_height, issue.tracker.name, 0, 0, 'L', 1) - pdf.Cell(30, row_height, issue.status.name, 0, 0, 'L', 1) - pdf.Cell(30, row_height, issue.priority.name, 0, 0, 'L', 1) - pdf.Cell(40, row_height, issue.assigned_to ? issue.assigned_to.to_s : '', 0, 0, 'L', 1) - pdf.Cell(25, row_height, format_date(issue.updated_on), 0, 0, 'L', 1) - pdf.MultiCell(0, row_height, (project == issue.project ? issue.subject : "#{issue.project} - #{issue.subject}")) - pdf.Line(10, pdf.GetY, 287, pdf.GetY) - pdf.SetY(pdf.GetY() + 1) - end + end pdf.Output end Index: vendor/plugins/rfpdf/lib/rfpdf/fpdf.rb =================================================================== --- vendor/plugins/rfpdf/lib/rfpdf/fpdf.rb (revision 2819) +++ vendor/plugins/rfpdf/lib/rfpdf/fpdf.rb (working copy) @@ -19,6 +19,10 @@ # Handle '\n' at the beginning of a string # Bookmarks contributed by Sylvain Lafleur. +# Matthew Keniston & Alex Mendes +# BackOffice Associates +# 7/7/09 fixes for: 1) export to PDF/CSV ignoring custom queries; 2) query display problems + require 'date' require 'zlib' @@ -279,6 +283,14 @@ end def AddPage(orientation='') +#Added 7/2/09 +#************************************ + #If next page already exists + if(getPage() < @pages.length() - 1) + setPage(getPage() + 1) + @y = 0 + @tMargin +#************************************ + else # Start a new page self.Open if @state==0 family=@FontFamily @@ -333,6 +345,7 @@ end @TextColor=tc @ColorFlag=cf + end end def Header @@ -872,6 +885,28 @@ @y=@y+h end end + + #Added 7/2/09 for pdf.rb +#*********************** + def getFontFamily + @FontFamily + end + + def getFontStyle + @FontStyle + end + + def getFontSize + @FontSizePt + end + def getPage() + @page + end + + def setPage(page) + @page = page + end +#********************** def GetX # Get x position Index: public/stylesheets/application.css =================================================================== --- public/stylesheets/application.css (revision 2819) +++ public/stylesheets/application.css (working copy) @@ -94,9 +94,20 @@ tr.project td.name a { padding-left: 16px; white-space:nowrap; } tr.project.parent td.name a { background: url('../images/bullet_toggle_minus.png') no-repeat; } + + /*** Changed 7/7/09 - nowrap was a bad idea! ***/ +tr.issue { text-align: center; white-space: normal; } +/* tr.issue { text-align: center; white-space: nowrap; } tr.issue td.subject, tr.issue td.category, td.assigned_to { white-space: normal; } +*/ + +/*** Changed 7/7/09 - Added left alignment for text-based custom columns ***/ +tr.issue td.subject, tr.issue td.text, tr.issue td.string { text-align: left; } +/* tr.issue td.subject { text-align: left; } +*/ + tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;} tr.entry { border: 1px solid #f8f8f8; }