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