##// END OF EJS Templates
Gantt code cleanup....
Jean-Philippe Lang -
r4408:98c7c179ca3f
parent child
Show More
@@ -1,1038 +1,993
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 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
24
25 # :nodoc:
25 # :nodoc:
26 # Some utility methods for the PDF export
26 # Some utility methods for the PDF export
27 class PDF
27 class PDF
28 MaxCharactorsForSubject = 45
28 MaxCharactorsForSubject = 45
29 TotalWidth = 280
29 TotalWidth = 280
30 LeftPaneWidth = 100
30 LeftPaneWidth = 100
31
31
32 def self.right_pane_width
32 def self.right_pane_width
33 TotalWidth - LeftPaneWidth
33 TotalWidth - LeftPaneWidth
34 end
34 end
35 end
35 end
36
36
37 attr_reader :year_from, :month_from, :date_from, :date_to, :zoom, :months, :truncated, :max_rows
37 attr_reader :year_from, :month_from, :date_from, :date_to, :zoom, :months, :truncated, :max_rows
38 attr_accessor :query
38 attr_accessor :query
39 attr_accessor :project
39 attr_accessor :project
40 attr_accessor :view
40 attr_accessor :view
41
41
42 def initialize(options={})
42 def initialize(options={})
43 options = options.dup
43 options = options.dup
44
44
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
56
57 zoom = (options[:zoom] || User.current.pref[:gantt_zoom]).to_i
57 zoom = (options[:zoom] || User.current.pref[:gantt_zoom]).to_i
58 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
58 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
59 months = (options[:months] || User.current.pref[:gantt_months]).to_i
59 months = (options[:months] || User.current.pref[:gantt_months]).to_i
60 @months = (months > 0 && months < 25) ? months : 6
60 @months = (months > 0 && months < 25) ? months : 6
61
61
62 # Save gantt parameters as user preference (zoom and months count)
62 # Save gantt parameters as user preference (zoom and months count)
63 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
63 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
64 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
64 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
65 User.current.preference.save
65 User.current.preference.save
66 end
66 end
67
67
68 @date_from = Date.civil(@year_from, @month_from, 1)
68 @date_from = Date.civil(@year_from, @month_from, 1)
69 @date_to = (@date_from >> @months) - 1
69 @date_to = (@date_from >> @months) - 1
70
70
71 @subjects = ''
71 @subjects = ''
72 @lines = ''
72 @lines = ''
73 @number_of_rows = nil
73 @number_of_rows = nil
74
74
75 @truncated = false
75 @truncated = false
76 if options.has_key?(:max_rows)
76 if options.has_key?(:max_rows)
77 @max_rows = options[:max_rows]
77 @max_rows = options[:max_rows]
78 else
78 else
79 @max_rows = Setting.gantt_items_limit.blank? ? nil : Setting.gantt_items_limit.to_i
79 @max_rows = Setting.gantt_items_limit.blank? ? nil : Setting.gantt_items_limit.to_i
80 end
80 end
81 end
81 end
82
82
83 def common_params
83 def common_params
84 { :controller => 'gantts', :action => 'show', :project_id => @project }
84 { :controller => 'gantts', :action => 'show', :project_id => @project }
85 end
85 end
86
86
87 def params
87 def params
88 common_params.merge({ :zoom => zoom, :year => year_from, :month => month_from, :months => months })
88 common_params.merge({ :zoom => zoom, :year => year_from, :month => month_from, :months => months })
89 end
89 end
90
90
91 def params_previous
91 def params_previous
92 common_params.merge({:year => (date_from << months).year, :month => (date_from << months).month, :zoom => zoom, :months => months })
92 common_params.merge({:year => (date_from << months).year, :month => (date_from << months).month, :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, :month => (date_from >> months).month, :zoom => zoom, :months => months })
96 common_params.merge({:year => (date_from >> months).year, :month => (date_from >> months).month, :zoom => zoom, :months => months })
97 end
97 end
98
98
99 ### Extracted from the HTML view/helpers
99 ### Extracted from the HTML view/helpers
100 # Returns the number of rows that will be rendered on the Gantt chart
100 # Returns the number of rows that will be rendered on the Gantt chart
101 def number_of_rows
101 def number_of_rows
102 return @number_of_rows if @number_of_rows
102 return @number_of_rows if @number_of_rows
103
103
104 rows = if @project
104 rows = if @project
105 number_of_rows_on_project(@project)
105 number_of_rows_on_project(@project)
106 else
106 else
107 Project.roots.visible.has_module('issue_tracking').inject(0) do |total, project|
107 Project.roots.visible.has_module('issue_tracking').inject(0) do |total, project|
108 total += number_of_rows_on_project(project)
108 total += number_of_rows_on_project(project)
109 end
109 end
110 end
110 end
111
111
112 rows > @max_rows ? @max_rows : rows
112 rows > @max_rows ? @max_rows : rows
113 end
113 end
114
114
115 # Returns the number of rows that will be used to list a project on
115 # Returns the number of rows that will be used to list a project on
116 # the Gantt chart. This will recurse for each subproject.
116 # the Gantt chart. This will recurse for each subproject.
117 def number_of_rows_on_project(project)
117 def number_of_rows_on_project(project)
118 # Remove the project requirement for Versions because it will
118 # Remove the project requirement for Versions because it will
119 # restrict issues to only be on the current project. This
119 # restrict issues to only be on the current project. This
120 # ends up missing issues which are assigned to shared versions.
120 # ends up missing issues which are assigned to shared versions.
121 @query.project = nil if @query.project
121 @query.project = nil if @query.project
122
122
123 # One Root project
123 # One Root project
124 count = 1
124 count = 1
125 # Issues without a Version
125 # Issues without a Version
126 count += project.issues.for_gantt.without_version.with_query(@query).count
126 count += project.issues.for_gantt.without_version.with_query(@query).count
127
127
128 # Versions
128 # Versions
129 count += project.versions.count
129 count += project.versions.count
130
130
131 # Issues on the Versions
131 # Issues on the Versions
132 project.versions.each do |version|
132 project.versions.each do |version|
133 count += version.fixed_issues.for_gantt.with_query(@query).count
133 count += version.fixed_issues.for_gantt.with_query(@query).count
134 end
134 end
135
135
136 # Subprojects
136 # Subprojects
137 project.children.visible.has_module('issue_tracking').each do |subproject|
137 project.children.visible.has_module('issue_tracking').each do |subproject|
138 count += number_of_rows_on_project(subproject)
138 count += number_of_rows_on_project(subproject)
139 end
139 end
140
140
141 count
141 count
142 end
142 end
143
143
144 # Renders the subjects of the Gantt chart, the left side.
144 # Renders the subjects of the Gantt chart, the left side.
145 def subjects(options={})
145 def subjects(options={})
146 render(options.merge(:only => :subjects)) unless @subjects_rendered
146 render(options.merge(:only => :subjects)) unless @subjects_rendered
147 @subjects
147 @subjects
148 end
148 end
149
149
150 # Renders the lines of the Gantt chart, the right side
150 # Renders the lines of the Gantt chart, the right side
151 def lines(options={})
151 def lines(options={})
152 render(options.merge(:only => :lines)) unless @lines_rendered
152 render(options.merge(:only => :lines)) unless @lines_rendered
153 @lines
153 @lines
154 end
154 end
155
155
156 def render(options={})
156 def render(options={})
157 options = {:indent => 4, :render => :subject, :format => :html}.merge(options)
157 options = {:indent => 4, :render => :subject, :format => :html}.merge(options)
158
158
159 @subjects = '' unless options[:only] == :lines
159 @subjects = '' unless options[:only] == :lines
160 @lines = '' unless options[:only] == :subjects
160 @lines = '' unless options[:only] == :subjects
161 @number_of_rows = 0
161 @number_of_rows = 0
162
162
163 if @project
163 if @project
164 render_project(@project, options)
164 render_project(@project, options)
165 else
165 else
166 Project.roots.visible.has_module('issue_tracking').each do |project|
166 Project.roots.visible.has_module('issue_tracking').each do |project|
167 render_project(project, options)
167 render_project(project, options)
168 break if abort?
168 break if abort?
169 end
169 end
170 end
170 end
171
171
172 @subjects_rendered = true unless options[:only] == :lines
172 @subjects_rendered = true unless options[:only] == :lines
173 @lines_rendered = true unless options[:only] == :subjects
173 @lines_rendered = true unless options[:only] == :subjects
174
174
175 render_end(options)
175 render_end(options)
176 end
176 end
177
177
178 def render_project(project, options={})
178 def render_project(project, options={})
179 options[:top] = 0 unless options.key? :top
179 options[:top] = 0 unless options.key? :top
180 options[:indent_increment] = 20 unless options.key? :indent_increment
180 options[:indent_increment] = 20 unless options.key? :indent_increment
181 options[:top_increment] = 20 unless options.key? :top_increment
181 options[:top_increment] = 20 unless options.key? :top_increment
182
182
183 subject_for_project(project, options) unless options[:only] == :lines
183 subject_for_project(project, options) unless options[:only] == :lines
184 line_for_project(project, options) unless options[:only] == :subjects
184 line_for_project(project, options) unless options[:only] == :subjects
185
185
186 options[:top] += options[:top_increment]
186 options[:top] += options[:top_increment]
187 options[:indent] += options[:indent_increment]
187 options[:indent] += options[:indent_increment]
188 @number_of_rows += 1
188 @number_of_rows += 1
189 return if abort?
189 return if abort?
190
190
191 # Second, Issues without a version
191 # Second, Issues without a version
192 issues = project.issues.for_gantt.without_version.with_query(@query).all(:limit => current_limit)
192 issues = project.issues.for_gantt.without_version.with_query(@query).all(:limit => current_limit)
193 sort_issues!(issues)
193 sort_issues!(issues)
194 if issues
194 if issues
195 render_issues(issues, options)
195 render_issues(issues, options)
196 return if abort?
196 return if abort?
197 end
197 end
198
198
199 # Third, Versions
199 # Third, Versions
200 project.versions.sort.each do |version|
200 project.versions.sort.each do |version|
201 render_version(version, options)
201 render_version(version, options)
202 return if abort?
202 return if abort?
203 end
203 end
204
204
205 # Fourth, subprojects
205 # Fourth, subprojects
206 project.children.visible.has_module('issue_tracking').each do |project|
206 project.children.visible.has_module('issue_tracking').each do |project|
207 render_project(project, options)
207 render_project(project, options)
208 return if abort?
208 return if abort?
209 end unless project.leaf?
209 end unless project.leaf?
210
210
211 # Remove indent to hit the next sibling
211 # Remove indent to hit the next sibling
212 options[:indent] -= options[:indent_increment]
212 options[:indent] -= options[:indent_increment]
213 end
213 end
214
214
215 def render_issues(issues, options={})
215 def render_issues(issues, options={})
216 issues.each do |i|
216 issues.each do |i|
217 subject_for_issue(i, options) unless options[:only] == :lines
217 subject_for_issue(i, options) unless options[:only] == :lines
218 line_for_issue(i, options) unless options[:only] == :subjects
218 line_for_issue(i, options) unless options[:only] == :subjects
219
219
220 options[:top] += options[:top_increment]
220 options[:top] += options[:top_increment]
221 @number_of_rows += 1
221 @number_of_rows += 1
222 return if abort?
222 return if abort?
223 end
223 end
224 end
224 end
225
225
226 def render_version(version, options={})
226 def render_version(version, options={})
227 # Version header
227 # Version header
228 subject_for_version(version, options) unless options[:only] == :lines
228 subject_for_version(version, options) unless options[:only] == :lines
229 line_for_version(version, options) unless options[:only] == :subjects
229 line_for_version(version, options) unless options[:only] == :subjects
230
230
231 options[:top] += options[:top_increment]
231 options[:top] += options[:top_increment]
232 @number_of_rows += 1
232 @number_of_rows += 1
233 return if abort?
233 return if abort?
234
234
235 # Remove the project requirement for Versions because it will
235 # Remove the project requirement for Versions because it will
236 # restrict issues to only be on the current project. This
236 # restrict issues to only be on the current project. This
237 # ends up missing issues which are assigned to shared versions.
237 # ends up missing issues which are assigned to shared versions.
238 @query.project = nil if @query.project
238 @query.project = nil if @query.project
239
239
240 issues = version.fixed_issues.for_gantt.with_query(@query).all(:limit => current_limit)
240 issues = version.fixed_issues.for_gantt.with_query(@query).all(:limit => current_limit)
241 if issues
241 if issues
242 sort_issues!(issues)
242 sort_issues!(issues)
243 # Indent issues
243 # Indent issues
244 options[:indent] += options[:indent_increment]
244 options[:indent] += options[:indent_increment]
245 render_issues(issues, options)
245 render_issues(issues, options)
246 options[:indent] -= options[:indent_increment]
246 options[:indent] -= options[:indent_increment]
247 end
247 end
248 end
248 end
249
249
250 def render_end(options={})
250 def render_end(options={})
251 case options[:format]
251 case options[:format]
252 when :pdf
252 when :pdf
253 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
253 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
254 end
254 end
255 end
255 end
256
256
257 def subject_for_project(project, options)
257 def subject_for_project(project, options)
258 case options[:format]
258 case options[:format]
259 when :html
259 when :html
260 output = ''
260 output = ''
261
261
262 output << "<div class='project-name' style='position: absolute;line-height:1.2em;height:16px;top:#{options[:top]}px;left:#{options[:indent]}px;overflow:hidden;'><small> "
262 output << "<div class='project-name' style='position: absolute;line-height:1.2em;height:16px;top:#{options[:top]}px;left:#{options[:indent]}px;overflow:hidden;'><small> "
263 if project.is_a? Project
263 if project.is_a? Project
264 output << "<span class='icon icon-projects #{project.overdue? ? 'project-overdue' : ''}'>"
264 output << "<span class='icon icon-projects #{project.overdue? ? 'project-overdue' : ''}'>"
265 output << view.link_to_project(project)
265 output << view.link_to_project(project)
266 output << '</span>'
266 output << '</span>'
267 else
267 else
268 ActiveRecord::Base.logger.debug "Gantt#subject_for_project was not given a project"
268 ActiveRecord::Base.logger.debug "Gantt#subject_for_project was not given a project"
269 ''
269 ''
270 end
270 end
271 output << "</small></div>"
271 output << "</small></div>"
272 @subjects << output
272 @subjects << output
273 output
273 output
274 when :image
274 when :image
275
275
276 options[:image].fill('black')
276 options[:image].fill('black')
277 options[:image].stroke('transparent')
277 options[:image].stroke('transparent')
278 options[:image].stroke_width(1)
278 options[:image].stroke_width(1)
279 options[:image].text(options[:indent], options[:top] + 2, project.name)
279 options[:image].text(options[:indent], options[:top] + 2, project.name)
280 when :pdf
280 when :pdf
281 pdf_new_page?(options)
281 pdf_new_page?(options)
282 options[:pdf].SetY(options[:top])
282 options[:pdf].SetY(options[:top])
283 options[:pdf].SetX(15)
283 options[:pdf].SetX(15)
284
284
285 char_limit = PDF::MaxCharactorsForSubject - options[:indent]
285 char_limit = PDF::MaxCharactorsForSubject - options[:indent]
286 options[:pdf].Cell(options[:subject_width]-15, 5, (" " * options[:indent]) +"#{project.name}".sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR")
286 options[:pdf].Cell(options[:subject_width]-15, 5, (" " * options[:indent]) +"#{project.name}".sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR")
287
287
288 options[:pdf].SetY(options[:top])
288 options[:pdf].SetY(options[:top])
289 options[:pdf].SetX(options[:subject_width])
289 options[:pdf].SetX(options[:subject_width])
290 options[:pdf].Cell(options[:g_width], 5, "", "LR")
290 options[:pdf].Cell(options[:g_width], 5, "", "LR")
291 end
291 end
292 end
292 end
293
293
294 def line_for_project(project, options)
294 def line_for_project(project, options)
295 # Skip versions that don't have a start_date or due date
295 # Skip versions that don't have a start_date or due date
296 if project.is_a?(Project) && project.start_date && project.due_date
296 if project.is_a?(Project) && project.start_date && project.due_date
297 options[:zoom] ||= 1
297 options[:zoom] ||= 1
298 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
298 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
299
299
300
300
301 case options[:format]
301 case options[:format]
302 when :html
302 when :html
303 output = ''
303 output = ''
304 i_left = ((project.start_date - self.date_from)*options[:zoom]).floor
304 i_left = ((project.start_date - self.date_from)*options[:zoom]).floor
305
305
306 start_date = project.start_date
306 start_date = project.start_date
307 start_date ||= self.date_from
307 start_date ||= self.date_from
308 start_left = ((start_date - self.date_from)*options[:zoom]).floor
308 start_left = ((start_date - self.date_from)*options[:zoom]).floor
309
309
310 i_end_date = ((project.due_date <= self.date_to) ? project.due_date : self.date_to )
310 i_end_date = ((project.due_date <= self.date_to) ? project.due_date : self.date_to )
311 i_done_date = start_date + ((project.due_date - start_date+1)* project.completed_percent(:include_subprojects => true)/100).floor
311 i_done_date = start_date + ((project.due_date - start_date+1)* project.completed_percent(:include_subprojects => true)/100).floor
312 i_done_date = (i_done_date <= self.date_from ? self.date_from : i_done_date )
312 i_done_date = (i_done_date <= self.date_from ? self.date_from : i_done_date )
313 i_done_date = (i_done_date >= self.date_to ? self.date_to : i_done_date )
313 i_done_date = (i_done_date >= self.date_to ? self.date_to : i_done_date )
314
314
315 i_late_date = [i_end_date, Date.today].min if start_date < Date.today
315 i_late_date = [i_end_date, Date.today].min if start_date < Date.today
316 i_end = ((i_end_date - self.date_from) * options[:zoom]).floor
316 i_end = ((i_end_date - self.date_from) * options[:zoom]).floor
317
317
318 i_width = (i_end - i_left + 1).floor - 2 # total width of the issue (- 2 for left and right borders)
318 i_width = (i_end - i_left + 1).floor - 2 # total width of the issue (- 2 for left and right borders)
319 d_width = ((i_done_date - start_date)*options[:zoom]).floor - 2 # done width
319 d_width = ((i_done_date - start_date)*options[:zoom]).floor - 2 # done width
320 l_width = i_late_date ? ((i_late_date - start_date+1)*options[:zoom]).floor - 2 : 0 # delay width
320 l_width = i_late_date ? ((i_late_date - start_date+1)*options[:zoom]).floor - 2 : 0 # delay width
321
321
322 # Bar graphic
322 # Bar graphic
323
323
324 # Make sure that negative i_left and i_width don't
324 # Make sure that negative i_left and i_width don't
325 # overflow the subject
325 # overflow the subject
326 if i_end > 0 && i_left <= options[:g_width]
326 if i_end > 0 && i_left <= options[:g_width]
327 output << "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:#{ i_width }px;' class='task project_todo'>&nbsp;</div>"
327 output << "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:#{ i_width }px;' class='task project_todo'>&nbsp;</div>"
328 end
328 end
329
329
330 if l_width > 0 && i_left <= options[:g_width]
330 if l_width > 0 && i_left <= options[:g_width]
331 output << "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:#{ l_width }px;' class='task project_late'>&nbsp;</div>"
331 output << "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:#{ l_width }px;' class='task project_late'>&nbsp;</div>"
332 end
332 end
333 if d_width > 0 && i_left <= options[:g_width]
333 if d_width > 0 && i_left <= options[:g_width]
334 output<< "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:#{ d_width }px;' class='task project_done'>&nbsp;</div>"
334 output<< "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:#{ d_width }px;' class='task project_done'>&nbsp;</div>"
335 end
335 end
336
336
337
337
338 # Starting diamond
338 # Starting diamond
339 if start_left <= options[:g_width] && start_left > 0
339 if start_left <= options[:g_width] && start_left > 0
340 output << "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:15px;' class='task project-line starting'>&nbsp;</div>"
340 output << "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:15px;' class='task project-line starting'>&nbsp;</div>"
341 output << "<div style='top:#{ options[:top] }px;left:#{ start_left + 12 }px;' class='task label'>"
341 output << "<div style='top:#{ options[:top] }px;left:#{ start_left + 12 }px;' class='task label'>"
342 output << "</div>"
342 output << "</div>"
343 end
343 end
344
344
345 # Ending diamond
345 # Ending diamond
346 # Don't show items too far ahead
346 # Don't show items too far ahead
347 if i_end <= options[:g_width] && i_end > 0
347 if i_end <= options[:g_width] && i_end > 0
348 output << "<div style='top:#{ options[:top] }px;left:#{ i_end }px;width:15px;' class='task project-line ending'>&nbsp;</div>"
348 output << "<div style='top:#{ options[:top] }px;left:#{ i_end }px;width:15px;' class='task project-line ending'>&nbsp;</div>"
349 end
349 end
350
350
351 # DIsplay the Project name and %
351 # DIsplay the Project name and %
352 if i_end <= options[:g_width]
352 if i_end <= options[:g_width]
353 # Display the status even if it's floated off to the left
353 # Display the status even if it's floated off to the left
354 status_px = i_end + 12 # 12px for the diamond
354 status_px = i_end + 12 # 12px for the diamond
355 status_px = 0 if status_px <= 0
355 status_px = 0 if status_px <= 0
356
356
357 output << "<div style='top:#{ options[:top] }px;left:#{ status_px }px;' class='task label project-name'>"
357 output << "<div style='top:#{ options[:top] }px;left:#{ status_px }px;' class='task label project-name'>"
358 output << "<strong>#{h project } #{h project.completed_percent(:include_subprojects => true).to_i.to_s}%</strong>"
358 output << "<strong>#{h project } #{h project.completed_percent(:include_subprojects => true).to_i.to_s}%</strong>"
359 output << "</div>"
359 output << "</div>"
360 end
360 end
361 @lines << output
361 @lines << output
362 output
362 output
363 when :image
363 when :image
364 options[:image].stroke('transparent')
364 options[:image].stroke('transparent')
365 i_left = options[:subject_width] + ((project.due_date - self.date_from)*options[:zoom]).floor
365 i_left = options[:subject_width] + ((project.due_date - self.date_from)*options[:zoom]).floor
366
366
367 # Make sure negative i_left doesn't overflow the subject
367 # Make sure negative i_left doesn't overflow the subject
368 if i_left > options[:subject_width]
368 if i_left > options[:subject_width]
369 options[:image].fill('blue')
369 options[:image].fill('blue')
370 options[:image].rectangle(i_left, options[:top], i_left + 6, options[:top] - 6)
370 options[:image].rectangle(i_left, options[:top], i_left + 6, options[:top] - 6)
371 options[:image].fill('black')
371 options[:image].fill('black')
372 options[:image].text(i_left + 11, options[:top] + 1, project.name)
372 options[:image].text(i_left + 11, options[:top] + 1, project.name)
373 end
373 end
374 when :pdf
374 when :pdf
375 options[:pdf].SetY(options[:top]+1.5)
375 options[:pdf].SetY(options[:top]+1.5)
376 i_left = ((project.due_date - @date_from)*options[:zoom])
376 i_left = ((project.due_date - @date_from)*options[:zoom])
377
377
378 # Make sure negative i_left doesn't overflow the subject
378 # Make sure negative i_left doesn't overflow the subject
379 if i_left > 0
379 if i_left > 0
380 options[:pdf].SetX(options[:subject_width] + i_left)
380 options[:pdf].SetX(options[:subject_width] + i_left)
381 options[:pdf].SetFillColor(50,50,200)
381 options[:pdf].SetFillColor(50,50,200)
382 options[:pdf].Cell(2, 2, "", 0, 0, "", 1)
382 options[:pdf].Cell(2, 2, "", 0, 0, "", 1)
383
383
384 options[:pdf].SetY(options[:top]+1.5)
384 options[:pdf].SetY(options[:top]+1.5)
385 options[:pdf].SetX(options[:subject_width] + i_left + 3)
385 options[:pdf].SetX(options[:subject_width] + i_left + 3)
386 options[:pdf].Cell(30, 2, "#{project.name}")
386 options[:pdf].Cell(30, 2, "#{project.name}")
387 end
387 end
388 end
388 end
389 else
389 else
390 ActiveRecord::Base.logger.debug "Gantt#line_for_project was not given a project with a start_date"
390 ActiveRecord::Base.logger.debug "Gantt#line_for_project was not given a project with a start_date"
391 ''
391 ''
392 end
392 end
393 end
393 end
394
394
395 def subject_for_version(version, options)
395 def subject_for_version(version, options)
396 case options[:format]
396 case options[:format]
397 when :html
397 when :html
398 output = ''
398 output = ''
399 output << "<div class='version-name' style='position: absolute;line-height:1.2em;height:16px;top:#{options[:top]}px;left:#{options[:indent]}px;overflow:hidden;'><small> "
399 output << "<div class='version-name' style='position: absolute;line-height:1.2em;height:16px;top:#{options[:top]}px;left:#{options[:indent]}px;overflow:hidden;'><small> "
400 if version.is_a? Version
400 if version.is_a? Version
401 output << "<span class='icon icon-package #{version.behind_schedule? ? 'version-behind-schedule' : ''} #{version.overdue? ? 'version-overdue' : ''}'>"
401 output << "<span class='icon icon-package #{version.behind_schedule? ? 'version-behind-schedule' : ''} #{version.overdue? ? 'version-overdue' : ''}'>"
402 output << view.link_to_version(version)
402 output << view.link_to_version(version)
403 output << '</span>'
403 output << '</span>'
404 else
404 else
405 ActiveRecord::Base.logger.debug "Gantt#subject_for_version was not given a version"
405 ActiveRecord::Base.logger.debug "Gantt#subject_for_version was not given a version"
406 ''
406 ''
407 end
407 end
408 output << "</small></div>"
408 output << "</small></div>"
409 @subjects << output
409 @subjects << output
410 output
410 output
411 when :image
411 when :image
412 options[:image].fill('black')
412 options[:image].fill('black')
413 options[:image].stroke('transparent')
413 options[:image].stroke('transparent')
414 options[:image].stroke_width(1)
414 options[:image].stroke_width(1)
415 options[:image].text(options[:indent], options[:top] + 2, version.to_s_with_project)
415 options[:image].text(options[:indent], options[:top] + 2, version.to_s_with_project)
416 when :pdf
416 when :pdf
417 pdf_new_page?(options)
417 pdf_new_page?(options)
418 options[:pdf].SetY(options[:top])
418 options[:pdf].SetY(options[:top])
419 options[:pdf].SetX(15)
419 options[:pdf].SetX(15)
420
420
421 char_limit = PDF::MaxCharactorsForSubject - options[:indent]
421 char_limit = PDF::MaxCharactorsForSubject - options[:indent]
422 options[:pdf].Cell(options[:subject_width]-15, 5, (" " * options[:indent]) +"#{version.to_s_with_project}".sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR")
422 options[:pdf].Cell(options[:subject_width]-15, 5, (" " * options[:indent]) +"#{version.to_s_with_project}".sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR")
423
423
424 options[:pdf].SetY(options[:top])
424 options[:pdf].SetY(options[:top])
425 options[:pdf].SetX(options[:subject_width])
425 options[:pdf].SetX(options[:subject_width])
426 options[:pdf].Cell(options[:g_width], 5, "", "LR")
426 options[:pdf].Cell(options[:g_width], 5, "", "LR")
427 end
427 end
428 end
428 end
429
429
430 def line_for_version(version, options)
430 def line_for_version(version, options)
431 # Skip versions that don't have a start_date
431 # Skip versions that don't have a start_date
432 if version.is_a?(Version) && version.start_date && version.due_date
432 if version.is_a?(Version) && version.start_date && version.due_date
433 options[:zoom] ||= 1
433 options[:zoom] ||= 1
434 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
434 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
435
435
436 case options[:format]
436 case options[:format]
437 when :html
437 when :html
438 output = ''
438 coords = coordinates(version.fixed_issues.minimum('start_date'), version.due_date, version.completed_pourcent, options[:zoom])
439 i_left = ((version.start_date - self.date_from)*options[:zoom]).floor
439 label = "#{h version } #{h version.completed_pourcent.to_i.to_s}%"
440 # TODO: or version.fixed_issues.collect(&:start_date).min
440 label = h("#{version.project} -") + label unless @project && @project == version.project
441 start_date = version.fixed_issues.minimum('start_date') if version.fixed_issues.present?
441 output = html_task(options[:top], coords, :css => "version task", :label => label, :markers => true)
442 start_date ||= self.date_from
443 start_left = ((start_date - self.date_from)*options[:zoom]).floor
444
445 i_end_date = ((version.due_date <= self.date_to) ? version.due_date : self.date_to )
446 i_done_date = start_date + ((version.due_date - start_date+1)* version.completed_pourcent/100).floor
447 i_done_date = (i_done_date <= self.date_from ? self.date_from : i_done_date )
448 i_done_date = (i_done_date >= self.date_to ? self.date_to : i_done_date )
449
442
450 i_late_date = [i_end_date, Date.today].min if start_date < Date.today
451
452 i_width = (i_left - start_left + 1).floor - 2 # total width of the issue (- 2 for left and right borders)
453 d_width = ((i_done_date - start_date)*options[:zoom]).floor - 2 # done width
454 l_width = i_late_date ? ((i_late_date - start_date+1)*options[:zoom]).floor - 2 : 0 # delay width
455
456 i_end = ((i_end_date - self.date_from) * options[:zoom]).floor # Ending pixel
457
458 # Bar graphic
459
460 # Make sure that negative i_left and i_width don't
461 # overflow the subject
462 if i_width > 0 && i_left <= options[:g_width]
463 output << "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:#{ i_width }px;' class='task milestone_todo'>&nbsp;</div>"
464 end
465 if l_width > 0 && i_left <= options[:g_width]
466 output << "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:#{ l_width }px;' class='task milestone_late'>&nbsp;</div>"
467 end
468 if d_width > 0 && i_left <= options[:g_width]
469 output<< "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:#{ d_width }px;' class='task milestone_done'>&nbsp;</div>"
470 end
471
472
473 # Starting diamond
474 if start_left <= options[:g_width] && start_left > 0
475 output << "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:15px;' class='task milestone starting'>&nbsp;</div>"
476 output << "<div style='top:#{ options[:top] }px;left:#{ start_left + 12 }px;background:#fff;' class='task'>"
477 output << "</div>"
478 end
479
480 # Ending diamond
481 # Don't show items too far ahead
482 if i_left <= options[:g_width] && i_end > 0
483 output << "<div style='top:#{ options[:top] }px;left:#{ i_end }px;width:15px;' class='task milestone ending'>&nbsp;</div>"
484 end
485
486 # Display the Version name and %
487 if i_end <= options[:g_width]
488 # Display the status even if it's floated off to the left
489 status_px = i_end + 12 # 12px for the diamond
490 status_px = 0 if status_px <= 0
491
492 output << "<div style='top:#{ options[:top] }px;left:#{ status_px }px;' class='task label version-name'>"
493 output << h("#{version.project} -") unless @project && @project == version.project
494 output << "<strong>#{h version } #{h version.completed_pourcent.to_i.to_s}%</strong>"
495 output << "</div>"
496 end
497 @lines << output
443 @lines << output
498 output
444 output
499 when :image
445 when :image
500 options[:image].stroke('transparent')
446 options[:image].stroke('transparent')
501 i_left = options[:subject_width] + ((version.start_date - @date_from)*options[:zoom]).floor
447 i_left = options[:subject_width] + ((version.start_date - @date_from)*options[:zoom]).floor
502
448
503 # Make sure negative i_left doesn't overflow the subject
449 # Make sure negative i_left doesn't overflow the subject
504 if i_left > options[:subject_width]
450 if i_left > options[:subject_width]
505 options[:image].fill('green')
451 options[:image].fill('green')
506 options[:image].rectangle(i_left, options[:top], i_left + 6, options[:top] - 6)
452 options[:image].rectangle(i_left, options[:top], i_left + 6, options[:top] - 6)
507 options[:image].fill('black')
453 options[:image].fill('black')
508 options[:image].text(i_left + 11, options[:top] + 1, version.name)
454 options[:image].text(i_left + 11, options[:top] + 1, version.name)
509 end
455 end
510 when :pdf
456 when :pdf
511 options[:pdf].SetY(options[:top]+1.5)
457 options[:pdf].SetY(options[:top]+1.5)
512 i_left = ((version.start_date - @date_from)*options[:zoom])
458 i_left = ((version.start_date - @date_from)*options[:zoom])
513
459
514 # Make sure negative i_left doesn't overflow the subject
460 # Make sure negative i_left doesn't overflow the subject
515 if i_left > 0
461 if i_left > 0
516 options[:pdf].SetX(options[:subject_width] + i_left)
462 options[:pdf].SetX(options[:subject_width] + i_left)
517 options[:pdf].SetFillColor(50,200,50)
463 options[:pdf].SetFillColor(50,200,50)
518 options[:pdf].Cell(2, 2, "", 0, 0, "", 1)
464 options[:pdf].Cell(2, 2, "", 0, 0, "", 1)
519
465
520 options[:pdf].SetY(options[:top]+1.5)
466 options[:pdf].SetY(options[:top]+1.5)
521 options[:pdf].SetX(options[:subject_width] + i_left + 3)
467 options[:pdf].SetX(options[:subject_width] + i_left + 3)
522 options[:pdf].Cell(30, 2, "#{version.name}")
468 options[:pdf].Cell(30, 2, "#{version.name}")
523 end
469 end
524 end
470 end
525 else
471 else
526 ActiveRecord::Base.logger.debug "Gantt#line_for_version was not given a version with a start_date"
472 ActiveRecord::Base.logger.debug "Gantt#line_for_version was not given a version with a start_date"
527 ''
473 ''
528 end
474 end
529 end
475 end
530
476
531 def subject_for_issue(issue, options)
477 def subject_for_issue(issue, options)
532 case options[:format]
478 case options[:format]
533 when :html
479 when :html
534 output = ''
480 output = ''
535 output << "<div class='tooltip'>"
481 output << "<div class='tooltip'>"
536 output << "<div class='issue-subject' style='position: absolute;line-height:1.2em;height:16px;top:#{options[:top]}px;left:#{options[:indent]}px;overflow:hidden;'><small> "
482 output << "<div class='issue-subject' style='position: absolute;line-height:1.2em;height:16px;top:#{options[:top]}px;left:#{options[:indent]}px;overflow:hidden;'><small> "
537 if issue.is_a? Issue
483 if issue.is_a? Issue
538 css_classes = []
484 css_classes = []
539 css_classes << 'issue-overdue' if issue.overdue?
485 css_classes << 'issue-overdue' if issue.overdue?
540 css_classes << 'issue-behind-schedule' if issue.behind_schedule?
486 css_classes << 'issue-behind-schedule' if issue.behind_schedule?
541 css_classes << 'icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
487 css_classes << 'icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
542
488
543 if issue.assigned_to.present?
489 if issue.assigned_to.present?
544 assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
490 assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
545 output << view.avatar(issue.assigned_to, :class => 'gravatar icon-gravatar', :size => 10, :title => assigned_string)
491 output << view.avatar(issue.assigned_to, :class => 'gravatar icon-gravatar', :size => 10, :title => assigned_string)
546 end
492 end
547 output << "<span class='#{css_classes.join(' ')}'>"
493 output << "<span class='#{css_classes.join(' ')}'>"
548 output << view.link_to_issue(issue)
494 output << view.link_to_issue(issue)
549 output << '</span>'
495 output << '</span>'
550 else
496 else
551 ActiveRecord::Base.logger.debug "Gantt#subject_for_issue was not given an issue"
497 ActiveRecord::Base.logger.debug "Gantt#subject_for_issue was not given an issue"
552 ''
498 ''
553 end
499 end
554 output << "</small></div>"
500 output << "</small></div>"
555
501
556 # Tooltip
502 # Tooltip
557 if issue.is_a? Issue
503 if issue.is_a? Issue
558 output << "<span class='tip' style='position: absolute;top:#{ options[:top].to_i + 16 }px;left:#{ options[:indent].to_i + 20 }px;'>"
504 output << "<span class='tip' style='position: absolute;top:#{ options[:top].to_i + 16 }px;left:#{ options[:indent].to_i + 20 }px;'>"
559 output << view.render_issue_tooltip(issue)
505 output << view.render_issue_tooltip(issue)
560 output << "</span>"
506 output << "</span>"
561 end
507 end
562
508
563 output << "</div>"
509 output << "</div>"
564 @subjects << output
510 @subjects << output
565 output
511 output
566 when :image
512 when :image
567 options[:image].fill('black')
513 options[:image].fill('black')
568 options[:image].stroke('transparent')
514 options[:image].stroke('transparent')
569 options[:image].stroke_width(1)
515 options[:image].stroke_width(1)
570 options[:image].text(options[:indent], options[:top] + 2, issue.subject)
516 options[:image].text(options[:indent], options[:top] + 2, issue.subject)
571 when :pdf
517 when :pdf
572 pdf_new_page?(options)
518 pdf_new_page?(options)
573 options[:pdf].SetY(options[:top])
519 options[:pdf].SetY(options[:top])
574 options[:pdf].SetX(15)
520 options[:pdf].SetX(15)
575
521
576 char_limit = PDF::MaxCharactorsForSubject - options[:indent]
522 char_limit = PDF::MaxCharactorsForSubject - options[:indent]
577 options[:pdf].Cell(options[:subject_width]-15, 5, (" " * options[:indent]) +"#{issue.tracker} #{issue.id}: #{issue.subject}".sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR")
523 options[:pdf].Cell(options[:subject_width]-15, 5, (" " * options[:indent]) +"#{issue.tracker} #{issue.id}: #{issue.subject}".sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR")
578
524
579 options[:pdf].SetY(options[:top])
525 options[:pdf].SetY(options[:top])
580 options[:pdf].SetX(options[:subject_width])
526 options[:pdf].SetX(options[:subject_width])
581 options[:pdf].Cell(options[:g_width], 5, "", "LR")
527 options[:pdf].Cell(options[:g_width], 5, "", "LR")
582 end
528 end
583 end
529 end
584
530
585 def line_for_issue(issue, options)
531 def line_for_issue(issue, options)
586 # Skip issues that don't have a due_before (due_date or version's due_date)
532 # Skip issues that don't have a due_before (due_date or version's due_date)
587 if issue.is_a?(Issue) && issue.due_before
533 if issue.is_a?(Issue) && issue.due_before
588 case options[:format]
534 case options[:format]
589 when :html
535 when :html
590 coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom])
536 coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom])
591 css = "task " + (issue.leaf? ? 'leaf' : 'parent')
537 css = "task " + (issue.leaf? ? 'leaf' : 'parent')
592 output = html_task(options[:top], coords, :css => css, :label => "#{ issue.status.name } #{ issue.done_ratio }%", :issue => issue)
538 output = html_task(options[:top], coords, :css => css, :label => "#{ issue.status.name } #{ issue.done_ratio }%", :issue => issue)
593
539
594 @lines << output
540 @lines << output
595 output
541 output
596
542
597 when :image
543 when :image
598 # Handle nil start_dates, rare but can happen.
544 # Handle nil start_dates, rare but can happen.
599 i_start_date = if issue.start_date && issue.start_date >= @date_from
545 i_start_date = if issue.start_date && issue.start_date >= @date_from
600 issue.start_date
546 issue.start_date
601 else
547 else
602 @date_from
548 @date_from
603 end
549 end
604
550
605 i_end_date = (issue.due_before <= date_to ? issue.due_before : date_to )
551 i_end_date = (issue.due_before <= date_to ? issue.due_before : date_to )
606 i_done_date = i_start_date + ((issue.due_before - i_start_date+1)*issue.done_ratio/100).floor
552 i_done_date = i_start_date + ((issue.due_before - i_start_date+1)*issue.done_ratio/100).floor
607 i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date )
553 i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date )
608 i_done_date = (i_done_date >= date_to ? date_to : i_done_date )
554 i_done_date = (i_done_date >= date_to ? date_to : i_done_date )
609 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
555 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
610
556
611 i_left = options[:subject_width] + ((i_start_date - @date_from)*options[:zoom]).floor
557 i_left = options[:subject_width] + ((i_start_date - @date_from)*options[:zoom]).floor
612 i_width = ((i_end_date - i_start_date + 1)*options[:zoom]).floor # total width of the issue
558 i_width = ((i_end_date - i_start_date + 1)*options[:zoom]).floor # total width of the issue
613 d_width = ((i_done_date - i_start_date)*options[:zoom]).floor # done width
559 d_width = ((i_done_date - i_start_date)*options[:zoom]).floor # done width
614 l_width = i_late_date ? ((i_late_date - i_start_date+1)*options[:zoom]).floor : 0 # delay width
560 l_width = i_late_date ? ((i_late_date - i_start_date+1)*options[:zoom]).floor : 0 # delay width
615
561
616
562
617 # Make sure that negative i_left and i_width don't
563 # Make sure that negative i_left and i_width don't
618 # overflow the subject
564 # overflow the subject
619 if i_width > 0
565 if i_width > 0
620 options[:image].fill('grey')
566 options[:image].fill('grey')
621 options[:image].rectangle(i_left, options[:top], i_left + i_width, options[:top] - 6)
567 options[:image].rectangle(i_left, options[:top], i_left + i_width, options[:top] - 6)
622 options[:image].fill('red')
568 options[:image].fill('red')
623 options[:image].rectangle(i_left, options[:top], i_left + l_width, options[:top] - 6) if l_width > 0
569 options[:image].rectangle(i_left, options[:top], i_left + l_width, options[:top] - 6) if l_width > 0
624 options[:image].fill('blue')
570 options[:image].fill('blue')
625 options[:image].rectangle(i_left, options[:top], i_left + d_width, options[:top] - 6) if d_width > 0
571 options[:image].rectangle(i_left, options[:top], i_left + d_width, options[:top] - 6) if d_width > 0
626 end
572 end
627
573
628 # Show the status and % done next to the subject if it overflows
574 # Show the status and % done next to the subject if it overflows
629 options[:image].fill('black')
575 options[:image].fill('black')
630 if i_width > 0
576 if i_width > 0
631 options[:image].text(i_left + i_width + 5,options[:top] + 1, "#{issue.status.name} #{issue.done_ratio}%")
577 options[:image].text(i_left + i_width + 5,options[:top] + 1, "#{issue.status.name} #{issue.done_ratio}%")
632 else
578 else
633 options[:image].text(options[:subject_width] + 5,options[:top] + 1, "#{issue.status.name} #{issue.done_ratio}%")
579 options[:image].text(options[:subject_width] + 5,options[:top] + 1, "#{issue.status.name} #{issue.done_ratio}%")
634 end
580 end
635
581
636 when :pdf
582 when :pdf
637 options[:pdf].SetY(options[:top]+1.5)
583 options[:pdf].SetY(options[:top]+1.5)
638 # Handle nil start_dates, rare but can happen.
584 # Handle nil start_dates, rare but can happen.
639 i_start_date = if issue.start_date && issue.start_date >= @date_from
585 i_start_date = if issue.start_date && issue.start_date >= @date_from
640 issue.start_date
586 issue.start_date
641 else
587 else
642 @date_from
588 @date_from
643 end
589 end
644
590
645 i_end_date = (issue.due_before <= @date_to ? issue.due_before : @date_to )
591 i_end_date = (issue.due_before <= @date_to ? issue.due_before : @date_to )
646
592
647 i_done_date = i_start_date + ((issue.due_before - i_start_date+1)*issue.done_ratio/100).floor
593 i_done_date = i_start_date + ((issue.due_before - i_start_date+1)*issue.done_ratio/100).floor
648 i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date )
594 i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date )
649 i_done_date = (i_done_date >= @date_to ? @date_to : i_done_date )
595 i_done_date = (i_done_date >= @date_to ? @date_to : i_done_date )
650
596
651 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
597 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
652
598
653 i_left = ((i_start_date - @date_from)*options[:zoom])
599 i_left = ((i_start_date - @date_from)*options[:zoom])
654 i_width = ((i_end_date - i_start_date + 1)*options[:zoom])
600 i_width = ((i_end_date - i_start_date + 1)*options[:zoom])
655 d_width = ((i_done_date - i_start_date)*options[:zoom])
601 d_width = ((i_done_date - i_start_date)*options[:zoom])
656 l_width = ((i_late_date - i_start_date+1)*options[:zoom]) if i_late_date
602 l_width = ((i_late_date - i_start_date+1)*options[:zoom]) if i_late_date
657 l_width ||= 0
603 l_width ||= 0
658
604
659 # Make sure that negative i_left and i_width don't
605 # Make sure that negative i_left and i_width don't
660 # overflow the subject
606 # overflow the subject
661 if i_width > 0
607 if i_width > 0
662 options[:pdf].SetX(options[:subject_width] + i_left)
608 options[:pdf].SetX(options[:subject_width] + i_left)
663 options[:pdf].SetFillColor(200,200,200)
609 options[:pdf].SetFillColor(200,200,200)
664 options[:pdf].Cell(i_width, 2, "", 0, 0, "", 1)
610 options[:pdf].Cell(i_width, 2, "", 0, 0, "", 1)
665 end
611 end
666
612
667 if l_width > 0
613 if l_width > 0
668 options[:pdf].SetY(options[:top]+1.5)
614 options[:pdf].SetY(options[:top]+1.5)
669 options[:pdf].SetX(options[:subject_width] + i_left)
615 options[:pdf].SetX(options[:subject_width] + i_left)
670 options[:pdf].SetFillColor(255,100,100)
616 options[:pdf].SetFillColor(255,100,100)
671 options[:pdf].Cell(l_width, 2, "", 0, 0, "", 1)
617 options[:pdf].Cell(l_width, 2, "", 0, 0, "", 1)
672 end
618 end
673 if d_width > 0
619 if d_width > 0
674 options[:pdf].SetY(options[:top]+1.5)
620 options[:pdf].SetY(options[:top]+1.5)
675 options[:pdf].SetX(options[:subject_width] + i_left)
621 options[:pdf].SetX(options[:subject_width] + i_left)
676 options[:pdf].SetFillColor(100,100,255)
622 options[:pdf].SetFillColor(100,100,255)
677 options[:pdf].Cell(d_width, 2, "", 0, 0, "", 1)
623 options[:pdf].Cell(d_width, 2, "", 0, 0, "", 1)
678 end
624 end
679
625
680 options[:pdf].SetY(options[:top]+1.5)
626 options[:pdf].SetY(options[:top]+1.5)
681
627
682 # Make sure that negative i_left and i_width don't
628 # Make sure that negative i_left and i_width don't
683 # overflow the subject
629 # overflow the subject
684 if (i_left + i_width) >= 0
630 if (i_left + i_width) >= 0
685 options[:pdf].SetX(options[:subject_width] + i_left + i_width)
631 options[:pdf].SetX(options[:subject_width] + i_left + i_width)
686 else
632 else
687 options[:pdf].SetX(options[:subject_width])
633 options[:pdf].SetX(options[:subject_width])
688 end
634 end
689 options[:pdf].Cell(30, 2, "#{issue.status} #{issue.done_ratio}%")
635 options[:pdf].Cell(30, 2, "#{issue.status} #{issue.done_ratio}%")
690 end
636 end
691 else
637 else
692 ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
638 ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
693 ''
639 ''
694 end
640 end
695 end
641 end
696
642
697 # Generates a gantt image
643 # Generates a gantt image
698 # Only defined if RMagick is avalaible
644 # Only defined if RMagick is avalaible
699 def to_image(format='PNG')
645 def to_image(format='PNG')
700 date_to = (@date_from >> @months)-1
646 date_to = (@date_from >> @months)-1
701 show_weeks = @zoom > 1
647 show_weeks = @zoom > 1
702 show_days = @zoom > 2
648 show_days = @zoom > 2
703
649
704 subject_width = 400
650 subject_width = 400
705 header_heigth = 18
651 header_heigth = 18
706 # width of one day in pixels
652 # width of one day in pixels
707 zoom = @zoom*2
653 zoom = @zoom*2
708 g_width = (@date_to - @date_from + 1)*zoom
654 g_width = (@date_to - @date_from + 1)*zoom
709 g_height = 20 * number_of_rows + 30
655 g_height = 20 * number_of_rows + 30
710 headers_heigth = (show_weeks ? 2*header_heigth : header_heigth)
656 headers_heigth = (show_weeks ? 2*header_heigth : header_heigth)
711 height = g_height + headers_heigth
657 height = g_height + headers_heigth
712
658
713 imgl = Magick::ImageList.new
659 imgl = Magick::ImageList.new
714 imgl.new_image(subject_width+g_width+1, height)
660 imgl.new_image(subject_width+g_width+1, height)
715 gc = Magick::Draw.new
661 gc = Magick::Draw.new
716
662
717 # Subjects
663 # Subjects
718 subjects(:image => gc, :top => (headers_heigth + 20), :indent => 4, :format => :image)
664 subjects(:image => gc, :top => (headers_heigth + 20), :indent => 4, :format => :image)
719
665
720 # Months headers
666 # Months headers
721 month_f = @date_from
667 month_f = @date_from
722 left = subject_width
668 left = subject_width
723 @months.times do
669 @months.times do
724 width = ((month_f >> 1) - month_f) * zoom
670 width = ((month_f >> 1) - month_f) * zoom
725 gc.fill('white')
671 gc.fill('white')
726 gc.stroke('grey')
672 gc.stroke('grey')
727 gc.stroke_width(1)
673 gc.stroke_width(1)
728 gc.rectangle(left, 0, left + width, height)
674 gc.rectangle(left, 0, left + width, height)
729 gc.fill('black')
675 gc.fill('black')
730 gc.stroke('transparent')
676 gc.stroke('transparent')
731 gc.stroke_width(1)
677 gc.stroke_width(1)
732 gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
678 gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
733 left = left + width
679 left = left + width
734 month_f = month_f >> 1
680 month_f = month_f >> 1
735 end
681 end
736
682
737 # Weeks headers
683 # Weeks headers
738 if show_weeks
684 if show_weeks
739 left = subject_width
685 left = subject_width
740 height = header_heigth
686 height = header_heigth
741 if @date_from.cwday == 1
687 if @date_from.cwday == 1
742 # date_from is monday
688 # date_from is monday
743 week_f = date_from
689 week_f = date_from
744 else
690 else
745 # find next monday after date_from
691 # find next monday after date_from
746 week_f = @date_from + (7 - @date_from.cwday + 1)
692 week_f = @date_from + (7 - @date_from.cwday + 1)
747 width = (7 - @date_from.cwday + 1) * zoom
693 width = (7 - @date_from.cwday + 1) * zoom
748 gc.fill('white')
694 gc.fill('white')
749 gc.stroke('grey')
695 gc.stroke('grey')
750 gc.stroke_width(1)
696 gc.stroke_width(1)
751 gc.rectangle(left, header_heigth, left + width, 2*header_heigth + g_height-1)
697 gc.rectangle(left, header_heigth, left + width, 2*header_heigth + g_height-1)
752 left = left + width
698 left = left + width
753 end
699 end
754 while week_f <= date_to
700 while week_f <= date_to
755 width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
701 width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
756 gc.fill('white')
702 gc.fill('white')
757 gc.stroke('grey')
703 gc.stroke('grey')
758 gc.stroke_width(1)
704 gc.stroke_width(1)
759 gc.rectangle(left.round, header_heigth, left.round + width, 2*header_heigth + g_height-1)
705 gc.rectangle(left.round, header_heigth, left.round + width, 2*header_heigth + g_height-1)
760 gc.fill('black')
706 gc.fill('black')
761 gc.stroke('transparent')
707 gc.stroke('transparent')
762 gc.stroke_width(1)
708 gc.stroke_width(1)
763 gc.text(left.round + 2, header_heigth + 14, week_f.cweek.to_s)
709 gc.text(left.round + 2, header_heigth + 14, week_f.cweek.to_s)
764 left = left + width
710 left = left + width
765 week_f = week_f+7
711 week_f = week_f+7
766 end
712 end
767 end
713 end
768
714
769 # Days details (week-end in grey)
715 # Days details (week-end in grey)
770 if show_days
716 if show_days
771 left = subject_width
717 left = subject_width
772 height = g_height + header_heigth - 1
718 height = g_height + header_heigth - 1
773 wday = @date_from.cwday
719 wday = @date_from.cwday
774 (date_to - @date_from + 1).to_i.times do
720 (date_to - @date_from + 1).to_i.times do
775 width = zoom
721 width = zoom
776 gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
722 gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
777 gc.stroke('grey')
723 gc.stroke('grey')
778 gc.stroke_width(1)
724 gc.stroke_width(1)
779 gc.rectangle(left, 2*header_heigth, left + width, 2*header_heigth + g_height-1)
725 gc.rectangle(left, 2*header_heigth, left + width, 2*header_heigth + g_height-1)
780 left = left + width
726 left = left + width
781 wday = wday + 1
727 wday = wday + 1
782 wday = 1 if wday > 7
728 wday = 1 if wday > 7
783 end
729 end
784 end
730 end
785
731
786 # border
732 # border
787 gc.fill('transparent')
733 gc.fill('transparent')
788 gc.stroke('grey')
734 gc.stroke('grey')
789 gc.stroke_width(1)
735 gc.stroke_width(1)
790 gc.rectangle(0, 0, subject_width+g_width, headers_heigth)
736 gc.rectangle(0, 0, subject_width+g_width, headers_heigth)
791 gc.stroke('black')
737 gc.stroke('black')
792 gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_heigth-1)
738 gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_heigth-1)
793
739
794 # content
740 # content
795 top = headers_heigth + 20
741 top = headers_heigth + 20
796
742
797 lines(:image => gc, :top => top, :zoom => zoom, :subject_width => subject_width, :format => :image)
743 lines(:image => gc, :top => top, :zoom => zoom, :subject_width => subject_width, :format => :image)
798
744
799 # today red line
745 # today red line
800 if Date.today >= @date_from and Date.today <= date_to
746 if Date.today >= @date_from and Date.today <= date_to
801 gc.stroke('red')
747 gc.stroke('red')
802 x = (Date.today-@date_from+1)*zoom + subject_width
748 x = (Date.today-@date_from+1)*zoom + subject_width
803 gc.line(x, headers_heigth, x, headers_heigth + g_height-1)
749 gc.line(x, headers_heigth, x, headers_heigth + g_height-1)
804 end
750 end
805
751
806 gc.draw(imgl)
752 gc.draw(imgl)
807 imgl.format = format
753 imgl.format = format
808 imgl.to_blob
754 imgl.to_blob
809 end if Object.const_defined?(:Magick)
755 end if Object.const_defined?(:Magick)
810
756
811 def to_pdf
757 def to_pdf
812 pdf = ::Redmine::Export::PDF::IFPDF.new(current_language)
758 pdf = ::Redmine::Export::PDF::IFPDF.new(current_language)
813 pdf.SetTitle("#{l(:label_gantt)} #{project}")
759 pdf.SetTitle("#{l(:label_gantt)} #{project}")
814 pdf.AliasNbPages
760 pdf.AliasNbPages
815 pdf.footer_date = format_date(Date.today)
761 pdf.footer_date = format_date(Date.today)
816 pdf.AddPage("L")
762 pdf.AddPage("L")
817 pdf.SetFontStyle('B',12)
763 pdf.SetFontStyle('B',12)
818 pdf.SetX(15)
764 pdf.SetX(15)
819 pdf.Cell(PDF::LeftPaneWidth, 20, project.to_s)
765 pdf.Cell(PDF::LeftPaneWidth, 20, project.to_s)
820 pdf.Ln
766 pdf.Ln
821 pdf.SetFontStyle('B',9)
767 pdf.SetFontStyle('B',9)
822
768
823 subject_width = PDF::LeftPaneWidth
769 subject_width = PDF::LeftPaneWidth
824 header_heigth = 5
770 header_heigth = 5
825
771
826 headers_heigth = header_heigth
772 headers_heigth = header_heigth
827 show_weeks = false
773 show_weeks = false
828 show_days = false
774 show_days = false
829
775
830 if self.months < 7
776 if self.months < 7
831 show_weeks = true
777 show_weeks = true
832 headers_heigth = 2*header_heigth
778 headers_heigth = 2*header_heigth
833 if self.months < 3
779 if self.months < 3
834 show_days = true
780 show_days = true
835 headers_heigth = 3*header_heigth
781 headers_heigth = 3*header_heigth
836 end
782 end
837 end
783 end
838
784
839 g_width = PDF.right_pane_width
785 g_width = PDF.right_pane_width
840 zoom = (g_width) / (self.date_to - self.date_from + 1)
786 zoom = (g_width) / (self.date_to - self.date_from + 1)
841 g_height = 120
787 g_height = 120
842 t_height = g_height + headers_heigth
788 t_height = g_height + headers_heigth
843
789
844 y_start = pdf.GetY
790 y_start = pdf.GetY
845
791
846 # Months headers
792 # Months headers
847 month_f = self.date_from
793 month_f = self.date_from
848 left = subject_width
794 left = subject_width
849 height = header_heigth
795 height = header_heigth
850 self.months.times do
796 self.months.times do
851 width = ((month_f >> 1) - month_f) * zoom
797 width = ((month_f >> 1) - month_f) * zoom
852 pdf.SetY(y_start)
798 pdf.SetY(y_start)
853 pdf.SetX(left)
799 pdf.SetX(left)
854 pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
800 pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
855 left = left + width
801 left = left + width
856 month_f = month_f >> 1
802 month_f = month_f >> 1
857 end
803 end
858
804
859 # Weeks headers
805 # Weeks headers
860 if show_weeks
806 if show_weeks
861 left = subject_width
807 left = subject_width
862 height = header_heigth
808 height = header_heigth
863 if self.date_from.cwday == 1
809 if self.date_from.cwday == 1
864 # self.date_from is monday
810 # self.date_from is monday
865 week_f = self.date_from
811 week_f = self.date_from
866 else
812 else
867 # find next monday after self.date_from
813 # find next monday after self.date_from
868 week_f = self.date_from + (7 - self.date_from.cwday + 1)
814 week_f = self.date_from + (7 - self.date_from.cwday + 1)
869 width = (7 - self.date_from.cwday + 1) * zoom-1
815 width = (7 - self.date_from.cwday + 1) * zoom-1
870 pdf.SetY(y_start + header_heigth)
816 pdf.SetY(y_start + header_heigth)
871 pdf.SetX(left)
817 pdf.SetX(left)
872 pdf.Cell(width + 1, height, "", "LTR")
818 pdf.Cell(width + 1, height, "", "LTR")
873 left = left + width+1
819 left = left + width+1
874 end
820 end
875 while week_f <= self.date_to
821 while week_f <= self.date_to
876 width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom
822 width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom
877 pdf.SetY(y_start + header_heigth)
823 pdf.SetY(y_start + header_heigth)
878 pdf.SetX(left)
824 pdf.SetX(left)
879 pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
825 pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
880 left = left + width
826 left = left + width
881 week_f = week_f+7
827 week_f = week_f+7
882 end
828 end
883 end
829 end
884
830
885 # Days headers
831 # Days headers
886 if show_days
832 if show_days
887 left = subject_width
833 left = subject_width
888 height = header_heigth
834 height = header_heigth
889 wday = self.date_from.cwday
835 wday = self.date_from.cwday
890 pdf.SetFontStyle('B',7)
836 pdf.SetFontStyle('B',7)
891 (self.date_to - self.date_from + 1).to_i.times do
837 (self.date_to - self.date_from + 1).to_i.times do
892 width = zoom
838 width = zoom
893 pdf.SetY(y_start + 2 * header_heigth)
839 pdf.SetY(y_start + 2 * header_heigth)
894 pdf.SetX(left)
840 pdf.SetX(left)
895 pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C")
841 pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C")
896 left = left + width
842 left = left + width
897 wday = wday + 1
843 wday = wday + 1
898 wday = 1 if wday > 7
844 wday = 1 if wday > 7
899 end
845 end
900 end
846 end
901
847
902 pdf.SetY(y_start)
848 pdf.SetY(y_start)
903 pdf.SetX(15)
849 pdf.SetX(15)
904 pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1)
850 pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1)
905
851
906 # Tasks
852 # Tasks
907 top = headers_heigth + y_start
853 top = headers_heigth + y_start
908 options = {
854 options = {
909 :top => top,
855 :top => top,
910 :zoom => zoom,
856 :zoom => zoom,
911 :subject_width => subject_width,
857 :subject_width => subject_width,
912 :g_width => g_width,
858 :g_width => g_width,
913 :indent => 0,
859 :indent => 0,
914 :indent_increment => 5,
860 :indent_increment => 5,
915 :top_increment => 5,
861 :top_increment => 5,
916 :format => :pdf,
862 :format => :pdf,
917 :pdf => pdf
863 :pdf => pdf
918 }
864 }
919 render(options)
865 render(options)
920 pdf.Output
866 pdf.Output
921 end
867 end
922
868
923 private
869 private
924
870
925 def coordinates(start_date, end_date, progress, zoom=nil)
871 def coordinates(start_date, end_date, progress, zoom=nil)
926 zoom ||= @zoom
872 zoom ||= @zoom
927
873
928 coords = {}
874 coords = {}
929 if start_date && end_date && start_date < self.date_to && end_date > self.date_from
875 if start_date && end_date && start_date < self.date_to && end_date > self.date_from
930 if start_date > self.date_from
876 if start_date > self.date_from
931 coords[:start] = start_date - self.date_from
877 coords[:start] = start_date - self.date_from
932 coords[:bar_start] = start_date - self.date_from
878 coords[:bar_start] = start_date - self.date_from
933 else
879 else
934 coords[:bar_start] = 0
880 coords[:bar_start] = 0
935 end
881 end
936 if end_date < self.date_to
882 if end_date < self.date_to
937 coords[:end] = end_date - self.date_from
883 coords[:end] = end_date - self.date_from
938 coords[:bar_end] = end_date - self.date_from + 1
884 coords[:bar_end] = end_date - self.date_from + 1
939 else
885 else
940 coords[:bar_end] = self.date_to - self.date_from + 1
886 coords[:bar_end] = self.date_to - self.date_from + 1
941 end
887 end
942
888
943 if progress
889 if progress
944 progress_date = start_date + (end_date - start_date) * (progress / 100.0)
890 progress_date = start_date + (end_date - start_date) * (progress / 100.0)
945 if progress_date > self.date_from && progress_date > start_date
891 if progress_date > self.date_from && progress_date > start_date
946 if progress_date < self.date_to
892 if progress_date < self.date_to
947 coords[:bar_progress_end] = progress_date - self.date_from + 1
893 coords[:bar_progress_end] = progress_date - self.date_from + 1
948 else
894 else
949 coords[:bar_progress_end] = self.date_to - self.date_from + 1
895 coords[:bar_progress_end] = self.date_to - self.date_from + 1
950 end
896 end
951 end
897 end
952
898
953 if progress_date < Date.today
899 if progress_date < Date.today
954 late_date = [Date.today, end_date].min
900 late_date = [Date.today, end_date].min
955 if late_date > self.date_from && late_date > start_date
901 if late_date > self.date_from && late_date > start_date
956 if late_date < self.date_to
902 if late_date < self.date_to
957 coords[:bar_late_end] = late_date - self.date_from + 1
903 coords[:bar_late_end] = late_date - self.date_from + 1
958 else
904 else
959 coords[:bar_late_end] = self.date_to - self.date_from + 1
905 coords[:bar_late_end] = self.date_to - self.date_from + 1
960 end
906 end
961 end
907 end
962 end
908 end
963 end
909 end
964 end
910 end
965
911
966 # Transforms dates into pixels witdh
912 # Transforms dates into pixels witdh
967 coords.keys.each do |key|
913 coords.keys.each do |key|
968 coords[key] = (coords[key] * zoom).floor
914 coords[key] = (coords[key] * zoom).floor
969 end
915 end
970 coords
916 coords
971 end
917 end
972
918
973 # Sorts a collection of issues by start_date, due_date, id for gantt rendering
919 # Sorts a collection of issues by start_date, due_date, id for gantt rendering
974 def sort_issues!(issues)
920 def sort_issues!(issues)
975 issues.sort! do |a, b|
921 issues.sort! do |a, b|
976 cmp = 0
922 cmp = 0
977 cmp = (a.start_date <=> b.start_date) if a.start_date? && b.start_date?
923 cmp = (a.start_date <=> b.start_date) if a.start_date? && b.start_date?
978 cmp = (a.due_date <=> b.due_date) if cmp == 0 && a.due_date? && b.due_date?
924 cmp = (a.due_date <=> b.due_date) if cmp == 0 && a.due_date? && b.due_date?
979 cmp = (a.id <=> b.id) if cmp == 0
925 cmp = (a.id <=> b.id) if cmp == 0
980 cmp
926 cmp
981 end
927 end
982 end
928 end
983
929
984 def current_limit
930 def current_limit
985 if @max_rows
931 if @max_rows
986 @max_rows - @number_of_rows
932 @max_rows - @number_of_rows
987 else
933 else
988 nil
934 nil
989 end
935 end
990 end
936 end
991
937
992 def abort?
938 def abort?
993 if @max_rows && @number_of_rows >= @max_rows
939 if @max_rows && @number_of_rows >= @max_rows
994 @truncated = true
940 @truncated = true
995 end
941 end
996 end
942 end
997
943
998 def pdf_new_page?(options)
944 def pdf_new_page?(options)
999 if options[:top] > 180
945 if options[:top] > 180
1000 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
946 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
1001 options[:pdf].AddPage("L")
947 options[:pdf].AddPage("L")
1002 options[:top] = 15
948 options[:top] = 15
1003 options[:pdf].Line(15, options[:top] - 0.1, PDF::TotalWidth, options[:top] - 0.1)
949 options[:pdf].Line(15, options[:top] - 0.1, PDF::TotalWidth, options[:top] - 0.1)
1004 end
950 end
1005 end
951 end
1006
952
1007 def html_task(top, coords, options={})
953 def html_task(top, coords, options={})
1008 output = ''
954 output = ''
1009 # Renders the task bar, with progress and late
955 # Renders the task bar, with progress and late
1010 if coords[:bar_start] && coords[:bar_end]
956 if coords[:bar_start] && coords[:bar_end]
1011 output << "<div style='top:#{ top }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_todo'>&nbsp;</div>"
957 output << "<div style='top:#{ top }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_todo'>&nbsp;</div>"
1012
958
1013 if coords[:bar_late_end]
959 if coords[:bar_late_end]
1014 output << "<div style='top:#{ top }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_late_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_late'>&nbsp;</div>"
960 output << "<div style='top:#{ top }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_late_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_late'>&nbsp;</div>"
1015 end
961 end
1016 if coords[:bar_progress_end]
962 if coords[:bar_progress_end]
1017 output << "<div style='top:#{ top }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_progress_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_done'>&nbsp;</div>"
963 output << "<div style='top:#{ top }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_progress_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_done'>&nbsp;</div>"
1018 end
964 end
1019 end
965 end
966 # Renders the markers
967 if options[:markers]
968 if coords[:start]
969 output << "<div style='top:#{ top }px;left:#{ coords[:start] }px;width:15px;' class='#{options[:css]} marker starting'>&nbsp;</div>"
970 end
971 if coords[:end]
972 output << "<div style='top:#{ top }px;left:#{ coords[:end] }px;width:15px;' class='#{options[:css]} marker ending'>&nbsp;</div>"
973 end
974 end
1020 # Renders the label on the right
975 # Renders the label on the right
1021 if options[:label]
976 if options[:label]
1022 output << "<div style='top:#{ top }px;left:#{ (coords[:bar_end] || 0) + 5 }px;' class='#{options[:css]} label'>"
977 output << "<div style='top:#{ top }px;left:#{ (coords[:bar_end] || 0) + 5 }px;' class='#{options[:css]} label'>"
1023 output << options[:label]
978 output << options[:label]
1024 output << "</div>"
979 output << "</div>"
1025 end
980 end
1026 # Renders the tooltip
981 # Renders the tooltip
1027 if options[:issue] && coords[:bar_start] && coords[:bar_end]
982 if options[:issue] && coords[:bar_start] && coords[:bar_end]
1028 output << "<div class='tooltip' style='position: absolute;top:#{ top }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_end] - coords[:bar_start] }px;height:12px;'>"
983 output << "<div class='tooltip' style='position: absolute;top:#{ top }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_end] - coords[:bar_start] }px;height:12px;'>"
1029 output << '<span class="tip">'
984 output << '<span class="tip">'
1030 output << view.render_issue_tooltip(options[:issue])
985 output << view.render_issue_tooltip(options[:issue])
1031 output << "</span></div>"
986 output << "</span></div>"
1032 end
987 end
1033
988
1034 output
989 output
1035 end
990 end
1036 end
991 end
1037 end
992 end
1038 end
993 end
@@ -1,945 +1,946
1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
2
2
3 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
3 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
4 h1 {margin:0; padding:0; font-size: 24px;}
4 h1 {margin:0; padding:0; font-size: 24px;}
5 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
5 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
7 h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
7 h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
8
8
9 /***** Layout *****/
9 /***** Layout *****/
10 #wrapper {background: white;}
10 #wrapper {background: white;}
11
11
12 #top-menu {background: #2C4056; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
12 #top-menu {background: #2C4056; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
13 #top-menu ul {margin: 0; padding: 0;}
13 #top-menu ul {margin: 0; padding: 0;}
14 #top-menu li {
14 #top-menu li {
15 float:left;
15 float:left;
16 list-style-type:none;
16 list-style-type:none;
17 margin: 0px 0px 0px 0px;
17 margin: 0px 0px 0px 0px;
18 padding: 0px 0px 0px 0px;
18 padding: 0px 0px 0px 0px;
19 white-space:nowrap;
19 white-space:nowrap;
20 }
20 }
21 #top-menu a {color: #fff; margin-right: 8px; font-weight: bold;}
21 #top-menu a {color: #fff; margin-right: 8px; font-weight: bold;}
22 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
22 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
23
23
24 #account {float:right;}
24 #account {float:right;}
25
25
26 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
26 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
27 #header a {color:#f8f8f8;}
27 #header a {color:#f8f8f8;}
28 #header h1 a.ancestor { font-size: 80%; }
28 #header h1 a.ancestor { font-size: 80%; }
29 #quick-search {float:right;}
29 #quick-search {float:right;}
30
30
31 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
31 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
32 #main-menu ul {margin: 0; padding: 0;}
32 #main-menu ul {margin: 0; padding: 0;}
33 #main-menu li {
33 #main-menu li {
34 float:left;
34 float:left;
35 list-style-type:none;
35 list-style-type:none;
36 margin: 0px 2px 0px 0px;
36 margin: 0px 2px 0px 0px;
37 padding: 0px 0px 0px 0px;
37 padding: 0px 0px 0px 0px;
38 white-space:nowrap;
38 white-space:nowrap;
39 }
39 }
40 #main-menu li a {
40 #main-menu li a {
41 display: block;
41 display: block;
42 color: #fff;
42 color: #fff;
43 text-decoration: none;
43 text-decoration: none;
44 font-weight: bold;
44 font-weight: bold;
45 margin: 0;
45 margin: 0;
46 padding: 4px 10px 4px 10px;
46 padding: 4px 10px 4px 10px;
47 }
47 }
48 #main-menu li a:hover {background:#759FCF; color:#fff;}
48 #main-menu li a:hover {background:#759FCF; color:#fff;}
49 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
49 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
50
50
51 #admin-menu ul {margin: 0; padding: 0;}
51 #admin-menu ul {margin: 0; padding: 0;}
52 #admin-menu li {margin: 0; padding: 0 0 12px 0; list-style-type:none;}
52 #admin-menu li {margin: 0; padding: 0 0 12px 0; list-style-type:none;}
53
53
54 #admin-menu a { background-position: 0% 40%; background-repeat: no-repeat; padding-left: 20px; padding-top: 2px; padding-bottom: 3px;}
54 #admin-menu a { background-position: 0% 40%; background-repeat: no-repeat; padding-left: 20px; padding-top: 2px; padding-bottom: 3px;}
55 #admin-menu a.projects { background-image: url(../images/projects.png); }
55 #admin-menu a.projects { background-image: url(../images/projects.png); }
56 #admin-menu a.users { background-image: url(../images/user.png); }
56 #admin-menu a.users { background-image: url(../images/user.png); }
57 #admin-menu a.groups { background-image: url(../images/group.png); }
57 #admin-menu a.groups { background-image: url(../images/group.png); }
58 #admin-menu a.roles { background-image: url(../images/database_key.png); }
58 #admin-menu a.roles { background-image: url(../images/database_key.png); }
59 #admin-menu a.trackers { background-image: url(../images/ticket.png); }
59 #admin-menu a.trackers { background-image: url(../images/ticket.png); }
60 #admin-menu a.issue_statuses { background-image: url(../images/ticket_edit.png); }
60 #admin-menu a.issue_statuses { background-image: url(../images/ticket_edit.png); }
61 #admin-menu a.workflows { background-image: url(../images/ticket_go.png); }
61 #admin-menu a.workflows { background-image: url(../images/ticket_go.png); }
62 #admin-menu a.custom_fields { background-image: url(../images/textfield.png); }
62 #admin-menu a.custom_fields { background-image: url(../images/textfield.png); }
63 #admin-menu a.enumerations { background-image: url(../images/text_list_bullets.png); }
63 #admin-menu a.enumerations { background-image: url(../images/text_list_bullets.png); }
64 #admin-menu a.settings { background-image: url(../images/changeset.png); }
64 #admin-menu a.settings { background-image: url(../images/changeset.png); }
65 #admin-menu a.plugins { background-image: url(../images/plugin.png); }
65 #admin-menu a.plugins { background-image: url(../images/plugin.png); }
66 #admin-menu a.info { background-image: url(../images/help.png); }
66 #admin-menu a.info { background-image: url(../images/help.png); }
67 #admin-menu a.server_authentication { background-image: url(../images/server_key.png); }
67 #admin-menu a.server_authentication { background-image: url(../images/server_key.png); }
68
68
69 #main {background-color:#EEEEEE;}
69 #main {background-color:#EEEEEE;}
70
70
71 #sidebar{ float: right; width: 22%; position: relative; z-index: 9; padding: 0; margin: 0;}
71 #sidebar{ float: right; width: 22%; position: relative; z-index: 9; padding: 0; margin: 0;}
72 * html #sidebar{ width: 22%; }
72 * html #sidebar{ width: 22%; }
73 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
73 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
74 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
74 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
75 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
75 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
76 #sidebar .contextual { margin-right: 1em; }
76 #sidebar .contextual { margin-right: 1em; }
77
77
78 #content { width: 75%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; }
78 #content { width: 75%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; }
79 * html #content{ width: 75%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
79 * html #content{ width: 75%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
80 html>body #content { min-height: 600px; }
80 html>body #content { min-height: 600px; }
81 * html body #content { height: 600px; } /* IE */
81 * html body #content { height: 600px; } /* IE */
82
82
83 #main.nosidebar #sidebar{ display: none; }
83 #main.nosidebar #sidebar{ display: none; }
84 #main.nosidebar #content{ width: auto; border-right: 0; }
84 #main.nosidebar #content{ width: auto; border-right: 0; }
85
85
86 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
86 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
87
87
88 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
88 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
89 #login-form table td {padding: 6px;}
89 #login-form table td {padding: 6px;}
90 #login-form label {font-weight: bold;}
90 #login-form label {font-weight: bold;}
91 #login-form input#username, #login-form input#password { width: 300px; }
91 #login-form input#username, #login-form input#password { width: 300px; }
92
92
93 input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; }
93 input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; }
94
94
95 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
95 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
96
96
97 /***** Links *****/
97 /***** Links *****/
98 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
98 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
99 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
99 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
100 a img{ border: 0; }
100 a img{ border: 0; }
101
101
102 a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: #999; text-decoration: line-through; }
102 a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: #999; text-decoration: line-through; }
103
103
104 /***** Tables *****/
104 /***** Tables *****/
105 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
105 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
106 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
106 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
107 table.list td { vertical-align: top; }
107 table.list td { vertical-align: top; }
108 table.list td.id { width: 2%; text-align: center;}
108 table.list td.id { width: 2%; text-align: center;}
109 table.list td.checkbox { width: 15px; padding: 0px;}
109 table.list td.checkbox { width: 15px; padding: 0px;}
110 table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; }
110 table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; }
111 table.list td.buttons a { padding-right: 0.6em; }
111 table.list td.buttons a { padding-right: 0.6em; }
112 table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; }
112 table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; }
113
113
114 tr.project td.name a { white-space:nowrap; }
114 tr.project td.name a { white-space:nowrap; }
115
115
116 tr.project.idnt td.name span {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
116 tr.project.idnt td.name span {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
117 tr.project.idnt-1 td.name {padding-left: 0.5em;}
117 tr.project.idnt-1 td.name {padding-left: 0.5em;}
118 tr.project.idnt-2 td.name {padding-left: 2em;}
118 tr.project.idnt-2 td.name {padding-left: 2em;}
119 tr.project.idnt-3 td.name {padding-left: 3.5em;}
119 tr.project.idnt-3 td.name {padding-left: 3.5em;}
120 tr.project.idnt-4 td.name {padding-left: 5em;}
120 tr.project.idnt-4 td.name {padding-left: 5em;}
121 tr.project.idnt-5 td.name {padding-left: 6.5em;}
121 tr.project.idnt-5 td.name {padding-left: 6.5em;}
122 tr.project.idnt-6 td.name {padding-left: 8em;}
122 tr.project.idnt-6 td.name {padding-left: 8em;}
123 tr.project.idnt-7 td.name {padding-left: 9.5em;}
123 tr.project.idnt-7 td.name {padding-left: 9.5em;}
124 tr.project.idnt-8 td.name {padding-left: 11em;}
124 tr.project.idnt-8 td.name {padding-left: 11em;}
125 tr.project.idnt-9 td.name {padding-left: 12.5em;}
125 tr.project.idnt-9 td.name {padding-left: 12.5em;}
126
126
127 tr.issue { text-align: center; white-space: nowrap; }
127 tr.issue { text-align: center; white-space: nowrap; }
128 tr.issue td.subject, tr.issue td.category, td.assigned_to { white-space: normal; }
128 tr.issue td.subject, tr.issue td.category, td.assigned_to { white-space: normal; }
129 tr.issue td.subject { text-align: left; }
129 tr.issue td.subject { text-align: left; }
130 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
130 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
131
131
132 tr.issue.idnt td.subject a {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
132 tr.issue.idnt td.subject a {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
133 tr.issue.idnt-1 td.subject {padding-left: 0.5em;}
133 tr.issue.idnt-1 td.subject {padding-left: 0.5em;}
134 tr.issue.idnt-2 td.subject {padding-left: 2em;}
134 tr.issue.idnt-2 td.subject {padding-left: 2em;}
135 tr.issue.idnt-3 td.subject {padding-left: 3.5em;}
135 tr.issue.idnt-3 td.subject {padding-left: 3.5em;}
136 tr.issue.idnt-4 td.subject {padding-left: 5em;}
136 tr.issue.idnt-4 td.subject {padding-left: 5em;}
137 tr.issue.idnt-5 td.subject {padding-left: 6.5em;}
137 tr.issue.idnt-5 td.subject {padding-left: 6.5em;}
138 tr.issue.idnt-6 td.subject {padding-left: 8em;}
138 tr.issue.idnt-6 td.subject {padding-left: 8em;}
139 tr.issue.idnt-7 td.subject {padding-left: 9.5em;}
139 tr.issue.idnt-7 td.subject {padding-left: 9.5em;}
140 tr.issue.idnt-8 td.subject {padding-left: 11em;}
140 tr.issue.idnt-8 td.subject {padding-left: 11em;}
141 tr.issue.idnt-9 td.subject {padding-left: 12.5em;}
141 tr.issue.idnt-9 td.subject {padding-left: 12.5em;}
142
142
143 tr.entry { border: 1px solid #f8f8f8; }
143 tr.entry { border: 1px solid #f8f8f8; }
144 tr.entry td { white-space: nowrap; }
144 tr.entry td { white-space: nowrap; }
145 tr.entry td.filename { width: 30%; }
145 tr.entry td.filename { width: 30%; }
146 tr.entry td.size { text-align: right; font-size: 90%; }
146 tr.entry td.size { text-align: right; font-size: 90%; }
147 tr.entry td.revision, tr.entry td.author { text-align: center; }
147 tr.entry td.revision, tr.entry td.author { text-align: center; }
148 tr.entry td.age { text-align: right; }
148 tr.entry td.age { text-align: right; }
149 tr.entry.file td.filename a { margin-left: 16px; }
149 tr.entry.file td.filename a { margin-left: 16px; }
150
150
151 tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;}
151 tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;}
152 tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);}
152 tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);}
153
153
154 tr.changeset td.author { text-align: center; width: 15%; }
154 tr.changeset td.author { text-align: center; width: 15%; }
155 tr.changeset td.committed_on { text-align: center; width: 15%; }
155 tr.changeset td.committed_on { text-align: center; width: 15%; }
156
156
157 table.files tr.file td { text-align: center; }
157 table.files tr.file td { text-align: center; }
158 table.files tr.file td.filename { text-align: left; padding-left: 24px; }
158 table.files tr.file td.filename { text-align: left; padding-left: 24px; }
159 table.files tr.file td.digest { font-size: 80%; }
159 table.files tr.file td.digest { font-size: 80%; }
160
160
161 table.members td.roles, table.memberships td.roles { width: 45%; }
161 table.members td.roles, table.memberships td.roles { width: 45%; }
162
162
163 tr.message { height: 2.6em; }
163 tr.message { height: 2.6em; }
164 tr.message td.subject { padding-left: 20px; }
164 tr.message td.subject { padding-left: 20px; }
165 tr.message td.created_on { white-space: nowrap; }
165 tr.message td.created_on { white-space: nowrap; }
166 tr.message td.last_message { font-size: 80%; white-space: nowrap; }
166 tr.message td.last_message { font-size: 80%; white-space: nowrap; }
167 tr.message.locked td.subject { background: url(../images/locked.png) no-repeat 0 1px; }
167 tr.message.locked td.subject { background: url(../images/locked.png) no-repeat 0 1px; }
168 tr.message.sticky td.subject { background: url(../images/bullet_go.png) no-repeat 0 1px; font-weight: bold; }
168 tr.message.sticky td.subject { background: url(../images/bullet_go.png) no-repeat 0 1px; font-weight: bold; }
169
169
170 tr.version.closed, tr.version.closed a { color: #999; }
170 tr.version.closed, tr.version.closed a { color: #999; }
171 tr.version td.name { padding-left: 20px; }
171 tr.version td.name { padding-left: 20px; }
172 tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%; }
172 tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%; }
173 tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; }
173 tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; }
174
174
175 tr.user td { width:13%; }
175 tr.user td { width:13%; }
176 tr.user td.email { width:18%; }
176 tr.user td.email { width:18%; }
177 tr.user td { white-space: nowrap; }
177 tr.user td { white-space: nowrap; }
178 tr.user.locked, tr.user.registered { color: #aaa; }
178 tr.user.locked, tr.user.registered { color: #aaa; }
179 tr.user.locked a, tr.user.registered a { color: #aaa; }
179 tr.user.locked a, tr.user.registered a { color: #aaa; }
180
180
181 tr.time-entry { text-align: center; white-space: nowrap; }
181 tr.time-entry { text-align: center; white-space: nowrap; }
182 tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; }
182 tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; }
183 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
183 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
184 td.hours .hours-dec { font-size: 0.9em; }
184 td.hours .hours-dec { font-size: 0.9em; }
185
185
186 table.plugins td { vertical-align: middle; }
186 table.plugins td { vertical-align: middle; }
187 table.plugins td.configure { text-align: right; padding-right: 1em; }
187 table.plugins td.configure { text-align: right; padding-right: 1em; }
188 table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; }
188 table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; }
189 table.plugins span.description { display: block; font-size: 0.9em; }
189 table.plugins span.description { display: block; font-size: 0.9em; }
190 table.plugins span.url { display: block; font-size: 0.9em; }
190 table.plugins span.url { display: block; font-size: 0.9em; }
191
191
192 table.list tbody tr.group td { padding: 0.8em 0 0.5em 0.3em; font-weight: bold; border-bottom: 1px solid #ccc; }
192 table.list tbody tr.group td { padding: 0.8em 0 0.5em 0.3em; font-weight: bold; border-bottom: 1px solid #ccc; }
193 table.list tbody tr.group span.count { color: #aaa; font-size: 80%; }
193 table.list tbody tr.group span.count { color: #aaa; font-size: 80%; }
194
194
195 table.list tbody tr:hover { background-color:#ffffdd; }
195 table.list tbody tr:hover { background-color:#ffffdd; }
196 table.list tbody tr.group:hover { background-color:inherit; }
196 table.list tbody tr.group:hover { background-color:inherit; }
197 table td {padding:2px;}
197 table td {padding:2px;}
198 table p {margin:0;}
198 table p {margin:0;}
199 .odd {background-color:#f6f7f8;}
199 .odd {background-color:#f6f7f8;}
200 .even {background-color: #fff;}
200 .even {background-color: #fff;}
201
201
202 a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; }
202 a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; }
203 a.sort.asc { background-image: url(../images/sort_asc.png); }
203 a.sort.asc { background-image: url(../images/sort_asc.png); }
204 a.sort.desc { background-image: url(../images/sort_desc.png); }
204 a.sort.desc { background-image: url(../images/sort_desc.png); }
205
205
206 table.attributes { width: 100% }
206 table.attributes { width: 100% }
207 table.attributes th { vertical-align: top; text-align: left; }
207 table.attributes th { vertical-align: top; text-align: left; }
208 table.attributes td { vertical-align: top; }
208 table.attributes td { vertical-align: top; }
209
209
210 table.boards a.board, h3.comments { background: url(../images/comment.png) no-repeat 0% 50%; padding-left: 20px; }
210 table.boards a.board, h3.comments { background: url(../images/comment.png) no-repeat 0% 50%; padding-left: 20px; }
211
211
212 td.center {text-align:center;}
212 td.center {text-align:center;}
213
213
214 h3.version { background: url(../images/package.png) no-repeat 0% 50%; padding-left: 20px; }
214 h3.version { background: url(../images/package.png) no-repeat 0% 50%; padding-left: 20px; }
215
215
216 div.issues h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; }
216 div.issues h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; }
217 div.members h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; }
217 div.members h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; }
218 div.news h3 { background: url(../images/news.png) no-repeat 0% 50%; padding-left: 20px; }
218 div.news h3 { background: url(../images/news.png) no-repeat 0% 50%; padding-left: 20px; }
219 div.projects h3 { background: url(../images/projects.png) no-repeat 0% 50%; padding-left: 20px; }
219 div.projects h3 { background: url(../images/projects.png) no-repeat 0% 50%; padding-left: 20px; }
220
220
221 #watchers ul {margin: 0; padding: 0;}
221 #watchers ul {margin: 0; padding: 0;}
222 #watchers li {list-style-type:none;margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;}
222 #watchers li {list-style-type:none;margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;}
223 #watchers select {width: 95%; display: block;}
223 #watchers select {width: 95%; display: block;}
224 #watchers a.delete {opacity: 0.4;}
224 #watchers a.delete {opacity: 0.4;}
225 #watchers a.delete:hover {opacity: 1;}
225 #watchers a.delete:hover {opacity: 1;}
226 #watchers img.gravatar {vertical-align: middle;margin: 0 4px 2px 0;}
226 #watchers img.gravatar {vertical-align: middle;margin: 0 4px 2px 0;}
227
227
228 .highlight { background-color: #FCFD8D;}
228 .highlight { background-color: #FCFD8D;}
229 .highlight.token-1 { background-color: #faa;}
229 .highlight.token-1 { background-color: #faa;}
230 .highlight.token-2 { background-color: #afa;}
230 .highlight.token-2 { background-color: #afa;}
231 .highlight.token-3 { background-color: #aaf;}
231 .highlight.token-3 { background-color: #aaf;}
232
232
233 .box{
233 .box{
234 padding:6px;
234 padding:6px;
235 margin-bottom: 10px;
235 margin-bottom: 10px;
236 background-color:#f6f6f6;
236 background-color:#f6f6f6;
237 color:#505050;
237 color:#505050;
238 line-height:1.5em;
238 line-height:1.5em;
239 border: 1px solid #e4e4e4;
239 border: 1px solid #e4e4e4;
240 }
240 }
241
241
242 div.square {
242 div.square {
243 border: 1px solid #999;
243 border: 1px solid #999;
244 float: left;
244 float: left;
245 margin: .3em .4em 0 .4em;
245 margin: .3em .4em 0 .4em;
246 overflow: hidden;
246 overflow: hidden;
247 width: .6em; height: .6em;
247 width: .6em; height: .6em;
248 }
248 }
249 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
249 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
250 .contextual input, .contextual select {font-size:0.9em;}
250 .contextual input, .contextual select {font-size:0.9em;}
251 .message .contextual { margin-top: 0; }
251 .message .contextual { margin-top: 0; }
252
252
253 .splitcontentleft{float:left; width:49%;}
253 .splitcontentleft{float:left; width:49%;}
254 .splitcontentright{float:right; width:49%;}
254 .splitcontentright{float:right; width:49%;}
255 form {display: inline;}
255 form {display: inline;}
256 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
256 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
257 fieldset {border: 1px solid #e4e4e4; margin:0;}
257 fieldset {border: 1px solid #e4e4e4; margin:0;}
258 legend {color: #484848;}
258 legend {color: #484848;}
259 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
259 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
260 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
260 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
261 blockquote blockquote { margin-left: 0;}
261 blockquote blockquote { margin-left: 0;}
262 acronym { border-bottom: 1px dotted; cursor: help; }
262 acronym { border-bottom: 1px dotted; cursor: help; }
263 textarea.wiki-edit { width: 99%; }
263 textarea.wiki-edit { width: 99%; }
264 li p {margin-top: 0;}
264 li p {margin-top: 0;}
265 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
265 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
266 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
266 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
267 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
267 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
268 p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
268 p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
269
269
270 div.issue div.subject div div { padding-left: 16px; }
270 div.issue div.subject div div { padding-left: 16px; }
271 div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;}
271 div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;}
272 div.issue div.subject>div>p { margin-top: 0.5em; }
272 div.issue div.subject>div>p { margin-top: 0.5em; }
273 div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;}
273 div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;}
274
274
275 #issue_tree table.issues { border: 0; }
275 #issue_tree table.issues { border: 0; }
276 #issue_tree td.checkbox {display:none;}
276 #issue_tree td.checkbox {display:none;}
277
277
278 fieldset.collapsible { border-width: 1px 0 0 0; font-size: 0.9em; }
278 fieldset.collapsible { border-width: 1px 0 0 0; font-size: 0.9em; }
279 fieldset.collapsible legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; }
279 fieldset.collapsible legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; }
280 fieldset.collapsible.collapsed legend { background-image: url(../images/arrow_collapsed.png); }
280 fieldset.collapsible.collapsed legend { background-image: url(../images/arrow_collapsed.png); }
281
281
282 fieldset#date-range p { margin: 2px 0 2px 0; }
282 fieldset#date-range p { margin: 2px 0 2px 0; }
283 fieldset#filters table { border-collapse: collapse; }
283 fieldset#filters table { border-collapse: collapse; }
284 fieldset#filters table td { padding: 0; vertical-align: middle; }
284 fieldset#filters table td { padding: 0; vertical-align: middle; }
285 fieldset#filters tr.filter { height: 2em; }
285 fieldset#filters tr.filter { height: 2em; }
286 fieldset#filters td.add-filter { text-align: right; vertical-align: top; }
286 fieldset#filters td.add-filter { text-align: right; vertical-align: top; }
287 .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; }
287 .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; }
288
288
289 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
289 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
290 div#issue-changesets div.changeset { padding: 4px;}
290 div#issue-changesets div.changeset { padding: 4px;}
291 div#issue-changesets div.changeset { border-bottom: 1px solid #ddd; }
291 div#issue-changesets div.changeset { border-bottom: 1px solid #ddd; }
292 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
292 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
293
293
294 div#activity dl, #search-results { margin-left: 2em; }
294 div#activity dl, #search-results { margin-left: 2em; }
295 div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
295 div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
296 div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
296 div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
297 div#activity dt.me .time { border-bottom: 1px solid #999; }
297 div#activity dt.me .time { border-bottom: 1px solid #999; }
298 div#activity dt .time { color: #777; font-size: 80%; }
298 div#activity dt .time { color: #777; font-size: 80%; }
299 div#activity dd .description, #search-results dd .description { font-style: italic; }
299 div#activity dd .description, #search-results dd .description { font-style: italic; }
300 div#activity span.project:after, #search-results span.project:after { content: " -"; }
300 div#activity span.project:after, #search-results span.project:after { content: " -"; }
301 div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; }
301 div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; }
302
302
303 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
303 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
304
304
305 div#search-results-counts {float:right;}
305 div#search-results-counts {float:right;}
306 div#search-results-counts ul { margin-top: 0.5em; }
306 div#search-results-counts ul { margin-top: 0.5em; }
307 div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
307 div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
308
308
309 dt.issue { background-image: url(../images/ticket.png); }
309 dt.issue { background-image: url(../images/ticket.png); }
310 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
310 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
311 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
311 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
312 dt.issue-note { background-image: url(../images/ticket_note.png); }
312 dt.issue-note { background-image: url(../images/ticket_note.png); }
313 dt.changeset { background-image: url(../images/changeset.png); }
313 dt.changeset { background-image: url(../images/changeset.png); }
314 dt.news { background-image: url(../images/news.png); }
314 dt.news { background-image: url(../images/news.png); }
315 dt.message { background-image: url(../images/message.png); }
315 dt.message { background-image: url(../images/message.png); }
316 dt.reply { background-image: url(../images/comments.png); }
316 dt.reply { background-image: url(../images/comments.png); }
317 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
317 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
318 dt.attachment { background-image: url(../images/attachment.png); }
318 dt.attachment { background-image: url(../images/attachment.png); }
319 dt.document { background-image: url(../images/document.png); }
319 dt.document { background-image: url(../images/document.png); }
320 dt.project { background-image: url(../images/projects.png); }
320 dt.project { background-image: url(../images/projects.png); }
321 dt.time-entry { background-image: url(../images/time.png); }
321 dt.time-entry { background-image: url(../images/time.png); }
322
322
323 #search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
323 #search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
324
324
325 div#roadmap .related-issues { margin-bottom: 1em; }
325 div#roadmap .related-issues { margin-bottom: 1em; }
326 div#roadmap .related-issues td.checkbox { display: none; }
326 div#roadmap .related-issues td.checkbox { display: none; }
327 div#roadmap .wiki h1:first-child { display: none; }
327 div#roadmap .wiki h1:first-child { display: none; }
328 div#roadmap .wiki h1 { font-size: 120%; }
328 div#roadmap .wiki h1 { font-size: 120%; }
329 div#roadmap .wiki h2 { font-size: 110%; }
329 div#roadmap .wiki h2 { font-size: 110%; }
330
330
331 div#version-summary { float:right; width:380px; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
331 div#version-summary { float:right; width:380px; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
332 div#version-summary fieldset { margin-bottom: 1em; }
332 div#version-summary fieldset { margin-bottom: 1em; }
333 div#version-summary .total-hours { text-align: right; }
333 div#version-summary .total-hours { text-align: right; }
334
334
335 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
335 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
336 table#time-report tbody tr { font-style: italic; color: #777; }
336 table#time-report tbody tr { font-style: italic; color: #777; }
337 table#time-report tbody tr.last-level { font-style: normal; color: #555; }
337 table#time-report tbody tr.last-level { font-style: normal; color: #555; }
338 table#time-report tbody tr.total { font-style: normal; font-weight: bold; color: #555; background-color:#EEEEEE; }
338 table#time-report tbody tr.total { font-style: normal; font-weight: bold; color: #555; background-color:#EEEEEE; }
339 table#time-report .hours-dec { font-size: 0.9em; }
339 table#time-report .hours-dec { font-size: 0.9em; }
340
340
341 form .attributes { margin-bottom: 8px; }
341 form .attributes { margin-bottom: 8px; }
342 form .attributes p { padding-top: 1px; padding-bottom: 2px; }
342 form .attributes p { padding-top: 1px; padding-bottom: 2px; }
343 form .attributes select { min-width: 50%; }
343 form .attributes select { min-width: 50%; }
344
344
345 ul.projects { margin: 0; padding-left: 1em; }
345 ul.projects { margin: 0; padding-left: 1em; }
346 ul.projects.root { margin: 0; padding: 0; }
346 ul.projects.root { margin: 0; padding: 0; }
347 ul.projects ul.projects { border-left: 3px solid #e0e0e0; }
347 ul.projects ul.projects { border-left: 3px solid #e0e0e0; }
348 ul.projects li.root { list-style-type:none; margin-bottom: 1em; }
348 ul.projects li.root { list-style-type:none; margin-bottom: 1em; }
349 ul.projects li.child { list-style-type:none; margin-top: 1em;}
349 ul.projects li.child { list-style-type:none; margin-top: 1em;}
350 ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; }
350 ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; }
351 .my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; }
351 .my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; }
352
352
353 #tracker_project_ids ul { margin: 0; padding-left: 1em; }
353 #tracker_project_ids ul { margin: 0; padding-left: 1em; }
354 #tracker_project_ids li { list-style-type:none; }
354 #tracker_project_ids li { list-style-type:none; }
355
355
356 ul.properties {padding:0; font-size: 0.9em; color: #777;}
356 ul.properties {padding:0; font-size: 0.9em; color: #777;}
357 ul.properties li {list-style-type:none;}
357 ul.properties li {list-style-type:none;}
358 ul.properties li span {font-style:italic;}
358 ul.properties li span {font-style:italic;}
359
359
360 .total-hours { font-size: 110%; font-weight: bold; }
360 .total-hours { font-size: 110%; font-weight: bold; }
361 .total-hours span.hours-int { font-size: 120%; }
361 .total-hours span.hours-int { font-size: 120%; }
362
362
363 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
363 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
364 #user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; }
364 #user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; }
365
365
366 #workflow_copy_form select { width: 200px; }
366 #workflow_copy_form select { width: 200px; }
367
367
368 .pagination {font-size: 90%}
368 .pagination {font-size: 90%}
369 p.pagination {margin-top:8px;}
369 p.pagination {margin-top:8px;}
370
370
371 /***** Tabular forms ******/
371 /***** Tabular forms ******/
372 .tabular p{
372 .tabular p{
373 margin: 0;
373 margin: 0;
374 padding: 5px 0 8px 0;
374 padding: 5px 0 8px 0;
375 padding-left: 180px; /*width of left column containing the label elements*/
375 padding-left: 180px; /*width of left column containing the label elements*/
376 height: 1%;
376 height: 1%;
377 clear:left;
377 clear:left;
378 }
378 }
379
379
380 html>body .tabular p {overflow:hidden;}
380 html>body .tabular p {overflow:hidden;}
381
381
382 .tabular label{
382 .tabular label{
383 font-weight: bold;
383 font-weight: bold;
384 float: left;
384 float: left;
385 text-align: right;
385 text-align: right;
386 margin-left: -180px; /*width of left column*/
386 margin-left: -180px; /*width of left column*/
387 width: 175px; /*width of labels. Should be smaller than left column to create some right
387 width: 175px; /*width of labels. Should be smaller than left column to create some right
388 margin*/
388 margin*/
389 }
389 }
390
390
391 .tabular label.floating{
391 .tabular label.floating{
392 font-weight: normal;
392 font-weight: normal;
393 margin-left: 0px;
393 margin-left: 0px;
394 text-align: left;
394 text-align: left;
395 width: 270px;
395 width: 270px;
396 }
396 }
397
397
398 .tabular label.block{
398 .tabular label.block{
399 font-weight: normal;
399 font-weight: normal;
400 margin-left: 0px !important;
400 margin-left: 0px !important;
401 text-align: left;
401 text-align: left;
402 float: none;
402 float: none;
403 display: block;
403 display: block;
404 width: auto;
404 width: auto;
405 }
405 }
406
406
407 .tabular label.inline{
407 .tabular label.inline{
408 float:none;
408 float:none;
409 margin-left: 5px !important;
409 margin-left: 5px !important;
410 width: auto;
410 width: auto;
411 }
411 }
412
412
413 input#time_entry_comments { width: 90%;}
413 input#time_entry_comments { width: 90%;}
414
414
415 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
415 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
416
416
417 .tabular.settings p{ padding-left: 300px; }
417 .tabular.settings p{ padding-left: 300px; }
418 .tabular.settings label{ margin-left: -300px; width: 295px; }
418 .tabular.settings label{ margin-left: -300px; width: 295px; }
419 .tabular.settings textarea { width: 99%; }
419 .tabular.settings textarea { width: 99%; }
420
420
421 fieldset.settings label { display: block; }
421 fieldset.settings label { display: block; }
422 .parent { padding-left: 20px; }
422 .parent { padding-left: 20px; }
423
423
424 .required {color: #bb0000;}
424 .required {color: #bb0000;}
425 .summary {font-style: italic;}
425 .summary {font-style: italic;}
426
426
427 #attachments_fields input[type=text] {margin-left: 8px; }
427 #attachments_fields input[type=text] {margin-left: 8px; }
428
428
429 div.attachments { margin-top: 12px; }
429 div.attachments { margin-top: 12px; }
430 div.attachments p { margin:4px 0 2px 0; }
430 div.attachments p { margin:4px 0 2px 0; }
431 div.attachments img { vertical-align: middle; }
431 div.attachments img { vertical-align: middle; }
432 div.attachments span.author { font-size: 0.9em; color: #888; }
432 div.attachments span.author { font-size: 0.9em; color: #888; }
433
433
434 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
434 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
435 .other-formats span + span:before { content: "| "; }
435 .other-formats span + span:before { content: "| "; }
436
436
437 a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
437 a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
438
438
439 /* Project members tab */
439 /* Project members tab */
440 div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% }
440 div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% }
441 div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% }
441 div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% }
442 div#tab-content-members fieldset, div#tab-content-memberships fieldset, div#tab-content-users fieldset { padding:1em; margin-bottom: 1em; }
442 div#tab-content-members fieldset, div#tab-content-memberships fieldset, div#tab-content-users fieldset { padding:1em; margin-bottom: 1em; }
443 div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend, div#tab-content-users fieldset legend { font-weight: bold; }
443 div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend, div#tab-content-users fieldset legend { font-weight: bold; }
444 div#tab-content-members fieldset label, div#tab-content-memberships fieldset label, div#tab-content-users fieldset label { display: block; }
444 div#tab-content-members fieldset label, div#tab-content-memberships fieldset label, div#tab-content-users fieldset label { display: block; }
445 div#tab-content-members fieldset div, div#tab-content-users fieldset div { max-height: 400px; overflow:auto; }
445 div#tab-content-members fieldset div, div#tab-content-users fieldset div { max-height: 400px; overflow:auto; }
446
446
447 table.members td.group { padding-left: 20px; background: url(../images/group.png) no-repeat 0% 50%; }
447 table.members td.group { padding-left: 20px; background: url(../images/group.png) no-repeat 0% 50%; }
448
448
449 input#principal_search, input#user_search {width:100%}
449 input#principal_search, input#user_search {width:100%}
450
450
451 * html div#tab-content-members fieldset div { height: 450px; }
451 * html div#tab-content-members fieldset div { height: 450px; }
452
452
453 /***** Flash & error messages ****/
453 /***** Flash & error messages ****/
454 #errorExplanation, div.flash, .nodata, .warning {
454 #errorExplanation, div.flash, .nodata, .warning {
455 padding: 4px 4px 4px 30px;
455 padding: 4px 4px 4px 30px;
456 margin-bottom: 12px;
456 margin-bottom: 12px;
457 font-size: 1.1em;
457 font-size: 1.1em;
458 border: 2px solid;
458 border: 2px solid;
459 }
459 }
460
460
461 div.flash {margin-top: 8px;}
461 div.flash {margin-top: 8px;}
462
462
463 div.flash.error, #errorExplanation {
463 div.flash.error, #errorExplanation {
464 background: url(../images/exclamation.png) 8px 50% no-repeat;
464 background: url(../images/exclamation.png) 8px 50% no-repeat;
465 background-color: #ffe3e3;
465 background-color: #ffe3e3;
466 border-color: #dd0000;
466 border-color: #dd0000;
467 color: #880000;
467 color: #880000;
468 }
468 }
469
469
470 div.flash.notice {
470 div.flash.notice {
471 background: url(../images/true.png) 8px 5px no-repeat;
471 background: url(../images/true.png) 8px 5px no-repeat;
472 background-color: #dfffdf;
472 background-color: #dfffdf;
473 border-color: #9fcf9f;
473 border-color: #9fcf9f;
474 color: #005f00;
474 color: #005f00;
475 }
475 }
476
476
477 div.flash.warning {
477 div.flash.warning {
478 background: url(../images/warning.png) 8px 5px no-repeat;
478 background: url(../images/warning.png) 8px 5px no-repeat;
479 background-color: #FFEBC1;
479 background-color: #FFEBC1;
480 border-color: #FDBF3B;
480 border-color: #FDBF3B;
481 color: #A6750C;
481 color: #A6750C;
482 text-align: left;
482 text-align: left;
483 }
483 }
484
484
485 .nodata, .warning {
485 .nodata, .warning {
486 text-align: center;
486 text-align: center;
487 background-color: #FFEBC1;
487 background-color: #FFEBC1;
488 border-color: #FDBF3B;
488 border-color: #FDBF3B;
489 color: #A6750C;
489 color: #A6750C;
490 }
490 }
491
491
492 #errorExplanation ul { font-size: 0.9em;}
492 #errorExplanation ul { font-size: 0.9em;}
493 #errorExplanation h2, #errorExplanation p { display: none; }
493 #errorExplanation h2, #errorExplanation p { display: none; }
494
494
495 /***** Ajax indicator ******/
495 /***** Ajax indicator ******/
496 #ajax-indicator {
496 #ajax-indicator {
497 position: absolute; /* fixed not supported by IE */
497 position: absolute; /* fixed not supported by IE */
498 background-color:#eee;
498 background-color:#eee;
499 border: 1px solid #bbb;
499 border: 1px solid #bbb;
500 top:35%;
500 top:35%;
501 left:40%;
501 left:40%;
502 width:20%;
502 width:20%;
503 font-weight:bold;
503 font-weight:bold;
504 text-align:center;
504 text-align:center;
505 padding:0.6em;
505 padding:0.6em;
506 z-index:100;
506 z-index:100;
507 filter:alpha(opacity=50);
507 filter:alpha(opacity=50);
508 opacity: 0.5;
508 opacity: 0.5;
509 }
509 }
510
510
511 html>body #ajax-indicator { position: fixed; }
511 html>body #ajax-indicator { position: fixed; }
512
512
513 #ajax-indicator span {
513 #ajax-indicator span {
514 background-position: 0% 40%;
514 background-position: 0% 40%;
515 background-repeat: no-repeat;
515 background-repeat: no-repeat;
516 background-image: url(../images/loading.gif);
516 background-image: url(../images/loading.gif);
517 padding-left: 26px;
517 padding-left: 26px;
518 vertical-align: bottom;
518 vertical-align: bottom;
519 }
519 }
520
520
521 /***** Calendar *****/
521 /***** Calendar *****/
522 table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;}
522 table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;}
523 table.cal thead th {width: 14%; background-color:#EEEEEE; padding: 4px; }
523 table.cal thead th {width: 14%; background-color:#EEEEEE; padding: 4px; }
524 table.cal thead th.week-number {width: auto;}
524 table.cal thead th.week-number {width: auto;}
525 table.cal tbody tr {height: 100px;}
525 table.cal tbody tr {height: 100px;}
526 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
526 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
527 table.cal td.week-number { background-color:#EEEEEE; padding: 4px; border:none; font-size: 1em;}
527 table.cal td.week-number { background-color:#EEEEEE; padding: 4px; border:none; font-size: 1em;}
528 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
528 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
529 table.cal td.odd p.day-num {color: #bbb;}
529 table.cal td.odd p.day-num {color: #bbb;}
530 table.cal td.today {background:#ffffdd;}
530 table.cal td.today {background:#ffffdd;}
531 table.cal td.today p.day-num {font-weight: bold;}
531 table.cal td.today p.day-num {font-weight: bold;}
532 table.cal .starting a, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;}
532 table.cal .starting a, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;}
533 table.cal .ending a, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;}
533 table.cal .ending a, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;}
534 table.cal .starting.ending a, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;}
534 table.cal .starting.ending a, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;}
535 p.cal.legend span {display:block;}
535 p.cal.legend span {display:block;}
536
536
537 /***** Tooltips ******/
537 /***** Tooltips ******/
538 .tooltip{position:relative;z-index:24;}
538 .tooltip{position:relative;z-index:24;}
539 .tooltip:hover{z-index:25;color:#000;}
539 .tooltip:hover{z-index:25;color:#000;}
540 .tooltip span.tip{display: none; text-align:left;}
540 .tooltip span.tip{display: none; text-align:left;}
541
541
542 div.tooltip:hover span.tip{
542 div.tooltip:hover span.tip{
543 display:block;
543 display:block;
544 position:absolute;
544 position:absolute;
545 top:12px; left:24px; width:270px;
545 top:12px; left:24px; width:270px;
546 border:1px solid #555;
546 border:1px solid #555;
547 background-color:#fff;
547 background-color:#fff;
548 padding: 4px;
548 padding: 4px;
549 font-size: 0.8em;
549 font-size: 0.8em;
550 color:#505050;
550 color:#505050;
551 }
551 }
552
552
553 /***** Progress bar *****/
553 /***** Progress bar *****/
554 table.progress {
554 table.progress {
555 border: 1px solid #D7D7D7;
555 border: 1px solid #D7D7D7;
556 border-collapse: collapse;
556 border-collapse: collapse;
557 border-spacing: 0pt;
557 border-spacing: 0pt;
558 empty-cells: show;
558 empty-cells: show;
559 text-align: center;
559 text-align: center;
560 float:left;
560 float:left;
561 margin: 1px 6px 1px 0px;
561 margin: 1px 6px 1px 0px;
562 }
562 }
563
563
564 table.progress td { height: 0.9em; }
564 table.progress td { height: 0.9em; }
565 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
565 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
566 table.progress td.done { background: #DEF0DE none repeat scroll 0%; }
566 table.progress td.done { background: #DEF0DE none repeat scroll 0%; }
567 table.progress td.open { background: #FFF none repeat scroll 0%; }
567 table.progress td.open { background: #FFF none repeat scroll 0%; }
568 p.pourcent {font-size: 80%;}
568 p.pourcent {font-size: 80%;}
569 p.progress-info {clear: left; font-style: italic; font-size: 80%;}
569 p.progress-info {clear: left; font-style: italic; font-size: 80%;}
570
570
571 /***** Tabs *****/
571 /***** Tabs *****/
572 #content .tabs {height: 2.6em; margin-bottom:1.2em; position:relative; overflow:hidden;}
572 #content .tabs {height: 2.6em; margin-bottom:1.2em; position:relative; overflow:hidden;}
573 #content .tabs ul {margin:0; position:absolute; bottom:0; padding-left:1em; width: 2000px; border-bottom: 1px solid #bbbbbb;}
573 #content .tabs ul {margin:0; position:absolute; bottom:0; padding-left:1em; width: 2000px; border-bottom: 1px solid #bbbbbb;}
574 #content .tabs ul li {
574 #content .tabs ul li {
575 float:left;
575 float:left;
576 list-style-type:none;
576 list-style-type:none;
577 white-space:nowrap;
577 white-space:nowrap;
578 margin-right:8px;
578 margin-right:8px;
579 background:#fff;
579 background:#fff;
580 position:relative;
580 position:relative;
581 margin-bottom:-1px;
581 margin-bottom:-1px;
582 }
582 }
583 #content .tabs ul li a{
583 #content .tabs ul li a{
584 display:block;
584 display:block;
585 font-size: 0.9em;
585 font-size: 0.9em;
586 text-decoration:none;
586 text-decoration:none;
587 line-height:1.3em;
587 line-height:1.3em;
588 padding:4px 6px 4px 6px;
588 padding:4px 6px 4px 6px;
589 border: 1px solid #ccc;
589 border: 1px solid #ccc;
590 border-bottom: 1px solid #bbbbbb;
590 border-bottom: 1px solid #bbbbbb;
591 background-color: #eeeeee;
591 background-color: #eeeeee;
592 color:#777;
592 color:#777;
593 font-weight:bold;
593 font-weight:bold;
594 }
594 }
595
595
596 #content .tabs ul li a:hover {
596 #content .tabs ul li a:hover {
597 background-color: #ffffdd;
597 background-color: #ffffdd;
598 text-decoration:none;
598 text-decoration:none;
599 }
599 }
600
600
601 #content .tabs ul li a.selected {
601 #content .tabs ul li a.selected {
602 background-color: #fff;
602 background-color: #fff;
603 border: 1px solid #bbbbbb;
603 border: 1px solid #bbbbbb;
604 border-bottom: 1px solid #fff;
604 border-bottom: 1px solid #fff;
605 }
605 }
606
606
607 #content .tabs ul li a.selected:hover {
607 #content .tabs ul li a.selected:hover {
608 background-color: #fff;
608 background-color: #fff;
609 }
609 }
610
610
611 div.tabs-buttons { position:absolute; right: 0; width: 48px; height: 24px; background: white; bottom: 0; border-bottom: 1px solid #bbbbbb; }
611 div.tabs-buttons { position:absolute; right: 0; width: 48px; height: 24px; background: white; bottom: 0; border-bottom: 1px solid #bbbbbb; }
612
612
613 button.tab-left, button.tab-right {
613 button.tab-left, button.tab-right {
614 font-size: 0.9em;
614 font-size: 0.9em;
615 cursor: pointer;
615 cursor: pointer;
616 height:24px;
616 height:24px;
617 border: 1px solid #ccc;
617 border: 1px solid #ccc;
618 border-bottom: 1px solid #bbbbbb;
618 border-bottom: 1px solid #bbbbbb;
619 position:absolute;
619 position:absolute;
620 padding:4px;
620 padding:4px;
621 width: 20px;
621 width: 20px;
622 bottom: -1px;
622 bottom: -1px;
623 }
623 }
624
624
625 button.tab-left {
625 button.tab-left {
626 right: 20px;
626 right: 20px;
627 background: #eeeeee url(../images/bullet_arrow_left.png) no-repeat 50% 50%;
627 background: #eeeeee url(../images/bullet_arrow_left.png) no-repeat 50% 50%;
628 }
628 }
629
629
630 button.tab-right {
630 button.tab-right {
631 right: 0;
631 right: 0;
632 background: #eeeeee url(../images/bullet_arrow_right.png) no-repeat 50% 50%;
632 background: #eeeeee url(../images/bullet_arrow_right.png) no-repeat 50% 50%;
633 }
633 }
634
634
635 /***** Auto-complete *****/
635 /***** Auto-complete *****/
636 div.autocomplete {
636 div.autocomplete {
637 position:absolute;
637 position:absolute;
638 width:400px;
638 width:400px;
639 margin:0;
639 margin:0;
640 padding:0;
640 padding:0;
641 }
641 }
642 div.autocomplete ul {
642 div.autocomplete ul {
643 list-style-type:none;
643 list-style-type:none;
644 margin:0;
644 margin:0;
645 padding:0;
645 padding:0;
646 }
646 }
647 div.autocomplete ul li {
647 div.autocomplete ul li {
648 list-style-type:none;
648 list-style-type:none;
649 display:block;
649 display:block;
650 margin:-1px 0 0 0;
650 margin:-1px 0 0 0;
651 padding:2px;
651 padding:2px;
652 cursor:pointer;
652 cursor:pointer;
653 font-size: 90%;
653 font-size: 90%;
654 border: 1px solid #ccc;
654 border: 1px solid #ccc;
655 border-left: 1px solid #ccc;
655 border-left: 1px solid #ccc;
656 border-right: 1px solid #ccc;
656 border-right: 1px solid #ccc;
657 background-color:white;
657 background-color:white;
658 }
658 }
659 div.autocomplete ul li.selected { background-color: #ffb;}
659 div.autocomplete ul li.selected { background-color: #ffb;}
660 div.autocomplete ul li span.informal {
660 div.autocomplete ul li span.informal {
661 font-size: 80%;
661 font-size: 80%;
662 color: #aaa;
662 color: #aaa;
663 }
663 }
664
664
665 #parent_issue_candidates ul li {width: 500px;}
665 #parent_issue_candidates ul li {width: 500px;}
666 #related_issue_candidates ul li {width: 500px;}
666 #related_issue_candidates ul li {width: 500px;}
667
667
668 /***** Diff *****/
668 /***** Diff *****/
669 .diff_out { background: #fcc; }
669 .diff_out { background: #fcc; }
670 .diff_in { background: #cfc; }
670 .diff_in { background: #cfc; }
671
671
672 /***** Wiki *****/
672 /***** Wiki *****/
673 div.wiki table {
673 div.wiki table {
674 border: 1px solid #505050;
674 border: 1px solid #505050;
675 border-collapse: collapse;
675 border-collapse: collapse;
676 margin-bottom: 1em;
676 margin-bottom: 1em;
677 }
677 }
678
678
679 div.wiki table, div.wiki td, div.wiki th {
679 div.wiki table, div.wiki td, div.wiki th {
680 border: 1px solid #bbb;
680 border: 1px solid #bbb;
681 padding: 4px;
681 padding: 4px;
682 }
682 }
683
683
684 div.wiki .external {
684 div.wiki .external {
685 background-position: 0% 60%;
685 background-position: 0% 60%;
686 background-repeat: no-repeat;
686 background-repeat: no-repeat;
687 padding-left: 12px;
687 padding-left: 12px;
688 background-image: url(../images/external.png);
688 background-image: url(../images/external.png);
689 }
689 }
690
690
691 div.wiki a.new {
691 div.wiki a.new {
692 color: #b73535;
692 color: #b73535;
693 }
693 }
694
694
695 div.wiki pre {
695 div.wiki pre {
696 margin: 1em 1em 1em 1.6em;
696 margin: 1em 1em 1em 1.6em;
697 padding: 2px 2px 2px 0;
697 padding: 2px 2px 2px 0;
698 background-color: #fafafa;
698 background-color: #fafafa;
699 border: 1px solid #dadada;
699 border: 1px solid #dadada;
700 width:auto;
700 width:auto;
701 overflow-x: auto;
701 overflow-x: auto;
702 overflow-y: hidden;
702 overflow-y: hidden;
703 }
703 }
704
704
705 div.wiki ul.toc {
705 div.wiki ul.toc {
706 background-color: #ffffdd;
706 background-color: #ffffdd;
707 border: 1px solid #e4e4e4;
707 border: 1px solid #e4e4e4;
708 padding: 4px;
708 padding: 4px;
709 line-height: 1.2em;
709 line-height: 1.2em;
710 margin-bottom: 12px;
710 margin-bottom: 12px;
711 margin-right: 12px;
711 margin-right: 12px;
712 margin-left: 0;
712 margin-left: 0;
713 display: table
713 display: table
714 }
714 }
715 * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */
715 * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */
716
716
717 div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
717 div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
718 div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
718 div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
719 div.wiki ul.toc ul { margin: 0; padding: 0; }
719 div.wiki ul.toc ul { margin: 0; padding: 0; }
720 div.wiki ul.toc li { list-style-type:none; margin: 0;}
720 div.wiki ul.toc li { list-style-type:none; margin: 0;}
721 div.wiki ul.toc li li { margin-left: 1.5em; }
721 div.wiki ul.toc li li { margin-left: 1.5em; }
722 div.wiki ul.toc li li li { font-size: 0.8em; }
722 div.wiki ul.toc li li li { font-size: 0.8em; }
723
723
724 div.wiki ul.toc a {
724 div.wiki ul.toc a {
725 font-size: 0.9em;
725 font-size: 0.9em;
726 font-weight: normal;
726 font-weight: normal;
727 text-decoration: none;
727 text-decoration: none;
728 color: #606060;
728 color: #606060;
729 }
729 }
730 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
730 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
731
731
732 a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
732 a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
733 a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
733 a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
734 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
734 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
735
735
736 div.wiki img { vertical-align: middle; }
736 div.wiki img { vertical-align: middle; }
737
737
738 /***** My page layout *****/
738 /***** My page layout *****/
739 .block-receiver {
739 .block-receiver {
740 border:1px dashed #c0c0c0;
740 border:1px dashed #c0c0c0;
741 margin-bottom: 20px;
741 margin-bottom: 20px;
742 padding: 15px 0 15px 0;
742 padding: 15px 0 15px 0;
743 }
743 }
744
744
745 .mypage-box {
745 .mypage-box {
746 margin:0 0 20px 0;
746 margin:0 0 20px 0;
747 color:#505050;
747 color:#505050;
748 line-height:1.5em;
748 line-height:1.5em;
749 }
749 }
750
750
751 .handle {
751 .handle {
752 cursor: move;
752 cursor: move;
753 }
753 }
754
754
755 a.close-icon {
755 a.close-icon {
756 display:block;
756 display:block;
757 margin-top:3px;
757 margin-top:3px;
758 overflow:hidden;
758 overflow:hidden;
759 width:12px;
759 width:12px;
760 height:12px;
760 height:12px;
761 background-repeat: no-repeat;
761 background-repeat: no-repeat;
762 cursor:pointer;
762 cursor:pointer;
763 background-image:url('../images/close.png');
763 background-image:url('../images/close.png');
764 }
764 }
765
765
766 a.close-icon:hover {
766 a.close-icon:hover {
767 background-image:url('../images/close_hl.png');
767 background-image:url('../images/close_hl.png');
768 }
768 }
769
769
770 /***** Gantt chart *****/
770 /***** Gantt chart *****/
771 .gantt_hdr {
771 .gantt_hdr {
772 position:absolute;
772 position:absolute;
773 top:0;
773 top:0;
774 height:16px;
774 height:16px;
775 border-top: 1px solid #c0c0c0;
775 border-top: 1px solid #c0c0c0;
776 border-bottom: 1px solid #c0c0c0;
776 border-bottom: 1px solid #c0c0c0;
777 border-right: 1px solid #c0c0c0;
777 border-right: 1px solid #c0c0c0;
778 text-align: center;
778 text-align: center;
779 overflow: hidden;
779 overflow: hidden;
780 }
780 }
781
781
782 .task {
782 .task {
783 position: absolute;
783 position: absolute;
784 height:8px;
784 height:8px;
785 font-size:0.8em;
785 font-size:0.8em;
786 color:#888;
786 color:#888;
787 padding:0;
787 padding:0;
788 margin:0;
788 margin:0;
789 line-height:0.8em;
789 line-height:0.8em;
790 white-space:nowrap;
790 white-space:nowrap;
791 }
791 }
792
792
793 .task.label {width:100%;}
793 .task.label {width:100%;}
794
794
795 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
795 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
796 .task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; }
796 .task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; }
797 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
797 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
798
798
799 .task_todo.parent { background: #888; border: 1px solid #888; height: 6px;}
799 .task_todo.parent { background: #888; border: 1px solid #888; height: 6px;}
800 .task_late.parent, .task_done.parent { height: 3px;}
800 .task_late.parent, .task_done.parent { height: 3px;}
801 .task_todo.parent .left { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -5px; left: 0px; top: -1px;}
801 .task_todo.parent .left { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -5px; left: 0px; top: -1px;}
802 .task_todo.parent .right { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-right: -5px; right: 0px; top: -1px;}
802 .task_todo.parent .right { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-right: -5px; right: 0px; top: -1px;}
803
803
804 .milestone { background-image:url(../images/version_marker.png); background-repeat: no-repeat; border: 0; }
804 .version.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
805 .milestone_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
805 .version.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
806 .milestone_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
806 .version.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
807 .milestone_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
807 .version.marker { background-image:url(../images/version_marker.png); background-repeat: no-repeat; border: 0; }
808
808 .project-line { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; }
809 .project-line { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; }
809 .project_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
810 .project_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
810 .project_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
811 .project_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
811 .project_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
812 .project_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
812
813
813 .version-behind-schedule a, .issue-behind-schedule a {color: #f66914;}
814 .version-behind-schedule a, .issue-behind-schedule a {color: #f66914;}
814 .version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;}
815 .version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;}
815
816
816 /***** Icons *****/
817 /***** Icons *****/
817 .icon {
818 .icon {
818 background-position: 0% 50%;
819 background-position: 0% 50%;
819 background-repeat: no-repeat;
820 background-repeat: no-repeat;
820 padding-left: 20px;
821 padding-left: 20px;
821 padding-top: 2px;
822 padding-top: 2px;
822 padding-bottom: 3px;
823 padding-bottom: 3px;
823 }
824 }
824
825
825 .icon-add { background-image: url(../images/add.png); }
826 .icon-add { background-image: url(../images/add.png); }
826 .icon-edit { background-image: url(../images/edit.png); }
827 .icon-edit { background-image: url(../images/edit.png); }
827 .icon-copy { background-image: url(../images/copy.png); }
828 .icon-copy { background-image: url(../images/copy.png); }
828 .icon-duplicate { background-image: url(../images/duplicate.png); }
829 .icon-duplicate { background-image: url(../images/duplicate.png); }
829 .icon-del { background-image: url(../images/delete.png); }
830 .icon-del { background-image: url(../images/delete.png); }
830 .icon-move { background-image: url(../images/move.png); }
831 .icon-move { background-image: url(../images/move.png); }
831 .icon-save { background-image: url(../images/save.png); }
832 .icon-save { background-image: url(../images/save.png); }
832 .icon-cancel { background-image: url(../images/cancel.png); }
833 .icon-cancel { background-image: url(../images/cancel.png); }
833 .icon-multiple { background-image: url(../images/table_multiple.png); }
834 .icon-multiple { background-image: url(../images/table_multiple.png); }
834 .icon-folder { background-image: url(../images/folder.png); }
835 .icon-folder { background-image: url(../images/folder.png); }
835 .open .icon-folder { background-image: url(../images/folder_open.png); }
836 .open .icon-folder { background-image: url(../images/folder_open.png); }
836 .icon-package { background-image: url(../images/package.png); }
837 .icon-package { background-image: url(../images/package.png); }
837 .icon-home { background-image: url(../images/home.png); }
838 .icon-home { background-image: url(../images/home.png); }
838 .icon-user { background-image: url(../images/user.png); }
839 .icon-user { background-image: url(../images/user.png); }
839 .icon-projects { background-image: url(../images/projects.png); }
840 .icon-projects { background-image: url(../images/projects.png); }
840 .icon-help { background-image: url(../images/help.png); }
841 .icon-help { background-image: url(../images/help.png); }
841 .icon-attachment { background-image: url(../images/attachment.png); }
842 .icon-attachment { background-image: url(../images/attachment.png); }
842 .icon-history { background-image: url(../images/history.png); }
843 .icon-history { background-image: url(../images/history.png); }
843 .icon-time { background-image: url(../images/time.png); }
844 .icon-time { background-image: url(../images/time.png); }
844 .icon-time-add { background-image: url(../images/time_add.png); }
845 .icon-time-add { background-image: url(../images/time_add.png); }
845 .icon-stats { background-image: url(../images/stats.png); }
846 .icon-stats { background-image: url(../images/stats.png); }
846 .icon-warning { background-image: url(../images/warning.png); }
847 .icon-warning { background-image: url(../images/warning.png); }
847 .icon-fav { background-image: url(../images/fav.png); }
848 .icon-fav { background-image: url(../images/fav.png); }
848 .icon-fav-off { background-image: url(../images/fav_off.png); }
849 .icon-fav-off { background-image: url(../images/fav_off.png); }
849 .icon-reload { background-image: url(../images/reload.png); }
850 .icon-reload { background-image: url(../images/reload.png); }
850 .icon-lock { background-image: url(../images/locked.png); }
851 .icon-lock { background-image: url(../images/locked.png); }
851 .icon-unlock { background-image: url(../images/unlock.png); }
852 .icon-unlock { background-image: url(../images/unlock.png); }
852 .icon-checked { background-image: url(../images/true.png); }
853 .icon-checked { background-image: url(../images/true.png); }
853 .icon-details { background-image: url(../images/zoom_in.png); }
854 .icon-details { background-image: url(../images/zoom_in.png); }
854 .icon-report { background-image: url(../images/report.png); }
855 .icon-report { background-image: url(../images/report.png); }
855 .icon-comment { background-image: url(../images/comment.png); }
856 .icon-comment { background-image: url(../images/comment.png); }
856 .icon-summary { background-image: url(../images/lightning.png); }
857 .icon-summary { background-image: url(../images/lightning.png); }
857 .icon-server-authentication { background-image: url(../images/server_key.png); }
858 .icon-server-authentication { background-image: url(../images/server_key.png); }
858 .icon-issue { background-image: url(../images/ticket.png); }
859 .icon-issue { background-image: url(../images/ticket.png); }
859 .icon-zoom-in { background-image: url(../images/zoom_in.png); }
860 .icon-zoom-in { background-image: url(../images/zoom_in.png); }
860 .icon-zoom-out { background-image: url(../images/zoom_out.png); }
861 .icon-zoom-out { background-image: url(../images/zoom_out.png); }
861
862
862 .icon-file { background-image: url(../images/files/default.png); }
863 .icon-file { background-image: url(../images/files/default.png); }
863 .icon-file.text-plain { background-image: url(../images/files/text.png); }
864 .icon-file.text-plain { background-image: url(../images/files/text.png); }
864 .icon-file.text-x-c { background-image: url(../images/files/c.png); }
865 .icon-file.text-x-c { background-image: url(../images/files/c.png); }
865 .icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); }
866 .icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); }
866 .icon-file.text-x-php { background-image: url(../images/files/php.png); }
867 .icon-file.text-x-php { background-image: url(../images/files/php.png); }
867 .icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); }
868 .icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); }
868 .icon-file.text-xml { background-image: url(../images/files/xml.png); }
869 .icon-file.text-xml { background-image: url(../images/files/xml.png); }
869 .icon-file.image-gif { background-image: url(../images/files/image.png); }
870 .icon-file.image-gif { background-image: url(../images/files/image.png); }
870 .icon-file.image-jpeg { background-image: url(../images/files/image.png); }
871 .icon-file.image-jpeg { background-image: url(../images/files/image.png); }
871 .icon-file.image-png { background-image: url(../images/files/image.png); }
872 .icon-file.image-png { background-image: url(../images/files/image.png); }
872 .icon-file.image-tiff { background-image: url(../images/files/image.png); }
873 .icon-file.image-tiff { background-image: url(../images/files/image.png); }
873 .icon-file.application-pdf { background-image: url(../images/files/pdf.png); }
874 .icon-file.application-pdf { background-image: url(../images/files/pdf.png); }
874 .icon-file.application-zip { background-image: url(../images/files/zip.png); }
875 .icon-file.application-zip { background-image: url(../images/files/zip.png); }
875 .icon-file.application-x-gzip { background-image: url(../images/files/zip.png); }
876 .icon-file.application-x-gzip { background-image: url(../images/files/zip.png); }
876
877
877 img.gravatar {
878 img.gravatar {
878 padding: 2px;
879 padding: 2px;
879 border: solid 1px #d5d5d5;
880 border: solid 1px #d5d5d5;
880 background: #fff;
881 background: #fff;
881 }
882 }
882
883
883 div.issue img.gravatar {
884 div.issue img.gravatar {
884 float: right;
885 float: right;
885 margin: 0 0 0 1em;
886 margin: 0 0 0 1em;
886 padding: 5px;
887 padding: 5px;
887 }
888 }
888
889
889 div.issue table img.gravatar {
890 div.issue table img.gravatar {
890 height: 14px;
891 height: 14px;
891 width: 14px;
892 width: 14px;
892 padding: 2px;
893 padding: 2px;
893 float: left;
894 float: left;
894 margin: 0 0.5em 0 0;
895 margin: 0 0.5em 0 0;
895 }
896 }
896
897
897 h2 img.gravatar {
898 h2 img.gravatar {
898 padding: 3px;
899 padding: 3px;
899 margin: -2px 4px -4px 0;
900 margin: -2px 4px -4px 0;
900 vertical-align: top;
901 vertical-align: top;
901 }
902 }
902
903
903 h4 img.gravatar {
904 h4 img.gravatar {
904 padding: 3px;
905 padding: 3px;
905 margin: -6px 0 -4px 0;
906 margin: -6px 0 -4px 0;
906 vertical-align: top;
907 vertical-align: top;
907 }
908 }
908
909
909 td.username img.gravatar {
910 td.username img.gravatar {
910 float: left;
911 float: left;
911 margin: 0 1em 0 0;
912 margin: 0 1em 0 0;
912 }
913 }
913
914
914 #activity dt img.gravatar {
915 #activity dt img.gravatar {
915 float: left;
916 float: left;
916 margin: 0 1em 1em 0;
917 margin: 0 1em 1em 0;
917 }
918 }
918
919
919 /* Used on 12px Gravatar img tags without the icon background */
920 /* Used on 12px Gravatar img tags without the icon background */
920 .icon-gravatar {
921 .icon-gravatar {
921 float: left;
922 float: left;
922 margin-right: 4px;
923 margin-right: 4px;
923 }
924 }
924
925
925 #activity dt,
926 #activity dt,
926 .journal {
927 .journal {
927 clear: left;
928 clear: left;
928 }
929 }
929
930
930 .journal-link {
931 .journal-link {
931 float: right;
932 float: right;
932 }
933 }
933
934
934 h2 img { vertical-align:middle; }
935 h2 img { vertical-align:middle; }
935
936
936 .hascontextmenu { cursor: context-menu; }
937 .hascontextmenu { cursor: context-menu; }
937
938
938 /***** Media print specific styles *****/
939 /***** Media print specific styles *****/
939 @media print {
940 @media print {
940 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
941 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
941 #main { background: #fff; }
942 #main { background: #fff; }
942 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
943 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
943 #wiki_add_attachment { display:none; }
944 #wiki_add_attachment { display:none; }
944 .hide-when-print { display: none; }
945 .hide-when-print { display: none; }
945 }
946 }
@@ -1,727 +1,727
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 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 require File.expand_path('../../../../../test_helper', __FILE__)
18 require File.expand_path('../../../../../test_helper', __FILE__)
19
19
20 class Redmine::Helpers::GanttTest < ActiveSupport::TestCase
20 class Redmine::Helpers::GanttTest < ActiveSupport::TestCase
21 # Utility methods and classes so assert_select can be used.
21 # Utility methods and classes so assert_select can be used.
22 class GanttViewTest < ActionView::Base
22 class GanttViewTest < ActionView::Base
23 include ActionView::Helpers::UrlHelper
23 include ActionView::Helpers::UrlHelper
24 include ActionView::Helpers::TextHelper
24 include ActionView::Helpers::TextHelper
25 include ActionController::UrlWriter
25 include ActionController::UrlWriter
26 include ApplicationHelper
26 include ApplicationHelper
27 include ProjectsHelper
27 include ProjectsHelper
28 include IssuesHelper
28 include IssuesHelper
29
29
30 def self.default_url_options
30 def self.default_url_options
31 {:only_path => true }
31 {:only_path => true }
32 end
32 end
33
33
34 end
34 end
35
35
36 include ActionController::Assertions::SelectorAssertions
36 include ActionController::Assertions::SelectorAssertions
37
37
38 def setup
38 def setup
39 @response = ActionController::TestResponse.new
39 @response = ActionController::TestResponse.new
40 # Fixtures
40 # Fixtures
41 ProjectCustomField.delete_all
41 ProjectCustomField.delete_all
42 Project.destroy_all
42 Project.destroy_all
43
43
44 User.current = User.find(1)
44 User.current = User.find(1)
45 end
45 end
46
46
47 def build_view
47 def build_view
48 @view = GanttViewTest.new
48 @view = GanttViewTest.new
49 end
49 end
50
50
51 def html_document
51 def html_document
52 HTML::Document.new(@response.body)
52 HTML::Document.new(@response.body)
53 end
53 end
54
54
55 # Creates a Gantt chart for a 4 week span
55 # Creates a Gantt chart for a 4 week span
56 def create_gantt(project=Project.generate!, options={})
56 def create_gantt(project=Project.generate!, options={})
57 @project = project
57 @project = project
58 @gantt = Redmine::Helpers::Gantt.new(options)
58 @gantt = Redmine::Helpers::Gantt.new(options)
59 @gantt.project = @project
59 @gantt.project = @project
60 @gantt.query = Query.generate_default!(:project => @project)
60 @gantt.query = Query.generate_default!(:project => @project)
61 @gantt.view = build_view
61 @gantt.view = build_view
62 @gantt.instance_variable_set('@date_from', options[:date_from] || 2.weeks.ago.to_date)
62 @gantt.instance_variable_set('@date_from', options[:date_from] || 2.weeks.ago.to_date)
63 @gantt.instance_variable_set('@date_to', options[:date_to] || 2.weeks.from_now.to_date)
63 @gantt.instance_variable_set('@date_to', options[:date_to] || 2.weeks.from_now.to_date)
64 end
64 end
65
65
66 context "#number_of_rows" do
66 context "#number_of_rows" do
67
67
68 context "with one project" do
68 context "with one project" do
69 should "return the number of rows just for that project"
69 should "return the number of rows just for that project"
70 end
70 end
71
71
72 context "with no project" do
72 context "with no project" do
73 should "return the total number of rows for all the projects, resursively"
73 should "return the total number of rows for all the projects, resursively"
74 end
74 end
75
75
76 should "not exceed max_rows option" do
76 should "not exceed max_rows option" do
77 p = Project.generate!
77 p = Project.generate!
78 5.times do
78 5.times do
79 Issue.generate_for_project!(p)
79 Issue.generate_for_project!(p)
80 end
80 end
81
81
82 create_gantt(p)
82 create_gantt(p)
83 @gantt.render
83 @gantt.render
84 assert_equal 6, @gantt.number_of_rows
84 assert_equal 6, @gantt.number_of_rows
85 assert !@gantt.truncated
85 assert !@gantt.truncated
86
86
87 create_gantt(p, :max_rows => 3)
87 create_gantt(p, :max_rows => 3)
88 @gantt.render
88 @gantt.render
89 assert_equal 3, @gantt.number_of_rows
89 assert_equal 3, @gantt.number_of_rows
90 assert @gantt.truncated
90 assert @gantt.truncated
91 end
91 end
92 end
92 end
93
93
94 context "#number_of_rows_on_project" do
94 context "#number_of_rows_on_project" do
95 setup do
95 setup do
96 create_gantt
96 create_gantt
97 end
97 end
98
98
99 should "clear the @query.project so cross-project issues and versions can be counted" do
99 should "clear the @query.project so cross-project issues and versions can be counted" do
100 assert @gantt.query.project
100 assert @gantt.query.project
101 @gantt.number_of_rows_on_project(@project)
101 @gantt.number_of_rows_on_project(@project)
102 assert_nil @gantt.query.project
102 assert_nil @gantt.query.project
103 end
103 end
104
104
105 should "count 1 for the project itself" do
105 should "count 1 for the project itself" do
106 assert_equal 1, @gantt.number_of_rows_on_project(@project)
106 assert_equal 1, @gantt.number_of_rows_on_project(@project)
107 end
107 end
108
108
109 should "count the number of issues without a version" do
109 should "count the number of issues without a version" do
110 @project.issues << Issue.generate_for_project!(@project, :fixed_version => nil)
110 @project.issues << Issue.generate_for_project!(@project, :fixed_version => nil)
111 assert_equal 2, @gantt.number_of_rows_on_project(@project)
111 assert_equal 2, @gantt.number_of_rows_on_project(@project)
112 end
112 end
113
113
114 should "count the number of versions" do
114 should "count the number of versions" do
115 @project.versions << Version.generate!
115 @project.versions << Version.generate!
116 @project.versions << Version.generate!
116 @project.versions << Version.generate!
117 assert_equal 3, @gantt.number_of_rows_on_project(@project)
117 assert_equal 3, @gantt.number_of_rows_on_project(@project)
118 end
118 end
119
119
120 should "count the number of issues on versions, including cross-project" do
120 should "count the number of issues on versions, including cross-project" do
121 version = Version.generate!
121 version = Version.generate!
122 @project.versions << version
122 @project.versions << version
123 @project.issues << Issue.generate_for_project!(@project, :fixed_version => version)
123 @project.issues << Issue.generate_for_project!(@project, :fixed_version => version)
124
124
125 assert_equal 3, @gantt.number_of_rows_on_project(@project)
125 assert_equal 3, @gantt.number_of_rows_on_project(@project)
126 end
126 end
127
127
128 should "recursive and count the number of rows on each subproject" do
128 should "recursive and count the number of rows on each subproject" do
129 @project.versions << Version.generate! # +1
129 @project.versions << Version.generate! # +1
130
130
131 @subproject = Project.generate!(:enabled_module_names => ['issue_tracking']) # +1
131 @subproject = Project.generate!(:enabled_module_names => ['issue_tracking']) # +1
132 @subproject.set_parent!(@project)
132 @subproject.set_parent!(@project)
133 @subproject.issues << Issue.generate_for_project!(@subproject) # +1
133 @subproject.issues << Issue.generate_for_project!(@subproject) # +1
134 @subproject.issues << Issue.generate_for_project!(@subproject) # +1
134 @subproject.issues << Issue.generate_for_project!(@subproject) # +1
135
135
136 @subsubproject = Project.generate!(:enabled_module_names => ['issue_tracking']) # +1
136 @subsubproject = Project.generate!(:enabled_module_names => ['issue_tracking']) # +1
137 @subsubproject.set_parent!(@subproject)
137 @subsubproject.set_parent!(@subproject)
138 @subsubproject.issues << Issue.generate_for_project!(@subsubproject) # +1
138 @subsubproject.issues << Issue.generate_for_project!(@subsubproject) # +1
139
139
140 assert_equal 7, @gantt.number_of_rows_on_project(@project) # +1 for self
140 assert_equal 7, @gantt.number_of_rows_on_project(@project) # +1 for self
141 end
141 end
142 end
142 end
143
143
144 # TODO: more of an integration test
144 # TODO: more of an integration test
145 context "#subjects" do
145 context "#subjects" do
146 setup do
146 setup do
147 create_gantt
147 create_gantt
148 @project.enabled_module_names = [:issue_tracking]
148 @project.enabled_module_names = [:issue_tracking]
149 @tracker = Tracker.generate!
149 @tracker = Tracker.generate!
150 @project.trackers << @tracker
150 @project.trackers << @tracker
151 @version = Version.generate!(:effective_date => 1.week.from_now.to_date, :sharing => 'none')
151 @version = Version.generate!(:effective_date => 1.week.from_now.to_date, :sharing => 'none')
152 @project.versions << @version
152 @project.versions << @version
153
153
154 @issue = Issue.generate!(:fixed_version => @version,
154 @issue = Issue.generate!(:fixed_version => @version,
155 :subject => "gantt#line_for_project",
155 :subject => "gantt#line_for_project",
156 :tracker => @tracker,
156 :tracker => @tracker,
157 :project => @project,
157 :project => @project,
158 :done_ratio => 30,
158 :done_ratio => 30,
159 :start_date => Date.yesterday,
159 :start_date => Date.yesterday,
160 :due_date => 1.week.from_now.to_date)
160 :due_date => 1.week.from_now.to_date)
161 @project.issues << @issue
161 @project.issues << @issue
162
162
163 @response.body = @gantt.subjects
163 @response.body = @gantt.subjects
164 end
164 end
165
165
166 context "project" do
166 context "project" do
167 should "be rendered" do
167 should "be rendered" do
168 assert_select "div.project-name a", /#{@project.name}/
168 assert_select "div.project-name a", /#{@project.name}/
169 end
169 end
170
170
171 should "have an indent of 4" do
171 should "have an indent of 4" do
172 assert_select "div.project-name[style*=left:4px]"
172 assert_select "div.project-name[style*=left:4px]"
173 end
173 end
174 end
174 end
175
175
176 context "version" do
176 context "version" do
177 should "be rendered" do
177 should "be rendered" do
178 assert_select "div.version-name a", /#{@version.name}/
178 assert_select "div.version-name a", /#{@version.name}/
179 end
179 end
180
180
181 should "be indented 24 (one level)" do
181 should "be indented 24 (one level)" do
182 assert_select "div.version-name[style*=left:24px]"
182 assert_select "div.version-name[style*=left:24px]"
183 end
183 end
184 end
184 end
185
185
186 context "issue" do
186 context "issue" do
187 should "be rendered" do
187 should "be rendered" do
188 assert_select "div.issue-subject", /#{@issue.subject}/
188 assert_select "div.issue-subject", /#{@issue.subject}/
189 end
189 end
190
190
191 should "be indented 44 (two levels)" do
191 should "be indented 44 (two levels)" do
192 assert_select "div.issue-subject[style*=left:44px]"
192 assert_select "div.issue-subject[style*=left:44px]"
193 end
193 end
194 end
194 end
195 end
195 end
196
196
197 context "#lines" do
197 context "#lines" do
198 setup do
198 setup do
199 create_gantt
199 create_gantt
200 @project.enabled_module_names = [:issue_tracking]
200 @project.enabled_module_names = [:issue_tracking]
201 @tracker = Tracker.generate!
201 @tracker = Tracker.generate!
202 @project.trackers << @tracker
202 @project.trackers << @tracker
203 @version = Version.generate!(:effective_date => 1.week.from_now.to_date)
203 @version = Version.generate!(:effective_date => 1.week.from_now.to_date)
204 @project.versions << @version
204 @project.versions << @version
205 @issue = Issue.generate!(:fixed_version => @version,
205 @issue = Issue.generate!(:fixed_version => @version,
206 :subject => "gantt#line_for_project",
206 :subject => "gantt#line_for_project",
207 :tracker => @tracker,
207 :tracker => @tracker,
208 :project => @project,
208 :project => @project,
209 :done_ratio => 30,
209 :done_ratio => 30,
210 :start_date => Date.yesterday,
210 :start_date => Date.yesterday,
211 :due_date => 1.week.from_now.to_date)
211 :due_date => 1.week.from_now.to_date)
212 @project.issues << @issue
212 @project.issues << @issue
213
213
214 @response.body = @gantt.lines
214 @response.body = @gantt.lines
215 end
215 end
216
216
217 context "project" do
217 context "project" do
218 should "be rendered" do
218 should "be rendered" do
219 assert_select "div.project_todo"
219 assert_select "div.project_todo"
220 assert_select "div.project-line.starting"
220 assert_select "div.project-line.starting"
221 assert_select "div.project-line.ending"
221 assert_select "div.project-line.ending"
222 assert_select "div.label.project-name", /#{@project.name}/
222 assert_select "div.label.project-name", /#{@project.name}/
223 end
223 end
224 end
224 end
225
225
226 context "version" do
226 context "version" do
227 should "be rendered" do
227 should "be rendered" do
228 assert_select "div.milestone_todo"
228 assert_select "div.version.task_todo"
229 assert_select "div.milestone.starting"
229 assert_select "div.version.starting"
230 assert_select "div.milestone.ending"
230 assert_select "div.version.ending"
231 assert_select "div.label.version-name", /#{@version.name}/
231 assert_select "div.label.version", /#{@version.name}/
232 end
232 end
233 end
233 end
234
234
235 context "issue" do
235 context "issue" do
236 should "be rendered" do
236 should "be rendered" do
237 assert_select "div.task_todo"
237 assert_select "div.task_todo"
238 assert_select "div.task.label", /#{@issue.done_ratio}/
238 assert_select "div.task.label", /#{@issue.done_ratio}/
239 assert_select "div.tooltip", /#{@issue.subject}/
239 assert_select "div.tooltip", /#{@issue.subject}/
240 end
240 end
241 end
241 end
242 end
242 end
243
243
244 context "#render_project" do
244 context "#render_project" do
245 should "be tested"
245 should "be tested"
246 end
246 end
247
247
248 context "#render_issues" do
248 context "#render_issues" do
249 should "be tested"
249 should "be tested"
250 end
250 end
251
251
252 context "#render_version" do
252 context "#render_version" do
253 should "be tested"
253 should "be tested"
254 end
254 end
255
255
256 context "#subject_for_project" do
256 context "#subject_for_project" do
257 setup do
257 setup do
258 create_gantt
258 create_gantt
259 end
259 end
260
260
261 context ":html format" do
261 context ":html format" do
262 should "add an absolute positioned div" do
262 should "add an absolute positioned div" do
263 @response.body = @gantt.subject_for_project(@project, {:format => :html})
263 @response.body = @gantt.subject_for_project(@project, {:format => :html})
264 assert_select "div[style*=absolute]"
264 assert_select "div[style*=absolute]"
265 end
265 end
266
266
267 should "use the indent option to move the div to the right" do
267 should "use the indent option to move the div to the right" do
268 @response.body = @gantt.subject_for_project(@project, {:format => :html, :indent => 40})
268 @response.body = @gantt.subject_for_project(@project, {:format => :html, :indent => 40})
269 assert_select "div[style*=left:40]"
269 assert_select "div[style*=left:40]"
270 end
270 end
271
271
272 should "include the project name" do
272 should "include the project name" do
273 @response.body = @gantt.subject_for_project(@project, {:format => :html})
273 @response.body = @gantt.subject_for_project(@project, {:format => :html})
274 assert_select 'div', :text => /#{@project.name}/
274 assert_select 'div', :text => /#{@project.name}/
275 end
275 end
276
276
277 should "include a link to the project" do
277 should "include a link to the project" do
278 @response.body = @gantt.subject_for_project(@project, {:format => :html})
278 @response.body = @gantt.subject_for_project(@project, {:format => :html})
279 assert_select 'a[href=?]', "/projects/#{@project.identifier}", :text => /#{@project.name}/
279 assert_select 'a[href=?]', "/projects/#{@project.identifier}", :text => /#{@project.name}/
280 end
280 end
281
281
282 should "style overdue projects" do
282 should "style overdue projects" do
283 @project.enabled_module_names = [:issue_tracking]
283 @project.enabled_module_names = [:issue_tracking]
284 @project.versions << Version.generate!(:effective_date => Date.yesterday)
284 @project.versions << Version.generate!(:effective_date => Date.yesterday)
285
285
286 assert @project.overdue?, "Need an overdue project for this test"
286 assert @project.overdue?, "Need an overdue project for this test"
287 @response.body = @gantt.subject_for_project(@project, {:format => :html})
287 @response.body = @gantt.subject_for_project(@project, {:format => :html})
288
288
289 assert_select 'div span.project-overdue'
289 assert_select 'div span.project-overdue'
290 end
290 end
291
291
292
292
293 end
293 end
294
294
295 should "test the PNG format"
295 should "test the PNG format"
296 should "test the PDF format"
296 should "test the PDF format"
297 end
297 end
298
298
299 context "#line_for_project" do
299 context "#line_for_project" do
300 setup do
300 setup do
301 create_gantt
301 create_gantt
302 @project.enabled_module_names = [:issue_tracking]
302 @project.enabled_module_names = [:issue_tracking]
303 @tracker = Tracker.generate!
303 @tracker = Tracker.generate!
304 @project.trackers << @tracker
304 @project.trackers << @tracker
305 @version = Version.generate!(:effective_date => Date.yesterday)
305 @version = Version.generate!(:effective_date => Date.yesterday)
306 @project.versions << @version
306 @project.versions << @version
307
307
308 @project.issues << Issue.generate!(:fixed_version => @version,
308 @project.issues << Issue.generate!(:fixed_version => @version,
309 :subject => "gantt#line_for_project",
309 :subject => "gantt#line_for_project",
310 :tracker => @tracker,
310 :tracker => @tracker,
311 :project => @project,
311 :project => @project,
312 :done_ratio => 30,
312 :done_ratio => 30,
313 :start_date => Date.yesterday,
313 :start_date => Date.yesterday,
314 :due_date => 1.week.from_now.to_date)
314 :due_date => 1.week.from_now.to_date)
315 end
315 end
316
316
317 context ":html format" do
317 context ":html format" do
318 context "todo line" do
318 context "todo line" do
319 should "start from the starting point on the left" do
319 should "start from the starting point on the left" do
320 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
320 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
321 assert_select "div.project_todo[style*=left:52px]"
321 assert_select "div.project_todo[style*=left:52px]"
322 end
322 end
323
323
324 should "be the total width of the project" do
324 should "be the total width of the project" do
325 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
325 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
326 assert_select "div.project_todo[style*=width:31px]"
326 assert_select "div.project_todo[style*=width:31px]"
327 end
327 end
328
328
329 end
329 end
330
330
331 context "late line" do
331 context "late line" do
332 should "start from the starting point on the left" do
332 should "start from the starting point on the left" do
333 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
333 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
334 assert_select "div.project_late[style*=left:52px]"
334 assert_select "div.project_late[style*=left:52px]"
335 end
335 end
336
336
337 should "be the total delayed width of the project" do
337 should "be the total delayed width of the project" do
338 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
338 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
339 assert_select "div.project_late[style*=width:6px]"
339 assert_select "div.project_late[style*=width:6px]"
340 end
340 end
341 end
341 end
342
342
343 context "done line" do
343 context "done line" do
344 should "start from the starting point on the left" do
344 should "start from the starting point on the left" do
345 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
345 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
346 assert_select "div.project_done[style*=left:52px]"
346 assert_select "div.project_done[style*=left:52px]"
347 end
347 end
348
348
349 should "Be the total done width of the project" do
349 should "Be the total done width of the project" do
350 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
350 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
351 assert_select "div.project_done[style*=left:52px]"
351 assert_select "div.project_done[style*=left:52px]"
352 end
352 end
353 end
353 end
354
354
355 context "starting marker" do
355 context "starting marker" do
356 should "not appear if the starting point is off the gantt chart" do
356 should "not appear if the starting point is off the gantt chart" do
357 # Shift the date range of the chart
357 # Shift the date range of the chart
358 @gantt.instance_variable_set('@date_from', Date.today)
358 @gantt.instance_variable_set('@date_from', Date.today)
359
359
360 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
360 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
361 assert_select "div.project-line.starting", false
361 assert_select "div.project-line.starting", false
362 end
362 end
363
363
364 should "appear at the starting point" do
364 should "appear at the starting point" do
365 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
365 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
366 assert_select "div.project-line.starting[style*=left:52px]"
366 assert_select "div.project-line.starting[style*=left:52px]"
367 end
367 end
368 end
368 end
369
369
370 context "ending marker" do
370 context "ending marker" do
371 should "not appear if the starting point is off the gantt chart" do
371 should "not appear if the starting point is off the gantt chart" do
372 # Shift the date range of the chart
372 # Shift the date range of the chart
373 @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date)
373 @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date)
374
374
375 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
375 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
376 assert_select "div.project-line.ending", false
376 assert_select "div.project-line.ending", false
377
377
378 end
378 end
379
379
380 should "appear at the end of the date range" do
380 should "appear at the end of the date range" do
381 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
381 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
382 assert_select "div.project-line.ending[style*=left:84px]"
382 assert_select "div.project-line.ending[style*=left:84px]"
383 end
383 end
384 end
384 end
385
385
386 context "status content" do
386 context "status content" do
387 should "appear at the far left, even if it's far in the past" do
387 should "appear at the far left, even if it's far in the past" do
388 @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date)
388 @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date)
389
389
390 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
390 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
391 assert_select "div.project-name", /#{@project.name}/
391 assert_select "div.project-name", /#{@project.name}/
392 end
392 end
393
393
394 should "show the project name" do
394 should "show the project name" do
395 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
395 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
396 assert_select "div.project-name", /#{@project.name}/
396 assert_select "div.project-name", /#{@project.name}/
397 end
397 end
398
398
399 should "show the percent complete" do
399 should "show the percent complete" do
400 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
400 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
401 assert_select "div.project-name", /0%/
401 assert_select "div.project-name", /0%/
402 end
402 end
403 end
403 end
404 end
404 end
405
405
406 should "test the PNG format"
406 should "test the PNG format"
407 should "test the PDF format"
407 should "test the PDF format"
408 end
408 end
409
409
410 context "#subject_for_version" do
410 context "#subject_for_version" do
411 setup do
411 setup do
412 create_gantt
412 create_gantt
413 @project.enabled_module_names = [:issue_tracking]
413 @project.enabled_module_names = [:issue_tracking]
414 @tracker = Tracker.generate!
414 @tracker = Tracker.generate!
415 @project.trackers << @tracker
415 @project.trackers << @tracker
416 @version = Version.generate!(:effective_date => Date.yesterday)
416 @version = Version.generate!(:effective_date => Date.yesterday)
417 @project.versions << @version
417 @project.versions << @version
418
418
419 @project.issues << Issue.generate!(:fixed_version => @version,
419 @project.issues << Issue.generate!(:fixed_version => @version,
420 :subject => "gantt#subject_for_version",
420 :subject => "gantt#subject_for_version",
421 :tracker => @tracker,
421 :tracker => @tracker,
422 :project => @project,
422 :project => @project,
423 :start_date => Date.today)
423 :start_date => Date.today)
424
424
425 end
425 end
426
426
427 context ":html format" do
427 context ":html format" do
428 should "add an absolute positioned div" do
428 should "add an absolute positioned div" do
429 @response.body = @gantt.subject_for_version(@version, {:format => :html})
429 @response.body = @gantt.subject_for_version(@version, {:format => :html})
430 assert_select "div[style*=absolute]"
430 assert_select "div[style*=absolute]"
431 end
431 end
432
432
433 should "use the indent option to move the div to the right" do
433 should "use the indent option to move the div to the right" do
434 @response.body = @gantt.subject_for_version(@version, {:format => :html, :indent => 40})
434 @response.body = @gantt.subject_for_version(@version, {:format => :html, :indent => 40})
435 assert_select "div[style*=left:40]"
435 assert_select "div[style*=left:40]"
436 end
436 end
437
437
438 should "include the version name" do
438 should "include the version name" do
439 @response.body = @gantt.subject_for_version(@version, {:format => :html})
439 @response.body = @gantt.subject_for_version(@version, {:format => :html})
440 assert_select 'div', :text => /#{@version.name}/
440 assert_select 'div', :text => /#{@version.name}/
441 end
441 end
442
442
443 should "include a link to the version" do
443 should "include a link to the version" do
444 @response.body = @gantt.subject_for_version(@version, {:format => :html})
444 @response.body = @gantt.subject_for_version(@version, {:format => :html})
445 assert_select 'a[href=?]', Regexp.escape("/versions/show/#{@version.to_param}"), :text => /#{@version.name}/
445 assert_select 'a[href=?]', Regexp.escape("/versions/show/#{@version.to_param}"), :text => /#{@version.name}/
446 end
446 end
447
447
448 should "style late versions" do
448 should "style late versions" do
449 assert @version.overdue?, "Need an overdue version for this test"
449 assert @version.overdue?, "Need an overdue version for this test"
450 @response.body = @gantt.subject_for_version(@version, {:format => :html})
450 @response.body = @gantt.subject_for_version(@version, {:format => :html})
451
451
452 assert_select 'div span.version-behind-schedule'
452 assert_select 'div span.version-behind-schedule'
453 end
453 end
454
454
455 should "style behind schedule versions" do
455 should "style behind schedule versions" do
456 assert @version.behind_schedule?, "Need a behind schedule version for this test"
456 assert @version.behind_schedule?, "Need a behind schedule version for this test"
457 @response.body = @gantt.subject_for_version(@version, {:format => :html})
457 @response.body = @gantt.subject_for_version(@version, {:format => :html})
458
458
459 assert_select 'div span.version-behind-schedule'
459 assert_select 'div span.version-behind-schedule'
460 end
460 end
461 end
461 end
462 should "test the PNG format"
462 should "test the PNG format"
463 should "test the PDF format"
463 should "test the PDF format"
464 end
464 end
465
465
466 context "#line_for_version" do
466 context "#line_for_version" do
467 setup do
467 setup do
468 create_gantt
468 create_gantt
469 @project.enabled_module_names = [:issue_tracking]
469 @project.enabled_module_names = [:issue_tracking]
470 @tracker = Tracker.generate!
470 @tracker = Tracker.generate!
471 @project.trackers << @tracker
471 @project.trackers << @tracker
472 @version = Version.generate!(:effective_date => 1.week.from_now.to_date)
472 @version = Version.generate!(:effective_date => 1.week.from_now.to_date)
473 @project.versions << @version
473 @project.versions << @version
474
474
475 @project.issues << Issue.generate!(:fixed_version => @version,
475 @project.issues << Issue.generate!(:fixed_version => @version,
476 :subject => "gantt#line_for_project",
476 :subject => "gantt#line_for_project",
477 :tracker => @tracker,
477 :tracker => @tracker,
478 :project => @project,
478 :project => @project,
479 :done_ratio => 30,
479 :done_ratio => 30,
480 :start_date => Date.yesterday,
480 :start_date => 1.week.ago.to_date,
481 :due_date => 1.week.from_now.to_date)
481 :due_date => 1.week.from_now.to_date)
482 end
482 end
483
483
484 context ":html format" do
484 context ":html format" do
485 context "todo line" do
485 context "todo line" do
486 should "start from the starting point on the left" do
486 should "start from the starting point on the left" do
487 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
487 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
488 assert_select "div.milestone_todo[style*=left:52px]"
488 assert_select "div.version.task_todo[style*=left:28px]", true, @response.body
489 end
489 end
490
490
491 should "be the total width of the version" do
491 should "be the total width of the version" do
492 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
492 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
493 assert_select "div.milestone_todo[style*=width:31px]"
493 assert_select "div.version.task_todo[style*=width:58px]", true, @response.body
494 end
494 end
495
495
496 end
496 end
497
497
498 context "late line" do
498 context "late line" do
499 should "start from the starting point on the left" do
499 should "start from the starting point on the left" do
500 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
500 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
501 assert_select "div.milestone_late[style*=left:52px]"
501 assert_select "div.version.task_late[style*=left:28px]", true, @response.body
502 end
502 end
503
503
504 should "be the total delayed width of the version" do
504 should "be the total delayed width of the version" do
505 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
505 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
506 assert_select "div.milestone_late[style*=width:6px]"
506 assert_select "div.version.task_late[style*=width:30px]", true, @response.body
507 end
507 end
508 end
508 end
509
509
510 context "done line" do
510 context "done line" do
511 should "start from the starting point on the left" do
511 should "start from the starting point on the left" do
512 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
512 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
513 assert_select "div.milestone_done[style*=left:52px]"
513 assert_select "div.version.task_done[style*=left:28px]", true, @response.body
514 end
514 end
515
515
516 should "Be the total done width of the version" do
516 should "Be the total done width of the version" do
517 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
517 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
518 assert_select "div.milestone_done[style*=left:52px]"
518 assert_select "div.version.task_done[style*=width:18px]", true, @response.body
519 end
519 end
520 end
520 end
521
521
522 context "starting marker" do
522 context "starting marker" do
523 should "not appear if the starting point is off the gantt chart" do
523 should "not appear if the starting point is off the gantt chart" do
524 # Shift the date range of the chart
524 # Shift the date range of the chart
525 @gantt.instance_variable_set('@date_from', Date.today)
525 @gantt.instance_variable_set('@date_from', Date.today)
526
526
527 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
527 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
528 assert_select "div.milestone.starting", false
528 assert_select "div.version.starting", false
529 end
529 end
530
530
531 should "appear at the starting point" do
531 should "appear at the starting point" do
532 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
532 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
533 assert_select "div.milestone.starting[style*=left:52px]"
533 assert_select "div.version.starting[style*=left:28px]", true, @response.body
534 end
534 end
535 end
535 end
536
536
537 context "ending marker" do
537 context "ending marker" do
538 should "not appear if the starting point is off the gantt chart" do
538 should "not appear if the starting point is off the gantt chart" do
539 # Shift the date range of the chart
539 # Shift the date range of the chart
540 @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date)
540 @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date)
541
541
542 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
542 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
543 assert_select "div.milestone.ending", false
543 assert_select "div.version.ending", false
544
544
545 end
545 end
546
546
547 should "appear at the end of the date range" do
547 should "appear at the end of the date range" do
548 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
548 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
549 assert_select "div.milestone.ending[style*=left:84px]"
549 assert_select "div.version.ending[style*=left:84px]", true, @response.body
550 end
550 end
551 end
551 end
552
552
553 context "status content" do
553 context "status content" do
554 should "appear at the far left, even if it's far in the past" do
554 should "appear at the far left, even if it's far in the past" do
555 @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date)
555 @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date)
556
556
557 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
557 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
558 assert_select "div.version-name", /#{@version.name}/
558 assert_select "div.version.label", /#{@version.name}/
559 end
559 end
560
560
561 should "show the version name" do
561 should "show the version name" do
562 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
562 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
563 assert_select "div.version-name", /#{@version.name}/
563 assert_select "div.version.label", /#{@version.name}/
564 end
564 end
565
565
566 should "show the percent complete" do
566 should "show the percent complete" do
567 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
567 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
568 assert_select "div.version-name", /30%/
568 assert_select "div.version.label", /30%/
569 end
569 end
570 end
570 end
571 end
571 end
572
572
573 should "test the PNG format"
573 should "test the PNG format"
574 should "test the PDF format"
574 should "test the PDF format"
575 end
575 end
576
576
577 context "#subject_for_issue" do
577 context "#subject_for_issue" do
578 setup do
578 setup do
579 create_gantt
579 create_gantt
580 @project.enabled_module_names = [:issue_tracking]
580 @project.enabled_module_names = [:issue_tracking]
581 @tracker = Tracker.generate!
581 @tracker = Tracker.generate!
582 @project.trackers << @tracker
582 @project.trackers << @tracker
583
583
584 @issue = Issue.generate!(:subject => "gantt#subject_for_issue",
584 @issue = Issue.generate!(:subject => "gantt#subject_for_issue",
585 :tracker => @tracker,
585 :tracker => @tracker,
586 :project => @project,
586 :project => @project,
587 :start_date => 3.days.ago.to_date,
587 :start_date => 3.days.ago.to_date,
588 :due_date => Date.yesterday)
588 :due_date => Date.yesterday)
589 @project.issues << @issue
589 @project.issues << @issue
590
590
591 end
591 end
592
592
593 context ":html format" do
593 context ":html format" do
594 should "add an absolute positioned div" do
594 should "add an absolute positioned div" do
595 @response.body = @gantt.subject_for_issue(@issue, {:format => :html})
595 @response.body = @gantt.subject_for_issue(@issue, {:format => :html})
596 assert_select "div[style*=absolute]"
596 assert_select "div[style*=absolute]"
597 end
597 end
598
598
599 should "use the indent option to move the div to the right" do
599 should "use the indent option to move the div to the right" do
600 @response.body = @gantt.subject_for_issue(@issue, {:format => :html, :indent => 40})
600 @response.body = @gantt.subject_for_issue(@issue, {:format => :html, :indent => 40})
601 assert_select "div[style*=left:40]"
601 assert_select "div[style*=left:40]"
602 end
602 end
603
603
604 should "include the issue subject" do
604 should "include the issue subject" do
605 @response.body = @gantt.subject_for_issue(@issue, {:format => :html})
605 @response.body = @gantt.subject_for_issue(@issue, {:format => :html})
606 assert_select 'div', :text => /#{@issue.subject}/
606 assert_select 'div', :text => /#{@issue.subject}/
607 end
607 end
608
608
609 should "include a link to the issue" do
609 should "include a link to the issue" do
610 @response.body = @gantt.subject_for_issue(@issue, {:format => :html})
610 @response.body = @gantt.subject_for_issue(@issue, {:format => :html})
611 assert_select 'a[href=?]', Regexp.escape("/issues/#{@issue.to_param}"), :text => /#{@tracker.name} ##{@issue.id}/
611 assert_select 'a[href=?]', Regexp.escape("/issues/#{@issue.to_param}"), :text => /#{@tracker.name} ##{@issue.id}/
612 end
612 end
613
613
614 should "style overdue issues" do
614 should "style overdue issues" do
615 assert @issue.overdue?, "Need an overdue issue for this test"
615 assert @issue.overdue?, "Need an overdue issue for this test"
616 @response.body = @gantt.subject_for_issue(@issue, {:format => :html})
616 @response.body = @gantt.subject_for_issue(@issue, {:format => :html})
617
617
618 assert_select 'div span.issue-overdue'
618 assert_select 'div span.issue-overdue'
619 end
619 end
620
620
621 end
621 end
622 should "test the PNG format"
622 should "test the PNG format"
623 should "test the PDF format"
623 should "test the PDF format"
624 end
624 end
625
625
626 context "#line_for_issue" do
626 context "#line_for_issue" do
627 setup do
627 setup do
628 create_gantt
628 create_gantt
629 @project.enabled_module_names = [:issue_tracking]
629 @project.enabled_module_names = [:issue_tracking]
630 @tracker = Tracker.generate!
630 @tracker = Tracker.generate!
631 @project.trackers << @tracker
631 @project.trackers << @tracker
632 @version = Version.generate!(:effective_date => 1.week.from_now.to_date)
632 @version = Version.generate!(:effective_date => 1.week.from_now.to_date)
633 @project.versions << @version
633 @project.versions << @version
634 @issue = Issue.generate!(:fixed_version => @version,
634 @issue = Issue.generate!(:fixed_version => @version,
635 :subject => "gantt#line_for_project",
635 :subject => "gantt#line_for_project",
636 :tracker => @tracker,
636 :tracker => @tracker,
637 :project => @project,
637 :project => @project,
638 :done_ratio => 30,
638 :done_ratio => 30,
639 :start_date => 1.week.ago.to_date,
639 :start_date => 1.week.ago.to_date,
640 :due_date => 1.week.from_now.to_date)
640 :due_date => 1.week.from_now.to_date)
641 @project.issues << @issue
641 @project.issues << @issue
642 end
642 end
643
643
644 context ":html format" do
644 context ":html format" do
645 context "todo line" do
645 context "todo line" do
646 should "start from the starting point on the left" do
646 should "start from the starting point on the left" do
647 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
647 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
648 assert_select "div.task_todo[style*=left:28px]", true, @response.body
648 assert_select "div.task_todo[style*=left:28px]", true, @response.body
649 end
649 end
650
650
651 should "be the total width of the issue" do
651 should "be the total width of the issue" do
652 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
652 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
653 assert_select "div.task_todo[style*=width:58px]", true, @response.body
653 assert_select "div.task_todo[style*=width:58px]", true, @response.body
654 end
654 end
655
655
656 end
656 end
657
657
658 context "late line" do
658 context "late line" do
659 should "start from the starting point on the left" do
659 should "start from the starting point on the left" do
660 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
660 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
661 assert_select "div.task_late[style*=left:28px]", true, @response.body
661 assert_select "div.task_late[style*=left:28px]", true, @response.body
662 end
662 end
663
663
664 should "be the total delayed width of the issue" do
664 should "be the total delayed width of the issue" do
665 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
665 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
666 assert_select "div.task_late[style*=width:30px]", true, @response.body
666 assert_select "div.task_late[style*=width:30px]", true, @response.body
667 end
667 end
668 end
668 end
669
669
670 context "done line" do
670 context "done line" do
671 should "start from the starting point on the left" do
671 should "start from the starting point on the left" do
672 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
672 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
673 assert_select "div.task_done[style*=left:28px]", true, @response.body
673 assert_select "div.task_done[style*=left:28px]", true, @response.body
674 end
674 end
675
675
676 should "Be the total done width of the issue" do
676 should "Be the total done width of the issue" do
677 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
677 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
678 assert_select "div.task_done[style*=width:18px]", true, @response.body
678 assert_select "div.task_done[style*=width:18px]", true, @response.body
679 end
679 end
680
680
681 should "not be the total done width if the chart starts after issue start date" do
681 should "not be the total done width if the chart starts after issue start date" do
682 create_gantt(@project, :date_from => 5.days.ago.to_date)
682 create_gantt(@project, :date_from => 5.days.ago.to_date)
683
683
684 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
684 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
685 assert_select "div.task_done[style*=left:0px]", true, @response.body
685 assert_select "div.task_done[style*=left:0px]", true, @response.body
686 assert_select "div.task_done[style*=width:10px]", true, @response.body
686 assert_select "div.task_done[style*=width:10px]", true, @response.body
687 end
687 end
688 end
688 end
689
689
690 context "status content" do
690 context "status content" do
691 should "appear at the far left, even if it's far in the past" do
691 should "appear at the far left, even if it's far in the past" do
692 @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date)
692 @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date)
693
693
694 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
694 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
695 assert_select "div.task.label", true, @response.body
695 assert_select "div.task.label", true, @response.body
696 end
696 end
697
697
698 should "show the issue status" do
698 should "show the issue status" do
699 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
699 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
700 assert_select "div.task.label", /#{@issue.status.name}/
700 assert_select "div.task.label", /#{@issue.status.name}/
701 end
701 end
702
702
703 should "show the percent complete" do
703 should "show the percent complete" do
704 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
704 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
705 assert_select "div.task.label", /30%/
705 assert_select "div.task.label", /30%/
706 end
706 end
707 end
707 end
708 end
708 end
709
709
710 should "have an issue tooltip" do
710 should "have an issue tooltip" do
711 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
711 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
712 assert_select "div.tooltip", /#{@issue.subject}/
712 assert_select "div.tooltip", /#{@issue.subject}/
713 end
713 end
714
714
715 should "test the PNG format"
715 should "test the PNG format"
716 should "test the PDF format"
716 should "test the PDF format"
717 end
717 end
718
718
719 context "#to_image" do
719 context "#to_image" do
720 should "be tested"
720 should "be tested"
721 end
721 end
722
722
723 context "#to_pdf" do
723 context "#to_pdf" do
724 should "be tested"
724 should "be tested"
725 end
725 end
726
726
727 end
727 end
General Comments 0
You need to be logged in to leave comments. Login now