##// END OF EJS Templates
add configuration of RMagcik font for CJK (Chinese, Japanese and Korean) (#4787)...
Toshi MARUYAMA -
r10663:c7ee26b14437
parent child
Show More
@@ -1,175 +1,197
1 # = Redmine configuration file
1 # = Redmine configuration file
2 #
2 #
3 # Each environment has it's own configuration options. If you are only
3 # Each environment has it's own configuration options. If you are only
4 # running in production, only the production block needs to be configured.
4 # running in production, only the production block needs to be configured.
5 # Environment specific configuration options override the default ones.
5 # Environment specific configuration options override the default ones.
6 #
6 #
7 # Note that this file needs to be a valid YAML file.
7 # Note that this file needs to be a valid YAML file.
8 # DO NOT USE TABS! Use 2 spaces instead of tabs for identation.
8 # DO NOT USE TABS! Use 2 spaces instead of tabs for identation.
9 #
9 #
10 # == Outgoing email settings (email_delivery setting)
10 # == Outgoing email settings (email_delivery setting)
11 #
11 #
12 # === Common configurations
12 # === Common configurations
13 #
13 #
14 # ==== Sendmail command
14 # ==== Sendmail command
15 #
15 #
16 # production:
16 # production:
17 # email_delivery:
17 # email_delivery:
18 # delivery_method: :sendmail
18 # delivery_method: :sendmail
19 #
19 #
20 # ==== Simple SMTP server at localhost
20 # ==== Simple SMTP server at localhost
21 #
21 #
22 # production:
22 # production:
23 # email_delivery:
23 # email_delivery:
24 # delivery_method: :smtp
24 # delivery_method: :smtp
25 # smtp_settings:
25 # smtp_settings:
26 # address: "localhost"
26 # address: "localhost"
27 # port: 25
27 # port: 25
28 #
28 #
29 # ==== SMTP server at example.com using LOGIN authentication and checking HELO for foo.com
29 # ==== SMTP server at example.com using LOGIN authentication and checking HELO for foo.com
30 #
30 #
31 # production:
31 # production:
32 # email_delivery:
32 # email_delivery:
33 # delivery_method: :smtp
33 # delivery_method: :smtp
34 # smtp_settings:
34 # smtp_settings:
35 # address: "example.com"
35 # address: "example.com"
36 # port: 25
36 # port: 25
37 # authentication: :login
37 # authentication: :login
38 # domain: 'foo.com'
38 # domain: 'foo.com'
39 # user_name: 'myaccount'
39 # user_name: 'myaccount'
40 # password: 'password'
40 # password: 'password'
41 #
41 #
42 # ==== SMTP server at example.com using PLAIN authentication
42 # ==== SMTP server at example.com using PLAIN authentication
43 #
43 #
44 # production:
44 # production:
45 # email_delivery:
45 # email_delivery:
46 # delivery_method: :smtp
46 # delivery_method: :smtp
47 # smtp_settings:
47 # smtp_settings:
48 # address: "example.com"
48 # address: "example.com"
49 # port: 25
49 # port: 25
50 # authentication: :plain
50 # authentication: :plain
51 # domain: 'example.com'
51 # domain: 'example.com'
52 # user_name: 'myaccount'
52 # user_name: 'myaccount'
53 # password: 'password'
53 # password: 'password'
54 #
54 #
55 # ==== SMTP server at using TLS (GMail)
55 # ==== SMTP server at using TLS (GMail)
56 #
56 #
57 # This might require some additional configuration. See the guides at:
57 # This might require some additional configuration. See the guides at:
58 # http://www.redmine.org/projects/redmine/wiki/EmailConfiguration
58 # http://www.redmine.org/projects/redmine/wiki/EmailConfiguration
59 #
59 #
60 # production:
60 # production:
61 # email_delivery:
61 # email_delivery:
62 # delivery_method: :smtp
62 # delivery_method: :smtp
63 # smtp_settings:
63 # smtp_settings:
64 # enable_starttls_auto: true
64 # enable_starttls_auto: true
65 # address: "smtp.gmail.com"
65 # address: "smtp.gmail.com"
66 # port: 587
66 # port: 587
67 # domain: "smtp.gmail.com" # 'your.domain.com' for GoogleApps
67 # domain: "smtp.gmail.com" # 'your.domain.com' for GoogleApps
68 # authentication: :plain
68 # authentication: :plain
69 # user_name: "your_email@gmail.com"
69 # user_name: "your_email@gmail.com"
70 # password: "your_password"
70 # password: "your_password"
71 #
71 #
72 #
72 #
73 # === More configuration options
73 # === More configuration options
74 #
74 #
75 # See the "Configuration options" at the following website for a list of the
75 # See the "Configuration options" at the following website for a list of the
76 # full options allowed:
76 # full options allowed:
77 #
77 #
78 # http://wiki.rubyonrails.org/rails/pages/HowToSendEmailsWithActionMailer
78 # http://wiki.rubyonrails.org/rails/pages/HowToSendEmailsWithActionMailer
79
79
80
80
81 # default configuration options for all environments
81 # default configuration options for all environments
82 default:
82 default:
83 # Outgoing emails configuration (see examples above)
83 # Outgoing emails configuration (see examples above)
84 email_delivery:
84 email_delivery:
85 delivery_method: :smtp
85 delivery_method: :smtp
86 smtp_settings:
86 smtp_settings:
87 address: smtp.example.net
87 address: smtp.example.net
88 port: 25
88 port: 25
89 domain: example.net
89 domain: example.net
90 authentication: :login
90 authentication: :login
91 user_name: "redmine@example.net"
91 user_name: "redmine@example.net"
92 password: "redmine"
92 password: "redmine"
93
93
94 # Absolute path to the directory where attachments are stored.
94 # Absolute path to the directory where attachments are stored.
95 # The default is the 'files' directory in your Redmine instance.
95 # The default is the 'files' directory in your Redmine instance.
96 # Your Redmine instance needs to have write permission on this
96 # Your Redmine instance needs to have write permission on this
97 # directory.
97 # directory.
98 # Examples:
98 # Examples:
99 # attachments_storage_path: /var/redmine/files
99 # attachments_storage_path: /var/redmine/files
100 # attachments_storage_path: D:/redmine/files
100 # attachments_storage_path: D:/redmine/files
101 attachments_storage_path:
101 attachments_storage_path:
102
102
103 # Configuration of the autologin cookie.
103 # Configuration of the autologin cookie.
104 # autologin_cookie_name: the name of the cookie (default: autologin)
104 # autologin_cookie_name: the name of the cookie (default: autologin)
105 # autologin_cookie_path: the cookie path (default: /)
105 # autologin_cookie_path: the cookie path (default: /)
106 # autologin_cookie_secure: true sets the cookie secure flag (default: false)
106 # autologin_cookie_secure: true sets the cookie secure flag (default: false)
107 autologin_cookie_name:
107 autologin_cookie_name:
108 autologin_cookie_path:
108 autologin_cookie_path:
109 autologin_cookie_secure:
109 autologin_cookie_secure:
110
110
111 # Configuration of SCM executable command.
111 # Configuration of SCM executable command.
112 #
112 #
113 # Absolute path (e.g. /usr/local/bin/hg) or command name (e.g. hg.exe, bzr.exe)
113 # Absolute path (e.g. /usr/local/bin/hg) or command name (e.g. hg.exe, bzr.exe)
114 # On Windows + CRuby, *.cmd, *.bat (e.g. hg.cmd, bzr.bat) does not work.
114 # On Windows + CRuby, *.cmd, *.bat (e.g. hg.cmd, bzr.bat) does not work.
115 #
115 #
116 # On Windows + JRuby 1.6.2, path which contains spaces does not work.
116 # On Windows + JRuby 1.6.2, path which contains spaces does not work.
117 # For example, "C:\Program Files\TortoiseHg\hg.exe".
117 # For example, "C:\Program Files\TortoiseHg\hg.exe".
118 # If you want to this feature, you need to install to the path which does not contains spaces.
118 # If you want to this feature, you need to install to the path which does not contains spaces.
119 # For example, "C:\TortoiseHg\hg.exe".
119 # For example, "C:\TortoiseHg\hg.exe".
120 #
120 #
121 # Examples:
121 # Examples:
122 # scm_subversion_command: svn # (default: svn)
122 # scm_subversion_command: svn # (default: svn)
123 # scm_mercurial_command: C:\Program Files\TortoiseHg\hg.exe # (default: hg)
123 # scm_mercurial_command: C:\Program Files\TortoiseHg\hg.exe # (default: hg)
124 # scm_git_command: /usr/local/bin/git # (default: git)
124 # scm_git_command: /usr/local/bin/git # (default: git)
125 # scm_cvs_command: cvs # (default: cvs)
125 # scm_cvs_command: cvs # (default: cvs)
126 # scm_bazaar_command: bzr.exe # (default: bzr)
126 # scm_bazaar_command: bzr.exe # (default: bzr)
127 # scm_darcs_command: darcs-1.0.9-i386-linux # (default: darcs)
127 # scm_darcs_command: darcs-1.0.9-i386-linux # (default: darcs)
128 #
128 #
129 scm_subversion_command:
129 scm_subversion_command:
130 scm_mercurial_command:
130 scm_mercurial_command:
131 scm_git_command:
131 scm_git_command:
132 scm_cvs_command:
132 scm_cvs_command:
133 scm_bazaar_command:
133 scm_bazaar_command:
134 scm_darcs_command:
134 scm_darcs_command:
135
135
136 # Key used to encrypt sensitive data in the database (SCM and LDAP passwords).
136 # Key used to encrypt sensitive data in the database (SCM and LDAP passwords).
137 # If you don't want to enable data encryption, just leave it blank.
137 # If you don't want to enable data encryption, just leave it blank.
138 # WARNING: losing/changing this key will make encrypted data unreadable.
138 # WARNING: losing/changing this key will make encrypted data unreadable.
139 #
139 #
140 # If you want to encrypt existing passwords in your database:
140 # If you want to encrypt existing passwords in your database:
141 # * set the cipher key here in your configuration file
141 # * set the cipher key here in your configuration file
142 # * encrypt data using 'rake db:encrypt RAILS_ENV=production'
142 # * encrypt data using 'rake db:encrypt RAILS_ENV=production'
143 #
143 #
144 # If you have encrypted data and want to change this key, you have to:
144 # If you have encrypted data and want to change this key, you have to:
145 # * decrypt data using 'rake db:decrypt RAILS_ENV=production' first
145 # * decrypt data using 'rake db:decrypt RAILS_ENV=production' first
146 # * change the cipher key here in your configuration file
146 # * change the cipher key here in your configuration file
147 # * encrypt data using 'rake db:encrypt RAILS_ENV=production'
147 # * encrypt data using 'rake db:encrypt RAILS_ENV=production'
148 database_cipher_key:
148 database_cipher_key:
149
149
150 # Set this to false to disable plugins' assets mirroring on startup.
150 # Set this to false to disable plugins' assets mirroring on startup.
151 # You can use `rake redmine:plugins:assets` to manually mirror assets
151 # You can use `rake redmine:plugins:assets` to manually mirror assets
152 # to public/plugin_assets when you install/upgrade a Redmine plugin.
152 # to public/plugin_assets when you install/upgrade a Redmine plugin.
153 #
153 #
154 #mirror_plugins_assets_on_startup: false
154 #mirror_plugins_assets_on_startup: false
155
155
156 # Your secret key for verifying cookie session data integrity. If you
156 # Your secret key for verifying cookie session data integrity. If you
157 # change this key, all old sessions will become invalid! Make sure the
157 # change this key, all old sessions will become invalid! Make sure the
158 # secret is at least 30 characters and all random, no regular words or
158 # secret is at least 30 characters and all random, no regular words or
159 # you'll be exposed to dictionary attacks.
159 # you'll be exposed to dictionary attacks.
160 #
160 #
161 # If you have a load-balancing Redmine cluster, you have to use the
161 # If you have a load-balancing Redmine cluster, you have to use the
162 # same secret token on each machine.
162 # same secret token on each machine.
163 #secret_token: 'change it to a long random string'
163 #secret_token: 'change it to a long random string'
164
164
165 # Absolute path (e.g. /usr/bin/convert, c:/im/convert.exe) to
165 # Absolute path (e.g. /usr/bin/convert, c:/im/convert.exe) to
166 # the ImageMagick's `convert` binary. Used to generate attachment thumbnails.
166 # the ImageMagick's `convert` binary. Used to generate attachment thumbnails.
167 #imagemagick_convert_command:
167 #imagemagick_convert_command:
168
168
169 # Configuration of RMagcik font.
170 #
171 # Redmine uses RMagcik in order to export gantt png.
172 # You don't need this setting if you don't install RMagcik.
173 #
174 # In CJK (Chinese, Japanese and Korean),
175 # in order to show CJK characters correctly,
176 # you need to set this configuration.
177 #
178 # Because there is no standard font across platforms in non CJK,
179 # you need to set a font installed in your server.
180 #
181 # This setting is not necessary in non CJK.
182 #
183 # Examples for Japanese:
184 # Windows:
185 # rmagick_font_path: C:\windows\fonts\msgothic.ttc
186 # Linux:
187 # rmagick_font_path: /usr/share/fonts/ipa-mincho/ipam.ttf
188 #
189 rmagick_font_path:
190
169 # specific configuration options for production environment
191 # specific configuration options for production environment
170 # that overrides the default ones
192 # that overrides the default ones
171 production:
193 production:
172
194
173 # specific configuration options for development environment
195 # specific configuration options for development environment
174 # that overrides the default ones
196 # that overrides the default ones
175 development:
197 development:
@@ -1,882 +1,883
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module Redmine
18 module Redmine
19 module Helpers
19 module Helpers
20 # Simple class to handle gantt chart data
20 # Simple class to handle gantt chart data
21 class Gantt
21 class Gantt
22 include ERB::Util
22 include ERB::Util
23 include Redmine::I18n
23 include Redmine::I18n
24 include Redmine::Utils::DateCalculation
24 include Redmine::Utils::DateCalculation
25
25
26 # :nodoc:
26 # :nodoc:
27 # Some utility methods for the PDF export
27 # Some utility methods for the PDF export
28 class PDF
28 class PDF
29 MaxCharactorsForSubject = 45
29 MaxCharactorsForSubject = 45
30 TotalWidth = 280
30 TotalWidth = 280
31 LeftPaneWidth = 100
31 LeftPaneWidth = 100
32
32
33 def self.right_pane_width
33 def self.right_pane_width
34 TotalWidth - LeftPaneWidth
34 TotalWidth - LeftPaneWidth
35 end
35 end
36 end
36 end
37
37
38 attr_reader :year_from, :month_from, :date_from, :date_to, :zoom, :months, :truncated, :max_rows
38 attr_reader :year_from, :month_from, :date_from, :date_to, :zoom, :months, :truncated, :max_rows
39 attr_accessor :query
39 attr_accessor :query
40 attr_accessor :project
40 attr_accessor :project
41 attr_accessor :view
41 attr_accessor :view
42
42
43 def initialize(options={})
43 def initialize(options={})
44 options = options.dup
44 options = options.dup
45 if options[:year] && options[:year].to_i >0
45 if options[:year] && options[:year].to_i >0
46 @year_from = options[:year].to_i
46 @year_from = options[:year].to_i
47 if options[:month] && options[:month].to_i >=1 && options[:month].to_i <= 12
47 if options[:month] && options[:month].to_i >=1 && options[:month].to_i <= 12
48 @month_from = options[:month].to_i
48 @month_from = options[:month].to_i
49 else
49 else
50 @month_from = 1
50 @month_from = 1
51 end
51 end
52 else
52 else
53 @month_from ||= Date.today.month
53 @month_from ||= Date.today.month
54 @year_from ||= Date.today.year
54 @year_from ||= Date.today.year
55 end
55 end
56 zoom = (options[:zoom] || User.current.pref[:gantt_zoom]).to_i
56 zoom = (options[:zoom] || User.current.pref[:gantt_zoom]).to_i
57 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
57 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
58 months = (options[:months] || User.current.pref[:gantt_months]).to_i
58 months = (options[:months] || User.current.pref[:gantt_months]).to_i
59 @months = (months > 0 && months < 25) ? months : 6
59 @months = (months > 0 && months < 25) ? months : 6
60 # Save gantt parameters as user preference (zoom and months count)
60 # Save gantt parameters as user preference (zoom and months count)
61 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] ||
61 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] ||
62 @months != User.current.pref[:gantt_months]))
62 @months != User.current.pref[:gantt_months]))
63 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
63 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
64 User.current.preference.save
64 User.current.preference.save
65 end
65 end
66 @date_from = Date.civil(@year_from, @month_from, 1)
66 @date_from = Date.civil(@year_from, @month_from, 1)
67 @date_to = (@date_from >> @months) - 1
67 @date_to = (@date_from >> @months) - 1
68 @subjects = ''
68 @subjects = ''
69 @lines = ''
69 @lines = ''
70 @number_of_rows = nil
70 @number_of_rows = nil
71 @issue_ancestors = []
71 @issue_ancestors = []
72 @truncated = false
72 @truncated = false
73 if options.has_key?(:max_rows)
73 if options.has_key?(:max_rows)
74 @max_rows = options[:max_rows]
74 @max_rows = options[:max_rows]
75 else
75 else
76 @max_rows = Setting.gantt_items_limit.blank? ? nil : Setting.gantt_items_limit.to_i
76 @max_rows = Setting.gantt_items_limit.blank? ? nil : Setting.gantt_items_limit.to_i
77 end
77 end
78 end
78 end
79
79
80 def common_params
80 def common_params
81 { :controller => 'gantts', :action => 'show', :project_id => @project }
81 { :controller => 'gantts', :action => 'show', :project_id => @project }
82 end
82 end
83
83
84 def params
84 def params
85 common_params.merge({:zoom => zoom, :year => year_from,
85 common_params.merge({:zoom => zoom, :year => year_from,
86 :month => month_from, :months => months})
86 :month => month_from, :months => months})
87 end
87 end
88
88
89 def params_previous
89 def params_previous
90 common_params.merge({:year => (date_from << months).year,
90 common_params.merge({:year => (date_from << months).year,
91 :month => (date_from << months).month,
91 :month => (date_from << months).month,
92 :zoom => zoom, :months => months})
92 :zoom => zoom, :months => months})
93 end
93 end
94
94
95 def params_next
95 def params_next
96 common_params.merge({:year => (date_from >> months).year,
96 common_params.merge({:year => (date_from >> months).year,
97 :month => (date_from >> months).month,
97 :month => (date_from >> months).month,
98 :zoom => zoom, :months => months})
98 :zoom => zoom, :months => months})
99 end
99 end
100
100
101 # Returns the number of rows that will be rendered on the Gantt chart
101 # Returns the number of rows that will be rendered on the Gantt chart
102 def number_of_rows
102 def number_of_rows
103 return @number_of_rows if @number_of_rows
103 return @number_of_rows if @number_of_rows
104 rows = projects.inject(0) {|total, p| total += number_of_rows_on_project(p)}
104 rows = projects.inject(0) {|total, p| total += number_of_rows_on_project(p)}
105 rows > @max_rows ? @max_rows : rows
105 rows > @max_rows ? @max_rows : rows
106 end
106 end
107
107
108 # Returns the number of rows that will be used to list a project on
108 # Returns the number of rows that will be used to list a project on
109 # the Gantt chart. This will recurse for each subproject.
109 # the Gantt chart. This will recurse for each subproject.
110 def number_of_rows_on_project(project)
110 def number_of_rows_on_project(project)
111 return 0 unless projects.include?(project)
111 return 0 unless projects.include?(project)
112 count = 1
112 count = 1
113 count += project_issues(project).size
113 count += project_issues(project).size
114 count += project_versions(project).size
114 count += project_versions(project).size
115 count
115 count
116 end
116 end
117
117
118 # Renders the subjects of the Gantt chart, the left side.
118 # Renders the subjects of the Gantt chart, the left side.
119 def subjects(options={})
119 def subjects(options={})
120 render(options.merge(:only => :subjects)) unless @subjects_rendered
120 render(options.merge(:only => :subjects)) unless @subjects_rendered
121 @subjects
121 @subjects
122 end
122 end
123
123
124 # Renders the lines of the Gantt chart, the right side
124 # Renders the lines of the Gantt chart, the right side
125 def lines(options={})
125 def lines(options={})
126 render(options.merge(:only => :lines)) unless @lines_rendered
126 render(options.merge(:only => :lines)) unless @lines_rendered
127 @lines
127 @lines
128 end
128 end
129
129
130 # Returns issues that will be rendered
130 # Returns issues that will be rendered
131 def issues
131 def issues
132 @issues ||= @query.issues(
132 @issues ||= @query.issues(
133 :include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
133 :include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
134 :order => "#{Project.table_name}.lft ASC, #{Issue.table_name}.id ASC",
134 :order => "#{Project.table_name}.lft ASC, #{Issue.table_name}.id ASC",
135 :limit => @max_rows
135 :limit => @max_rows
136 )
136 )
137 end
137 end
138
138
139 # Return all the project nodes that will be displayed
139 # Return all the project nodes that will be displayed
140 def projects
140 def projects
141 return @projects if @projects
141 return @projects if @projects
142 ids = issues.collect(&:project).uniq.collect(&:id)
142 ids = issues.collect(&:project).uniq.collect(&:id)
143 if ids.any?
143 if ids.any?
144 # All issues projects and their visible ancestors
144 # All issues projects and their visible ancestors
145 @projects = Project.visible.all(
145 @projects = Project.visible.all(
146 :joins => "LEFT JOIN #{Project.table_name} child ON #{Project.table_name}.lft <= child.lft AND #{Project.table_name}.rgt >= child.rgt",
146 :joins => "LEFT JOIN #{Project.table_name} child ON #{Project.table_name}.lft <= child.lft AND #{Project.table_name}.rgt >= child.rgt",
147 :conditions => ["child.id IN (?)", ids],
147 :conditions => ["child.id IN (?)", ids],
148 :order => "#{Project.table_name}.lft ASC"
148 :order => "#{Project.table_name}.lft ASC"
149 ).uniq
149 ).uniq
150 else
150 else
151 @projects = []
151 @projects = []
152 end
152 end
153 end
153 end
154
154
155 # Returns the issues that belong to +project+
155 # Returns the issues that belong to +project+
156 def project_issues(project)
156 def project_issues(project)
157 @issues_by_project ||= issues.group_by(&:project)
157 @issues_by_project ||= issues.group_by(&:project)
158 @issues_by_project[project] || []
158 @issues_by_project[project] || []
159 end
159 end
160
160
161 # Returns the distinct versions of the issues that belong to +project+
161 # Returns the distinct versions of the issues that belong to +project+
162 def project_versions(project)
162 def project_versions(project)
163 project_issues(project).collect(&:fixed_version).compact.uniq
163 project_issues(project).collect(&:fixed_version).compact.uniq
164 end
164 end
165
165
166 # Returns the issues that belong to +project+ and are assigned to +version+
166 # Returns the issues that belong to +project+ and are assigned to +version+
167 def version_issues(project, version)
167 def version_issues(project, version)
168 project_issues(project).select {|issue| issue.fixed_version == version}
168 project_issues(project).select {|issue| issue.fixed_version == version}
169 end
169 end
170
170
171 def render(options={})
171 def render(options={})
172 options = {:top => 0, :top_increment => 20,
172 options = {:top => 0, :top_increment => 20,
173 :indent_increment => 20, :render => :subject,
173 :indent_increment => 20, :render => :subject,
174 :format => :html}.merge(options)
174 :format => :html}.merge(options)
175 indent = options[:indent] || 4
175 indent = options[:indent] || 4
176 @subjects = '' unless options[:only] == :lines
176 @subjects = '' unless options[:only] == :lines
177 @lines = '' unless options[:only] == :subjects
177 @lines = '' unless options[:only] == :subjects
178 @number_of_rows = 0
178 @number_of_rows = 0
179 Project.project_tree(projects) do |project, level|
179 Project.project_tree(projects) do |project, level|
180 options[:indent] = indent + level * options[:indent_increment]
180 options[:indent] = indent + level * options[:indent_increment]
181 render_project(project, options)
181 render_project(project, options)
182 break if abort?
182 break if abort?
183 end
183 end
184 @subjects_rendered = true unless options[:only] == :lines
184 @subjects_rendered = true unless options[:only] == :lines
185 @lines_rendered = true unless options[:only] == :subjects
185 @lines_rendered = true unless options[:only] == :subjects
186 render_end(options)
186 render_end(options)
187 end
187 end
188
188
189 def render_project(project, options={})
189 def render_project(project, options={})
190 subject_for_project(project, options) unless options[:only] == :lines
190 subject_for_project(project, options) unless options[:only] == :lines
191 line_for_project(project, options) unless options[:only] == :subjects
191 line_for_project(project, options) unless options[:only] == :subjects
192 options[:top] += options[:top_increment]
192 options[:top] += options[:top_increment]
193 options[:indent] += options[:indent_increment]
193 options[:indent] += options[:indent_increment]
194 @number_of_rows += 1
194 @number_of_rows += 1
195 return if abort?
195 return if abort?
196 issues = project_issues(project).select {|i| i.fixed_version.nil?}
196 issues = project_issues(project).select {|i| i.fixed_version.nil?}
197 sort_issues!(issues)
197 sort_issues!(issues)
198 if issues
198 if issues
199 render_issues(issues, options)
199 render_issues(issues, options)
200 return if abort?
200 return if abort?
201 end
201 end
202 versions = project_versions(project)
202 versions = project_versions(project)
203 versions.each do |version|
203 versions.each do |version|
204 render_version(project, version, options)
204 render_version(project, version, options)
205 end
205 end
206 # Remove indent to hit the next sibling
206 # Remove indent to hit the next sibling
207 options[:indent] -= options[:indent_increment]
207 options[:indent] -= options[:indent_increment]
208 end
208 end
209
209
210 def render_issues(issues, options={})
210 def render_issues(issues, options={})
211 @issue_ancestors = []
211 @issue_ancestors = []
212 issues.each do |i|
212 issues.each do |i|
213 subject_for_issue(i, options) unless options[:only] == :lines
213 subject_for_issue(i, options) unless options[:only] == :lines
214 line_for_issue(i, options) unless options[:only] == :subjects
214 line_for_issue(i, options) unless options[:only] == :subjects
215 options[:top] += options[:top_increment]
215 options[:top] += options[:top_increment]
216 @number_of_rows += 1
216 @number_of_rows += 1
217 break if abort?
217 break if abort?
218 end
218 end
219 options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
219 options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
220 end
220 end
221
221
222 def render_version(project, version, options={})
222 def render_version(project, version, options={})
223 # Version header
223 # Version header
224 subject_for_version(version, options) unless options[:only] == :lines
224 subject_for_version(version, options) unless options[:only] == :lines
225 line_for_version(version, options) unless options[:only] == :subjects
225 line_for_version(version, options) unless options[:only] == :subjects
226 options[:top] += options[:top_increment]
226 options[:top] += options[:top_increment]
227 @number_of_rows += 1
227 @number_of_rows += 1
228 return if abort?
228 return if abort?
229 issues = version_issues(project, version)
229 issues = version_issues(project, version)
230 if issues
230 if issues
231 sort_issues!(issues)
231 sort_issues!(issues)
232 # Indent issues
232 # Indent issues
233 options[:indent] += options[:indent_increment]
233 options[:indent] += options[:indent_increment]
234 render_issues(issues, options)
234 render_issues(issues, options)
235 options[:indent] -= options[:indent_increment]
235 options[:indent] -= options[:indent_increment]
236 end
236 end
237 end
237 end
238
238
239 def render_end(options={})
239 def render_end(options={})
240 case options[:format]
240 case options[:format]
241 when :pdf
241 when :pdf
242 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
242 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
243 end
243 end
244 end
244 end
245
245
246 def subject_for_project(project, options)
246 def subject_for_project(project, options)
247 case options[:format]
247 case options[:format]
248 when :html
248 when :html
249 html_class = ""
249 html_class = ""
250 html_class << 'icon icon-projects '
250 html_class << 'icon icon-projects '
251 html_class << (project.overdue? ? 'project-overdue' : '')
251 html_class << (project.overdue? ? 'project-overdue' : '')
252 s = view.link_to_project(project).html_safe
252 s = view.link_to_project(project).html_safe
253 subject = view.content_tag(:span, s,
253 subject = view.content_tag(:span, s,
254 :class => html_class).html_safe
254 :class => html_class).html_safe
255 html_subject(options, subject, :css => "project-name")
255 html_subject(options, subject, :css => "project-name")
256 when :image
256 when :image
257 image_subject(options, project.name)
257 image_subject(options, project.name)
258 when :pdf
258 when :pdf
259 pdf_new_page?(options)
259 pdf_new_page?(options)
260 pdf_subject(options, project.name)
260 pdf_subject(options, project.name)
261 end
261 end
262 end
262 end
263
263
264 def line_for_project(project, options)
264 def line_for_project(project, options)
265 # Skip versions that don't have a start_date or due date
265 # Skip versions that don't have a start_date or due date
266 if project.is_a?(Project) && project.start_date && project.due_date
266 if project.is_a?(Project) && project.start_date && project.due_date
267 options[:zoom] ||= 1
267 options[:zoom] ||= 1
268 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
268 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
269 coords = coordinates(project.start_date, project.due_date, nil, options[:zoom])
269 coords = coordinates(project.start_date, project.due_date, nil, options[:zoom])
270 label = h(project)
270 label = h(project)
271 case options[:format]
271 case options[:format]
272 when :html
272 when :html
273 html_task(options, coords, :css => "project task", :label => label, :markers => true)
273 html_task(options, coords, :css => "project task", :label => label, :markers => true)
274 when :image
274 when :image
275 image_task(options, coords, :label => label, :markers => true, :height => 3)
275 image_task(options, coords, :label => label, :markers => true, :height => 3)
276 when :pdf
276 when :pdf
277 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8)
277 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8)
278 end
278 end
279 else
279 else
280 ActiveRecord::Base.logger.debug "Gantt#line_for_project was not given a project with a start_date"
280 ActiveRecord::Base.logger.debug "Gantt#line_for_project was not given a project with a start_date"
281 ''
281 ''
282 end
282 end
283 end
283 end
284
284
285 def subject_for_version(version, options)
285 def subject_for_version(version, options)
286 case options[:format]
286 case options[:format]
287 when :html
287 when :html
288 html_class = ""
288 html_class = ""
289 html_class << 'icon icon-package '
289 html_class << 'icon icon-package '
290 html_class << (version.behind_schedule? ? 'version-behind-schedule' : '') << " "
290 html_class << (version.behind_schedule? ? 'version-behind-schedule' : '') << " "
291 html_class << (version.overdue? ? 'version-overdue' : '')
291 html_class << (version.overdue? ? 'version-overdue' : '')
292 s = view.link_to_version(version).html_safe
292 s = view.link_to_version(version).html_safe
293 subject = view.content_tag(:span, s,
293 subject = view.content_tag(:span, s,
294 :class => html_class).html_safe
294 :class => html_class).html_safe
295 html_subject(options, subject, :css => "version-name")
295 html_subject(options, subject, :css => "version-name")
296 when :image
296 when :image
297 image_subject(options, version.to_s_with_project)
297 image_subject(options, version.to_s_with_project)
298 when :pdf
298 when :pdf
299 pdf_new_page?(options)
299 pdf_new_page?(options)
300 pdf_subject(options, version.to_s_with_project)
300 pdf_subject(options, version.to_s_with_project)
301 end
301 end
302 end
302 end
303
303
304 def line_for_version(version, options)
304 def line_for_version(version, options)
305 # Skip versions that don't have a start_date
305 # Skip versions that don't have a start_date
306 if version.is_a?(Version) && version.start_date && version.due_date
306 if version.is_a?(Version) && version.start_date && version.due_date
307 options[:zoom] ||= 1
307 options[:zoom] ||= 1
308 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
308 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
309 coords = coordinates(version.start_date,
309 coords = coordinates(version.start_date,
310 version.due_date, version.completed_pourcent,
310 version.due_date, version.completed_pourcent,
311 options[:zoom])
311 options[:zoom])
312 label = "#{h version} #{h version.completed_pourcent.to_i.to_s}%"
312 label = "#{h version} #{h version.completed_pourcent.to_i.to_s}%"
313 label = h("#{version.project} -") + label unless @project && @project == version.project
313 label = h("#{version.project} -") + label unless @project && @project == version.project
314 case options[:format]
314 case options[:format]
315 when :html
315 when :html
316 html_task(options, coords, :css => "version task", :label => label, :markers => true)
316 html_task(options, coords, :css => "version task", :label => label, :markers => true)
317 when :image
317 when :image
318 image_task(options, coords, :label => label, :markers => true, :height => 3)
318 image_task(options, coords, :label => label, :markers => true, :height => 3)
319 when :pdf
319 when :pdf
320 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8)
320 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8)
321 end
321 end
322 else
322 else
323 ActiveRecord::Base.logger.debug "Gantt#line_for_version was not given a version with a start_date"
323 ActiveRecord::Base.logger.debug "Gantt#line_for_version was not given a version with a start_date"
324 ''
324 ''
325 end
325 end
326 end
326 end
327
327
328 def subject_for_issue(issue, options)
328 def subject_for_issue(issue, options)
329 while @issue_ancestors.any? && !issue.is_descendant_of?(@issue_ancestors.last)
329 while @issue_ancestors.any? && !issue.is_descendant_of?(@issue_ancestors.last)
330 @issue_ancestors.pop
330 @issue_ancestors.pop
331 options[:indent] -= options[:indent_increment]
331 options[:indent] -= options[:indent_increment]
332 end
332 end
333 output = case options[:format]
333 output = case options[:format]
334 when :html
334 when :html
335 css_classes = ''
335 css_classes = ''
336 css_classes << ' issue-overdue' if issue.overdue?
336 css_classes << ' issue-overdue' if issue.overdue?
337 css_classes << ' issue-behind-schedule' if issue.behind_schedule?
337 css_classes << ' issue-behind-schedule' if issue.behind_schedule?
338 css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
338 css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
339 s = "".html_safe
339 s = "".html_safe
340 if issue.assigned_to.present?
340 if issue.assigned_to.present?
341 assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
341 assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
342 s << view.avatar(issue.assigned_to,
342 s << view.avatar(issue.assigned_to,
343 :class => 'gravatar icon-gravatar',
343 :class => 'gravatar icon-gravatar',
344 :size => 10,
344 :size => 10,
345 :title => assigned_string).to_s.html_safe
345 :title => assigned_string).to_s.html_safe
346 end
346 end
347 s << view.link_to_issue(issue).html_safe
347 s << view.link_to_issue(issue).html_safe
348 subject = view.content_tag(:span, s, :class => css_classes).html_safe
348 subject = view.content_tag(:span, s, :class => css_classes).html_safe
349 html_subject(options, subject, :css => "issue-subject",
349 html_subject(options, subject, :css => "issue-subject",
350 :title => issue.subject) + "\n"
350 :title => issue.subject) + "\n"
351 when :image
351 when :image
352 image_subject(options, issue.subject)
352 image_subject(options, issue.subject)
353 when :pdf
353 when :pdf
354 pdf_new_page?(options)
354 pdf_new_page?(options)
355 pdf_subject(options, issue.subject)
355 pdf_subject(options, issue.subject)
356 end
356 end
357 unless issue.leaf?
357 unless issue.leaf?
358 @issue_ancestors << issue
358 @issue_ancestors << issue
359 options[:indent] += options[:indent_increment]
359 options[:indent] += options[:indent_increment]
360 end
360 end
361 output
361 output
362 end
362 end
363
363
364 def line_for_issue(issue, options)
364 def line_for_issue(issue, options)
365 # Skip issues that don't have a due_before (due_date or version's due_date)
365 # Skip issues that don't have a due_before (due_date or version's due_date)
366 if issue.is_a?(Issue) && issue.due_before
366 if issue.is_a?(Issue) && issue.due_before
367 coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom])
367 coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom])
368 label = "#{issue.status.name} #{issue.done_ratio}%"
368 label = "#{issue.status.name} #{issue.done_ratio}%"
369 case options[:format]
369 case options[:format]
370 when :html
370 when :html
371 html_task(options, coords,
371 html_task(options, coords,
372 :css => "task " + (issue.leaf? ? 'leaf' : 'parent'),
372 :css => "task " + (issue.leaf? ? 'leaf' : 'parent'),
373 :label => label, :issue => issue,
373 :label => label, :issue => issue,
374 :markers => !issue.leaf?)
374 :markers => !issue.leaf?)
375 when :image
375 when :image
376 image_task(options, coords, :label => label)
376 image_task(options, coords, :label => label)
377 when :pdf
377 when :pdf
378 pdf_task(options, coords, :label => label)
378 pdf_task(options, coords, :label => label)
379 end
379 end
380 else
380 else
381 ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
381 ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
382 ''
382 ''
383 end
383 end
384 end
384 end
385
385
386 # Generates a gantt image
386 # Generates a gantt image
387 # Only defined if RMagick is avalaible
387 # Only defined if RMagick is avalaible
388 def to_image(format='PNG')
388 def to_image(format='PNG')
389 date_to = (@date_from >> @months) - 1
389 date_to = (@date_from >> @months) - 1
390 show_weeks = @zoom > 1
390 show_weeks = @zoom > 1
391 show_days = @zoom > 2
391 show_days = @zoom > 2
392 subject_width = 400
392 subject_width = 400
393 header_height = 18
393 header_height = 18
394 # width of one day in pixels
394 # width of one day in pixels
395 zoom = @zoom * 2
395 zoom = @zoom * 2
396 g_width = (@date_to - @date_from + 1) * zoom
396 g_width = (@date_to - @date_from + 1) * zoom
397 g_height = 20 * number_of_rows + 30
397 g_height = 20 * number_of_rows + 30
398 headers_height = (show_weeks ? 2 * header_height : header_height)
398 headers_height = (show_weeks ? 2 * header_height : header_height)
399 height = g_height + headers_height
399 height = g_height + headers_height
400 imgl = Magick::ImageList.new
400 imgl = Magick::ImageList.new
401 imgl.new_image(subject_width + g_width + 1, height)
401 imgl.new_image(subject_width + g_width + 1, height)
402 gc = Magick::Draw.new
402 gc = Magick::Draw.new
403 gc.font = Redmine::Configuration['rmagick_font_path'] || ""
403 # Subjects
404 # Subjects
404 gc.stroke('transparent')
405 gc.stroke('transparent')
405 subjects(:image => gc, :top => (headers_height + 20), :indent => 4, :format => :image)
406 subjects(:image => gc, :top => (headers_height + 20), :indent => 4, :format => :image)
406 # Months headers
407 # Months headers
407 month_f = @date_from
408 month_f = @date_from
408 left = subject_width
409 left = subject_width
409 @months.times do
410 @months.times do
410 width = ((month_f >> 1) - month_f) * zoom
411 width = ((month_f >> 1) - month_f) * zoom
411 gc.fill('white')
412 gc.fill('white')
412 gc.stroke('grey')
413 gc.stroke('grey')
413 gc.stroke_width(1)
414 gc.stroke_width(1)
414 gc.rectangle(left, 0, left + width, height)
415 gc.rectangle(left, 0, left + width, height)
415 gc.fill('black')
416 gc.fill('black')
416 gc.stroke('transparent')
417 gc.stroke('transparent')
417 gc.stroke_width(1)
418 gc.stroke_width(1)
418 gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
419 gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
419 left = left + width
420 left = left + width
420 month_f = month_f >> 1
421 month_f = month_f >> 1
421 end
422 end
422 # Weeks headers
423 # Weeks headers
423 if show_weeks
424 if show_weeks
424 left = subject_width
425 left = subject_width
425 height = header_height
426 height = header_height
426 if @date_from.cwday == 1
427 if @date_from.cwday == 1
427 # date_from is monday
428 # date_from is monday
428 week_f = date_from
429 week_f = date_from
429 else
430 else
430 # find next monday after date_from
431 # find next monday after date_from
431 week_f = @date_from + (7 - @date_from.cwday + 1)
432 week_f = @date_from + (7 - @date_from.cwday + 1)
432 width = (7 - @date_from.cwday + 1) * zoom
433 width = (7 - @date_from.cwday + 1) * zoom
433 gc.fill('white')
434 gc.fill('white')
434 gc.stroke('grey')
435 gc.stroke('grey')
435 gc.stroke_width(1)
436 gc.stroke_width(1)
436 gc.rectangle(left, header_height, left + width, 2 * header_height + g_height - 1)
437 gc.rectangle(left, header_height, left + width, 2 * header_height + g_height - 1)
437 left = left + width
438 left = left + width
438 end
439 end
439 while week_f <= date_to
440 while week_f <= date_to
440 width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
441 width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
441 gc.fill('white')
442 gc.fill('white')
442 gc.stroke('grey')
443 gc.stroke('grey')
443 gc.stroke_width(1)
444 gc.stroke_width(1)
444 gc.rectangle(left.round, header_height, left.round + width, 2 * header_height + g_height - 1)
445 gc.rectangle(left.round, header_height, left.round + width, 2 * header_height + g_height - 1)
445 gc.fill('black')
446 gc.fill('black')
446 gc.stroke('transparent')
447 gc.stroke('transparent')
447 gc.stroke_width(1)
448 gc.stroke_width(1)
448 gc.text(left.round + 2, header_height + 14, week_f.cweek.to_s)
449 gc.text(left.round + 2, header_height + 14, week_f.cweek.to_s)
449 left = left + width
450 left = left + width
450 week_f = week_f + 7
451 week_f = week_f + 7
451 end
452 end
452 end
453 end
453 # Days details (week-end in grey)
454 # Days details (week-end in grey)
454 if show_days
455 if show_days
455 left = subject_width
456 left = subject_width
456 height = g_height + header_height - 1
457 height = g_height + header_height - 1
457 wday = @date_from.cwday
458 wday = @date_from.cwday
458 (date_to - @date_from + 1).to_i.times do
459 (date_to - @date_from + 1).to_i.times do
459 width = zoom
460 width = zoom
460 gc.fill(non_working_week_days.include?(wday) ? '#eee' : 'white')
461 gc.fill(non_working_week_days.include?(wday) ? '#eee' : 'white')
461 gc.stroke('#ddd')
462 gc.stroke('#ddd')
462 gc.stroke_width(1)
463 gc.stroke_width(1)
463 gc.rectangle(left, 2 * header_height, left + width, 2 * header_height + g_height - 1)
464 gc.rectangle(left, 2 * header_height, left + width, 2 * header_height + g_height - 1)
464 left = left + width
465 left = left + width
465 wday = wday + 1
466 wday = wday + 1
466 wday = 1 if wday > 7
467 wday = 1 if wday > 7
467 end
468 end
468 end
469 end
469 # border
470 # border
470 gc.fill('transparent')
471 gc.fill('transparent')
471 gc.stroke('grey')
472 gc.stroke('grey')
472 gc.stroke_width(1)
473 gc.stroke_width(1)
473 gc.rectangle(0, 0, subject_width + g_width, headers_height)
474 gc.rectangle(0, 0, subject_width + g_width, headers_height)
474 gc.stroke('black')
475 gc.stroke('black')
475 gc.rectangle(0, 0, subject_width + g_width, g_height + headers_height - 1)
476 gc.rectangle(0, 0, subject_width + g_width, g_height + headers_height - 1)
476 # content
477 # content
477 top = headers_height + 20
478 top = headers_height + 20
478 gc.stroke('transparent')
479 gc.stroke('transparent')
479 lines(:image => gc, :top => top, :zoom => zoom,
480 lines(:image => gc, :top => top, :zoom => zoom,
480 :subject_width => subject_width, :format => :image)
481 :subject_width => subject_width, :format => :image)
481 # today red line
482 # today red line
482 if Date.today >= @date_from and Date.today <= date_to
483 if Date.today >= @date_from and Date.today <= date_to
483 gc.stroke('red')
484 gc.stroke('red')
484 x = (Date.today - @date_from + 1) * zoom + subject_width
485 x = (Date.today - @date_from + 1) * zoom + subject_width
485 gc.line(x, headers_height, x, headers_height + g_height - 1)
486 gc.line(x, headers_height, x, headers_height + g_height - 1)
486 end
487 end
487 gc.draw(imgl)
488 gc.draw(imgl)
488 imgl.format = format
489 imgl.format = format
489 imgl.to_blob
490 imgl.to_blob
490 end if Object.const_defined?(:Magick)
491 end if Object.const_defined?(:Magick)
491
492
492 def to_pdf
493 def to_pdf
493 pdf = ::Redmine::Export::PDF::ITCPDF.new(current_language)
494 pdf = ::Redmine::Export::PDF::ITCPDF.new(current_language)
494 pdf.SetTitle("#{l(:label_gantt)} #{project}")
495 pdf.SetTitle("#{l(:label_gantt)} #{project}")
495 pdf.alias_nb_pages
496 pdf.alias_nb_pages
496 pdf.footer_date = format_date(Date.today)
497 pdf.footer_date = format_date(Date.today)
497 pdf.AddPage("L")
498 pdf.AddPage("L")
498 pdf.SetFontStyle('B', 12)
499 pdf.SetFontStyle('B', 12)
499 pdf.SetX(15)
500 pdf.SetX(15)
500 pdf.RDMCell(PDF::LeftPaneWidth, 20, project.to_s)
501 pdf.RDMCell(PDF::LeftPaneWidth, 20, project.to_s)
501 pdf.Ln
502 pdf.Ln
502 pdf.SetFontStyle('B', 9)
503 pdf.SetFontStyle('B', 9)
503 subject_width = PDF::LeftPaneWidth
504 subject_width = PDF::LeftPaneWidth
504 header_height = 5
505 header_height = 5
505 headers_height = header_height
506 headers_height = header_height
506 show_weeks = false
507 show_weeks = false
507 show_days = false
508 show_days = false
508 if self.months < 7
509 if self.months < 7
509 show_weeks = true
510 show_weeks = true
510 headers_height = 2 * header_height
511 headers_height = 2 * header_height
511 if self.months < 3
512 if self.months < 3
512 show_days = true
513 show_days = true
513 headers_height = 3 * header_height
514 headers_height = 3 * header_height
514 end
515 end
515 end
516 end
516 g_width = PDF.right_pane_width
517 g_width = PDF.right_pane_width
517 zoom = (g_width) / (self.date_to - self.date_from + 1)
518 zoom = (g_width) / (self.date_to - self.date_from + 1)
518 g_height = 120
519 g_height = 120
519 t_height = g_height + headers_height
520 t_height = g_height + headers_height
520 y_start = pdf.GetY
521 y_start = pdf.GetY
521 # Months headers
522 # Months headers
522 month_f = self.date_from
523 month_f = self.date_from
523 left = subject_width
524 left = subject_width
524 height = header_height
525 height = header_height
525 self.months.times do
526 self.months.times do
526 width = ((month_f >> 1) - month_f) * zoom
527 width = ((month_f >> 1) - month_f) * zoom
527 pdf.SetY(y_start)
528 pdf.SetY(y_start)
528 pdf.SetX(left)
529 pdf.SetX(left)
529 pdf.RDMCell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
530 pdf.RDMCell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
530 left = left + width
531 left = left + width
531 month_f = month_f >> 1
532 month_f = month_f >> 1
532 end
533 end
533 # Weeks headers
534 # Weeks headers
534 if show_weeks
535 if show_weeks
535 left = subject_width
536 left = subject_width
536 height = header_height
537 height = header_height
537 if self.date_from.cwday == 1
538 if self.date_from.cwday == 1
538 # self.date_from is monday
539 # self.date_from is monday
539 week_f = self.date_from
540 week_f = self.date_from
540 else
541 else
541 # find next monday after self.date_from
542 # find next monday after self.date_from
542 week_f = self.date_from + (7 - self.date_from.cwday + 1)
543 week_f = self.date_from + (7 - self.date_from.cwday + 1)
543 width = (7 - self.date_from.cwday + 1) * zoom-1
544 width = (7 - self.date_from.cwday + 1) * zoom-1
544 pdf.SetY(y_start + header_height)
545 pdf.SetY(y_start + header_height)
545 pdf.SetX(left)
546 pdf.SetX(left)
546 pdf.RDMCell(width + 1, height, "", "LTR")
547 pdf.RDMCell(width + 1, height, "", "LTR")
547 left = left + width + 1
548 left = left + width + 1
548 end
549 end
549 while week_f <= self.date_to
550 while week_f <= self.date_to
550 width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom
551 width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom
551 pdf.SetY(y_start + header_height)
552 pdf.SetY(y_start + header_height)
552 pdf.SetX(left)
553 pdf.SetX(left)
553 pdf.RDMCell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
554 pdf.RDMCell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
554 left = left + width
555 left = left + width
555 week_f = week_f + 7
556 week_f = week_f + 7
556 end
557 end
557 end
558 end
558 # Days headers
559 # Days headers
559 if show_days
560 if show_days
560 left = subject_width
561 left = subject_width
561 height = header_height
562 height = header_height
562 wday = self.date_from.cwday
563 wday = self.date_from.cwday
563 pdf.SetFontStyle('B', 7)
564 pdf.SetFontStyle('B', 7)
564 (self.date_to - self.date_from + 1).to_i.times do
565 (self.date_to - self.date_from + 1).to_i.times do
565 width = zoom
566 width = zoom
566 pdf.SetY(y_start + 2 * header_height)
567 pdf.SetY(y_start + 2 * header_height)
567 pdf.SetX(left)
568 pdf.SetX(left)
568 pdf.RDMCell(width, height, day_name(wday).first, "LTR", 0, "C")
569 pdf.RDMCell(width, height, day_name(wday).first, "LTR", 0, "C")
569 left = left + width
570 left = left + width
570 wday = wday + 1
571 wday = wday + 1
571 wday = 1 if wday > 7
572 wday = 1 if wday > 7
572 end
573 end
573 end
574 end
574 pdf.SetY(y_start)
575 pdf.SetY(y_start)
575 pdf.SetX(15)
576 pdf.SetX(15)
576 pdf.RDMCell(subject_width + g_width - 15, headers_height, "", 1)
577 pdf.RDMCell(subject_width + g_width - 15, headers_height, "", 1)
577 # Tasks
578 # Tasks
578 top = headers_height + y_start
579 top = headers_height + y_start
579 options = {
580 options = {
580 :top => top,
581 :top => top,
581 :zoom => zoom,
582 :zoom => zoom,
582 :subject_width => subject_width,
583 :subject_width => subject_width,
583 :g_width => g_width,
584 :g_width => g_width,
584 :indent => 0,
585 :indent => 0,
585 :indent_increment => 5,
586 :indent_increment => 5,
586 :top_increment => 5,
587 :top_increment => 5,
587 :format => :pdf,
588 :format => :pdf,
588 :pdf => pdf
589 :pdf => pdf
589 }
590 }
590 render(options)
591 render(options)
591 pdf.Output
592 pdf.Output
592 end
593 end
593
594
594 private
595 private
595
596
596 def coordinates(start_date, end_date, progress, zoom=nil)
597 def coordinates(start_date, end_date, progress, zoom=nil)
597 zoom ||= @zoom
598 zoom ||= @zoom
598 coords = {}
599 coords = {}
599 if start_date && end_date && start_date < self.date_to && end_date > self.date_from
600 if start_date && end_date && start_date < self.date_to && end_date > self.date_from
600 if start_date > self.date_from
601 if start_date > self.date_from
601 coords[:start] = start_date - self.date_from
602 coords[:start] = start_date - self.date_from
602 coords[:bar_start] = start_date - self.date_from
603 coords[:bar_start] = start_date - self.date_from
603 else
604 else
604 coords[:bar_start] = 0
605 coords[:bar_start] = 0
605 end
606 end
606 if end_date < self.date_to
607 if end_date < self.date_to
607 coords[:end] = end_date - self.date_from
608 coords[:end] = end_date - self.date_from
608 coords[:bar_end] = end_date - self.date_from + 1
609 coords[:bar_end] = end_date - self.date_from + 1
609 else
610 else
610 coords[:bar_end] = self.date_to - self.date_from + 1
611 coords[:bar_end] = self.date_to - self.date_from + 1
611 end
612 end
612 if progress
613 if progress
613 progress_date = start_date + (end_date - start_date + 1) * (progress / 100.0)
614 progress_date = start_date + (end_date - start_date + 1) * (progress / 100.0)
614 if progress_date > self.date_from && progress_date > start_date
615 if progress_date > self.date_from && progress_date > start_date
615 if progress_date < self.date_to
616 if progress_date < self.date_to
616 coords[:bar_progress_end] = progress_date - self.date_from
617 coords[:bar_progress_end] = progress_date - self.date_from
617 else
618 else
618 coords[:bar_progress_end] = self.date_to - self.date_from + 1
619 coords[:bar_progress_end] = self.date_to - self.date_from + 1
619 end
620 end
620 end
621 end
621 if progress_date < Date.today
622 if progress_date < Date.today
622 late_date = [Date.today, end_date].min
623 late_date = [Date.today, end_date].min
623 if late_date > self.date_from && late_date > start_date
624 if late_date > self.date_from && late_date > start_date
624 if late_date < self.date_to
625 if late_date < self.date_to
625 coords[:bar_late_end] = late_date - self.date_from + 1
626 coords[:bar_late_end] = late_date - self.date_from + 1
626 else
627 else
627 coords[:bar_late_end] = self.date_to - self.date_from + 1
628 coords[:bar_late_end] = self.date_to - self.date_from + 1
628 end
629 end
629 end
630 end
630 end
631 end
631 end
632 end
632 end
633 end
633 # Transforms dates into pixels witdh
634 # Transforms dates into pixels witdh
634 coords.keys.each do |key|
635 coords.keys.each do |key|
635 coords[key] = (coords[key] * zoom).floor
636 coords[key] = (coords[key] * zoom).floor
636 end
637 end
637 coords
638 coords
638 end
639 end
639
640
640 # Sorts a collection of issues by start_date, due_date, id for gantt rendering
641 # Sorts a collection of issues by start_date, due_date, id for gantt rendering
641 def sort_issues!(issues)
642 def sort_issues!(issues)
642 issues.sort! { |a, b| gantt_issue_compare(a, b) }
643 issues.sort! { |a, b| gantt_issue_compare(a, b) }
643 end
644 end
644
645
645 # TODO: top level issues should be sorted by start date
646 # TODO: top level issues should be sorted by start date
646 def gantt_issue_compare(x, y)
647 def gantt_issue_compare(x, y)
647 if x.root_id == y.root_id
648 if x.root_id == y.root_id
648 x.lft <=> y.lft
649 x.lft <=> y.lft
649 else
650 else
650 x.root_id <=> y.root_id
651 x.root_id <=> y.root_id
651 end
652 end
652 end
653 end
653
654
654 def current_limit
655 def current_limit
655 if @max_rows
656 if @max_rows
656 @max_rows - @number_of_rows
657 @max_rows - @number_of_rows
657 else
658 else
658 nil
659 nil
659 end
660 end
660 end
661 end
661
662
662 def abort?
663 def abort?
663 if @max_rows && @number_of_rows >= @max_rows
664 if @max_rows && @number_of_rows >= @max_rows
664 @truncated = true
665 @truncated = true
665 end
666 end
666 end
667 end
667
668
668 def pdf_new_page?(options)
669 def pdf_new_page?(options)
669 if options[:top] > 180
670 if options[:top] > 180
670 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
671 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
671 options[:pdf].AddPage("L")
672 options[:pdf].AddPage("L")
672 options[:top] = 15
673 options[:top] = 15
673 options[:pdf].Line(15, options[:top] - 0.1, PDF::TotalWidth, options[:top] - 0.1)
674 options[:pdf].Line(15, options[:top] - 0.1, PDF::TotalWidth, options[:top] - 0.1)
674 end
675 end
675 end
676 end
676
677
677 def html_subject(params, subject, options={})
678 def html_subject(params, subject, options={})
678 style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;"
679 style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;"
679 style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width]
680 style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width]
680 output = view.content_tag('div', subject,
681 output = view.content_tag('div', subject,
681 :class => options[:css], :style => style,
682 :class => options[:css], :style => style,
682 :title => options[:title])
683 :title => options[:title])
683 @subjects << output
684 @subjects << output
684 output
685 output
685 end
686 end
686
687
687 def pdf_subject(params, subject, options={})
688 def pdf_subject(params, subject, options={})
688 params[:pdf].SetY(params[:top])
689 params[:pdf].SetY(params[:top])
689 params[:pdf].SetX(15)
690 params[:pdf].SetX(15)
690 char_limit = PDF::MaxCharactorsForSubject - params[:indent]
691 char_limit = PDF::MaxCharactorsForSubject - params[:indent]
691 params[:pdf].RDMCell(params[:subject_width] - 15, 5,
692 params[:pdf].RDMCell(params[:subject_width] - 15, 5,
692 (" " * params[:indent]) +
693 (" " * params[:indent]) +
693 subject.to_s.sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'),
694 subject.to_s.sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'),
694 "LR")
695 "LR")
695 params[:pdf].SetY(params[:top])
696 params[:pdf].SetY(params[:top])
696 params[:pdf].SetX(params[:subject_width])
697 params[:pdf].SetX(params[:subject_width])
697 params[:pdf].RDMCell(params[:g_width], 5, "", "LR")
698 params[:pdf].RDMCell(params[:g_width], 5, "", "LR")
698 end
699 end
699
700
700 def image_subject(params, subject, options={})
701 def image_subject(params, subject, options={})
701 params[:image].fill('black')
702 params[:image].fill('black')
702 params[:image].stroke('transparent')
703 params[:image].stroke('transparent')
703 params[:image].stroke_width(1)
704 params[:image].stroke_width(1)
704 params[:image].text(params[:indent], params[:top] + 2, subject)
705 params[:image].text(params[:indent], params[:top] + 2, subject)
705 end
706 end
706
707
707 def html_task(params, coords, options={})
708 def html_task(params, coords, options={})
708 output = ''
709 output = ''
709 # Renders the task bar, with progress and late
710 # Renders the task bar, with progress and late
710 if coords[:bar_start] && coords[:bar_end]
711 if coords[:bar_start] && coords[:bar_end]
711 width = coords[:bar_end] - coords[:bar_start] - 2
712 width = coords[:bar_end] - coords[:bar_start] - 2
712 style = ""
713 style = ""
713 style << "top:#{params[:top]}px;"
714 style << "top:#{params[:top]}px;"
714 style << "left:#{coords[:bar_start]}px;"
715 style << "left:#{coords[:bar_start]}px;"
715 style << "width:#{width}px;"
716 style << "width:#{width}px;"
716 output << view.content_tag(:div, '&nbsp;'.html_safe,
717 output << view.content_tag(:div, '&nbsp;'.html_safe,
717 :style => style,
718 :style => style,
718 :class => "#{options[:css]} task_todo")
719 :class => "#{options[:css]} task_todo")
719 if coords[:bar_late_end]
720 if coords[:bar_late_end]
720 width = coords[:bar_late_end] - coords[:bar_start] - 2
721 width = coords[:bar_late_end] - coords[:bar_start] - 2
721 style = ""
722 style = ""
722 style << "top:#{params[:top]}px;"
723 style << "top:#{params[:top]}px;"
723 style << "left:#{coords[:bar_start]}px;"
724 style << "left:#{coords[:bar_start]}px;"
724 style << "width:#{width}px;"
725 style << "width:#{width}px;"
725 output << view.content_tag(:div, '&nbsp;'.html_safe,
726 output << view.content_tag(:div, '&nbsp;'.html_safe,
726 :style => style,
727 :style => style,
727 :class => "#{options[:css]} task_late")
728 :class => "#{options[:css]} task_late")
728 end
729 end
729 if coords[:bar_progress_end]
730 if coords[:bar_progress_end]
730 width = coords[:bar_progress_end] - coords[:bar_start] - 2
731 width = coords[:bar_progress_end] - coords[:bar_start] - 2
731 style = ""
732 style = ""
732 style << "top:#{params[:top]}px;"
733 style << "top:#{params[:top]}px;"
733 style << "left:#{coords[:bar_start]}px;"
734 style << "left:#{coords[:bar_start]}px;"
734 style << "width:#{width}px;"
735 style << "width:#{width}px;"
735 output << view.content_tag(:div, '&nbsp;'.html_safe,
736 output << view.content_tag(:div, '&nbsp;'.html_safe,
736 :style => style,
737 :style => style,
737 :class => "#{options[:css]} task_done")
738 :class => "#{options[:css]} task_done")
738 end
739 end
739 end
740 end
740 # Renders the markers
741 # Renders the markers
741 if options[:markers]
742 if options[:markers]
742 if coords[:start]
743 if coords[:start]
743 style = ""
744 style = ""
744 style << "top:#{params[:top]}px;"
745 style << "top:#{params[:top]}px;"
745 style << "left:#{coords[:start]}px;"
746 style << "left:#{coords[:start]}px;"
746 style << "width:15px;"
747 style << "width:15px;"
747 output << view.content_tag(:div, '&nbsp;'.html_safe,
748 output << view.content_tag(:div, '&nbsp;'.html_safe,
748 :style => style,
749 :style => style,
749 :class => "#{options[:css]} marker starting")
750 :class => "#{options[:css]} marker starting")
750 end
751 end
751 if coords[:end]
752 if coords[:end]
752 style = ""
753 style = ""
753 style << "top:#{params[:top]}px;"
754 style << "top:#{params[:top]}px;"
754 style << "left:#{coords[:end] + params[:zoom]}px;"
755 style << "left:#{coords[:end] + params[:zoom]}px;"
755 style << "width:15px;"
756 style << "width:15px;"
756 output << view.content_tag(:div, '&nbsp;'.html_safe,
757 output << view.content_tag(:div, '&nbsp;'.html_safe,
757 :style => style,
758 :style => style,
758 :class => "#{options[:css]} marker ending")
759 :class => "#{options[:css]} marker ending")
759 end
760 end
760 end
761 end
761 # Renders the label on the right
762 # Renders the label on the right
762 if options[:label]
763 if options[:label]
763 style = ""
764 style = ""
764 style << "top:#{params[:top]}px;"
765 style << "top:#{params[:top]}px;"
765 style << "left:#{(coords[:bar_end] || 0) + 8}px;"
766 style << "left:#{(coords[:bar_end] || 0) + 8}px;"
766 style << "width:15px;"
767 style << "width:15px;"
767 output << view.content_tag(:div, options[:label],
768 output << view.content_tag(:div, options[:label],
768 :style => style,
769 :style => style,
769 :class => "#{options[:css]} label")
770 :class => "#{options[:css]} label")
770 end
771 end
771 # Renders the tooltip
772 # Renders the tooltip
772 if options[:issue] && coords[:bar_start] && coords[:bar_end]
773 if options[:issue] && coords[:bar_start] && coords[:bar_end]
773 s = view.content_tag(:span,
774 s = view.content_tag(:span,
774 view.render_issue_tooltip(options[:issue]).html_safe,
775 view.render_issue_tooltip(options[:issue]).html_safe,
775 :class => "tip")
776 :class => "tip")
776 style = ""
777 style = ""
777 style << "position: absolute;"
778 style << "position: absolute;"
778 style << "top:#{params[:top]}px;"
779 style << "top:#{params[:top]}px;"
779 style << "left:#{coords[:bar_start]}px;"
780 style << "left:#{coords[:bar_start]}px;"
780 style << "width:#{coords[:bar_end] - coords[:bar_start]}px;"
781 style << "width:#{coords[:bar_end] - coords[:bar_start]}px;"
781 style << "height:12px;"
782 style << "height:12px;"
782 output << view.content_tag(:div, s.html_safe,
783 output << view.content_tag(:div, s.html_safe,
783 :style => style,
784 :style => style,
784 :class => "tooltip")
785 :class => "tooltip")
785 end
786 end
786 @lines << output
787 @lines << output
787 output
788 output
788 end
789 end
789
790
790 def pdf_task(params, coords, options={})
791 def pdf_task(params, coords, options={})
791 height = options[:height] || 2
792 height = options[:height] || 2
792 # Renders the task bar, with progress and late
793 # Renders the task bar, with progress and late
793 if coords[:bar_start] && coords[:bar_end]
794 if coords[:bar_start] && coords[:bar_end]
794 params[:pdf].SetY(params[:top] + 1.5)
795 params[:pdf].SetY(params[:top] + 1.5)
795 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
796 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
796 params[:pdf].SetFillColor(200, 200, 200)
797 params[:pdf].SetFillColor(200, 200, 200)
797 params[:pdf].RDMCell(coords[:bar_end] - coords[:bar_start], height, "", 0, 0, "", 1)
798 params[:pdf].RDMCell(coords[:bar_end] - coords[:bar_start], height, "", 0, 0, "", 1)
798 if coords[:bar_late_end]
799 if coords[:bar_late_end]
799 params[:pdf].SetY(params[:top] + 1.5)
800 params[:pdf].SetY(params[:top] + 1.5)
800 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
801 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
801 params[:pdf].SetFillColor(255, 100, 100)
802 params[:pdf].SetFillColor(255, 100, 100)
802 params[:pdf].RDMCell(coords[:bar_late_end] - coords[:bar_start], height, "", 0, 0, "", 1)
803 params[:pdf].RDMCell(coords[:bar_late_end] - coords[:bar_start], height, "", 0, 0, "", 1)
803 end
804 end
804 if coords[:bar_progress_end]
805 if coords[:bar_progress_end]
805 params[:pdf].SetY(params[:top] + 1.5)
806 params[:pdf].SetY(params[:top] + 1.5)
806 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
807 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
807 params[:pdf].SetFillColor(90, 200, 90)
808 params[:pdf].SetFillColor(90, 200, 90)
808 params[:pdf].RDMCell(coords[:bar_progress_end] - coords[:bar_start], height, "", 0, 0, "", 1)
809 params[:pdf].RDMCell(coords[:bar_progress_end] - coords[:bar_start], height, "", 0, 0, "", 1)
809 end
810 end
810 end
811 end
811 # Renders the markers
812 # Renders the markers
812 if options[:markers]
813 if options[:markers]
813 if coords[:start]
814 if coords[:start]
814 params[:pdf].SetY(params[:top] + 1)
815 params[:pdf].SetY(params[:top] + 1)
815 params[:pdf].SetX(params[:subject_width] + coords[:start] - 1)
816 params[:pdf].SetX(params[:subject_width] + coords[:start] - 1)
816 params[:pdf].SetFillColor(50, 50, 200)
817 params[:pdf].SetFillColor(50, 50, 200)
817 params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1)
818 params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1)
818 end
819 end
819 if coords[:end]
820 if coords[:end]
820 params[:pdf].SetY(params[:top] + 1)
821 params[:pdf].SetY(params[:top] + 1)
821 params[:pdf].SetX(params[:subject_width] + coords[:end] - 1)
822 params[:pdf].SetX(params[:subject_width] + coords[:end] - 1)
822 params[:pdf].SetFillColor(50, 50, 200)
823 params[:pdf].SetFillColor(50, 50, 200)
823 params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1)
824 params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1)
824 end
825 end
825 end
826 end
826 # Renders the label on the right
827 # Renders the label on the right
827 if options[:label]
828 if options[:label]
828 params[:pdf].SetX(params[:subject_width] + (coords[:bar_end] || 0) + 5)
829 params[:pdf].SetX(params[:subject_width] + (coords[:bar_end] || 0) + 5)
829 params[:pdf].RDMCell(30, 2, options[:label])
830 params[:pdf].RDMCell(30, 2, options[:label])
830 end
831 end
831 end
832 end
832
833
833 def image_task(params, coords, options={})
834 def image_task(params, coords, options={})
834 height = options[:height] || 6
835 height = options[:height] || 6
835 # Renders the task bar, with progress and late
836 # Renders the task bar, with progress and late
836 if coords[:bar_start] && coords[:bar_end]
837 if coords[:bar_start] && coords[:bar_end]
837 params[:image].fill('#aaa')
838 params[:image].fill('#aaa')
838 params[:image].rectangle(params[:subject_width] + coords[:bar_start],
839 params[:image].rectangle(params[:subject_width] + coords[:bar_start],
839 params[:top],
840 params[:top],
840 params[:subject_width] + coords[:bar_end],
841 params[:subject_width] + coords[:bar_end],
841 params[:top] - height)
842 params[:top] - height)
842 if coords[:bar_late_end]
843 if coords[:bar_late_end]
843 params[:image].fill('#f66')
844 params[:image].fill('#f66')
844 params[:image].rectangle(params[:subject_width] + coords[:bar_start],
845 params[:image].rectangle(params[:subject_width] + coords[:bar_start],
845 params[:top],
846 params[:top],
846 params[:subject_width] + coords[:bar_late_end],
847 params[:subject_width] + coords[:bar_late_end],
847 params[:top] - height)
848 params[:top] - height)
848 end
849 end
849 if coords[:bar_progress_end]
850 if coords[:bar_progress_end]
850 params[:image].fill('#00c600')
851 params[:image].fill('#00c600')
851 params[:image].rectangle(params[:subject_width] + coords[:bar_start],
852 params[:image].rectangle(params[:subject_width] + coords[:bar_start],
852 params[:top],
853 params[:top],
853 params[:subject_width] + coords[:bar_progress_end],
854 params[:subject_width] + coords[:bar_progress_end],
854 params[:top] - height)
855 params[:top] - height)
855 end
856 end
856 end
857 end
857 # Renders the markers
858 # Renders the markers
858 if options[:markers]
859 if options[:markers]
859 if coords[:start]
860 if coords[:start]
860 x = params[:subject_width] + coords[:start]
861 x = params[:subject_width] + coords[:start]
861 y = params[:top] - height / 2
862 y = params[:top] - height / 2
862 params[:image].fill('blue')
863 params[:image].fill('blue')
863 params[:image].polygon(x - 4, y, x, y - 4, x + 4, y, x, y + 4)
864 params[:image].polygon(x - 4, y, x, y - 4, x + 4, y, x, y + 4)
864 end
865 end
865 if coords[:end]
866 if coords[:end]
866 x = params[:subject_width] + coords[:end] + params[:zoom]
867 x = params[:subject_width] + coords[:end] + params[:zoom]
867 y = params[:top] - height / 2
868 y = params[:top] - height / 2
868 params[:image].fill('blue')
869 params[:image].fill('blue')
869 params[:image].polygon(x - 4, y, x, y - 4, x + 4, y, x, y + 4)
870 params[:image].polygon(x - 4, y, x, y - 4, x + 4, y, x, y + 4)
870 end
871 end
871 end
872 end
872 # Renders the label on the right
873 # Renders the label on the right
873 if options[:label]
874 if options[:label]
874 params[:image].fill('black')
875 params[:image].fill('black')
875 params[:image].text(params[:subject_width] + (coords[:bar_end] || 0) + 5,
876 params[:image].text(params[:subject_width] + (coords[:bar_end] || 0) + 5,
876 params[:top] + 1,
877 params[:top] + 1,
877 options[:label])
878 options[:label])
878 end
879 end
879 end
880 end
880 end
881 end
881 end
882 end
882 end
883 end
General Comments 0
You need to be logged in to leave comments. Login now