@@ -371,12 +371,16 module IssuesHelper | |||||
371 | def issues_to_csv(issues, project, query, options={}) |
|
371 | def issues_to_csv(issues, project, query, options={}) | |
372 | decimal_separator = l(:general_csv_decimal_separator) |
|
372 | decimal_separator = l(:general_csv_decimal_separator) | |
373 | encoding = l(:general_csv_encoding) |
|
373 | encoding = l(:general_csv_encoding) | |
374 | columns = (options[:columns] == 'all' ? query.available_columns : query.columns) |
|
374 | columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns) | |
|
375 | if options[:description] | |||
|
376 | if description = query.available_columns.detect {|q| q.name == :description} | |||
|
377 | columns << description | |||
|
378 | end | |||
|
379 | end | |||
375 |
|
380 | |||
376 | export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv| |
|
381 | export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv| | |
377 | # csv header fields |
|
382 | # csv header fields | |
378 |
csv << [ "#" ] + columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) } |
|
383 | csv << [ "#" ] + columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) } | |
379 | (options[:description] ? [Redmine::CodesetUtil.from_utf8(l(:field_description), encoding)] : []) |
|
|||
380 |
|
384 | |||
381 | # csv lines |
|
385 | # csv lines | |
382 | issues.each do |issue| |
|
386 | issues.each do |issue| | |
@@ -398,8 +402,7 module IssuesHelper | |||||
398 | end |
|
402 | end | |
399 | s.to_s |
|
403 | s.to_s | |
400 | end |
|
404 | end | |
401 |
csv << [ issue.id.to_s ] + col_values.collect {|c| Redmine::CodesetUtil.from_utf8(c.to_s, encoding) } |
|
405 | csv << [ issue.id.to_s ] + col_values.collect {|c| Redmine::CodesetUtil.from_utf8(c.to_s, encoding) } | |
402 | (options[:description] ? [Redmine::CodesetUtil.from_utf8(issue.description, encoding)] : []) |
|
|||
403 | end |
|
406 | end | |
404 | end |
|
407 | end | |
405 | export |
|
408 | export |
@@ -50,6 +50,14 module QueriesHelper | |||||
50 | end |
|
50 | end | |
51 | end |
|
51 | end | |
52 |
|
52 | |||
|
53 | def available_block_columns_tags(query) | |||
|
54 | tags = ''.html_safe | |||
|
55 | query.available_block_columns.each do |column| | |||
|
56 | tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column)) + " #{column.caption}", :class => 'inline') | |||
|
57 | end | |||
|
58 | tags | |||
|
59 | end | |||
|
60 | ||||
53 | def column_header(column) |
|
61 | def column_header(column) | |
54 | column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption, |
|
62 | column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption, | |
55 | :default_order => column.default_order) : |
|
63 | :default_order => column.default_order) : | |
@@ -70,6 +78,8 module QueriesHelper | |||||
70 | when 'String' |
|
78 | when 'String' | |
71 | if column.name == :subject |
|
79 | if column.name == :subject | |
72 | link_to(h(value), :controller => 'issues', :action => 'show', :id => issue) |
|
80 | link_to(h(value), :controller => 'issues', :action => 'show', :id => issue) | |
|
81 | elsif column.name == :description | |||
|
82 | issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : '' | |||
73 | else |
|
83 | else | |
74 | h(value) |
|
84 | h(value) | |
75 | end |
|
85 | end |
@@ -27,6 +27,7 class QueryColumn | |||||
27 | self.groupable = name.to_s |
|
27 | self.groupable = name.to_s | |
28 | end |
|
28 | end | |
29 | self.default_order = options[:default_order] |
|
29 | self.default_order = options[:default_order] | |
|
30 | @inline = options.key?(:inline) ? options[:inline] : true | |||
30 | @caption_key = options[:caption] || "field_#{name}" |
|
31 | @caption_key = options[:caption] || "field_#{name}" | |
31 | end |
|
32 | end | |
32 |
|
33 | |||
@@ -43,6 +44,10 class QueryColumn | |||||
43 | @sortable.is_a?(Proc) ? @sortable.call : @sortable |
|
44 | @sortable.is_a?(Proc) ? @sortable.call : @sortable | |
44 | end |
|
45 | end | |
45 |
|
46 | |||
|
47 | def inline? | |||
|
48 | @inline | |||
|
49 | end | |||
|
50 | ||||
46 | def value(issue) |
|
51 | def value(issue) | |
47 | issue.send name |
|
52 | issue.send name | |
48 | end |
|
53 | end | |
@@ -58,6 +63,7 class QueryCustomFieldColumn < QueryColumn | |||||
58 | self.name = "cf_#{custom_field.id}".to_sym |
|
63 | self.name = "cf_#{custom_field.id}".to_sym | |
59 | self.sortable = custom_field.order_statement || false |
|
64 | self.sortable = custom_field.order_statement || false | |
60 | self.groupable = custom_field.group_statement || false |
|
65 | self.groupable = custom_field.group_statement || false | |
|
66 | @inline = true | |||
61 | @cf = custom_field |
|
67 | @cf = custom_field | |
62 | end |
|
68 | end | |
63 |
|
69 | |||
@@ -153,7 +159,8 class Query < ActiveRecord::Base | |||||
153 | QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"), |
|
159 | QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"), | |
154 | QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true), |
|
160 | QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true), | |
155 | QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'), |
|
161 | QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'), | |
156 | QueryColumn.new(:relations, :caption => :label_related_issues) |
|
162 | QueryColumn.new(:relations, :caption => :label_related_issues), | |
|
163 | QueryColumn.new(:description, :inline => false) | |||
157 | ] |
|
164 | ] | |
158 | cattr_reader :available_columns |
|
165 | cattr_reader :available_columns | |
159 |
|
166 | |||
@@ -506,6 +513,22 class Query < ActiveRecord::Base | |||||
506 | end.compact |
|
513 | end.compact | |
507 | end |
|
514 | end | |
508 |
|
515 | |||
|
516 | def inline_columns | |||
|
517 | columns.select(&:inline?) | |||
|
518 | end | |||
|
519 | ||||
|
520 | def block_columns | |||
|
521 | columns.reject(&:inline?) | |||
|
522 | end | |||
|
523 | ||||
|
524 | def available_inline_columns | |||
|
525 | available_columns.select(&:inline?) | |||
|
526 | end | |||
|
527 | ||||
|
528 | def available_block_columns | |||
|
529 | available_columns.reject(&:inline?) | |||
|
530 | end | |||
|
531 | ||||
509 | def default_columns_names |
|
532 | def default_columns_names | |
510 | @default_columns_names ||= begin |
|
533 | @default_columns_names ||= begin | |
511 | default_columns = Setting.issue_list_default_columns.map(&:to_sym) |
|
534 | default_columns = Setting.issue_list_default_columns.map(&:to_sym) |
@@ -10,7 +10,7 | |||||
10 | :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %> |
|
10 | :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %> | |
11 | </th> |
|
11 | </th> | |
12 | <%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %> |
|
12 | <%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %> | |
13 | <% query.columns.each do |column| %> |
|
13 | <% query.inline_columns.each do |column| %> | |
14 | <%= column_header(column) %> |
|
14 | <%= column_header(column) %> | |
15 | <% end %> |
|
15 | <% end %> | |
16 | </tr> |
|
16 | </tr> | |
@@ -21,7 +21,7 | |||||
21 | <% if @query.grouped? && (group = @query.group_by_column.value(issue)) != previous_group %> |
|
21 | <% if @query.grouped? && (group = @query.group_by_column.value(issue)) != previous_group %> | |
22 | <% reset_cycle %> |
|
22 | <% reset_cycle %> | |
23 | <tr class="group open"> |
|
23 | <tr class="group open"> | |
24 | <td colspan="<%= query.columns.size + 2 %>"> |
|
24 | <td colspan="<%= query.inline_columns.size + 2 %>"> | |
25 | <span class="expander" onclick="toggleRowGroup(this);"> </span> |
|
25 | <span class="expander" onclick="toggleRowGroup(this);"> </span> | |
26 | <%= group.blank? ? l(:label_none) : column_content(@query.group_by_column, issue) %> <span class="count"><%= @issue_count_by_group[group] %></span> |
|
26 | <%= group.blank? ? l(:label_none) : column_content(@query.group_by_column, issue) %> <span class="count"><%= @issue_count_by_group[group] %></span> | |
27 | <%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}", |
|
27 | <%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}", | |
@@ -33,8 +33,15 | |||||
33 | <tr id="issue-<%= issue.id %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= issue.css_classes %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>"> |
|
33 | <tr id="issue-<%= issue.id %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= issue.css_classes %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>"> | |
34 | <td class="checkbox hide-when-print"><%= check_box_tag("ids[]", issue.id, false, :id => nil) %></td> |
|
34 | <td class="checkbox hide-when-print"><%= check_box_tag("ids[]", issue.id, false, :id => nil) %></td> | |
35 | <td class="id"><%= link_to issue.id, issue_path(issue) %></td> |
|
35 | <td class="id"><%= link_to issue.id, issue_path(issue) %></td> | |
36 | <%= raw query.columns.map {|column| "<td class=\"#{column.css_classes}\">#{column_content(column, issue)}</td>"}.join %> |
|
36 | <%= raw query.inline_columns.map {|column| "<td class=\"#{column.css_classes}\">#{column_content(column, issue)}</td>"}.join %> | |
37 | </tr> |
|
37 | </tr> | |
|
38 | <% @query.block_columns.each do |column| | |||
|
39 | if (text = column_content(column, issue)) && text.present? -%> | |||
|
40 | <tr class="<%= current_cycle %>"> | |||
|
41 | <td colspan="<%= @query.inline_columns.size + 2 %>" class="<%= column.css_classes %>"><%= text %></td> | |||
|
42 | </tr> | |||
|
43 | <% end -%> | |||
|
44 | <% end -%> | |||
38 | <% end -%> |
|
45 | <% end -%> | |
39 | </tbody> |
|
46 | </tbody> | |
40 | </table> |
|
47 | </table> |
@@ -34,6 +34,10 | |||||
34 | @query.group_by) |
|
34 | @query.group_by) | |
35 | ) %></td> |
|
35 | ) %></td> | |
36 | </tr> |
|
36 | </tr> | |
|
37 | <tr> | |||
|
38 | <td><%= l(:button_show) %></td> | |||
|
39 | <td><%= available_block_columns_tags(@query) %></td> | |||
|
40 | </tr> | |||
37 | </table> |
|
41 | </table> | |
38 | </div> |
|
42 | </div> | |
39 | </fieldset> |
|
43 | </fieldset> | |
@@ -73,7 +77,7 | |||||
73 | <label><%= radio_button_tag 'columns', 'all' %> <%= l(:description_all_columns) %></label> |
|
77 | <label><%= radio_button_tag 'columns', 'all' %> <%= l(:description_all_columns) %></label> | |
74 | </p> |
|
78 | </p> | |
75 | <p> |
|
79 | <p> | |
76 | <label><%= check_box_tag 'description', '1' %> <%= l(:field_description) %></label> |
|
80 | <label><%= check_box_tag 'description', '1', @query.has_column?(:description) %> <%= l(:field_description) %></label> | |
77 | </p> |
|
81 | </p> | |
78 | <p class="buttons"> |
|
82 | <p class="buttons"> | |
79 | <%= submit_tag l(:button_export), :name => nil, :onclick => "hideModal(this);" %> |
|
83 | <%= submit_tag l(:button_export), :name => nil, :onclick => "hideModal(this);" %> |
@@ -4,7 +4,7 | |||||
4 | <%= label_tag "available_columns", l(:description_available_columns) %> |
|
4 | <%= label_tag "available_columns", l(:description_available_columns) %> | |
5 | <br /> |
|
5 | <br /> | |
6 | <%= select_tag 'available_columns', |
|
6 | <%= select_tag 'available_columns', | |
7 | options_for_select((query.available_columns - query.columns).collect {|column| [column.caption, column.name]}), |
|
7 | options_for_select((query.available_inline_columns - query.columns).collect {|column| [column.caption, column.name]}), | |
8 | :multiple => true, :size => 10, :style => "width:150px", |
|
8 | :multiple => true, :size => 10, :style => "width:150px", | |
9 | :ondblclick => "moveOptions(this.form.available_columns, this.form.selected_columns);" %> |
|
9 | :ondblclick => "moveOptions(this.form.available_columns, this.form.selected_columns);" %> | |
10 | </td> |
|
10 | </td> | |
@@ -18,7 +18,7 | |||||
18 | <%= label_tag "selected_columns", l(:description_selected_columns) %> |
|
18 | <%= label_tag "selected_columns", l(:description_selected_columns) %> | |
19 | <br /> |
|
19 | <br /> | |
20 | <%= select_tag((defined?(tag_name) ? tag_name : 'c[]'), |
|
20 | <%= select_tag((defined?(tag_name) ? tag_name : 'c[]'), | |
21 | options_for_select(query.columns.collect {|column| [column.caption, column.name]}), |
|
21 | options_for_select(query.inline_columns.collect {|column| [column.caption, column.name]}), | |
22 | :id => 'selected_columns', :multiple => true, :size => 10, :style => "width:150px", |
|
22 | :id => 'selected_columns', :multiple => true, :size => 10, :style => "width:150px", | |
23 | :ondblclick => "moveOptions(this.form.selected_columns, this.form.available_columns);") %> |
|
23 | :ondblclick => "moveOptions(this.form.selected_columns, this.form.available_columns);") %> | |
24 | </td> |
|
24 | </td> |
@@ -21,6 +21,9 | |||||
21 |
|
21 | |||
22 | <p><label for="query_group_by"><%= l(:field_group_by) %></label> |
|
22 | <p><label for="query_group_by"><%= l(:field_group_by) %></label> | |
23 | <%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %></p> |
|
23 | <%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %></p> | |
|
24 | ||||
|
25 | <p><label><%= l(:button_show) %></label> | |||
|
26 | <%= available_block_columns_tags(@query) %></p> | |||
24 | </div> |
|
27 | </div> | |
25 |
|
28 | |||
26 | <fieldset id="filters"><legend><%= l(:label_filter_plural) %></legend> |
|
29 | <fieldset id="filters"><legend><%= l(:label_filter_plural) %></legend> |
@@ -403,6 +403,9 class TCPDF | |||||
403 | Error("Incorrect orientation: #{orientation}") |
|
403 | Error("Incorrect orientation: #{orientation}") | |
404 | end |
|
404 | end | |
405 |
|
405 | |||
|
406 | @fw = @w_pt/@k | |||
|
407 | @fh = @h_pt/@k | |||
|
408 | ||||
406 | @cur_orientation = @def_orientation |
|
409 | @cur_orientation = @def_orientation | |
407 | @w = @w_pt/@k |
|
410 | @w = @w_pt/@k | |
408 | @h = @h_pt/@k |
|
411 | @h = @h_pt/@k | |
@@ -3615,9 +3618,9 class TCPDF | |||||
3615 | restspace = GetPageHeight() - GetY() - GetBreakMargin(); |
|
3618 | restspace = GetPageHeight() - GetY() - GetBreakMargin(); | |
3616 |
|
3619 | |||
3617 | writeHTML(html, true, fill); # write html text |
|
3620 | writeHTML(html, true, fill); # write html text | |
|
3621 | SetX(x) | |||
3618 |
|
3622 | |||
3619 | currentY = GetY(); |
|
3623 | currentY = GetY(); | |
3620 |
|
||||
3621 | @auto_page_break = false; |
|
3624 | @auto_page_break = false; | |
3622 | # check if a new page has been created |
|
3625 | # check if a new page has been created | |
3623 | if (@page > pagenum) |
|
3626 | if (@page > pagenum) | |
@@ -3625,11 +3628,13 class TCPDF | |||||
3625 | currentpage = @page; |
|
3628 | currentpage = @page; | |
3626 | @page = pagenum; |
|
3629 | @page = pagenum; | |
3627 | SetY(GetPageHeight() - restspace - GetBreakMargin()); |
|
3630 | SetY(GetPageHeight() - restspace - GetBreakMargin()); | |
|
3631 | SetX(x) | |||
3628 | Cell(w, restspace - 1, "", b, 0, 'L', 0); |
|
3632 | Cell(w, restspace - 1, "", b, 0, 'L', 0); | |
3629 | b = b2; |
|
3633 | b = b2; | |
3630 | @page += 1; |
|
3634 | @page += 1; | |
3631 | while @page < currentpage |
|
3635 | while @page < currentpage | |
3632 | SetY(@t_margin); # put cursor at the beginning of text |
|
3636 | SetY(@t_margin); # put cursor at the beginning of text | |
|
3637 | SetX(x) | |||
3633 | Cell(w, @page_break_trigger - @t_margin, "", b, 0, 'L', 0); |
|
3638 | Cell(w, @page_break_trigger - @t_margin, "", b, 0, 'L', 0); | |
3634 | @page += 1; |
|
3639 | @page += 1; | |
3635 | end |
|
3640 | end | |
@@ -3638,10 +3643,12 class TCPDF | |||||
3638 | end |
|
3643 | end | |
3639 | # design a cell around the text on last page |
|
3644 | # design a cell around the text on last page | |
3640 | SetY(@t_margin); # put cursor at the beginning of text |
|
3645 | SetY(@t_margin); # put cursor at the beginning of text | |
|
3646 | SetX(x) | |||
3641 | Cell(w, currentY - @t_margin, "", b, 0, 'L', 0); |
|
3647 | Cell(w, currentY - @t_margin, "", b, 0, 'L', 0); | |
3642 | else |
|
3648 | else | |
3643 | SetY(y); # put cursor at the beginning of text |
|
3649 | SetY(y); # put cursor at the beginning of text | |
3644 | # design a cell around the text |
|
3650 | # design a cell around the text | |
|
3651 | SetX(x) | |||
3645 | Cell(w, [h, (currentY - y)].max, "", border, 0, 'L', 0); |
|
3652 | Cell(w, [h, (currentY - y)].max, "", border, 0, 'L', 0); | |
3646 | end |
|
3653 | end | |
3647 | @auto_page_break = true; |
|
3654 | @auto_page_break = true; |
@@ -34,12 +34,12 module Redmine | |||||
34 | include Redmine::I18n |
|
34 | include Redmine::I18n | |
35 | attr_accessor :footer_date |
|
35 | attr_accessor :footer_date | |
36 |
|
36 | |||
37 | def initialize(lang) |
|
37 | def initialize(lang, orientation='P') | |
38 | @@k_path_cache = Rails.root.join('tmp', 'pdf') |
|
38 | @@k_path_cache = Rails.root.join('tmp', 'pdf') | |
39 | FileUtils.mkdir_p @@k_path_cache unless File::exist?(@@k_path_cache) |
|
39 | FileUtils.mkdir_p @@k_path_cache unless File::exist?(@@k_path_cache) | |
40 | set_language_if_valid lang |
|
40 | set_language_if_valid lang | |
41 | pdf_encoding = l(:general_pdf_encoding).upcase |
|
41 | pdf_encoding = l(:general_pdf_encoding).upcase | |
42 |
super( |
|
42 | super(orientation, 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding) | |
43 | case current_language.to_s.downcase |
|
43 | case current_language.to_s.downcase | |
44 | when 'vi' |
|
44 | when 'vi' | |
45 | @font_for_content = 'DejaVuSans' |
|
45 | @font_for_content = 'DejaVuSans' | |
@@ -236,7 +236,7 module Redmine | |||||
236 |
|
236 | |||
237 | # fetch row values |
|
237 | # fetch row values | |
238 | def fetch_row_values(issue, query, level) |
|
238 | def fetch_row_values(issue, query, level) | |
239 | query.columns.collect do |column| |
|
239 | query.inline_columns.collect do |column| | |
240 | s = if column.is_a?(QueryCustomFieldColumn) |
|
240 | s = if column.is_a?(QueryCustomFieldColumn) | |
241 | cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id} |
|
241 | cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id} | |
242 | show_value(cv) |
|
242 | show_value(cv) | |
@@ -263,10 +263,10 module Redmine | |||||
263 | # by captions |
|
263 | # by captions | |
264 | pdf.SetFontStyle('B',8) |
|
264 | pdf.SetFontStyle('B',8) | |
265 | col_padding = pdf.GetStringWidth('OO') |
|
265 | col_padding = pdf.GetStringWidth('OO') | |
266 | col_width_min = query.columns.map {|v| pdf.GetStringWidth(v.caption) + col_padding} |
|
266 | col_width_min = query.inline_columns.map {|v| pdf.GetStringWidth(v.caption) + col_padding} | |
267 | col_width_max = Array.new(col_width_min) |
|
267 | col_width_max = Array.new(col_width_min) | |
268 | col_width_avg = Array.new(col_width_min) |
|
268 | col_width_avg = Array.new(col_width_min) | |
269 | word_width_max = query.columns.map {|c| |
|
269 | word_width_max = query.inline_columns.map {|c| | |
270 | n = 10 |
|
270 | n = 10 | |
271 | c.caption.split.each {|w| |
|
271 | c.caption.split.each {|w| | |
272 | x = pdf.GetStringWidth(w) + col_padding |
|
272 | x = pdf.GetStringWidth(w) + col_padding | |
@@ -370,13 +370,13 module Redmine | |||||
370 | # render it background to find the max height used |
|
370 | # render it background to find the max height used | |
371 | base_x = pdf.GetX |
|
371 | base_x = pdf.GetX | |
372 | base_y = pdf.GetY |
|
372 | base_y = pdf.GetY | |
373 | max_height = issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true) |
|
373 | max_height = issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, row_height, true) | |
374 | pdf.Rect(base_x, base_y, table_width + col_id_width, max_height, 'FD'); |
|
374 | pdf.Rect(base_x, base_y, table_width + col_id_width, max_height, 'FD'); | |
375 | pdf.SetXY(base_x, base_y); |
|
375 | pdf.SetXY(base_x, base_y); | |
376 |
|
376 | |||
377 | # write the cells on page |
|
377 | # write the cells on page | |
378 | pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1) |
|
378 | pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1) | |
379 | issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true) |
|
379 | issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, row_height, true) | |
380 | issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width) |
|
380 | issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width) | |
381 | pdf.SetY(base_y + max_height); |
|
381 | pdf.SetY(base_y + max_height); | |
382 |
|
382 | |||
@@ -387,7 +387,7 module Redmine | |||||
387 |
|
387 | |||
388 | # Returns a PDF string of a list of issues |
|
388 | # Returns a PDF string of a list of issues | |
389 | def issues_to_pdf(issues, project, query) |
|
389 | def issues_to_pdf(issues, project, query) | |
390 | pdf = ITCPDF.new(current_language) |
|
390 | pdf = ITCPDF.new(current_language, "L") | |
391 | title = query.new_record? ? l(:label_issue_plural) : query.name |
|
391 | title = query.new_record? ? l(:label_issue_plural) : query.name | |
392 | title = "#{project} - #{title}" if project |
|
392 | title = "#{project} - #{title}" if project | |
393 | pdf.SetTitle(title) |
|
393 | pdf.SetTitle(title) | |
@@ -407,11 +407,17 module Redmine | |||||
407 | # column widths |
|
407 | # column widths | |
408 | table_width = page_width - right_margin - 10 # fixed left margin |
|
408 | table_width = page_width - right_margin - 10 # fixed left margin | |
409 | col_width = [] |
|
409 | col_width = [] | |
410 | unless query.columns.empty? |
|
410 | unless query.inline_columns.empty? | |
411 | col_width = calc_col_width(issues, query, table_width - col_id_width, pdf) |
|
411 | col_width = calc_col_width(issues, query, table_width - col_id_width, pdf) | |
412 | table_width = col_width.inject(0) {|s,v| s += v} |
|
412 | table_width = col_width.inject(0) {|s,v| s += v} | |
413 | end |
|
413 | end | |
414 |
|
414 | |||
|
415 | # use full width if the description is displayed | |||
|
416 | if table_width > 0 && query.has_column?(:description) | |||
|
417 | col_width = col_width.map {|w| w = w * (page_width - right_margin - 10 - col_id_width) / table_width} | |||
|
418 | table_width = col_width.inject(0) {|s,v| s += v} | |||
|
419 | end | |||
|
420 | ||||
415 | # title |
|
421 | # title | |
416 | pdf.SetFontStyle('B',11) |
|
422 | pdf.SetFontStyle('B',11) | |
417 | pdf.RDMCell(190,10, title) |
|
423 | pdf.RDMCell(190,10, title) | |
@@ -454,6 +460,13 module Redmine | |||||
454 | issues_to_pdf_write_cells(pdf, col_values, col_width, row_height) |
|
460 | issues_to_pdf_write_cells(pdf, col_values, col_width, row_height) | |
455 | issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width) |
|
461 | issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width) | |
456 | pdf.SetY(base_y + max_height); |
|
462 | pdf.SetY(base_y + max_height); | |
|
463 | ||||
|
464 | if query.has_column?(:description) && issue.description? | |||
|
465 | pdf.SetX(10) | |||
|
466 | pdf.SetAutoPageBreak(true, 20) | |||
|
467 | pdf.RDMwriteHTMLCell(0, 5, 10, 0, issue.description.to_s, issue.attachments, "LRBT") | |||
|
468 | pdf.SetAutoPageBreak(false) | |||
|
469 | end | |||
457 | end |
|
470 | end | |
458 |
|
471 | |||
459 | if issues.size == Setting.issues_export_limit.to_i |
|
472 | if issues.size == Setting.issues_export_limit.to_i |
@@ -149,6 +149,8 tr.issue td.subject, tr.issue td.category, td.assigned_to, tr.issue td.string, t | |||||
149 | tr.issue td.subject, tr.issue td.relations { text-align: left; } |
|
149 | tr.issue td.subject, tr.issue td.relations { text-align: left; } | |
150 | tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;} |
|
150 | tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;} | |
151 | tr.issue td.relations span {white-space: nowrap;} |
|
151 | tr.issue td.relations span {white-space: nowrap;} | |
|
152 | table.issues td.description {color:#777; font-size:90%; padding:4px 4px 4px 24px; text-align:left; white-space:normal;} | |||
|
153 | table.issues td.description pre {white-space:normal;} | |||
152 |
|
154 | |||
153 | tr.issue.idnt td.subject a {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;} |
|
155 | tr.issue.idnt td.subject a {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;} | |
154 | tr.issue.idnt-1 td.subject {padding-left: 0.5em;} |
|
156 | tr.issue.idnt-1 td.subject {padding-left: 0.5em;} |
@@ -418,7 +418,7 class IssuesControllerTest < ActionController::TestCase | |||||
418 | assert_equal 'text/csv; header=present', @response.content_type |
|
418 | assert_equal 'text/csv; header=present', @response.content_type | |
419 | assert @response.body.starts_with?("#,") |
|
419 | assert @response.body.starts_with?("#,") | |
420 | lines = @response.body.chomp.split("\n") |
|
420 | lines = @response.body.chomp.split("\n") | |
421 | assert_equal assigns(:query).available_columns.size + 1, lines[0].split(',').size |
|
421 | assert_equal assigns(:query).available_inline_columns.size + 1, lines[0].split(',').size | |
422 | end |
|
422 | end | |
423 |
|
423 | |||
424 | def test_index_csv_with_multi_column_field |
|
424 | def test_index_csv_with_multi_column_field | |
@@ -825,6 +825,17 class IssuesControllerTest < ActionController::TestCase | |||||
825 | assert_equal 'application/pdf', response.content_type |
|
825 | assert_equal 'application/pdf', response.content_type | |
826 | end |
|
826 | end | |
827 |
|
827 | |||
|
828 | def test_index_with_description_column | |||
|
829 | get :index, :set_filter => 1, :c => %w(subject description) | |||
|
830 | ||||
|
831 | assert_select 'table.issues thead th', 3 # columns: chekbox + id + subject | |||
|
832 | assert_select 'td.description[colspan=3]', :text => 'Unable to print recipes' | |||
|
833 | ||||
|
834 | get :index, :set_filter => 1, :c => %w(subject description), :format => 'pdf' | |||
|
835 | assert_response :success | |||
|
836 | assert_equal 'application/pdf', response.content_type | |||
|
837 | end | |||
|
838 | ||||
828 | def test_index_send_html_if_query_is_invalid |
|
839 | def test_index_send_html_if_query_is_invalid | |
829 | get :index, :f => ['start_date'], :op => {:start_date => '='} |
|
840 | get :index, :f => ['start_date'], :op => {:start_date => '='} | |
830 | assert_equal 'text/html', @response.content_type |
|
841 | assert_equal 'text/html', @response.content_type |
@@ -737,7 +737,9 class QueryTest < ActiveSupport::TestCase | |||||
737 |
|
737 | |||
738 | def test_default_columns |
|
738 | def test_default_columns | |
739 | q = Query.new |
|
739 | q = Query.new | |
740 |
assert |
|
740 | assert q.columns.any? | |
|
741 | assert q.inline_columns.any? | |||
|
742 | assert q.block_columns.empty? | |||
741 | end |
|
743 | end | |
742 |
|
744 | |||
743 | def test_set_column_names |
|
745 | def test_set_column_names | |
@@ -748,6 +750,21 class QueryTest < ActiveSupport::TestCase | |||||
748 | assert q.has_column?(c) |
|
750 | assert q.has_column?(c) | |
749 | end |
|
751 | end | |
750 |
|
752 | |||
|
753 | def test_inline_and_block_columns | |||
|
754 | q = Query.new | |||
|
755 | q.column_names = ['subject', 'description', 'tracker'] | |||
|
756 | ||||
|
757 | assert_equal [:subject, :tracker], q.inline_columns.map(&:name) | |||
|
758 | assert_equal [:description], q.block_columns.map(&:name) | |||
|
759 | end | |||
|
760 | ||||
|
761 | def test_custom_field_columns_should_be_inline | |||
|
762 | q = Query.new | |||
|
763 | columns = q.available_columns.select {|column| column.is_a? QueryCustomFieldColumn} | |||
|
764 | assert columns.any? | |||
|
765 | assert_nil columns.detect {|column| !column.inline?} | |||
|
766 | end | |||
|
767 | ||||
751 | def test_query_should_preload_spent_hours |
|
768 | def test_query_should_preload_spent_hours | |
752 | q = Query.new(:name => '_', :column_names => [:subject, :spent_hours]) |
|
769 | q = Query.new(:name => '_', :column_names => [:subject, :spent_hours]) | |
753 | assert q.has_column?(:spent_hours) |
|
770 | assert q.has_column?(:spent_hours) |
General Comments 0
You need to be logged in to leave comments.
Login now