##// END OF EJS Templates
Gantt code cleanup....
Jean-Philippe Lang -
r4409:edc35d4d5b04
parent child
Show More
@@ -1,993 +1,939
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 coords = coordinates(project.start_date, project.due_date, project.completed_percent(:include_subprojects => true), options[:zoom])
304 i_left = ((project.start_date - self.date_from)*options[:zoom]).floor
304 label = "#{h project } #{h project.completed_percent(:include_subprojects => true).to_i.to_s}%"
305
305 output = html_task(options[:top], coords, :css => "project task", :label => label, :markers => true)
306 start_date = project.start_date
307 start_date ||= self.date_from
308 start_left = ((start_date - self.date_from)*options[:zoom]).floor
309
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
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 )
314
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
317
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
320 l_width = i_late_date ? ((i_late_date - start_date+1)*options[:zoom]).floor - 2 : 0 # delay width
321
322 # Bar graphic
323
324 # Make sure that negative i_left and i_width don't
325 # overflow the subject
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>"
328 end
329
306
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>"
332 end
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>"
335 end
336
337
338 # Starting diamond
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>"
341 output << "<div style='top:#{ options[:top] }px;left:#{ start_left + 12 }px;' class='task label'>"
342 output << "</div>"
343 end
344
345 # Ending diamond
346 # Don't show items too far ahead
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>"
349 end
350
351 # DIsplay the Project name and %
352 if i_end <= options[:g_width]
353 # Display the status even if it's floated off to the left
354 status_px = i_end + 12 # 12px for the diamond
355 status_px = 0 if status_px <= 0
356
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>"
359 output << "</div>"
360 end
361 @lines << output
307 @lines << output
362 output
308 output
363 when :image
309 when :image
364 options[:image].stroke('transparent')
310 options[:image].stroke('transparent')
365 i_left = options[:subject_width] + ((project.due_date - self.date_from)*options[:zoom]).floor
311 i_left = options[:subject_width] + ((project.due_date - self.date_from)*options[:zoom]).floor
366
312
367 # Make sure negative i_left doesn't overflow the subject
313 # Make sure negative i_left doesn't overflow the subject
368 if i_left > options[:subject_width]
314 if i_left > options[:subject_width]
369 options[:image].fill('blue')
315 options[:image].fill('blue')
370 options[:image].rectangle(i_left, options[:top], i_left + 6, options[:top] - 6)
316 options[:image].rectangle(i_left, options[:top], i_left + 6, options[:top] - 6)
371 options[:image].fill('black')
317 options[:image].fill('black')
372 options[:image].text(i_left + 11, options[:top] + 1, project.name)
318 options[:image].text(i_left + 11, options[:top] + 1, project.name)
373 end
319 end
374 when :pdf
320 when :pdf
375 options[:pdf].SetY(options[:top]+1.5)
321 options[:pdf].SetY(options[:top]+1.5)
376 i_left = ((project.due_date - @date_from)*options[:zoom])
322 i_left = ((project.due_date - @date_from)*options[:zoom])
377
323
378 # Make sure negative i_left doesn't overflow the subject
324 # Make sure negative i_left doesn't overflow the subject
379 if i_left > 0
325 if i_left > 0
380 options[:pdf].SetX(options[:subject_width] + i_left)
326 options[:pdf].SetX(options[:subject_width] + i_left)
381 options[:pdf].SetFillColor(50,50,200)
327 options[:pdf].SetFillColor(50,50,200)
382 options[:pdf].Cell(2, 2, "", 0, 0, "", 1)
328 options[:pdf].Cell(2, 2, "", 0, 0, "", 1)
383
329
384 options[:pdf].SetY(options[:top]+1.5)
330 options[:pdf].SetY(options[:top]+1.5)
385 options[:pdf].SetX(options[:subject_width] + i_left + 3)
331 options[:pdf].SetX(options[:subject_width] + i_left + 3)
386 options[:pdf].Cell(30, 2, "#{project.name}")
332 options[:pdf].Cell(30, 2, "#{project.name}")
387 end
333 end
388 end
334 end
389 else
335 else
390 ActiveRecord::Base.logger.debug "Gantt#line_for_project was not given a project with a start_date"
336 ActiveRecord::Base.logger.debug "Gantt#line_for_project was not given a project with a start_date"
391 ''
337 ''
392 end
338 end
393 end
339 end
394
340
395 def subject_for_version(version, options)
341 def subject_for_version(version, options)
396 case options[:format]
342 case options[:format]
397 when :html
343 when :html
398 output = ''
344 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> "
345 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
346 if version.is_a? Version
401 output << "<span class='icon icon-package #{version.behind_schedule? ? 'version-behind-schedule' : ''} #{version.overdue? ? 'version-overdue' : ''}'>"
347 output << "<span class='icon icon-package #{version.behind_schedule? ? 'version-behind-schedule' : ''} #{version.overdue? ? 'version-overdue' : ''}'>"
402 output << view.link_to_version(version)
348 output << view.link_to_version(version)
403 output << '</span>'
349 output << '</span>'
404 else
350 else
405 ActiveRecord::Base.logger.debug "Gantt#subject_for_version was not given a version"
351 ActiveRecord::Base.logger.debug "Gantt#subject_for_version was not given a version"
406 ''
352 ''
407 end
353 end
408 output << "</small></div>"
354 output << "</small></div>"
409 @subjects << output
355 @subjects << output
410 output
356 output
411 when :image
357 when :image
412 options[:image].fill('black')
358 options[:image].fill('black')
413 options[:image].stroke('transparent')
359 options[:image].stroke('transparent')
414 options[:image].stroke_width(1)
360 options[:image].stroke_width(1)
415 options[:image].text(options[:indent], options[:top] + 2, version.to_s_with_project)
361 options[:image].text(options[:indent], options[:top] + 2, version.to_s_with_project)
416 when :pdf
362 when :pdf
417 pdf_new_page?(options)
363 pdf_new_page?(options)
418 options[:pdf].SetY(options[:top])
364 options[:pdf].SetY(options[:top])
419 options[:pdf].SetX(15)
365 options[:pdf].SetX(15)
420
366
421 char_limit = PDF::MaxCharactorsForSubject - options[:indent]
367 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")
368 options[:pdf].Cell(options[:subject_width]-15, 5, (" " * options[:indent]) +"#{version.to_s_with_project}".sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR")
423
369
424 options[:pdf].SetY(options[:top])
370 options[:pdf].SetY(options[:top])
425 options[:pdf].SetX(options[:subject_width])
371 options[:pdf].SetX(options[:subject_width])
426 options[:pdf].Cell(options[:g_width], 5, "", "LR")
372 options[:pdf].Cell(options[:g_width], 5, "", "LR")
427 end
373 end
428 end
374 end
429
375
430 def line_for_version(version, options)
376 def line_for_version(version, options)
431 # Skip versions that don't have a start_date
377 # Skip versions that don't have a start_date
432 if version.is_a?(Version) && version.start_date && version.due_date
378 if version.is_a?(Version) && version.start_date && version.due_date
433 options[:zoom] ||= 1
379 options[:zoom] ||= 1
434 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
380 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
435
381
436 case options[:format]
382 case options[:format]
437 when :html
383 when :html
438 coords = coordinates(version.fixed_issues.minimum('start_date'), version.due_date, version.completed_pourcent, options[:zoom])
384 coords = coordinates(version.fixed_issues.minimum('start_date'), version.due_date, version.completed_pourcent, options[:zoom])
439 label = "#{h version } #{h version.completed_pourcent.to_i.to_s}%"
385 label = "#{h version } #{h version.completed_pourcent.to_i.to_s}%"
440 label = h("#{version.project} -") + label unless @project && @project == version.project
386 label = h("#{version.project} -") + label unless @project && @project == version.project
441 output = html_task(options[:top], coords, :css => "version task", :label => label, :markers => true)
387 output = html_task(options[:top], coords, :css => "version task", :label => label, :markers => true)
442
388
443 @lines << output
389 @lines << output
444 output
390 output
445 when :image
391 when :image
446 options[:image].stroke('transparent')
392 options[:image].stroke('transparent')
447 i_left = options[:subject_width] + ((version.start_date - @date_from)*options[:zoom]).floor
393 i_left = options[:subject_width] + ((version.start_date - @date_from)*options[:zoom]).floor
448
394
449 # Make sure negative i_left doesn't overflow the subject
395 # Make sure negative i_left doesn't overflow the subject
450 if i_left > options[:subject_width]
396 if i_left > options[:subject_width]
451 options[:image].fill('green')
397 options[:image].fill('green')
452 options[:image].rectangle(i_left, options[:top], i_left + 6, options[:top] - 6)
398 options[:image].rectangle(i_left, options[:top], i_left + 6, options[:top] - 6)
453 options[:image].fill('black')
399 options[:image].fill('black')
454 options[:image].text(i_left + 11, options[:top] + 1, version.name)
400 options[:image].text(i_left + 11, options[:top] + 1, version.name)
455 end
401 end
456 when :pdf
402 when :pdf
457 options[:pdf].SetY(options[:top]+1.5)
403 options[:pdf].SetY(options[:top]+1.5)
458 i_left = ((version.start_date - @date_from)*options[:zoom])
404 i_left = ((version.start_date - @date_from)*options[:zoom])
459
405
460 # Make sure negative i_left doesn't overflow the subject
406 # Make sure negative i_left doesn't overflow the subject
461 if i_left > 0
407 if i_left > 0
462 options[:pdf].SetX(options[:subject_width] + i_left)
408 options[:pdf].SetX(options[:subject_width] + i_left)
463 options[:pdf].SetFillColor(50,200,50)
409 options[:pdf].SetFillColor(50,200,50)
464 options[:pdf].Cell(2, 2, "", 0, 0, "", 1)
410 options[:pdf].Cell(2, 2, "", 0, 0, "", 1)
465
411
466 options[:pdf].SetY(options[:top]+1.5)
412 options[:pdf].SetY(options[:top]+1.5)
467 options[:pdf].SetX(options[:subject_width] + i_left + 3)
413 options[:pdf].SetX(options[:subject_width] + i_left + 3)
468 options[:pdf].Cell(30, 2, "#{version.name}")
414 options[:pdf].Cell(30, 2, "#{version.name}")
469 end
415 end
470 end
416 end
471 else
417 else
472 ActiveRecord::Base.logger.debug "Gantt#line_for_version was not given a version with a start_date"
418 ActiveRecord::Base.logger.debug "Gantt#line_for_version was not given a version with a start_date"
473 ''
419 ''
474 end
420 end
475 end
421 end
476
422
477 def subject_for_issue(issue, options)
423 def subject_for_issue(issue, options)
478 case options[:format]
424 case options[:format]
479 when :html
425 when :html
480 output = ''
426 output = ''
481 output << "<div class='tooltip'>"
427 output << "<div class='tooltip'>"
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> "
428 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> "
483 if issue.is_a? Issue
429 if issue.is_a? Issue
484 css_classes = []
430 css_classes = []
485 css_classes << 'issue-overdue' if issue.overdue?
431 css_classes << 'issue-overdue' if issue.overdue?
486 css_classes << 'issue-behind-schedule' if issue.behind_schedule?
432 css_classes << 'issue-behind-schedule' if issue.behind_schedule?
487 css_classes << 'icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
433 css_classes << 'icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
488
434
489 if issue.assigned_to.present?
435 if issue.assigned_to.present?
490 assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
436 assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
491 output << view.avatar(issue.assigned_to, :class => 'gravatar icon-gravatar', :size => 10, :title => assigned_string)
437 output << view.avatar(issue.assigned_to, :class => 'gravatar icon-gravatar', :size => 10, :title => assigned_string)
492 end
438 end
493 output << "<span class='#{css_classes.join(' ')}'>"
439 output << "<span class='#{css_classes.join(' ')}'>"
494 output << view.link_to_issue(issue)
440 output << view.link_to_issue(issue)
495 output << '</span>'
441 output << '</span>'
496 else
442 else
497 ActiveRecord::Base.logger.debug "Gantt#subject_for_issue was not given an issue"
443 ActiveRecord::Base.logger.debug "Gantt#subject_for_issue was not given an issue"
498 ''
444 ''
499 end
445 end
500 output << "</small></div>"
446 output << "</small></div>"
501
447
502 # Tooltip
448 # Tooltip
503 if issue.is_a? Issue
449 if issue.is_a? Issue
504 output << "<span class='tip' style='position: absolute;top:#{ options[:top].to_i + 16 }px;left:#{ options[:indent].to_i + 20 }px;'>"
450 output << "<span class='tip' style='position: absolute;top:#{ options[:top].to_i + 16 }px;left:#{ options[:indent].to_i + 20 }px;'>"
505 output << view.render_issue_tooltip(issue)
451 output << view.render_issue_tooltip(issue)
506 output << "</span>"
452 output << "</span>"
507 end
453 end
508
454
509 output << "</div>"
455 output << "</div>"
510 @subjects << output
456 @subjects << output
511 output
457 output
512 when :image
458 when :image
513 options[:image].fill('black')
459 options[:image].fill('black')
514 options[:image].stroke('transparent')
460 options[:image].stroke('transparent')
515 options[:image].stroke_width(1)
461 options[:image].stroke_width(1)
516 options[:image].text(options[:indent], options[:top] + 2, issue.subject)
462 options[:image].text(options[:indent], options[:top] + 2, issue.subject)
517 when :pdf
463 when :pdf
518 pdf_new_page?(options)
464 pdf_new_page?(options)
519 options[:pdf].SetY(options[:top])
465 options[:pdf].SetY(options[:top])
520 options[:pdf].SetX(15)
466 options[:pdf].SetX(15)
521
467
522 char_limit = PDF::MaxCharactorsForSubject - options[:indent]
468 char_limit = PDF::MaxCharactorsForSubject - options[:indent]
523 options[:pdf].Cell(options[:subject_width]-15, 5, (" " * options[:indent]) +"#{issue.tracker} #{issue.id}: #{issue.subject}".sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR")
469 options[:pdf].Cell(options[:subject_width]-15, 5, (" " * options[:indent]) +"#{issue.tracker} #{issue.id}: #{issue.subject}".sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR")
524
470
525 options[:pdf].SetY(options[:top])
471 options[:pdf].SetY(options[:top])
526 options[:pdf].SetX(options[:subject_width])
472 options[:pdf].SetX(options[:subject_width])
527 options[:pdf].Cell(options[:g_width], 5, "", "LR")
473 options[:pdf].Cell(options[:g_width], 5, "", "LR")
528 end
474 end
529 end
475 end
530
476
531 def line_for_issue(issue, options)
477 def line_for_issue(issue, options)
532 # Skip issues that don't have a due_before (due_date or version's due_date)
478 # Skip issues that don't have a due_before (due_date or version's due_date)
533 if issue.is_a?(Issue) && issue.due_before
479 if issue.is_a?(Issue) && issue.due_before
534 case options[:format]
480 case options[:format]
535 when :html
481 when :html
536 coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom])
482 coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom])
537 css = "task " + (issue.leaf? ? 'leaf' : 'parent')
483 css = "task " + (issue.leaf? ? 'leaf' : 'parent')
538 output = html_task(options[:top], coords, :css => css, :label => "#{ issue.status.name } #{ issue.done_ratio }%", :issue => issue)
484 output = html_task(options[:top], coords, :css => css, :label => "#{ issue.status.name } #{ issue.done_ratio }%", :issue => issue)
539
485
540 @lines << output
486 @lines << output
541 output
487 output
542
488
543 when :image
489 when :image
544 # Handle nil start_dates, rare but can happen.
490 # Handle nil start_dates, rare but can happen.
545 i_start_date = if issue.start_date && issue.start_date >= @date_from
491 i_start_date = if issue.start_date && issue.start_date >= @date_from
546 issue.start_date
492 issue.start_date
547 else
493 else
548 @date_from
494 @date_from
549 end
495 end
550
496
551 i_end_date = (issue.due_before <= date_to ? issue.due_before : date_to )
497 i_end_date = (issue.due_before <= date_to ? issue.due_before : date_to )
552 i_done_date = i_start_date + ((issue.due_before - i_start_date+1)*issue.done_ratio/100).floor
498 i_done_date = i_start_date + ((issue.due_before - i_start_date+1)*issue.done_ratio/100).floor
553 i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date )
499 i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date )
554 i_done_date = (i_done_date >= date_to ? date_to : i_done_date )
500 i_done_date = (i_done_date >= date_to ? date_to : i_done_date )
555 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
501 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
556
502
557 i_left = options[:subject_width] + ((i_start_date - @date_from)*options[:zoom]).floor
503 i_left = options[:subject_width] + ((i_start_date - @date_from)*options[:zoom]).floor
558 i_width = ((i_end_date - i_start_date + 1)*options[:zoom]).floor # total width of the issue
504 i_width = ((i_end_date - i_start_date + 1)*options[:zoom]).floor # total width of the issue
559 d_width = ((i_done_date - i_start_date)*options[:zoom]).floor # done width
505 d_width = ((i_done_date - i_start_date)*options[:zoom]).floor # done width
560 l_width = i_late_date ? ((i_late_date - i_start_date+1)*options[:zoom]).floor : 0 # delay width
506 l_width = i_late_date ? ((i_late_date - i_start_date+1)*options[:zoom]).floor : 0 # delay width
561
507
562
508
563 # Make sure that negative i_left and i_width don't
509 # Make sure that negative i_left and i_width don't
564 # overflow the subject
510 # overflow the subject
565 if i_width > 0
511 if i_width > 0
566 options[:image].fill('grey')
512 options[:image].fill('grey')
567 options[:image].rectangle(i_left, options[:top], i_left + i_width, options[:top] - 6)
513 options[:image].rectangle(i_left, options[:top], i_left + i_width, options[:top] - 6)
568 options[:image].fill('red')
514 options[:image].fill('red')
569 options[:image].rectangle(i_left, options[:top], i_left + l_width, options[:top] - 6) if l_width > 0
515 options[:image].rectangle(i_left, options[:top], i_left + l_width, options[:top] - 6) if l_width > 0
570 options[:image].fill('blue')
516 options[:image].fill('blue')
571 options[:image].rectangle(i_left, options[:top], i_left + d_width, options[:top] - 6) if d_width > 0
517 options[:image].rectangle(i_left, options[:top], i_left + d_width, options[:top] - 6) if d_width > 0
572 end
518 end
573
519
574 # Show the status and % done next to the subject if it overflows
520 # Show the status and % done next to the subject if it overflows
575 options[:image].fill('black')
521 options[:image].fill('black')
576 if i_width > 0
522 if i_width > 0
577 options[:image].text(i_left + i_width + 5,options[:top] + 1, "#{issue.status.name} #{issue.done_ratio}%")
523 options[:image].text(i_left + i_width + 5,options[:top] + 1, "#{issue.status.name} #{issue.done_ratio}%")
578 else
524 else
579 options[:image].text(options[:subject_width] + 5,options[:top] + 1, "#{issue.status.name} #{issue.done_ratio}%")
525 options[:image].text(options[:subject_width] + 5,options[:top] + 1, "#{issue.status.name} #{issue.done_ratio}%")
580 end
526 end
581
527
582 when :pdf
528 when :pdf
583 options[:pdf].SetY(options[:top]+1.5)
529 options[:pdf].SetY(options[:top]+1.5)
584 # Handle nil start_dates, rare but can happen.
530 # Handle nil start_dates, rare but can happen.
585 i_start_date = if issue.start_date && issue.start_date >= @date_from
531 i_start_date = if issue.start_date && issue.start_date >= @date_from
586 issue.start_date
532 issue.start_date
587 else
533 else
588 @date_from
534 @date_from
589 end
535 end
590
536
591 i_end_date = (issue.due_before <= @date_to ? issue.due_before : @date_to )
537 i_end_date = (issue.due_before <= @date_to ? issue.due_before : @date_to )
592
538
593 i_done_date = i_start_date + ((issue.due_before - i_start_date+1)*issue.done_ratio/100).floor
539 i_done_date = i_start_date + ((issue.due_before - i_start_date+1)*issue.done_ratio/100).floor
594 i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date )
540 i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date )
595 i_done_date = (i_done_date >= @date_to ? @date_to : i_done_date )
541 i_done_date = (i_done_date >= @date_to ? @date_to : i_done_date )
596
542
597 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
543 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
598
544
599 i_left = ((i_start_date - @date_from)*options[:zoom])
545 i_left = ((i_start_date - @date_from)*options[:zoom])
600 i_width = ((i_end_date - i_start_date + 1)*options[:zoom])
546 i_width = ((i_end_date - i_start_date + 1)*options[:zoom])
601 d_width = ((i_done_date - i_start_date)*options[:zoom])
547 d_width = ((i_done_date - i_start_date)*options[:zoom])
602 l_width = ((i_late_date - i_start_date+1)*options[:zoom]) if i_late_date
548 l_width = ((i_late_date - i_start_date+1)*options[:zoom]) if i_late_date
603 l_width ||= 0
549 l_width ||= 0
604
550
605 # Make sure that negative i_left and i_width don't
551 # Make sure that negative i_left and i_width don't
606 # overflow the subject
552 # overflow the subject
607 if i_width > 0
553 if i_width > 0
608 options[:pdf].SetX(options[:subject_width] + i_left)
554 options[:pdf].SetX(options[:subject_width] + i_left)
609 options[:pdf].SetFillColor(200,200,200)
555 options[:pdf].SetFillColor(200,200,200)
610 options[:pdf].Cell(i_width, 2, "", 0, 0, "", 1)
556 options[:pdf].Cell(i_width, 2, "", 0, 0, "", 1)
611 end
557 end
612
558
613 if l_width > 0
559 if l_width > 0
614 options[:pdf].SetY(options[:top]+1.5)
560 options[:pdf].SetY(options[:top]+1.5)
615 options[:pdf].SetX(options[:subject_width] + i_left)
561 options[:pdf].SetX(options[:subject_width] + i_left)
616 options[:pdf].SetFillColor(255,100,100)
562 options[:pdf].SetFillColor(255,100,100)
617 options[:pdf].Cell(l_width, 2, "", 0, 0, "", 1)
563 options[:pdf].Cell(l_width, 2, "", 0, 0, "", 1)
618 end
564 end
619 if d_width > 0
565 if d_width > 0
620 options[:pdf].SetY(options[:top]+1.5)
566 options[:pdf].SetY(options[:top]+1.5)
621 options[:pdf].SetX(options[:subject_width] + i_left)
567 options[:pdf].SetX(options[:subject_width] + i_left)
622 options[:pdf].SetFillColor(100,100,255)
568 options[:pdf].SetFillColor(100,100,255)
623 options[:pdf].Cell(d_width, 2, "", 0, 0, "", 1)
569 options[:pdf].Cell(d_width, 2, "", 0, 0, "", 1)
624 end
570 end
625
571
626 options[:pdf].SetY(options[:top]+1.5)
572 options[:pdf].SetY(options[:top]+1.5)
627
573
628 # Make sure that negative i_left and i_width don't
574 # Make sure that negative i_left and i_width don't
629 # overflow the subject
575 # overflow the subject
630 if (i_left + i_width) >= 0
576 if (i_left + i_width) >= 0
631 options[:pdf].SetX(options[:subject_width] + i_left + i_width)
577 options[:pdf].SetX(options[:subject_width] + i_left + i_width)
632 else
578 else
633 options[:pdf].SetX(options[:subject_width])
579 options[:pdf].SetX(options[:subject_width])
634 end
580 end
635 options[:pdf].Cell(30, 2, "#{issue.status} #{issue.done_ratio}%")
581 options[:pdf].Cell(30, 2, "#{issue.status} #{issue.done_ratio}%")
636 end
582 end
637 else
583 else
638 ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
584 ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
639 ''
585 ''
640 end
586 end
641 end
587 end
642
588
643 # Generates a gantt image
589 # Generates a gantt image
644 # Only defined if RMagick is avalaible
590 # Only defined if RMagick is avalaible
645 def to_image(format='PNG')
591 def to_image(format='PNG')
646 date_to = (@date_from >> @months)-1
592 date_to = (@date_from >> @months)-1
647 show_weeks = @zoom > 1
593 show_weeks = @zoom > 1
648 show_days = @zoom > 2
594 show_days = @zoom > 2
649
595
650 subject_width = 400
596 subject_width = 400
651 header_heigth = 18
597 header_heigth = 18
652 # width of one day in pixels
598 # width of one day in pixels
653 zoom = @zoom*2
599 zoom = @zoom*2
654 g_width = (@date_to - @date_from + 1)*zoom
600 g_width = (@date_to - @date_from + 1)*zoom
655 g_height = 20 * number_of_rows + 30
601 g_height = 20 * number_of_rows + 30
656 headers_heigth = (show_weeks ? 2*header_heigth : header_heigth)
602 headers_heigth = (show_weeks ? 2*header_heigth : header_heigth)
657 height = g_height + headers_heigth
603 height = g_height + headers_heigth
658
604
659 imgl = Magick::ImageList.new
605 imgl = Magick::ImageList.new
660 imgl.new_image(subject_width+g_width+1, height)
606 imgl.new_image(subject_width+g_width+1, height)
661 gc = Magick::Draw.new
607 gc = Magick::Draw.new
662
608
663 # Subjects
609 # Subjects
664 subjects(:image => gc, :top => (headers_heigth + 20), :indent => 4, :format => :image)
610 subjects(:image => gc, :top => (headers_heigth + 20), :indent => 4, :format => :image)
665
611
666 # Months headers
612 # Months headers
667 month_f = @date_from
613 month_f = @date_from
668 left = subject_width
614 left = subject_width
669 @months.times do
615 @months.times do
670 width = ((month_f >> 1) - month_f) * zoom
616 width = ((month_f >> 1) - month_f) * zoom
671 gc.fill('white')
617 gc.fill('white')
672 gc.stroke('grey')
618 gc.stroke('grey')
673 gc.stroke_width(1)
619 gc.stroke_width(1)
674 gc.rectangle(left, 0, left + width, height)
620 gc.rectangle(left, 0, left + width, height)
675 gc.fill('black')
621 gc.fill('black')
676 gc.stroke('transparent')
622 gc.stroke('transparent')
677 gc.stroke_width(1)
623 gc.stroke_width(1)
678 gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
624 gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
679 left = left + width
625 left = left + width
680 month_f = month_f >> 1
626 month_f = month_f >> 1
681 end
627 end
682
628
683 # Weeks headers
629 # Weeks headers
684 if show_weeks
630 if show_weeks
685 left = subject_width
631 left = subject_width
686 height = header_heigth
632 height = header_heigth
687 if @date_from.cwday == 1
633 if @date_from.cwday == 1
688 # date_from is monday
634 # date_from is monday
689 week_f = date_from
635 week_f = date_from
690 else
636 else
691 # find next monday after date_from
637 # find next monday after date_from
692 week_f = @date_from + (7 - @date_from.cwday + 1)
638 week_f = @date_from + (7 - @date_from.cwday + 1)
693 width = (7 - @date_from.cwday + 1) * zoom
639 width = (7 - @date_from.cwday + 1) * zoom
694 gc.fill('white')
640 gc.fill('white')
695 gc.stroke('grey')
641 gc.stroke('grey')
696 gc.stroke_width(1)
642 gc.stroke_width(1)
697 gc.rectangle(left, header_heigth, left + width, 2*header_heigth + g_height-1)
643 gc.rectangle(left, header_heigth, left + width, 2*header_heigth + g_height-1)
698 left = left + width
644 left = left + width
699 end
645 end
700 while week_f <= date_to
646 while week_f <= date_to
701 width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
647 width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
702 gc.fill('white')
648 gc.fill('white')
703 gc.stroke('grey')
649 gc.stroke('grey')
704 gc.stroke_width(1)
650 gc.stroke_width(1)
705 gc.rectangle(left.round, header_heigth, left.round + width, 2*header_heigth + g_height-1)
651 gc.rectangle(left.round, header_heigth, left.round + width, 2*header_heigth + g_height-1)
706 gc.fill('black')
652 gc.fill('black')
707 gc.stroke('transparent')
653 gc.stroke('transparent')
708 gc.stroke_width(1)
654 gc.stroke_width(1)
709 gc.text(left.round + 2, header_heigth + 14, week_f.cweek.to_s)
655 gc.text(left.round + 2, header_heigth + 14, week_f.cweek.to_s)
710 left = left + width
656 left = left + width
711 week_f = week_f+7
657 week_f = week_f+7
712 end
658 end
713 end
659 end
714
660
715 # Days details (week-end in grey)
661 # Days details (week-end in grey)
716 if show_days
662 if show_days
717 left = subject_width
663 left = subject_width
718 height = g_height + header_heigth - 1
664 height = g_height + header_heigth - 1
719 wday = @date_from.cwday
665 wday = @date_from.cwday
720 (date_to - @date_from + 1).to_i.times do
666 (date_to - @date_from + 1).to_i.times do
721 width = zoom
667 width = zoom
722 gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
668 gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
723 gc.stroke('grey')
669 gc.stroke('grey')
724 gc.stroke_width(1)
670 gc.stroke_width(1)
725 gc.rectangle(left, 2*header_heigth, left + width, 2*header_heigth + g_height-1)
671 gc.rectangle(left, 2*header_heigth, left + width, 2*header_heigth + g_height-1)
726 left = left + width
672 left = left + width
727 wday = wday + 1
673 wday = wday + 1
728 wday = 1 if wday > 7
674 wday = 1 if wday > 7
729 end
675 end
730 end
676 end
731
677
732 # border
678 # border
733 gc.fill('transparent')
679 gc.fill('transparent')
734 gc.stroke('grey')
680 gc.stroke('grey')
735 gc.stroke_width(1)
681 gc.stroke_width(1)
736 gc.rectangle(0, 0, subject_width+g_width, headers_heigth)
682 gc.rectangle(0, 0, subject_width+g_width, headers_heigth)
737 gc.stroke('black')
683 gc.stroke('black')
738 gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_heigth-1)
684 gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_heigth-1)
739
685
740 # content
686 # content
741 top = headers_heigth + 20
687 top = headers_heigth + 20
742
688
743 lines(:image => gc, :top => top, :zoom => zoom, :subject_width => subject_width, :format => :image)
689 lines(:image => gc, :top => top, :zoom => zoom, :subject_width => subject_width, :format => :image)
744
690
745 # today red line
691 # today red line
746 if Date.today >= @date_from and Date.today <= date_to
692 if Date.today >= @date_from and Date.today <= date_to
747 gc.stroke('red')
693 gc.stroke('red')
748 x = (Date.today-@date_from+1)*zoom + subject_width
694 x = (Date.today-@date_from+1)*zoom + subject_width
749 gc.line(x, headers_heigth, x, headers_heigth + g_height-1)
695 gc.line(x, headers_heigth, x, headers_heigth + g_height-1)
750 end
696 end
751
697
752 gc.draw(imgl)
698 gc.draw(imgl)
753 imgl.format = format
699 imgl.format = format
754 imgl.to_blob
700 imgl.to_blob
755 end if Object.const_defined?(:Magick)
701 end if Object.const_defined?(:Magick)
756
702
757 def to_pdf
703 def to_pdf
758 pdf = ::Redmine::Export::PDF::IFPDF.new(current_language)
704 pdf = ::Redmine::Export::PDF::IFPDF.new(current_language)
759 pdf.SetTitle("#{l(:label_gantt)} #{project}")
705 pdf.SetTitle("#{l(:label_gantt)} #{project}")
760 pdf.AliasNbPages
706 pdf.AliasNbPages
761 pdf.footer_date = format_date(Date.today)
707 pdf.footer_date = format_date(Date.today)
762 pdf.AddPage("L")
708 pdf.AddPage("L")
763 pdf.SetFontStyle('B',12)
709 pdf.SetFontStyle('B',12)
764 pdf.SetX(15)
710 pdf.SetX(15)
765 pdf.Cell(PDF::LeftPaneWidth, 20, project.to_s)
711 pdf.Cell(PDF::LeftPaneWidth, 20, project.to_s)
766 pdf.Ln
712 pdf.Ln
767 pdf.SetFontStyle('B',9)
713 pdf.SetFontStyle('B',9)
768
714
769 subject_width = PDF::LeftPaneWidth
715 subject_width = PDF::LeftPaneWidth
770 header_heigth = 5
716 header_heigth = 5
771
717
772 headers_heigth = header_heigth
718 headers_heigth = header_heigth
773 show_weeks = false
719 show_weeks = false
774 show_days = false
720 show_days = false
775
721
776 if self.months < 7
722 if self.months < 7
777 show_weeks = true
723 show_weeks = true
778 headers_heigth = 2*header_heigth
724 headers_heigth = 2*header_heigth
779 if self.months < 3
725 if self.months < 3
780 show_days = true
726 show_days = true
781 headers_heigth = 3*header_heigth
727 headers_heigth = 3*header_heigth
782 end
728 end
783 end
729 end
784
730
785 g_width = PDF.right_pane_width
731 g_width = PDF.right_pane_width
786 zoom = (g_width) / (self.date_to - self.date_from + 1)
732 zoom = (g_width) / (self.date_to - self.date_from + 1)
787 g_height = 120
733 g_height = 120
788 t_height = g_height + headers_heigth
734 t_height = g_height + headers_heigth
789
735
790 y_start = pdf.GetY
736 y_start = pdf.GetY
791
737
792 # Months headers
738 # Months headers
793 month_f = self.date_from
739 month_f = self.date_from
794 left = subject_width
740 left = subject_width
795 height = header_heigth
741 height = header_heigth
796 self.months.times do
742 self.months.times do
797 width = ((month_f >> 1) - month_f) * zoom
743 width = ((month_f >> 1) - month_f) * zoom
798 pdf.SetY(y_start)
744 pdf.SetY(y_start)
799 pdf.SetX(left)
745 pdf.SetX(left)
800 pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
746 pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
801 left = left + width
747 left = left + width
802 month_f = month_f >> 1
748 month_f = month_f >> 1
803 end
749 end
804
750
805 # Weeks headers
751 # Weeks headers
806 if show_weeks
752 if show_weeks
807 left = subject_width
753 left = subject_width
808 height = header_heigth
754 height = header_heigth
809 if self.date_from.cwday == 1
755 if self.date_from.cwday == 1
810 # self.date_from is monday
756 # self.date_from is monday
811 week_f = self.date_from
757 week_f = self.date_from
812 else
758 else
813 # find next monday after self.date_from
759 # find next monday after self.date_from
814 week_f = self.date_from + (7 - self.date_from.cwday + 1)
760 week_f = self.date_from + (7 - self.date_from.cwday + 1)
815 width = (7 - self.date_from.cwday + 1) * zoom-1
761 width = (7 - self.date_from.cwday + 1) * zoom-1
816 pdf.SetY(y_start + header_heigth)
762 pdf.SetY(y_start + header_heigth)
817 pdf.SetX(left)
763 pdf.SetX(left)
818 pdf.Cell(width + 1, height, "", "LTR")
764 pdf.Cell(width + 1, height, "", "LTR")
819 left = left + width+1
765 left = left + width+1
820 end
766 end
821 while week_f <= self.date_to
767 while week_f <= self.date_to
822 width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom
768 width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom
823 pdf.SetY(y_start + header_heigth)
769 pdf.SetY(y_start + header_heigth)
824 pdf.SetX(left)
770 pdf.SetX(left)
825 pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
771 pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
826 left = left + width
772 left = left + width
827 week_f = week_f+7
773 week_f = week_f+7
828 end
774 end
829 end
775 end
830
776
831 # Days headers
777 # Days headers
832 if show_days
778 if show_days
833 left = subject_width
779 left = subject_width
834 height = header_heigth
780 height = header_heigth
835 wday = self.date_from.cwday
781 wday = self.date_from.cwday
836 pdf.SetFontStyle('B',7)
782 pdf.SetFontStyle('B',7)
837 (self.date_to - self.date_from + 1).to_i.times do
783 (self.date_to - self.date_from + 1).to_i.times do
838 width = zoom
784 width = zoom
839 pdf.SetY(y_start + 2 * header_heigth)
785 pdf.SetY(y_start + 2 * header_heigth)
840 pdf.SetX(left)
786 pdf.SetX(left)
841 pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C")
787 pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C")
842 left = left + width
788 left = left + width
843 wday = wday + 1
789 wday = wday + 1
844 wday = 1 if wday > 7
790 wday = 1 if wday > 7
845 end
791 end
846 end
792 end
847
793
848 pdf.SetY(y_start)
794 pdf.SetY(y_start)
849 pdf.SetX(15)
795 pdf.SetX(15)
850 pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1)
796 pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1)
851
797
852 # Tasks
798 # Tasks
853 top = headers_heigth + y_start
799 top = headers_heigth + y_start
854 options = {
800 options = {
855 :top => top,
801 :top => top,
856 :zoom => zoom,
802 :zoom => zoom,
857 :subject_width => subject_width,
803 :subject_width => subject_width,
858 :g_width => g_width,
804 :g_width => g_width,
859 :indent => 0,
805 :indent => 0,
860 :indent_increment => 5,
806 :indent_increment => 5,
861 :top_increment => 5,
807 :top_increment => 5,
862 :format => :pdf,
808 :format => :pdf,
863 :pdf => pdf
809 :pdf => pdf
864 }
810 }
865 render(options)
811 render(options)
866 pdf.Output
812 pdf.Output
867 end
813 end
868
814
869 private
815 private
870
816
871 def coordinates(start_date, end_date, progress, zoom=nil)
817 def coordinates(start_date, end_date, progress, zoom=nil)
872 zoom ||= @zoom
818 zoom ||= @zoom
873
819
874 coords = {}
820 coords = {}
875 if start_date && end_date && start_date < self.date_to && end_date > self.date_from
821 if start_date && end_date && start_date < self.date_to && end_date > self.date_from
876 if start_date > self.date_from
822 if start_date > self.date_from
877 coords[:start] = start_date - self.date_from
823 coords[:start] = start_date - self.date_from
878 coords[:bar_start] = start_date - self.date_from
824 coords[:bar_start] = start_date - self.date_from
879 else
825 else
880 coords[:bar_start] = 0
826 coords[:bar_start] = 0
881 end
827 end
882 if end_date < self.date_to
828 if end_date < self.date_to
883 coords[:end] = end_date - self.date_from
829 coords[:end] = end_date - self.date_from
884 coords[:bar_end] = end_date - self.date_from + 1
830 coords[:bar_end] = end_date - self.date_from + 1
885 else
831 else
886 coords[:bar_end] = self.date_to - self.date_from + 1
832 coords[:bar_end] = self.date_to - self.date_from + 1
887 end
833 end
888
834
889 if progress
835 if progress
890 progress_date = start_date + (end_date - start_date) * (progress / 100.0)
836 progress_date = start_date + (end_date - start_date) * (progress / 100.0)
891 if progress_date > self.date_from && progress_date > start_date
837 if progress_date > self.date_from && progress_date > start_date
892 if progress_date < self.date_to
838 if progress_date < self.date_to
893 coords[:bar_progress_end] = progress_date - self.date_from + 1
839 coords[:bar_progress_end] = progress_date - self.date_from + 1
894 else
840 else
895 coords[:bar_progress_end] = self.date_to - self.date_from + 1
841 coords[:bar_progress_end] = self.date_to - self.date_from + 1
896 end
842 end
897 end
843 end
898
844
899 if progress_date < Date.today
845 if progress_date < Date.today
900 late_date = [Date.today, end_date].min
846 late_date = [Date.today, end_date].min
901 if late_date > self.date_from && late_date > start_date
847 if late_date > self.date_from && late_date > start_date
902 if late_date < self.date_to
848 if late_date < self.date_to
903 coords[:bar_late_end] = late_date - self.date_from + 1
849 coords[:bar_late_end] = late_date - self.date_from + 1
904 else
850 else
905 coords[:bar_late_end] = self.date_to - self.date_from + 1
851 coords[:bar_late_end] = self.date_to - self.date_from + 1
906 end
852 end
907 end
853 end
908 end
854 end
909 end
855 end
910 end
856 end
911
857
912 # Transforms dates into pixels witdh
858 # Transforms dates into pixels witdh
913 coords.keys.each do |key|
859 coords.keys.each do |key|
914 coords[key] = (coords[key] * zoom).floor
860 coords[key] = (coords[key] * zoom).floor
915 end
861 end
916 coords
862 coords
917 end
863 end
918
864
919 # Sorts a collection of issues by start_date, due_date, id for gantt rendering
865 # Sorts a collection of issues by start_date, due_date, id for gantt rendering
920 def sort_issues!(issues)
866 def sort_issues!(issues)
921 issues.sort! do |a, b|
867 issues.sort! do |a, b|
922 cmp = 0
868 cmp = 0
923 cmp = (a.start_date <=> b.start_date) if a.start_date? && b.start_date?
869 cmp = (a.start_date <=> b.start_date) if a.start_date? && b.start_date?
924 cmp = (a.due_date <=> b.due_date) if cmp == 0 && a.due_date? && b.due_date?
870 cmp = (a.due_date <=> b.due_date) if cmp == 0 && a.due_date? && b.due_date?
925 cmp = (a.id <=> b.id) if cmp == 0
871 cmp = (a.id <=> b.id) if cmp == 0
926 cmp
872 cmp
927 end
873 end
928 end
874 end
929
875
930 def current_limit
876 def current_limit
931 if @max_rows
877 if @max_rows
932 @max_rows - @number_of_rows
878 @max_rows - @number_of_rows
933 else
879 else
934 nil
880 nil
935 end
881 end
936 end
882 end
937
883
938 def abort?
884 def abort?
939 if @max_rows && @number_of_rows >= @max_rows
885 if @max_rows && @number_of_rows >= @max_rows
940 @truncated = true
886 @truncated = true
941 end
887 end
942 end
888 end
943
889
944 def pdf_new_page?(options)
890 def pdf_new_page?(options)
945 if options[:top] > 180
891 if options[:top] > 180
946 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
892 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
947 options[:pdf].AddPage("L")
893 options[:pdf].AddPage("L")
948 options[:top] = 15
894 options[:top] = 15
949 options[:pdf].Line(15, options[:top] - 0.1, PDF::TotalWidth, options[:top] - 0.1)
895 options[:pdf].Line(15, options[:top] - 0.1, PDF::TotalWidth, options[:top] - 0.1)
950 end
896 end
951 end
897 end
952
898
953 def html_task(top, coords, options={})
899 def html_task(top, coords, options={})
954 output = ''
900 output = ''
955 # Renders the task bar, with progress and late
901 # Renders the task bar, with progress and late
956 if coords[:bar_start] && coords[:bar_end]
902 if coords[:bar_start] && coords[:bar_end]
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>"
903 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>"
958
904
959 if coords[:bar_late_end]
905 if coords[:bar_late_end]
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>"
906 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>"
961 end
907 end
962 if coords[:bar_progress_end]
908 if coords[:bar_progress_end]
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>"
909 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>"
964 end
910 end
965 end
911 end
966 # Renders the markers
912 # Renders the markers
967 if options[:markers]
913 if options[:markers]
968 if coords[:start]
914 if coords[:start]
969 output << "<div style='top:#{ top }px;left:#{ coords[:start] }px;width:15px;' class='#{options[:css]} marker starting'>&nbsp;</div>"
915 output << "<div style='top:#{ top }px;left:#{ coords[:start] }px;width:15px;' class='#{options[:css]} marker starting'>&nbsp;</div>"
970 end
916 end
971 if coords[:end]
917 if coords[:end]
972 output << "<div style='top:#{ top }px;left:#{ coords[:end] }px;width:15px;' class='#{options[:css]} marker ending'>&nbsp;</div>"
918 output << "<div style='top:#{ top }px;left:#{ coords[:end] }px;width:15px;' class='#{options[:css]} marker ending'>&nbsp;</div>"
973 end
919 end
974 end
920 end
975 # Renders the label on the right
921 # Renders the label on the right
976 if options[:label]
922 if options[:label]
977 output << "<div style='top:#{ top }px;left:#{ (coords[:bar_end] || 0) + 5 }px;' class='#{options[:css]} label'>"
923 output << "<div style='top:#{ top }px;left:#{ (coords[:bar_end] || 0) + 5 }px;' class='#{options[:css]} label'>"
978 output << options[:label]
924 output << options[:label]
979 output << "</div>"
925 output << "</div>"
980 end
926 end
981 # Renders the tooltip
927 # Renders the tooltip
982 if options[:issue] && coords[:bar_start] && coords[:bar_end]
928 if options[:issue] && coords[:bar_start] && coords[:bar_end]
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;'>"
929 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;'>"
984 output << '<span class="tip">'
930 output << '<span class="tip">'
985 output << view.render_issue_tooltip(options[:issue])
931 output << view.render_issue_tooltip(options[:issue])
986 output << "</span></div>"
932 output << "</span></div>"
987 end
933 end
988
934
989 output
935 output
990 end
936 end
991 end
937 end
992 end
938 end
993 end
939 end
@@ -1,946 +1,947
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 .task.label.project, .task.label.version { font-weight: bold; }
794
795
795 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
796 .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; }
797 .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; }
798 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
798
799
799 .task_todo.parent { background: #888; border: 1px solid #888; height: 6px;}
800 .task_todo.parent { background: #888; border: 1px solid #888; height: 6px;}
800 .task_late.parent, .task_done.parent { height: 3px;}
801 .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;}
802 .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;}
803 .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
804
804 .version.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
805 .version.task_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 .version.task_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 .version.task_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 .version.marker { background-image:url(../images/version_marker.png); background-repeat: no-repeat; border: 0; }
808
809
809 .project-line { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; }
810 .project.task_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;}
811 .project.task_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;}
812 .project.task_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;}
813 .project.marker { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; }
813
814
814 .version-behind-schedule a, .issue-behind-schedule a {color: #f66914;}
815 .version-behind-schedule a, .issue-behind-schedule a {color: #f66914;}
815 .version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;}
816 .version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;}
816
817
817 /***** Icons *****/
818 /***** Icons *****/
818 .icon {
819 .icon {
819 background-position: 0% 50%;
820 background-position: 0% 50%;
820 background-repeat: no-repeat;
821 background-repeat: no-repeat;
821 padding-left: 20px;
822 padding-left: 20px;
822 padding-top: 2px;
823 padding-top: 2px;
823 padding-bottom: 3px;
824 padding-bottom: 3px;
824 }
825 }
825
826
826 .icon-add { background-image: url(../images/add.png); }
827 .icon-add { background-image: url(../images/add.png); }
827 .icon-edit { background-image: url(../images/edit.png); }
828 .icon-edit { background-image: url(../images/edit.png); }
828 .icon-copy { background-image: url(../images/copy.png); }
829 .icon-copy { background-image: url(../images/copy.png); }
829 .icon-duplicate { background-image: url(../images/duplicate.png); }
830 .icon-duplicate { background-image: url(../images/duplicate.png); }
830 .icon-del { background-image: url(../images/delete.png); }
831 .icon-del { background-image: url(../images/delete.png); }
831 .icon-move { background-image: url(../images/move.png); }
832 .icon-move { background-image: url(../images/move.png); }
832 .icon-save { background-image: url(../images/save.png); }
833 .icon-save { background-image: url(../images/save.png); }
833 .icon-cancel { background-image: url(../images/cancel.png); }
834 .icon-cancel { background-image: url(../images/cancel.png); }
834 .icon-multiple { background-image: url(../images/table_multiple.png); }
835 .icon-multiple { background-image: url(../images/table_multiple.png); }
835 .icon-folder { background-image: url(../images/folder.png); }
836 .icon-folder { background-image: url(../images/folder.png); }
836 .open .icon-folder { background-image: url(../images/folder_open.png); }
837 .open .icon-folder { background-image: url(../images/folder_open.png); }
837 .icon-package { background-image: url(../images/package.png); }
838 .icon-package { background-image: url(../images/package.png); }
838 .icon-home { background-image: url(../images/home.png); }
839 .icon-home { background-image: url(../images/home.png); }
839 .icon-user { background-image: url(../images/user.png); }
840 .icon-user { background-image: url(../images/user.png); }
840 .icon-projects { background-image: url(../images/projects.png); }
841 .icon-projects { background-image: url(../images/projects.png); }
841 .icon-help { background-image: url(../images/help.png); }
842 .icon-help { background-image: url(../images/help.png); }
842 .icon-attachment { background-image: url(../images/attachment.png); }
843 .icon-attachment { background-image: url(../images/attachment.png); }
843 .icon-history { background-image: url(../images/history.png); }
844 .icon-history { background-image: url(../images/history.png); }
844 .icon-time { background-image: url(../images/time.png); }
845 .icon-time { background-image: url(../images/time.png); }
845 .icon-time-add { background-image: url(../images/time_add.png); }
846 .icon-time-add { background-image: url(../images/time_add.png); }
846 .icon-stats { background-image: url(../images/stats.png); }
847 .icon-stats { background-image: url(../images/stats.png); }
847 .icon-warning { background-image: url(../images/warning.png); }
848 .icon-warning { background-image: url(../images/warning.png); }
848 .icon-fav { background-image: url(../images/fav.png); }
849 .icon-fav { background-image: url(../images/fav.png); }
849 .icon-fav-off { background-image: url(../images/fav_off.png); }
850 .icon-fav-off { background-image: url(../images/fav_off.png); }
850 .icon-reload { background-image: url(../images/reload.png); }
851 .icon-reload { background-image: url(../images/reload.png); }
851 .icon-lock { background-image: url(../images/locked.png); }
852 .icon-lock { background-image: url(../images/locked.png); }
852 .icon-unlock { background-image: url(../images/unlock.png); }
853 .icon-unlock { background-image: url(../images/unlock.png); }
853 .icon-checked { background-image: url(../images/true.png); }
854 .icon-checked { background-image: url(../images/true.png); }
854 .icon-details { background-image: url(../images/zoom_in.png); }
855 .icon-details { background-image: url(../images/zoom_in.png); }
855 .icon-report { background-image: url(../images/report.png); }
856 .icon-report { background-image: url(../images/report.png); }
856 .icon-comment { background-image: url(../images/comment.png); }
857 .icon-comment { background-image: url(../images/comment.png); }
857 .icon-summary { background-image: url(../images/lightning.png); }
858 .icon-summary { background-image: url(../images/lightning.png); }
858 .icon-server-authentication { background-image: url(../images/server_key.png); }
859 .icon-server-authentication { background-image: url(../images/server_key.png); }
859 .icon-issue { background-image: url(../images/ticket.png); }
860 .icon-issue { background-image: url(../images/ticket.png); }
860 .icon-zoom-in { background-image: url(../images/zoom_in.png); }
861 .icon-zoom-in { background-image: url(../images/zoom_in.png); }
861 .icon-zoom-out { background-image: url(../images/zoom_out.png); }
862 .icon-zoom-out { background-image: url(../images/zoom_out.png); }
862
863
863 .icon-file { background-image: url(../images/files/default.png); }
864 .icon-file { background-image: url(../images/files/default.png); }
864 .icon-file.text-plain { background-image: url(../images/files/text.png); }
865 .icon-file.text-plain { background-image: url(../images/files/text.png); }
865 .icon-file.text-x-c { background-image: url(../images/files/c.png); }
866 .icon-file.text-x-c { background-image: url(../images/files/c.png); }
866 .icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); }
867 .icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); }
867 .icon-file.text-x-php { background-image: url(../images/files/php.png); }
868 .icon-file.text-x-php { background-image: url(../images/files/php.png); }
868 .icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); }
869 .icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); }
869 .icon-file.text-xml { background-image: url(../images/files/xml.png); }
870 .icon-file.text-xml { background-image: url(../images/files/xml.png); }
870 .icon-file.image-gif { background-image: url(../images/files/image.png); }
871 .icon-file.image-gif { background-image: url(../images/files/image.png); }
871 .icon-file.image-jpeg { background-image: url(../images/files/image.png); }
872 .icon-file.image-jpeg { background-image: url(../images/files/image.png); }
872 .icon-file.image-png { background-image: url(../images/files/image.png); }
873 .icon-file.image-png { background-image: url(../images/files/image.png); }
873 .icon-file.image-tiff { background-image: url(../images/files/image.png); }
874 .icon-file.image-tiff { background-image: url(../images/files/image.png); }
874 .icon-file.application-pdf { background-image: url(../images/files/pdf.png); }
875 .icon-file.application-pdf { background-image: url(../images/files/pdf.png); }
875 .icon-file.application-zip { background-image: url(../images/files/zip.png); }
876 .icon-file.application-zip { background-image: url(../images/files/zip.png); }
876 .icon-file.application-x-gzip { background-image: url(../images/files/zip.png); }
877 .icon-file.application-x-gzip { background-image: url(../images/files/zip.png); }
877
878
878 img.gravatar {
879 img.gravatar {
879 padding: 2px;
880 padding: 2px;
880 border: solid 1px #d5d5d5;
881 border: solid 1px #d5d5d5;
881 background: #fff;
882 background: #fff;
882 }
883 }
883
884
884 div.issue img.gravatar {
885 div.issue img.gravatar {
885 float: right;
886 float: right;
886 margin: 0 0 0 1em;
887 margin: 0 0 0 1em;
887 padding: 5px;
888 padding: 5px;
888 }
889 }
889
890
890 div.issue table img.gravatar {
891 div.issue table img.gravatar {
891 height: 14px;
892 height: 14px;
892 width: 14px;
893 width: 14px;
893 padding: 2px;
894 padding: 2px;
894 float: left;
895 float: left;
895 margin: 0 0.5em 0 0;
896 margin: 0 0.5em 0 0;
896 }
897 }
897
898
898 h2 img.gravatar {
899 h2 img.gravatar {
899 padding: 3px;
900 padding: 3px;
900 margin: -2px 4px -4px 0;
901 margin: -2px 4px -4px 0;
901 vertical-align: top;
902 vertical-align: top;
902 }
903 }
903
904
904 h4 img.gravatar {
905 h4 img.gravatar {
905 padding: 3px;
906 padding: 3px;
906 margin: -6px 0 -4px 0;
907 margin: -6px 0 -4px 0;
907 vertical-align: top;
908 vertical-align: top;
908 }
909 }
909
910
910 td.username img.gravatar {
911 td.username img.gravatar {
911 float: left;
912 float: left;
912 margin: 0 1em 0 0;
913 margin: 0 1em 0 0;
913 }
914 }
914
915
915 #activity dt img.gravatar {
916 #activity dt img.gravatar {
916 float: left;
917 float: left;
917 margin: 0 1em 1em 0;
918 margin: 0 1em 1em 0;
918 }
919 }
919
920
920 /* Used on 12px Gravatar img tags without the icon background */
921 /* Used on 12px Gravatar img tags without the icon background */
921 .icon-gravatar {
922 .icon-gravatar {
922 float: left;
923 float: left;
923 margin-right: 4px;
924 margin-right: 4px;
924 }
925 }
925
926
926 #activity dt,
927 #activity dt,
927 .journal {
928 .journal {
928 clear: left;
929 clear: left;
929 }
930 }
930
931
931 .journal-link {
932 .journal-link {
932 float: right;
933 float: right;
933 }
934 }
934
935
935 h2 img { vertical-align:middle; }
936 h2 img { vertical-align:middle; }
936
937
937 .hascontextmenu { cursor: context-menu; }
938 .hascontextmenu { cursor: context-menu; }
938
939
939 /***** Media print specific styles *****/
940 /***** Media print specific styles *****/
940 @media print {
941 @media print {
941 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
942 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
942 #main { background: #fff; }
943 #main { background: #fff; }
943 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
944 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
944 #wiki_add_attachment { display:none; }
945 #wiki_add_attachment { display:none; }
945 .hide-when-print { display: none; }
946 .hide-when-print { display: none; }
946 }
947 }
@@ -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.task_todo"
220 assert_select "div.project-line.starting"
220 assert_select "div.project.starting"
221 assert_select "div.project-line.ending"
221 assert_select "div.project.ending"
222 assert_select "div.label.project-name", /#{@project.name}/
222 assert_select "div.label.project", /#{@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.version.task_todo"
228 assert_select "div.version.task_todo"
229 assert_select "div.version.starting"
229 assert_select "div.version.starting"
230 assert_select "div.version.ending"
230 assert_select "div.version.ending"
231 assert_select "div.label.version", /#{@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 => 1.week.ago.to_date,
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.task_todo[style*=left:28px]", true, @response.body
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.task_todo[style*=width:58px]", true, @response.body
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.task_late[style*=left:28px]", true, @response.body
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.task_late[style*=width:30px]", true, @response.body
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.task_done[style*=left:28px]", true, @response.body
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.task_done[style*=width:18px]", true, @response.body
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.starting", false, @response.body
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.starting[style*=left:28px]", true, @response.body
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.ending", false, @response.body
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.ending[style*=left:84px]", true, @response.body
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.label", /#{@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.label", /#{@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.label", /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 => 1.week.ago.to_date,
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.version.task_todo[style*=left:28px]", true, @response.body
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.version.task_todo[style*=width:58px]", true, @response.body
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.version.task_late[style*=left:28px]", true, @response.body
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.version.task_late[style*=width:30px]", true, @response.body
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.version.task_done[style*=left:28px]", true, @response.body
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.version.task_done[style*=width:18px]", true, @response.body
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.version.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.version.starting[style*=left:28px]", true, @response.body
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.version.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.version.ending[style*=left:84px]", true, @response.body
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.label", /#{@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.label", /#{@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.label", /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