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