Patch #3614 » export_issues_to_pdf_csv_r2823_win32.diff
| app/helpers/issues_helper.rb (working copy) | ||
|---|---|---|
| 129 | 129 |
end |
| 130 | 130 |
end |
| 131 | 131 |
|
| 132 |
def issues_to_csv(issues, project = nil) |
|
| 132 |
def retrieveHeaders(query, custom_fields) |
|
| 133 |
headers = Array.new(query.column_names.length()) |
|
| 134 |
0.upto(query.column_names.length() - 1) do |counter| |
|
| 135 |
headerName = nil |
|
| 136 |
columnName = query.column_names[counter].to_s() |
|
| 137 |
case columnName |
|
| 138 |
when 'status' |
|
| 139 |
headerName = l(:field_status) |
|
| 140 |
when 'project' |
|
| 141 |
headerName = l(:field_project) |
|
| 142 |
when 'tracker' |
|
| 143 |
headerName = l(:field_tracker) |
|
| 144 |
when 'priority' |
|
| 145 |
headerName = l(:field_priority) |
|
| 146 |
when 'subject' |
|
| 147 |
headerName = l(:field_subject) |
|
| 148 |
when 'assigned_to' |
|
| 149 |
headerName = l(:field_assigned_to) |
|
| 150 |
when 'category' |
|
| 151 |
headerName = l(:field_category) |
|
| 152 |
when 'fixed_version' |
|
| 153 |
headerName = l(:field_fixed_version) |
|
| 154 |
when 'author' |
|
| 155 |
headerName = l(:field_author) |
|
| 156 |
when 'start_date' |
|
| 157 |
headerName = l(:field_start_date) |
|
| 158 |
when 'due_date' |
|
| 159 |
headerName = l(:field_due_date) |
|
| 160 |
when 'done_ratio' |
|
| 161 |
headerName = l(:field_done_ratio) |
|
| 162 |
when 'estimated_hours' |
|
| 163 |
headerName = l(:field_estimated_hours) |
|
| 164 |
when 'created_on' |
|
| 165 |
headerName = l(:field_created_on) |
|
| 166 |
when 'updated_on' |
|
| 167 |
headerName = l(:field_updated_on) |
|
| 168 |
else |
|
| 169 |
#custom field case... |
|
| 170 |
|
|
| 171 |
#parse the custom column id # from the column header |
|
| 172 |
id = getIDFromCustomColName(columnName) |
|
| 173 |
|
|
| 174 |
#match it with a custom field id to extract the correct header and field data |
|
| 175 |
custom_fields.each do |custom_field| #cycle through all possible custom fields |
|
| 176 |
if id == custom_field.id.to_s() |
|
| 177 |
headerName = custom_field.name.to_s() |
|
| 178 |
break |
|
| 179 |
end |
|
| 180 |
end |
|
| 181 |
end |
|
| 182 |
headers[counter] = headerName != nil ? headerName : '' |
|
| 183 |
end |
|
| 184 |
return headers |
|
| 185 |
end |
|
| 186 |
|
|
| 187 |
def retrieveFieldData(issues, query, decimal_separator,custom_fields) |
|
| 188 |
fields = Array.new(query.column_names.length()){[]}
|
|
| 189 |
#cycle through each issue (row) in the table.. |
|
| 190 |
issues.each do |issue| |
|
| 191 |
#cycle through each column in the row... |
|
| 192 |
#loading the column names from the passed query... |
|
| 193 |
#in order to load the correct column data for each issue --> fields2[column][issue] |
|
| 194 |
#this method also loads the headers for each column --> headers2[column] |
|
| 195 |
0.upto(query.column_names.length() - 1) do |counter| |
|
| 196 |
columnName = query.column_names[counter].to_s() |
|
| 197 |
case columnName |
|
| 198 |
when 'status' |
|
| 199 |
fieldData = issue.status.name |
|
| 200 |
when 'project' |
|
| 201 |
fieldData = issue.project.name |
|
| 202 |
when 'tracker' |
|
| 203 |
fieldData = issue.tracker.name |
|
| 204 |
when 'priority' |
|
| 205 |
fieldData = issue.priority.name |
|
| 206 |
when 'subject' |
|
| 207 |
fieldData= issue.subject |
|
| 208 |
when 'assigned_to' |
|
| 209 |
fieldData = issue.assigned_to |
|
| 210 |
when 'category' |
|
| 211 |
fieldData = issue.category |
|
| 212 |
when 'fixed_version' |
|
| 213 |
fieldData = issue.fixed_version |
|
| 214 |
when 'author' |
|
| 215 |
fieldData = issue.author.name |
|
| 216 |
when 'start_date' |
|
| 217 |
fieldData = format_date(issue.start_date) |
|
| 218 |
when 'due_date' |
|
| 219 |
fieldData = format_date(issue.due_date) |
|
| 220 |
when 'done_ratio' |
|
| 221 |
fieldData = issue.done_ratio |
|
| 222 |
when 'estimated_hours' |
|
| 223 |
fieldData = issue.estimated_hours.to_s.gsub('.', decimal_separator)
|
|
| 224 |
when 'created_on' |
|
| 225 |
fieldData = format_time(issue.created_on) |
|
| 226 |
when 'updated_on' |
|
| 227 |
fieldData = format_time(issue.updated_on) |
|
| 228 |
else |
|
| 229 |
#custom field case... |
|
| 230 |
|
|
| 231 |
#parse the custom column id # from the column header |
|
| 232 |
id = getIDFromCustomColName(columnName) |
|
| 233 |
|
|
| 234 |
#match it with a custom field id to extract the correct header and field data |
|
| 235 |
custom_fields.each do |custom_field| #cycle through all possible custom fields |
|
| 236 |
if id == custom_field.id.to_s() |
|
| 237 |
fieldData = show_value(issue.custom_value_for(custom_field)) |
|
| 238 |
break |
|
| 239 |
end |
|
| 240 |
end |
|
| 241 |
end #end case |
|
| 242 |
fields[counter][issue.id] = fieldData |
|
| 243 |
end #end upto |
|
| 244 |
end #end issues.each |
|
| 245 |
return fields |
|
| 246 |
end |
|
| 247 |
|
|
| 248 |
#Method changed 7/7/09 |
|
| 249 |
def issues_to_csv(issues, query, project = nil) |
|
| 133 | 250 |
ic = Iconv.new(l(:general_csv_encoding), 'UTF-8') |
| 134 | 251 |
decimal_separator = l(:general_csv_decimal_separator) |
| 135 | 252 |
export = StringIO.new |
| 136 | 253 |
CSV::Writer.generate(export, l(:general_csv_separator)) do |csv| |
| 137 |
# csv header fields |
|
| 138 |
headers = [ "#", |
|
| 254 |
# csv header fields |
|
| 255 |
|
|
| 256 |
#if the user has specified columns in the query... |
|
| 257 |
if(@query.column_names != nil) |
|
| 258 |
headers = Array.new(query.column_names.length()) #array for column headers |
|
| 259 |
fields = Array.new(query.column_names.length()){[]} #array for column fields
|
|
| 260 |
custom_fields = project.nil? ? IssueCustomField.for_all : project.all_issue_custom_fields |
|
| 261 |
|
|
| 262 |
# - _ - Retrieve the header and column data. |
|
| 263 |
|
|
| 264 |
fields = retrieveFieldData(issues,query,decimal_separator,custom_fields) |
|
| 265 |
headers = retrieveHeaders(query,custom_fields) |
|
| 266 |
#fields[col][row] is a multi-dimensional array which holds all the field data for the csv table. |
|
| 267 |
#...The first index is the column, and the second is the issue or row. |
|
| 268 |
# |
|
| 269 |
#headers[col] is an array which holds data for the column headers |
|
| 270 |
|
|
| 271 |
# - _ - Now, Print the headers and fields |
|
| 272 |
|
|
| 273 |
#Add bug number to the front of column headers list, via new array headersTemp |
|
| 274 |
#Make '#' the first entry, then append with the 'headers' array |
|
| 275 |
headersTemp = Array.new(1) |
|
| 276 |
headersTemp[0] = "#" |
|
| 277 |
0.upto(headers.length()-1) do |counter| |
|
| 278 |
headersTemp << headers[counter] |
|
| 279 |
end |
|
| 280 |
|
|
| 281 |
#print headers, via headersTemp |
|
| 282 |
csv << headersTemp.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
|
|
| 283 |
|
|
| 284 |
#add bug number to the field arrays, via a new array called together |
|
| 285 |
together = Array.new(issues.length()) |
|
| 286 |
issues.each do |issue| #for each issue... |
|
| 287 |
together[issue.id] = [issue.id.to_s()] #add the issue id |
|
| 288 |
#for each column in the issue... |
|
| 289 |
0.upto(@query.column_names.length() - 1) do |counter| |
|
| 290 |
# construct the issue rows from the columns. |
|
| 291 |
together[issue.id] << fields[counter][issue.id] |
|
| 292 |
end |
|
| 293 |
#print the fields to the screen, issue at a time, via together |
|
| 294 |
csv << together[issue.id].collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
|
|
| 295 |
end |
|
| 296 |
else # if no fields specified --> then display default fields in csv |
|
| 297 |
# csv default header fields |
|
| 298 |
headers = [ "#", |
|
| 139 | 299 |
l(:field_status), |
| 140 | 300 |
l(:field_project), |
| 141 | 301 |
l(:field_tracker), |
| ... | ... | |
| 183 | 343 |
csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
|
| 184 | 344 |
end |
| 185 | 345 |
end |
| 346 |
end |
|
| 186 | 347 |
export.rewind |
| 187 | 348 |
export |
| 188 | 349 |
end |
| app/helpers/custom_fields_helper.rb (working copy) | ||
|---|---|---|
| 85 | 85 |
def custom_field_formats_for_select |
| 86 | 86 |
CustomField::FIELD_FORMATS.sort {|a,b| a[1][:order]<=>b[1][:order]}.collect { |k| [ l(k[1][:name]), k[0] ] }
|
| 87 | 87 |
end |
| 88 |
end |
|
| 88 |
|
|
| 89 |
|
|
| 90 |
#Method Added 7/7/09 |
|
| 91 |
#Extracts the id from a custom column name |
|
| 92 |
#E.g. "cf_6" becomes "6", which is the id for the custom column |
|
| 93 |
def getIDFromCustomColName(colName) |
|
| 94 |
return colName.gsub(/.*_(.*)/, '\1') |
|
| 95 |
end |
|
| 96 |
|
|
| 97 |
#Method Added 7/7/09 |
|
| 98 |
#Capitalizes each word in a title |
|
| 99 |
def capitalizeEachWord(title) |
|
| 100 |
spaceFlag = nil |
|
| 101 |
newTitle = "" |
|
| 102 |
0.upto(title.length() - 1) do |cntr| |
|
| 103 |
char = title.slice(cntr..cntr) |
|
| 104 |
if(cntr == 0) |
|
| 105 |
char.capitalize! |
|
| 106 |
elsif(char == " ") |
|
| 107 |
spaceFlag = true |
|
| 108 |
elsif(spaceFlag) |
|
| 109 |
char.capitalize! |
|
| 110 |
spaceFlag = nil |
|
| 111 |
end |
|
| 112 |
newTitle = newTitle + char |
|
| 113 |
end |
|
| 114 |
return newTitle |
|
| 115 |
end |
|
| 116 |
|
|
| 117 |
#Method Added 7/7/09 |
|
| 118 |
#Returns the type of the custom field or the column name for default fields |
|
| 119 |
def getType(issue,pos) |
|
| 120 |
fieldType = "" |
|
| 121 |
|
|
| 122 |
if (@query.column_names != nil) # make sure we have user query |
|
| 123 |
|
|
| 124 |
col = @query.column_names[pos].to_s() |
|
| 125 |
|
|
| 126 |
if (issue.respond_to?(col)) |
|
| 127 |
fieldType = col.to_s() |
|
| 128 |
|
|
| 129 |
else # custom field |
|
| 130 |
custom_fields = @project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields |
|
| 131 |
columnName = @query.column_names[pos].to_s() |
|
| 132 |
id = getIDFromCustomColName(columnName) |
|
| 133 |
custom_fields.each do |custom_field| |
|
| 134 |
if id == custom_field.id.to_s() |
|
| 135 |
fieldType = custom_field.field_format.to_s() |
|
| 136 |
break |
|
| 137 |
end |
|
| 138 |
end |
|
| 139 |
end |
|
| 140 |
end |
|
| 141 |
return fieldType |
|
| 142 |
end |
|
| 143 |
|
|
| 144 |
#Method Added 7/2/09 |
|
| 145 |
#Given an issue and a column this returns the correct data |
|
| 146 |
def getValue(issue, pos) |
|
| 147 |
fieldValue = "" |
|
| 148 |
col = @query.column_names[pos].to_s() |
|
| 149 |
|
|
| 150 |
#Default field |
|
| 151 |
if(issue.respond_to?(col)) |
|
| 152 |
type = (issue.send(col)).type.to_s() |
|
| 153 |
case type |
|
| 154 |
when "Time" |
|
| 155 |
fieldValue = format_time(issue.send(col)) |
|
| 156 |
when "Date" |
|
| 157 |
fieldValue = format_date(issue.send(col)) |
|
| 158 |
when "Float", "String", "int", "Fixnum", "" |
|
| 159 |
fieldValue = issue.send(col).to_s() |
|
| 160 |
when "Text", "Tracker","IssueStatus", "IssueCategory", "Enumeration", "Version", "User", "Project" |
|
| 161 |
fieldValue = issue.send(col).name |
|
| 162 |
end |
|
| 163 |
|
|
| 164 |
#Custom Field |
|
| 165 |
else |
|
| 166 |
custom_fields = @project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields |
|
| 167 |
columnName = @query.column_names[pos].to_s() |
|
| 168 |
id = getIDFromCustomColName(columnName) |
|
| 169 |
custom_fields.each do |custom_field| |
|
| 170 |
if id == custom_field.id.to_s() |
|
| 171 |
type = custom_field.field_format.to_s() |
|
| 172 |
fieldValue = show_value(issue.custom_value_for(custom_field)) |
|
| 173 |
break |
|
| 174 |
end |
|
| 175 |
end |
|
| 176 |
|
|
| 177 |
#If type is a boolean value |
|
| 178 |
if type == "bool" |
|
| 179 |
fieldValue = (fieldValue == "1") ? "YES" : "NO" |
|
| 180 |
end |
|
| 181 |
end |
|
| 182 |
|
|
| 183 |
return fieldValue |
|
| 184 |
end |
|
| 185 |
end |
|
| app/controllers/issues_controller.rb (working copy) | ||
|---|---|---|
| 77 | 77 |
render :template => 'issues/index.rhtml', :layout => !request.xhr? |
| 78 | 78 |
} |
| 79 | 79 |
format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
|
| 80 |
format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
|
|
| 81 |
format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
|
|
| 80 |
#Changed 7/7/09 - call smarter csv and pdf functions |
|
| 81 |
format.csv { send_data(issues_to_csv(@issues, @query, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
|
|
| 82 |
# format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
|
|
| 83 |
format.pdf { send_data(issues_to_pdf(@issues, @query, @project), :type => 'application/pdf', :filename => 'export.pdf') }
|
|
| 84 |
# format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
|
|
| 85 |
|
|
| 82 | 86 |
end |
| 83 | 87 |
else |
| 84 | 88 |
# Send html if the query is not valid |
| app/views/issues/_list.rhtml (working copy) | ||
|---|---|---|
| 25 | 25 |
<tr id="issue-<%= issue.id %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= issue.css_classes %>">
|
| 26 | 26 |
<td class="checkbox"><%= check_box_tag("ids[]", issue.id, false, :id => nil) %></td>
|
| 27 | 27 |
<td><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td> |
| 28 |
<% query.columns.each do |column| %><%= content_tag 'td', column_content(column, issue), :class => column.name %><% end %> |
|
| 29 |
</tr> |
|
| 28 |
<!-- Changed 7/8/09 - support smarter css class for custom fields --> |
|
| 29 |
|
|
| 30 |
<% pos = 0 %> |
|
| 31 |
<% query.columns.each do |column| %> |
|
| 32 |
<% value = column.name %> |
|
| 33 |
|
|
| 34 |
<% if query.column_names != nil %> <!-- it's a user query, so may contain custom fields --> |
|
| 35 |
<% value = getType(issue, pos) %> |
|
| 36 |
<% pos = pos + 1 %> |
|
| 37 |
<% end %> |
|
| 38 |
|
|
| 39 |
<%= content_tag 'td', column_content(column, issue), :class => value %> |
|
| 40 |
<% end -%> |
|
| 41 |
</tr> |
|
| 30 | 42 |
<% end -%> |
| 31 | 43 |
</tbody> |
| 32 | 44 |
</table> |
| lib/redmine/export/pdf.rb (working copy) | ||
|---|---|---|
| 22 | 22 |
module Redmine |
| 23 | 23 |
module Export |
| 24 | 24 |
module PDF |
| 25 |
#Added 7/2/09 |
|
| 26 |
#***************************************************************************** |
|
| 27 |
MAXCOLSIZE = 60 #Maximum column size before wrappping (string length) |
|
| 28 |
MAXPDFWIDTH = 250 #Approximate width of pdf document |
|
| 29 |
#***************************************************************************** |
|
| 25 | 30 |
include ActionView::Helpers::TextHelper |
| 26 | 31 |
include ActionView::Helpers::NumberHelper |
| 27 | 32 |
|
| ... | ... | |
| 96 | 101 |
super w,h,txt,border,ln,align,fill,link |
| 97 | 102 |
end |
| 98 | 103 |
|
| 99 |
def Footer |
|
| 104 |
#Method Changed 7/2/09 |
|
| 105 |
def Footer |
|
| 106 |
tempSize = getFontSize() |
|
| 107 |
tempFamily = getFontFamily() |
|
| 108 |
tempStyle = getFontStyle() |
|
| 100 | 109 |
SetFont(@font_for_footer, 'I', 8) |
| 101 | 110 |
SetY(-15) |
| 102 | 111 |
SetX(15) |
| ... | ... | |
| 104 | 113 |
SetY(-15) |
| 105 | 114 |
SetX(-30) |
| 106 | 115 |
Cell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
|
| 116 |
SetFont(tempFamily, tempStyle, tempSize) |
|
| 107 | 117 |
end |
| 108 | 118 |
end |
| 109 |
|
|
| 110 |
# Returns a PDF string of a list of issues |
|
| 111 |
def issues_to_pdf(issues, project, query) |
|
| 112 |
pdf = IFPDF.new(current_language) |
|
| 113 |
title = project ? "#{project} - #{l(:label_issue_plural)}" : "#{l(:label_issue_plural)}"
|
|
| 119 |
#Method Added 7/2/09 |
|
| 120 |
def printColumnHeadersPDF(query, issues, colWidth, row_height, fontSize, pdf, maxCustomContent) |
|
| 121 |
# headers |
|
| 122 |
pdf.SetFontStyle('B',fontSize)
|
|
| 123 |
pdf.SetFillColor(230, 230, 230) |
|
| 124 |
|
|
| 125 |
#Print the column header |
|
| 126 |
pdf.Cell(20, row_height, "#", 0, 0, 'L', 1) |
|
| 127 |
|
|
| 128 |
0.upto(@query.column_names.length - 1) do |cntr| |
|
| 129 |
colName = query.column_names[cntr].to_s() |
|
| 130 |
re = /^cf.*/ #identifier for the column names of custom fields |
|
| 131 |
|
|
| 132 |
# custom field |
|
| 133 |
if (re.match(colName)) |
|
| 134 |
|
|
| 135 |
custom_fields = @project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields |
|
| 136 |
id = getIDFromCustomColName(colName) |
|
| 137 |
custom_fields.each do |custom_field| #find the name |
|
| 138 |
if id == custom_field.id.to_s() |
|
| 139 |
colName = custom_field.name.to_s() |
|
| 140 |
break |
|
| 141 |
end |
|
| 142 |
end |
|
| 143 |
end |
|
| 144 |
|
|
| 145 |
colName = colName.gsub(/[_]/, ' ').to_s() # convert underscores to spaces |
|
| 146 |
colName = capitalizeEachWord(colName) |
|
| 147 |
pdf.Cell(colWidth[cntr], row_height, colName, 0, 0, 'L', 1) |
|
| 148 |
end |
|
| 149 |
|
|
| 150 |
end |
|
| 151 |
|
|
| 152 |
#Method Added 7/2/09 |
|
| 153 |
def printFieldsPDF(query, issues, colWidth, row_height, fontSize, pdf, rowHeightMultiCell, maxMultiColWidth) |
|
| 154 |
|
|
| 155 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 156 |
pdf.Ln |
|
| 157 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 158 |
pdf.SetY(pdf.GetY() + 1) |
|
| 159 |
pdf.SetFontStyle('',fontSize)
|
|
| 160 |
pdf.SetFillColor(255, 255, 255) |
|
| 161 |
|
|
| 162 |
#Print the rows |
|
| 163 |
issues.each do |issue| |
|
| 164 |
flag = 0 #How many lines occur during a text wrapping |
|
| 165 |
flagPageBreak = 0 #Check if a page break has occurred |
|
| 166 |
pageBreakPos = 0 #Position after the page break used for restoring |
|
| 167 |
|
|
| 168 |
pdf.Cell(20, row_height, issue.id.to_s, 0, 0, 'L', 1) |
|
| 169 |
|
|
| 170 |
0.upto(query.column_names.length - 1) do |cntr| |
|
| 171 |
|
|
| 172 |
if(getValue(issue,cntr).length < MAXCOLSIZE) #No text wrapping |
|
| 173 |
pdf.SetFontStyle('',fontSize)
|
|
| 174 |
pdf.Cell(colWidth[cntr], row_height, getValue(issue,cntr)) |
|
| 175 |
else #For text wrapping |
|
| 176 |
beforeX = pdf.GetX() #Before X position |
|
| 177 |
beforeY = pdf.GetY() #Before Y position |
|
| 178 |
pad = 1 #Extra Height of muli-cells |
|
| 179 |
|
|
| 180 |
pdf.SetY(pdf.GetY() + pad) |
|
| 181 |
pdf.SetX(beforeX) |
|
| 182 |
pdf.MultiCell(colWidth[cntr], rowHeightMultiCell, getValue(issue,cntr)) |
|
| 183 |
|
|
| 184 |
afterX = pdf.GetX() |
|
| 185 |
pdf.SetY(pdf.GetY() + pad) |
|
| 186 |
pdf.SetX(afterX) |
|
| 187 |
|
|
| 188 |
currentPage = pdf.getPage() |
|
| 189 |
afterY = pdf.GetY() #After Y position |
|
| 190 |
dif = afterY - beforeY #Difference after text wrapping |
|
| 191 |
if(afterY < beforeY) |
|
| 192 |
pdf.setPage(pdf.getPage - 1) |
|
| 193 |
flagPageBreak = 1 |
|
| 194 |
if(pageBreakPos < afterY) |
|
| 195 |
pageBreakPos = afterY |
|
| 196 |
end |
|
| 197 |
end |
|
| 198 |
|
|
| 199 |
#Count the number of lines skipped |
|
| 200 |
if(dif/rowHeightMultiCell > flag) |
|
| 201 |
flag = 0 |
|
| 202 |
while (dif > 0) |
|
| 203 |
dif = dif - rowHeightMultiCell |
|
| 204 |
flag = flag + 1 |
|
| 205 |
end |
|
| 206 |
end |
|
| 207 |
|
|
| 208 |
pdf.SetY(beforeY) |
|
| 209 |
pdf.SetX(beforeX + maxMultiColWidth) |
|
| 210 |
|
|
| 211 |
end |
|
| 212 |
end |
|
| 213 |
if(flagPageBreak == 1) #Restore Page after break |
|
| 214 |
pdf.setPage(pdf.getPage() + 1) |
|
| 215 |
pdf.SetY(pageBreakPos - row_height) |
|
| 216 |
flagPageBreak = 0 |
|
| 217 |
else |
|
| 218 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 219 |
end |
|
| 220 |
#Restore after wrapping |
|
| 221 |
if(flag > 0) |
|
| 222 |
pdf.SetY(pdf.GetY() + rowHeightMultiCell * flag) |
|
| 223 |
else |
|
| 224 |
pdf.SetY(pdf.GetY() + 10) |
|
| 225 |
end |
|
| 226 |
end |
|
| 227 |
end |
|
| 228 |
|
|
| 229 |
#Method Added 7/7/09 |
|
| 230 |
def printUserQuery(pdf, row_height) |
|
| 231 |
|
|
| 232 |
rowHeightMultiCell = 4 #Default height for multi-cell |
|
| 233 |
fontSize = 10 #Font size before shinking |
|
| 234 |
maxMultiColWidth = 120 #Maximium column width before wrapping |
|
| 235 |
maxCustomContent = 0 #Issue that contains the most custom fields |
|
| 236 |
|
|
| 237 |
arrMaxColLen = Array.new(@query.column_names.length) #Column Width in terms of characters based on the longest string |
|
| 238 |
colWidth = Array.new(@query.column_names.length) #Contains the width of each column in pdf units |
|
| 239 |
|
|
| 240 |
|
|
| 241 |
#Create an array containing the lengths of the largest fields in each column |
|
| 242 |
0.upto(@query.column_names.length - 1) do |cntr| |
|
| 243 |
currentMax = 0; |
|
| 244 |
@issues.each do |issue| |
|
| 245 |
valueLength = getValue(issue,cntr).length() |
|
| 246 |
if(valueLength > currentMax) |
|
| 247 |
currentMax = valueLength |
|
| 248 |
end |
|
| 249 |
arrMaxColLen[cntr] = currentMax |
|
| 250 |
end |
|
| 251 |
end |
|
| 252 |
|
|
| 253 |
#See if any of the headers are longer than the largest value |
|
| 254 |
0.upto(@query.column_names.length - 1) do |cntr| |
|
| 255 |
len = 0 #Length of the column name header |
|
| 256 |
re = /^cf.*/ #Identify column fields by their column headers |
|
| 257 |
|
|
| 258 |
#Finds the length of the column name header |
|
| 259 |
if (!re.match(@query.column_names[cntr].to_s())) |
|
| 260 |
len = @query.column_names[cntr].to_s().length() |
|
| 261 |
else #Custom Field |
|
| 262 |
max = 0 |
|
| 263 |
0.upto(@issues.length - 1) do |issue| |
|
| 264 |
if(@issues[issue].custom_values.length() > max) |
|
| 265 |
max = @issues[issue].custom_values.length() |
|
| 266 |
maxCustomContent = issue #Issue that contains the most custom fields |
|
| 267 |
end |
|
| 268 |
end |
|
| 269 |
columnNameId = getIDFromCustomColName(@query.column_names[cntr].to_s()) |
|
| 270 |
0.upto(@issues[maxCustomContent].custom_values.length - 1) do |custom_value| |
|
| 271 |
customField = @issues[maxCustomContent].custom_values[custom_value].custom_field |
|
| 272 |
|
|
| 273 |
if (customField.id.to_s() == columnNameId) |
|
| 274 |
len = customField.name.length() #Length of the header of the custom fields |
|
| 275 |
end |
|
| 276 |
end |
|
| 277 |
end |
|
| 278 |
|
|
| 279 |
#Check if column name headers are larger than the fields |
|
| 280 |
if(arrMaxColLen[cntr] < len) |
|
| 281 |
arrMaxColLen[cntr] = len |
|
| 282 |
end |
|
| 283 |
end |
|
| 284 |
|
|
| 285 |
#Set each column width given the value lengths |
|
| 286 |
0.upto(@query.column_names.length - 1) do |cntr| |
|
| 287 |
ourColumnWidth = arrMaxColLen[cntr] * 1.5 + 10 |
|
| 288 |
|
|
| 289 |
colWidth[cntr] = (ourColumnWidth < maxMultiColWidth) ? ourColumnWidth : maxMultiColWidth |
|
| 290 |
end |
|
| 291 |
|
|
| 292 |
#Total column length of the whole page |
|
| 293 |
total = 0 |
|
| 294 |
0.upto(colWidth.length - 1) do |cntr| |
|
| 295 |
total = total + colWidth[cntr] |
|
| 296 |
end |
|
| 297 |
|
|
| 298 |
#Scales the font sizes based on column data width |
|
| 299 |
percent = 1.0 |
|
| 300 |
if(total > MAXPDFWIDTH) |
|
| 301 |
percent = MAXPDFWIDTH.to_f()/total.to_f() |
|
| 302 |
maxMultiColWidth = percent * maxMultiColWidth |
|
| 303 |
0.upto(@query.column_names.length - 1) do |cntr| |
|
| 304 |
colWidth[cntr] = colWidth[cntr] * percent |
|
| 305 |
end |
|
| 306 |
fontSize = fontSize * 0.75 * percent |
|
| 307 |
pdf.SetFontStyle('B',fontSize)
|
|
| 308 |
end |
|
| 309 |
|
|
| 310 |
printColumnHeadersPDF(@query, @issues, colWidth, row_height, fontSize, pdf, maxCustomContent) |
|
| 311 |
printFieldsPDF (@query, @issues, colWidth, row_height, fontSize, pdf, rowHeightMultiCell, maxMultiColWidth) |
|
| 312 |
end |
|
| 313 |
|
|
| 314 |
|
|
| 315 |
# Returns a PDF string of a list of issues |
|
| 316 |
def issues_to_pdf(issues, query, project) |
|
| 317 |
pdf = IFPDF.new(current_language) |
|
| 318 |
if(query.name != "_") |
|
| 319 |
title = project ? "#{project} - #{@query.name.to_s()}" : " #{@query.name.to_s()}"
|
|
| 320 |
else |
|
| 321 |
title = project ? "#{project} - #{l(:label_issue_plural)}" : "#{l(:label_issue_plural)}"
|
|
| 322 |
end |
|
| 114 | 323 |
pdf.SetTitle(title) |
| 115 | 324 |
pdf.AliasNbPages |
| 116 | 325 |
pdf.footer_date = format_date(Date.today) |
| ... | ... | |
| 122 | 331 |
pdf.Cell(190,10, title) |
| 123 | 332 |
pdf.Ln |
| 124 | 333 |
|
| 125 |
# headers |
|
| 126 |
pdf.SetFontStyle('B',10)
|
|
| 127 |
pdf.SetFillColor(230, 230, 230) |
|
| 128 |
pdf.Cell(15, row_height, "#", 0, 0, 'L', 1) |
|
| 129 |
pdf.Cell(30, row_height, l(:field_tracker), 0, 0, 'L', 1) |
|
| 130 |
pdf.Cell(30, row_height, l(:field_status), 0, 0, 'L', 1) |
|
| 131 |
pdf.Cell(30, row_height, l(:field_priority), 0, 0, 'L', 1) |
|
| 132 |
pdf.Cell(40, row_height, l(:field_assigned_to), 0, 0, 'L', 1) |
|
| 133 |
pdf.Cell(25, row_height, l(:field_updated_on), 0, 0, 'L', 1) |
|
| 134 |
pdf.Cell(0, row_height, l(:field_subject), 0, 0, 'L', 1) |
|
| 135 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 136 |
pdf.Ln |
|
| 137 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 138 |
pdf.SetY(pdf.GetY() + 1) |
|
| 334 |
# headers |
|
| 335 |
#If there are column names |
|
| 336 |
if(@query.column_names != nil) |
|
| 337 |
printUserQuery(pdf, row_height) |
|
| 338 |
else #If there are no columns |
|
| 339 |
pdf.SetFontStyle('B',10)
|
|
| 340 |
pdf.SetFillColor(230, 230, 230) |
|
| 341 |
pdf.Cell(15, row_height, "#", 0, 0, 'L', 1) |
|
| 342 |
pdf.Cell(30, row_height, l(:field_tracker), 0, 0, 'L', 1) |
|
| 343 |
pdf.Cell(30, row_height, l(:field_status), 0, 0, 'L', 1) |
|
| 344 |
pdf.Cell(30, row_height, l(:field_priority), 0, 0, 'L', 1) |
|
| 345 |
pdf.Cell(40, row_height, l(:field_assigned_to), 0, 0, 'L', 1) |
|
| 346 |
pdf.Cell(25, row_height, l(:field_updated_on), 0, 0, 'L', 1) |
|
| 347 |
pdf.Cell(0, row_height, l(:field_subject), 0, 0, 'L', 1) |
|
| 348 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 349 |
pdf.Ln |
|
| 350 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 351 |
pdf.SetY(pdf.GetY() + 1) |
|
| 139 | 352 |
|
| 140 |
# rows |
|
| 141 |
pdf.SetFontStyle('',9)
|
|
| 142 |
pdf.SetFillColor(255, 255, 255) |
|
| 143 |
group = false |
|
| 144 |
issues.each do |issue| |
|
| 145 |
if query.grouped? && issue.send(query.group_by) != group |
|
| 146 |
group = issue.send(query.group_by) |
|
| 147 |
pdf.SetFontStyle('B',10)
|
|
| 148 |
pdf.Cell(0, row_height, "#{group.blank? ? 'None' : group.to_s}", 0, 1, 'L')
|
|
| 149 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 150 |
pdf.SetY(pdf.GetY() + 0.5) |
|
| 151 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 152 |
pdf.SetY(pdf.GetY() + 1) |
|
| 153 |
pdf.SetFontStyle('',9)
|
|
| 353 |
# rows |
|
| 354 |
pdf.SetFontStyle('',9)
|
|
| 355 |
pdf.SetFillColor(255, 255, 255) |
|
| 356 |
group = false |
|
| 357 |
issues.each do |issue| |
|
| 358 |
if query.grouped? && issue.send(query.group_by) != group |
|
| 359 |
group = issue.send(query.group_by) |
|
| 360 |
pdf.SetFontStyle('B',10)
|
|
| 361 |
pdf.Cell(0, row_height, "#{group.blank? ? 'None' : group.to_s}", 0, 1, 'L')
|
|
| 362 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 363 |
pdf.SetY(pdf.GetY() + 0.5) |
|
| 364 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 365 |
pdf.SetY(pdf.GetY() + 1) |
|
| 366 |
pdf.SetFontStyle('',9)
|
|
| 367 |
end |
|
| 368 |
pdf.Cell(15, row_height, issue.id.to_s, 0, 0, 'L', 1) |
|
| 369 |
pdf.Cell(30, row_height, issue.tracker.name, 0, 0, 'L', 1) |
|
| 370 |
pdf.Cell(30, row_height, issue.status.name, 0, 0, 'L', 1) |
|
| 371 |
pdf.Cell(30, row_height, issue.priority.name, 0, 0, 'L', 1) |
|
| 372 |
pdf.Cell(40, row_height, issue.assigned_to ? issue.assigned_to.to_s : '', 0, 0, 'L', 1) |
|
| 373 |
pdf.Cell(25, row_height, format_date(issue.updated_on), 0, 0, 'L', 1) |
|
| 374 |
pdf.MultiCell(0, row_height, (project == issue.project ? issue.subject : "#{issue.project} - #{issue.subject}"))
|
|
| 375 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 376 |
pdf.SetY(pdf.GetY() + 1) |
|
| 154 | 377 |
end |
| 155 |
pdf.Cell(15, row_height, issue.id.to_s, 0, 0, 'L', 1) |
|
| 156 |
pdf.Cell(30, row_height, issue.tracker.name, 0, 0, 'L', 1) |
|
| 157 |
pdf.Cell(30, row_height, issue.status.name, 0, 0, 'L', 1) |
|
| 158 |
pdf.Cell(30, row_height, issue.priority.name, 0, 0, 'L', 1) |
|
| 159 |
pdf.Cell(40, row_height, issue.assigned_to ? issue.assigned_to.to_s : '', 0, 0, 'L', 1) |
|
| 160 |
pdf.Cell(25, row_height, format_date(issue.updated_on), 0, 0, 'L', 1) |
|
| 161 |
pdf.MultiCell(0, row_height, (project == issue.project ? issue.subject : "#{issue.project} - #{issue.subject}"))
|
|
| 162 |
pdf.Line(10, pdf.GetY, 287, pdf.GetY) |
|
| 163 |
pdf.SetY(pdf.GetY() + 1) |
|
| 164 |
end |
|
| 378 |
end |
|
| 165 | 379 |
pdf.Output |
| 166 |
end
|
|
| 380 |
end |
|
| 167 | 381 |
|
| 168 | 382 |
# Returns a PDF string of a single issue |
| 169 | 383 |
def issue_to_pdf(issue) |
| vendor/plugins/rfpdf/lib/rfpdf/fpdf.rb (working copy) | ||
|---|---|---|
| 279 | 279 |
end |
| 280 | 280 |
|
| 281 | 281 |
def AddPage(orientation='') |
| 282 |
#Added 7/2/09 |
|
| 283 |
#************************************ |
|
| 284 |
#If next page already exists |
|
| 285 |
if(getPage() < @pages.length() - 1) |
|
| 286 |
setPage(getPage() + 1) |
|
| 287 |
@y = 0 + @tMargin |
|
| 288 |
#************************************ |
|
| 289 |
else |
|
| 282 | 290 |
# Start a new page |
| 283 | 291 |
self.Open if @state==0 |
| 284 | 292 |
family=@FontFamily |
| ... | ... | |
| 313 | 321 |
out(fc) if fc!='0 g' |
| 314 | 322 |
@TextColor=tc |
| 315 | 323 |
@ColorFlag=cf |
| 324 |
end |
|
| 316 | 325 |
# Page header |
| 317 | 326 |
self.Header |
| 318 | 327 |
# Restore line width |
| ... | ... | |
| 870 | 879 |
@y=@y+@lasth |
| 871 | 880 |
else |
| 872 | 881 |
@y=@y+h |
| 873 |
end |
|
| 882 |
end |
|
| 883 |
end |
|
| 884 |
|
|
| 885 |
#Added 7/2/09 for pdf.rb |
|
| 886 |
#*********************** |
|
| 887 |
def getFontFamily |
|
| 888 |
@FontFamily |
|
| 889 |
end |
|
| 890 |
|
|
| 891 |
def getFontStyle |
|
| 892 |
@FontStyle |
|
| 893 |
end |
|
| 894 |
|
|
| 895 |
def getFontSize |
|
| 896 |
@FontSizePt |
|
| 874 | 897 |
end |
| 898 |
def getPage() |
|
| 899 |
@page |
|
| 900 |
end |
|
| 875 | 901 |
|
| 902 |
def setPage(page) |
|
| 903 |
@page = page |
|
| 904 |
end |
|
| 905 |
#********************** |
|
| 876 | 906 |
def GetX |
| 877 | 907 |
# Get x position |
| 878 | 908 |
@x |
| public/stylesheets/application.css (working copy) | ||
|---|---|---|
| 93 | 93 |
|
| 94 | 94 |
tr.project td.name a { padding-left: 16px; white-space:nowrap; }
|
| 95 | 95 |
tr.project.parent td.name a { background: url('../images/bullet_toggle_minus.png') no-repeat; }
|
| 96 |
|
|
| 97 |
tr.issue { text-align: center; white-space: nowrap; }
|
|
| 98 |
tr.issue td.subject, tr.issue td.category, td.assigned_to { white-space: normal; }
|
|
| 99 |
tr.issue td.subject { text-align: left; }
|
|
| 96 |
/*** Changed 7/7/09 - nowrap was a bad idea! ***/ |
|
| 97 |
tr.issue { text-align: center; white-space: normal; }
|
|
| 98 |
/*tr.issue { text-align: center; white-space: nowrap; }
|
|
| 99 |
tr.issue td.subject, tr.issue td.category, td.assigned_to { white-space: normal; }*/
|
|
| 100 |
tr.issue td.subject, tr.issue td.text, tr.issue td.string { text-align: left; }
|
|
| 101 |
/*tr.issue td.subject { text-align: left; }*/
|
|
| 100 | 102 |
tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
|
| 101 | 103 |
|
| 102 | 104 |
tr.entry { border: 1px solid #f8f8f8; }
|