##// END OF EJS Templates
Gantt chart can now be exported to a graphic file (png)....
Jean-Philippe Lang -
r660:edba1f692b5a
parent child
Show More
@@ -1,676 +1,681
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 'csv'
18 require 'csv'
19
19
20 class ProjectsController < ApplicationController
20 class ProjectsController < ApplicationController
21 layout 'base'
21 layout 'base'
22 before_filter :find_project, :except => [ :index, :list, :add ]
22 before_filter :find_project, :except => [ :index, :list, :add ]
23 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy ]
23 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy ]
24 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
24 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
25
25
26 cache_sweeper :project_sweeper, :only => [ :add, :edit, :archive, :unarchive, :destroy ]
26 cache_sweeper :project_sweeper, :only => [ :add, :edit, :archive, :unarchive, :destroy ]
27 cache_sweeper :issue_sweeper, :only => [ :add_issue ]
27 cache_sweeper :issue_sweeper, :only => [ :add_issue ]
28 cache_sweeper :version_sweeper, :only => [ :add_version ]
28 cache_sweeper :version_sweeper, :only => [ :add_version ]
29
29
30 helper :sort
30 helper :sort
31 include SortHelper
31 include SortHelper
32 helper :custom_fields
32 helper :custom_fields
33 include CustomFieldsHelper
33 include CustomFieldsHelper
34 helper :ifpdf
34 helper :ifpdf
35 include IfpdfHelper
35 include IfpdfHelper
36 helper IssuesHelper
36 helper IssuesHelper
37 helper :queries
37 helper :queries
38 include QueriesHelper
38 include QueriesHelper
39 helper :repositories
39 helper :repositories
40 include RepositoriesHelper
40 include RepositoriesHelper
41 include ProjectsHelper
41
42
42 def index
43 def index
43 list
44 list
44 render :action => 'list' unless request.xhr?
45 render :action => 'list' unless request.xhr?
45 end
46 end
46
47
47 # Lists public projects
48 # Lists public projects
48 def list
49 def list
49 sort_init "#{Project.table_name}.name", "asc"
50 sort_init "#{Project.table_name}.name", "asc"
50 sort_update
51 sort_update
51 @project_count = Project.count(:all, :conditions => Project.visible_by(logged_in_user))
52 @project_count = Project.count(:all, :conditions => Project.visible_by(logged_in_user))
52 @project_pages = Paginator.new self, @project_count,
53 @project_pages = Paginator.new self, @project_count,
53 15,
54 15,
54 params['page']
55 params['page']
55 @projects = Project.find :all, :order => sort_clause,
56 @projects = Project.find :all, :order => sort_clause,
56 :conditions => Project.visible_by(logged_in_user),
57 :conditions => Project.visible_by(logged_in_user),
57 :include => :parent,
58 :include => :parent,
58 :limit => @project_pages.items_per_page,
59 :limit => @project_pages.items_per_page,
59 :offset => @project_pages.current.offset
60 :offset => @project_pages.current.offset
60
61
61 render :action => "list", :layout => false if request.xhr?
62 render :action => "list", :layout => false if request.xhr?
62 end
63 end
63
64
64 # Add a new project
65 # Add a new project
65 def add
66 def add
66 @custom_fields = IssueCustomField.find(:all)
67 @custom_fields = IssueCustomField.find(:all)
67 @root_projects = Project.find(:all, :conditions => "parent_id is null")
68 @root_projects = Project.find(:all, :conditions => "parent_id is null")
68 @project = Project.new(params[:project])
69 @project = Project.new(params[:project])
69 if request.get?
70 if request.get?
70 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project) }
71 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project) }
71 else
72 else
72 @project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
73 @project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
73 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
74 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
74 @project.custom_values = @custom_values
75 @project.custom_values = @custom_values
75 if params[:repository_enabled] && params[:repository_enabled] == "1"
76 if params[:repository_enabled] && params[:repository_enabled] == "1"
76 @project.repository = Repository.factory(params[:repository_scm])
77 @project.repository = Repository.factory(params[:repository_scm])
77 @project.repository.attributes = params[:repository]
78 @project.repository.attributes = params[:repository]
78 end
79 end
79 if "1" == params[:wiki_enabled]
80 if "1" == params[:wiki_enabled]
80 @project.wiki = Wiki.new
81 @project.wiki = Wiki.new
81 @project.wiki.attributes = params[:wiki]
82 @project.wiki.attributes = params[:wiki]
82 end
83 end
83 if @project.save
84 if @project.save
84 flash[:notice] = l(:notice_successful_create)
85 flash[:notice] = l(:notice_successful_create)
85 redirect_to :controller => 'admin', :action => 'projects'
86 redirect_to :controller => 'admin', :action => 'projects'
86 end
87 end
87 end
88 end
88 end
89 end
89
90
90 # Show @project
91 # Show @project
91 def show
92 def show
92 @custom_values = @project.custom_values.find(:all, :include => :custom_field)
93 @custom_values = @project.custom_values.find(:all, :include => :custom_field)
93 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
94 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
94 @subprojects = @project.active_children
95 @subprojects = @project.active_children
95 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
96 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
96 @trackers = Tracker.find(:all, :order => 'position')
97 @trackers = Tracker.find(:all, :order => 'position')
97 @open_issues_by_tracker = Issue.count(:group => :tracker, :joins => "INNER JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{Issue.table_name}.status_id", :conditions => ["project_id=? and #{IssueStatus.table_name}.is_closed=?", @project.id, false])
98 @open_issues_by_tracker = Issue.count(:group => :tracker, :joins => "INNER JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{Issue.table_name}.status_id", :conditions => ["project_id=? and #{IssueStatus.table_name}.is_closed=?", @project.id, false])
98 @total_issues_by_tracker = Issue.count(:group => :tracker, :conditions => ["project_id=?", @project.id])
99 @total_issues_by_tracker = Issue.count(:group => :tracker, :conditions => ["project_id=?", @project.id])
99
100
100 @key = logged_in_user.get_or_create_rss_key.value if logged_in_user
101 @key = logged_in_user.get_or_create_rss_key.value if logged_in_user
101 end
102 end
102
103
103 def settings
104 def settings
104 @root_projects = Project::find(:all, :conditions => ["parent_id is null and id <> ?", @project.id])
105 @root_projects = Project::find(:all, :conditions => ["parent_id is null and id <> ?", @project.id])
105 @custom_fields = IssueCustomField.find(:all)
106 @custom_fields = IssueCustomField.find(:all)
106 @issue_category ||= IssueCategory.new
107 @issue_category ||= IssueCategory.new
107 @member ||= @project.members.new
108 @member ||= @project.members.new
108 @custom_values ||= ProjectCustomField.find(:all).collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
109 @custom_values ||= ProjectCustomField.find(:all).collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
109 end
110 end
110
111
111 # Edit @project
112 # Edit @project
112 def edit
113 def edit
113 if request.post?
114 if request.post?
114 @project.custom_fields = IssueCustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
115 @project.custom_fields = IssueCustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
115 if params[:custom_fields]
116 if params[:custom_fields]
116 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
117 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
117 @project.custom_values = @custom_values
118 @project.custom_values = @custom_values
118 end
119 end
119 if params[:repository_enabled]
120 if params[:repository_enabled]
120 case params[:repository_enabled]
121 case params[:repository_enabled]
121 when "0"
122 when "0"
122 @project.repository = nil
123 @project.repository = nil
123 when "1"
124 when "1"
124 @project.repository ||= Repository.factory(params[:repository_scm])
125 @project.repository ||= Repository.factory(params[:repository_scm])
125 @project.repository.update_attributes params[:repository] if @project.repository
126 @project.repository.update_attributes params[:repository] if @project.repository
126 end
127 end
127 end
128 end
128 if params[:wiki_enabled]
129 if params[:wiki_enabled]
129 case params[:wiki_enabled]
130 case params[:wiki_enabled]
130 when "0"
131 when "0"
131 @project.wiki.destroy if @project.wiki
132 @project.wiki.destroy if @project.wiki
132 when "1"
133 when "1"
133 @project.wiki ||= Wiki.new
134 @project.wiki ||= Wiki.new
134 @project.wiki.update_attributes params[:wiki]
135 @project.wiki.update_attributes params[:wiki]
135 end
136 end
136 end
137 end
137 @project.attributes = params[:project]
138 @project.attributes = params[:project]
138 if @project.save
139 if @project.save
139 flash[:notice] = l(:notice_successful_update)
140 flash[:notice] = l(:notice_successful_update)
140 redirect_to :action => 'settings', :id => @project
141 redirect_to :action => 'settings', :id => @project
141 else
142 else
142 settings
143 settings
143 render :action => 'settings'
144 render :action => 'settings'
144 end
145 end
145 end
146 end
146 end
147 end
147
148
148 def archive
149 def archive
149 @project.archive if request.post? && @project.active?
150 @project.archive if request.post? && @project.active?
150 redirect_to :controller => 'admin', :action => 'projects'
151 redirect_to :controller => 'admin', :action => 'projects'
151 end
152 end
152
153
153 def unarchive
154 def unarchive
154 @project.unarchive if request.post? && !@project.active?
155 @project.unarchive if request.post? && !@project.active?
155 redirect_to :controller => 'admin', :action => 'projects'
156 redirect_to :controller => 'admin', :action => 'projects'
156 end
157 end
157
158
158 # Delete @project
159 # Delete @project
159 def destroy
160 def destroy
160 @project_to_destroy = @project
161 @project_to_destroy = @project
161 if request.post? and params[:confirm]
162 if request.post? and params[:confirm]
162 @project_to_destroy.destroy
163 @project_to_destroy.destroy
163 redirect_to :controller => 'admin', :action => 'projects'
164 redirect_to :controller => 'admin', :action => 'projects'
164 end
165 end
165 # hide project in layout
166 # hide project in layout
166 @project = nil
167 @project = nil
167 end
168 end
168
169
169 # Add a new issue category to @project
170 # Add a new issue category to @project
170 def add_issue_category
171 def add_issue_category
171 @category = @project.issue_categories.build(params[:category])
172 @category = @project.issue_categories.build(params[:category])
172 if request.post? and @category.save
173 if request.post? and @category.save
173 respond_to do |format|
174 respond_to do |format|
174 format.html do
175 format.html do
175 flash[:notice] = l(:notice_successful_create)
176 flash[:notice] = l(:notice_successful_create)
176 redirect_to :action => 'settings', :tab => 'categories', :id => @project
177 redirect_to :action => 'settings', :tab => 'categories', :id => @project
177 end
178 end
178 format.js do
179 format.js do
179 # IE doesn't support the replace_html rjs method for select box options
180 # IE doesn't support the replace_html rjs method for select box options
180 render(:update) {|page| page.replace "issue_category_id",
181 render(:update) {|page| page.replace "issue_category_id",
181 content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
182 content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
182 }
183 }
183 end
184 end
184 end
185 end
185 end
186 end
186 end
187 end
187
188
188 # Add a new version to @project
189 # Add a new version to @project
189 def add_version
190 def add_version
190 @version = @project.versions.build(params[:version])
191 @version = @project.versions.build(params[:version])
191 if request.post? and @version.save
192 if request.post? and @version.save
192 flash[:notice] = l(:notice_successful_create)
193 flash[:notice] = l(:notice_successful_create)
193 redirect_to :action => 'settings', :tab => 'versions', :id => @project
194 redirect_to :action => 'settings', :tab => 'versions', :id => @project
194 end
195 end
195 end
196 end
196
197
197 # Add a new member to @project
198 # Add a new member to @project
198 def add_member
199 def add_member
199 @member = @project.members.build(params[:member])
200 @member = @project.members.build(params[:member])
200 if request.post? && @member.save
201 if request.post? && @member.save
201 respond_to do |format|
202 respond_to do |format|
202 format.html { redirect_to :action => 'settings', :tab => 'members', :id => @project }
203 format.html { redirect_to :action => 'settings', :tab => 'members', :id => @project }
203 format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'members'} }
204 format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'members'} }
204 end
205 end
205 else
206 else
206 settings
207 settings
207 render :action => 'settings'
208 render :action => 'settings'
208 end
209 end
209 end
210 end
210
211
211 # Show members list of @project
212 # Show members list of @project
212 def list_members
213 def list_members
213 @members = @project.members.find(:all)
214 @members = @project.members.find(:all)
214 end
215 end
215
216
216 # Add a new document to @project
217 # Add a new document to @project
217 def add_document
218 def add_document
218 @categories = Enumeration::get_values('DCAT')
219 @categories = Enumeration::get_values('DCAT')
219 @document = @project.documents.build(params[:document])
220 @document = @project.documents.build(params[:document])
220 if request.post? and @document.save
221 if request.post? and @document.save
221 # Save the attachments
222 # Save the attachments
222 params[:attachments].each { |a|
223 params[:attachments].each { |a|
223 Attachment.create(:container => @document, :file => a, :author => logged_in_user) unless a.size == 0
224 Attachment.create(:container => @document, :file => a, :author => logged_in_user) unless a.size == 0
224 } if params[:attachments] and params[:attachments].is_a? Array
225 } if params[:attachments] and params[:attachments].is_a? Array
225 flash[:notice] = l(:notice_successful_create)
226 flash[:notice] = l(:notice_successful_create)
226 Mailer.deliver_document_add(@document) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
227 Mailer.deliver_document_add(@document) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
227 redirect_to :action => 'list_documents', :id => @project
228 redirect_to :action => 'list_documents', :id => @project
228 end
229 end
229 end
230 end
230
231
231 # Show documents list of @project
232 # Show documents list of @project
232 def list_documents
233 def list_documents
233 @documents = @project.documents.find :all, :include => :category
234 @documents = @project.documents.find :all, :include => :category
234 end
235 end
235
236
236 # Add a new issue to @project
237 # Add a new issue to @project
237 def add_issue
238 def add_issue
238 @tracker = Tracker.find(params[:tracker_id])
239 @tracker = Tracker.find(params[:tracker_id])
239 @priorities = Enumeration::get_values('IPRI')
240 @priorities = Enumeration::get_values('IPRI')
240
241
241 default_status = IssueStatus.default
242 default_status = IssueStatus.default
242 unless default_status
243 unless default_status
243 flash.now[:error] = 'No default issue status defined. Please check your configuration.'
244 flash.now[:error] = 'No default issue status defined. Please check your configuration.'
244 render :nothing => true, :layout => true
245 render :nothing => true, :layout => true
245 return
246 return
246 end
247 end
247 @issue = Issue.new(:project => @project, :tracker => @tracker)
248 @issue = Issue.new(:project => @project, :tracker => @tracker)
248 @issue.status = default_status
249 @issue.status = default_status
249 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker))if logged_in_user
250 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker))if logged_in_user
250 if request.get?
251 if request.get?
251 @issue.start_date = Date.today
252 @issue.start_date = Date.today
252 @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) }
253 @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) }
253 else
254 else
254 @issue.attributes = params[:issue]
255 @issue.attributes = params[:issue]
255
256
256 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
257 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
257 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
258 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
258
259
259 @issue.author_id = self.logged_in_user.id if self.logged_in_user
260 @issue.author_id = self.logged_in_user.id if self.logged_in_user
260 # Multiple file upload
261 # Multiple file upload
261 @attachments = []
262 @attachments = []
262 params[:attachments].each { |a|
263 params[:attachments].each { |a|
263 @attachments << Attachment.new(:container => @issue, :file => a, :author => logged_in_user) unless a.size == 0
264 @attachments << Attachment.new(:container => @issue, :file => a, :author => logged_in_user) unless a.size == 0
264 } if params[:attachments] and params[:attachments].is_a? Array
265 } if params[:attachments] and params[:attachments].is_a? Array
265 @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
266 @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
266 @issue.custom_values = @custom_values
267 @issue.custom_values = @custom_values
267 if @issue.save
268 if @issue.save
268 @attachments.each(&:save)
269 @attachments.each(&:save)
269 flash[:notice] = l(:notice_successful_create)
270 flash[:notice] = l(:notice_successful_create)
270 Mailer.deliver_issue_add(@issue) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
271 Mailer.deliver_issue_add(@issue) if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
271 redirect_to :action => 'list_issues', :id => @project
272 redirect_to :action => 'list_issues', :id => @project
272 end
273 end
273 end
274 end
274 end
275 end
275
276
276 # Show filtered/sorted issues list of @project
277 # Show filtered/sorted issues list of @project
277 def list_issues
278 def list_issues
278 sort_init "#{Issue.table_name}.id", "desc"
279 sort_init "#{Issue.table_name}.id", "desc"
279 sort_update
280 sort_update
280
281
281 retrieve_query
282 retrieve_query
282
283
283 @results_per_page_options = [ 15, 25, 50, 100 ]
284 @results_per_page_options = [ 15, 25, 50, 100 ]
284 if params[:per_page] and @results_per_page_options.include? params[:per_page].to_i
285 if params[:per_page] and @results_per_page_options.include? params[:per_page].to_i
285 @results_per_page = params[:per_page].to_i
286 @results_per_page = params[:per_page].to_i
286 session[:results_per_page] = @results_per_page
287 session[:results_per_page] = @results_per_page
287 else
288 else
288 @results_per_page = session[:results_per_page] || 25
289 @results_per_page = session[:results_per_page] || 25
289 end
290 end
290
291
291 if @query.valid?
292 if @query.valid?
292 @issue_count = Issue.count(:include => [:status, :project, :custom_values], :conditions => @query.statement)
293 @issue_count = Issue.count(:include => [:status, :project, :custom_values], :conditions => @query.statement)
293 @issue_pages = Paginator.new self, @issue_count, @results_per_page, params['page']
294 @issue_pages = Paginator.new self, @issue_count, @results_per_page, params['page']
294 @issues = Issue.find :all, :order => sort_clause,
295 @issues = Issue.find :all, :order => sort_clause,
295 :include => [ :assigned_to, :status, :tracker, :project, :priority, :custom_values ],
296 :include => [ :assigned_to, :status, :tracker, :project, :priority, :custom_values ],
296 :conditions => @query.statement,
297 :conditions => @query.statement,
297 :limit => @issue_pages.items_per_page,
298 :limit => @issue_pages.items_per_page,
298 :offset => @issue_pages.current.offset
299 :offset => @issue_pages.current.offset
299 end
300 end
300 render :layout => false if request.xhr?
301 render :layout => false if request.xhr?
301 end
302 end
302
303
303 # Export filtered/sorted issues list to CSV
304 # Export filtered/sorted issues list to CSV
304 def export_issues_csv
305 def export_issues_csv
305 sort_init "#{Issue.table_name}.id", "desc"
306 sort_init "#{Issue.table_name}.id", "desc"
306 sort_update
307 sort_update
307
308
308 retrieve_query
309 retrieve_query
309 render :action => 'list_issues' and return unless @query.valid?
310 render :action => 'list_issues' and return unless @query.valid?
310
311
311 @issues = Issue.find :all, :order => sort_clause,
312 @issues = Issue.find :all, :order => sort_clause,
312 :include => [ :assigned_to, :author, :status, :tracker, :priority, :project, {:custom_values => :custom_field} ],
313 :include => [ :assigned_to, :author, :status, :tracker, :priority, :project, {:custom_values => :custom_field} ],
313 :conditions => @query.statement,
314 :conditions => @query.statement,
314 :limit => Setting.issues_export_limit.to_i
315 :limit => Setting.issues_export_limit.to_i
315
316
316 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
317 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
317 export = StringIO.new
318 export = StringIO.new
318 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
319 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
319 # csv header fields
320 # csv header fields
320 headers = [ "#", l(:field_status),
321 headers = [ "#", l(:field_status),
321 l(:field_project),
322 l(:field_project),
322 l(:field_tracker),
323 l(:field_tracker),
323 l(:field_priority),
324 l(:field_priority),
324 l(:field_subject),
325 l(:field_subject),
325 l(:field_assigned_to),
326 l(:field_assigned_to),
326 l(:field_author),
327 l(:field_author),
327 l(:field_start_date),
328 l(:field_start_date),
328 l(:field_due_date),
329 l(:field_due_date),
329 l(:field_done_ratio),
330 l(:field_done_ratio),
330 l(:field_created_on),
331 l(:field_created_on),
331 l(:field_updated_on)
332 l(:field_updated_on)
332 ]
333 ]
333 for custom_field in @project.all_custom_fields
334 for custom_field in @project.all_custom_fields
334 headers << custom_field.name
335 headers << custom_field.name
335 end
336 end
336 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
337 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
337 # csv lines
338 # csv lines
338 @issues.each do |issue|
339 @issues.each do |issue|
339 fields = [issue.id, issue.status.name,
340 fields = [issue.id, issue.status.name,
340 issue.project.name,
341 issue.project.name,
341 issue.tracker.name,
342 issue.tracker.name,
342 issue.priority.name,
343 issue.priority.name,
343 issue.subject,
344 issue.subject,
344 (issue.assigned_to ? issue.assigned_to.name : ""),
345 (issue.assigned_to ? issue.assigned_to.name : ""),
345 issue.author.name,
346 issue.author.name,
346 issue.start_date ? l_date(issue.start_date) : nil,
347 issue.start_date ? l_date(issue.start_date) : nil,
347 issue.due_date ? l_date(issue.due_date) : nil,
348 issue.due_date ? l_date(issue.due_date) : nil,
348 issue.done_ratio,
349 issue.done_ratio,
349 l_datetime(issue.created_on),
350 l_datetime(issue.created_on),
350 l_datetime(issue.updated_on)
351 l_datetime(issue.updated_on)
351 ]
352 ]
352 for custom_field in @project.all_custom_fields
353 for custom_field in @project.all_custom_fields
353 fields << (show_value issue.custom_value_for(custom_field))
354 fields << (show_value issue.custom_value_for(custom_field))
354 end
355 end
355 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
356 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
356 end
357 end
357 end
358 end
358 export.rewind
359 export.rewind
359 send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv')
360 send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv')
360 end
361 end
361
362
362 # Export filtered/sorted issues to PDF
363 # Export filtered/sorted issues to PDF
363 def export_issues_pdf
364 def export_issues_pdf
364 sort_init "#{Issue.table_name}.id", "desc"
365 sort_init "#{Issue.table_name}.id", "desc"
365 sort_update
366 sort_update
366
367
367 retrieve_query
368 retrieve_query
368 render :action => 'list_issues' and return unless @query.valid?
369 render :action => 'list_issues' and return unless @query.valid?
369
370
370 @issues = Issue.find :all, :order => sort_clause,
371 @issues = Issue.find :all, :order => sort_clause,
371 :include => [ :author, :status, :tracker, :priority, :project, :custom_values ],
372 :include => [ :author, :status, :tracker, :priority, :project, :custom_values ],
372 :conditions => @query.statement,
373 :conditions => @query.statement,
373 :limit => Setting.issues_export_limit.to_i
374 :limit => Setting.issues_export_limit.to_i
374
375
375 @options_for_rfpdf ||= {}
376 @options_for_rfpdf ||= {}
376 @options_for_rfpdf[:file_name] = "export.pdf"
377 @options_for_rfpdf[:file_name] = "export.pdf"
377 render :layout => false
378 render :layout => false
378 end
379 end
379
380
380 def move_issues
381 def move_issues
381 @issues = @project.issues.find(params[:issue_ids]) if params[:issue_ids]
382 @issues = @project.issues.find(params[:issue_ids]) if params[:issue_ids]
382 redirect_to :action => 'list_issues', :id => @project and return unless @issues
383 redirect_to :action => 'list_issues', :id => @project and return unless @issues
383 @projects = []
384 @projects = []
384 # find projects to which the user is allowed to move the issue
385 # find projects to which the user is allowed to move the issue
385 @logged_in_user.memberships.each {|m| @projects << m.project if Permission.allowed_to_role("projects/move_issues", m.role)}
386 @logged_in_user.memberships.each {|m| @projects << m.project if Permission.allowed_to_role("projects/move_issues", m.role)}
386 # issue can be moved to any tracker
387 # issue can be moved to any tracker
387 @trackers = Tracker.find(:all)
388 @trackers = Tracker.find(:all)
388 if request.post? and params[:new_project_id] and params[:new_tracker_id]
389 if request.post? and params[:new_project_id] and params[:new_tracker_id]
389 new_project = Project.find(params[:new_project_id])
390 new_project = Project.find(params[:new_project_id])
390 new_tracker = Tracker.find(params[:new_tracker_id])
391 new_tracker = Tracker.find(params[:new_tracker_id])
391 @issues.each { |i|
392 @issues.each { |i|
392 # project dependent properties
393 # project dependent properties
393 unless i.project_id == new_project.id
394 unless i.project_id == new_project.id
394 i.category = nil
395 i.category = nil
395 i.fixed_version = nil
396 i.fixed_version = nil
396 # delete issue relations
397 # delete issue relations
397 i.relations_from.clear
398 i.relations_from.clear
398 i.relations_to.clear
399 i.relations_to.clear
399 end
400 end
400 # move the issue
401 # move the issue
401 i.project = new_project
402 i.project = new_project
402 i.tracker = new_tracker
403 i.tracker = new_tracker
403 i.save
404 i.save
404 }
405 }
405 flash[:notice] = l(:notice_successful_update)
406 flash[:notice] = l(:notice_successful_update)
406 redirect_to :action => 'list_issues', :id => @project
407 redirect_to :action => 'list_issues', :id => @project
407 end
408 end
408 end
409 end
409
410
410 # Add a news to @project
411 # Add a news to @project
411 def add_news
412 def add_news
412 @news = News.new(:project => @project)
413 @news = News.new(:project => @project)
413 if request.post?
414 if request.post?
414 @news.attributes = params[:news]
415 @news.attributes = params[:news]
415 @news.author_id = self.logged_in_user.id if self.logged_in_user
416 @news.author_id = self.logged_in_user.id if self.logged_in_user
416 if @news.save
417 if @news.save
417 flash[:notice] = l(:notice_successful_create)
418 flash[:notice] = l(:notice_successful_create)
418 redirect_to :action => 'list_news', :id => @project
419 redirect_to :action => 'list_news', :id => @project
419 end
420 end
420 end
421 end
421 end
422 end
422
423
423 # Show news list of @project
424 # Show news list of @project
424 def list_news
425 def list_news
425 @news_pages, @news = paginate :news, :per_page => 10, :conditions => ["project_id=?", @project.id], :include => :author, :order => "#{News.table_name}.created_on DESC"
426 @news_pages, @news = paginate :news, :per_page => 10, :conditions => ["project_id=?", @project.id], :include => :author, :order => "#{News.table_name}.created_on DESC"
426 render :action => "list_news", :layout => false if request.xhr?
427 render :action => "list_news", :layout => false if request.xhr?
427 end
428 end
428
429
429 def add_file
430 def add_file
430 if request.post?
431 if request.post?
431 @version = @project.versions.find_by_id(params[:version_id])
432 @version = @project.versions.find_by_id(params[:version_id])
432 # Save the attachments
433 # Save the attachments
433 @attachments = []
434 @attachments = []
434 params[:attachments].each { |file|
435 params[:attachments].each { |file|
435 next unless file.size > 0
436 next unless file.size > 0
436 a = Attachment.create(:container => @version, :file => file, :author => logged_in_user)
437 a = Attachment.create(:container => @version, :file => file, :author => logged_in_user)
437 @attachments << a unless a.new_record?
438 @attachments << a unless a.new_record?
438 } if params[:attachments] and params[:attachments].is_a? Array
439 } if params[:attachments] and params[:attachments].is_a? Array
439 Mailer.deliver_attachments_add(@attachments) if !@attachments.empty? and Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
440 Mailer.deliver_attachments_add(@attachments) if !@attachments.empty? and Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
440 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
441 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
441 end
442 end
442 @versions = @project.versions.sort
443 @versions = @project.versions.sort
443 end
444 end
444
445
445 def list_files
446 def list_files
446 @versions = @project.versions.sort
447 @versions = @project.versions.sort
447 end
448 end
448
449
449 # Show changelog for @project
450 # Show changelog for @project
450 def changelog
451 def changelog
451 @trackers = Tracker.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
452 @trackers = Tracker.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
452 retrieve_selected_tracker_ids(@trackers)
453 retrieve_selected_tracker_ids(@trackers)
453 @versions = @project.versions.sort
454 @versions = @project.versions.sort
454 end
455 end
455
456
456 def roadmap
457 def roadmap
457 @trackers = Tracker.find(:all, :conditions => ["is_in_roadmap=?", true], :order => 'position')
458 @trackers = Tracker.find(:all, :conditions => ["is_in_roadmap=?", true], :order => 'position')
458 retrieve_selected_tracker_ids(@trackers)
459 retrieve_selected_tracker_ids(@trackers)
459 @versions = @project.versions.sort
460 @versions = @project.versions.sort
460 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
461 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
461 end
462 end
462
463
463 def activity
464 def activity
464 if params[:year] and params[:year].to_i > 1900
465 if params[:year] and params[:year].to_i > 1900
465 @year = params[:year].to_i
466 @year = params[:year].to_i
466 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
467 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
467 @month = params[:month].to_i
468 @month = params[:month].to_i
468 end
469 end
469 end
470 end
470 @year ||= Date.today.year
471 @year ||= Date.today.year
471 @month ||= Date.today.month
472 @month ||= Date.today.month
472
473
473 @date_from = Date.civil(@year, @month, 1)
474 @date_from = Date.civil(@year, @month, 1)
474 @date_to = @date_from >> 1
475 @date_to = @date_from >> 1
475
476
476 @events_by_day = Hash.new { |h,k| h[k] = [] }
477 @events_by_day = Hash.new { |h,k| h[k] = [] }
477
478
478 unless params[:show_issues] == "0"
479 unless params[:show_issues] == "0"
479 @project.issues.find(:all, :include => [:author], :conditions => ["#{Issue.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to] ).each { |i|
480 @project.issues.find(:all, :include => [:author], :conditions => ["#{Issue.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to] ).each { |i|
480 @events_by_day[i.created_on.to_date] << i
481 @events_by_day[i.created_on.to_date] << i
481 }
482 }
482 @project.issue_changes.find(:all, :include => :details, :conditions => ["(#{Journal.table_name}.created_on BETWEEN ? AND ?) AND (#{JournalDetail.table_name}.prop_key = 'status_id')", @date_from, @date_to] ).each { |i|
483 @project.issue_changes.find(:all, :include => :details, :conditions => ["(#{Journal.table_name}.created_on BETWEEN ? AND ?) AND (#{JournalDetail.table_name}.prop_key = 'status_id')", @date_from, @date_to] ).each { |i|
483 @events_by_day[i.created_on.to_date] << i
484 @events_by_day[i.created_on.to_date] << i
484 }
485 }
485 @show_issues = 1
486 @show_issues = 1
486 end
487 end
487
488
488 unless params[:show_news] == "0"
489 unless params[:show_news] == "0"
489 @project.news.find(:all, :conditions => ["#{News.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to], :include => :author ).each { |i|
490 @project.news.find(:all, :conditions => ["#{News.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to], :include => :author ).each { |i|
490 @events_by_day[i.created_on.to_date] << i
491 @events_by_day[i.created_on.to_date] << i
491 }
492 }
492 @show_news = 1
493 @show_news = 1
493 end
494 end
494
495
495 unless params[:show_files] == "0"
496 unless params[:show_files] == "0"
496 Attachment.find(:all, :select => "#{Attachment.table_name}.*",
497 Attachment.find(:all, :select => "#{Attachment.table_name}.*",
497 :joins => "LEFT JOIN #{Version.table_name} ON #{Version.table_name}.id = #{Attachment.table_name}.container_id",
498 :joins => "LEFT JOIN #{Version.table_name} ON #{Version.table_name}.id = #{Attachment.table_name}.container_id",
498 :conditions => ["#{Attachment.table_name}.container_type='Version' and #{Version.table_name}.project_id=? and #{Attachment.table_name}.created_on BETWEEN ? AND ? ", @project.id, @date_from, @date_to],
499 :conditions => ["#{Attachment.table_name}.container_type='Version' and #{Version.table_name}.project_id=? and #{Attachment.table_name}.created_on BETWEEN ? AND ? ", @project.id, @date_from, @date_to],
499 :include => :author ).each { |i|
500 :include => :author ).each { |i|
500 @events_by_day[i.created_on.to_date] << i
501 @events_by_day[i.created_on.to_date] << i
501 }
502 }
502 @show_files = 1
503 @show_files = 1
503 end
504 end
504
505
505 unless params[:show_documents] == "0"
506 unless params[:show_documents] == "0"
506 @project.documents.find(:all, :conditions => ["#{Document.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to] ).each { |i|
507 @project.documents.find(:all, :conditions => ["#{Document.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to] ).each { |i|
507 @events_by_day[i.created_on.to_date] << i
508 @events_by_day[i.created_on.to_date] << i
508 }
509 }
509 Attachment.find(:all, :select => "attachments.*",
510 Attachment.find(:all, :select => "attachments.*",
510 :joins => "LEFT JOIN #{Document.table_name} ON #{Document.table_name}.id = #{Attachment.table_name}.container_id",
511 :joins => "LEFT JOIN #{Document.table_name} ON #{Document.table_name}.id = #{Attachment.table_name}.container_id",
511 :conditions => ["#{Attachment.table_name}.container_type='Document' and #{Document.table_name}.project_id=? and #{Attachment.table_name}.created_on BETWEEN ? AND ? ", @project.id, @date_from, @date_to],
512 :conditions => ["#{Attachment.table_name}.container_type='Document' and #{Document.table_name}.project_id=? and #{Attachment.table_name}.created_on BETWEEN ? AND ? ", @project.id, @date_from, @date_to],
512 :include => :author ).each { |i|
513 :include => :author ).each { |i|
513 @events_by_day[i.created_on.to_date] << i
514 @events_by_day[i.created_on.to_date] << i
514 }
515 }
515 @show_documents = 1
516 @show_documents = 1
516 end
517 end
517
518
518 unless @project.wiki.nil? || params[:show_wiki_edits] == "0"
519 unless @project.wiki.nil? || params[:show_wiki_edits] == "0"
519 select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
520 select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
520 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title"
521 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title"
521 joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
522 joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
522 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id "
523 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id "
523 conditions = ["#{Wiki.table_name}.project_id = ? AND #{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?",
524 conditions = ["#{Wiki.table_name}.project_id = ? AND #{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?",
524 @project.id, @date_from, @date_to]
525 @project.id, @date_from, @date_to]
525
526
526 WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => conditions).each { |i|
527 WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => conditions).each { |i|
527 # We provide this alias so all events can be treated in the same manner
528 # We provide this alias so all events can be treated in the same manner
528 def i.created_on
529 def i.created_on
529 self.updated_on
530 self.updated_on
530 end
531 end
531 @events_by_day[i.created_on.to_date] << i
532 @events_by_day[i.created_on.to_date] << i
532 }
533 }
533 @show_wiki_edits = 1
534 @show_wiki_edits = 1
534 end
535 end
535
536
536 unless @project.repository.nil? || params[:show_changesets] == "0"
537 unless @project.repository.nil? || params[:show_changesets] == "0"
537 @project.repository.changesets.find(:all, :conditions => ["#{Changeset.table_name}.committed_on BETWEEN ? AND ?", @date_from, @date_to]).each { |i|
538 @project.repository.changesets.find(:all, :conditions => ["#{Changeset.table_name}.committed_on BETWEEN ? AND ?", @date_from, @date_to]).each { |i|
538 def i.created_on
539 def i.created_on
539 self.committed_on
540 self.committed_on
540 end
541 end
541 @events_by_day[i.created_on.to_date] << i
542 @events_by_day[i.created_on.to_date] << i
542 }
543 }
543 @show_changesets = 1
544 @show_changesets = 1
544 end
545 end
545
546
546 render :layout => false if request.xhr?
547 render :layout => false if request.xhr?
547 end
548 end
548
549
549 def calendar
550 def calendar
550 @trackers = Tracker.find(:all, :order => 'position')
551 @trackers = Tracker.find(:all, :order => 'position')
551 retrieve_selected_tracker_ids(@trackers)
552 retrieve_selected_tracker_ids(@trackers)
552
553
553 if params[:year] and params[:year].to_i > 1900
554 if params[:year] and params[:year].to_i > 1900
554 @year = params[:year].to_i
555 @year = params[:year].to_i
555 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
556 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
556 @month = params[:month].to_i
557 @month = params[:month].to_i
557 end
558 end
558 end
559 end
559 @year ||= Date.today.year
560 @year ||= Date.today.year
560 @month ||= Date.today.month
561 @month ||= Date.today.month
561
562
562 @date_from = Date.civil(@year, @month, 1)
563 @date_from = Date.civil(@year, @month, 1)
563 @date_to = (@date_from >> 1)-1
564 @date_to = (@date_from >> 1)-1
564 # start on monday
565 # start on monday
565 @date_from = @date_from - (@date_from.cwday-1)
566 @date_from = @date_from - (@date_from.cwday-1)
566 # finish on sunday
567 # finish on sunday
567 @date_to = @date_to + (7-@date_to.cwday)
568 @date_to = @date_to + (7-@date_to.cwday)
568
569
569 @events = []
570 @events = []
570 @project.issues_with_subprojects(params[:with_subprojects]) do
571 @project.issues_with_subprojects(params[:with_subprojects]) do
571 @events += Issue.find(:all,
572 @events += Issue.find(:all,
572 :include => [:tracker, :status, :assigned_to, :priority, :project],
573 :include => [:tracker, :status, :assigned_to, :priority, :project],
573 :conditions => ["((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?)) and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')})", @date_from, @date_to, @date_from, @date_to]
574 :conditions => ["((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?)) and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')})", @date_from, @date_to, @date_from, @date_to]
574 ) unless @selected_tracker_ids.empty?
575 ) unless @selected_tracker_ids.empty?
575 end
576 end
576 @events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
577 @events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
577
578
578 @ending_events_by_days = @events.group_by {|event| event.due_date}
579 @ending_events_by_days = @events.group_by {|event| event.due_date}
579 @starting_events_by_days = @events.group_by {|event| event.start_date}
580 @starting_events_by_days = @events.group_by {|event| event.start_date}
580
581
581 render :layout => false if request.xhr?
582 render :layout => false if request.xhr?
582 end
583 end
583
584
584 def gantt
585 def gantt
585 @trackers = Tracker.find(:all, :order => 'position')
586 @trackers = Tracker.find(:all, :order => 'position')
586 retrieve_selected_tracker_ids(@trackers)
587 retrieve_selected_tracker_ids(@trackers)
587
588
588 if params[:year] and params[:year].to_i >0
589 if params[:year] and params[:year].to_i >0
589 @year_from = params[:year].to_i
590 @year_from = params[:year].to_i
590 if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
591 if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
591 @month_from = params[:month].to_i
592 @month_from = params[:month].to_i
592 else
593 else
593 @month_from = 1
594 @month_from = 1
594 end
595 end
595 else
596 else
596 @month_from ||= (Date.today << 1).month
597 @month_from ||= (Date.today << 1).month
597 @year_from ||= (Date.today << 1).year
598 @year_from ||= (Date.today << 1).year
598 end
599 end
599
600
600 @zoom = (params[:zoom].to_i > 0 and params[:zoom].to_i < 5) ? params[:zoom].to_i : 2
601 @zoom = (params[:zoom].to_i > 0 and params[:zoom].to_i < 5) ? params[:zoom].to_i : 2
601 @months = (params[:months].to_i > 0 and params[:months].to_i < 25) ? params[:months].to_i : 6
602 @months = (params[:months].to_i > 0 and params[:months].to_i < 25) ? params[:months].to_i : 6
602
603
603 @date_from = Date.civil(@year_from, @month_from, 1)
604 @date_from = Date.civil(@year_from, @month_from, 1)
604 @date_to = (@date_from >> @months) - 1
605 @date_to = (@date_from >> @months) - 1
605
606
606 @events = []
607 @events = []
607 @project.issues_with_subprojects(params[:with_subprojects]) do
608 @project.issues_with_subprojects(params[:with_subprojects]) do
608 @events += Issue.find(:all,
609 @events += Issue.find(:all,
609 :order => "start_date, due_date",
610 :order => "start_date, due_date",
610 :include => [:tracker, :status, :assigned_to, :priority, :project],
611 :include => [:tracker, :status, :assigned_to, :priority, :project],
611 :conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to]
612 :conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to]
612 ) unless @selected_tracker_ids.empty?
613 ) unless @selected_tracker_ids.empty?
613 end
614 end
614 @events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
615 @events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
615 @events.sort! {|x,y| x.start_date <=> y.start_date }
616 @events.sort! {|x,y| x.start_date <=> y.start_date }
616
617
617 if params[:output]=='pdf'
618 if params[:format]=='pdf'
618 @options_for_rfpdf ||= {}
619 @options_for_rfpdf ||= {}
619 @options_for_rfpdf[:file_name] = "gantt.pdf"
620 @options_for_rfpdf[:file_name] = "#{@project.identifier}-gantt.pdf"
620 render :template => "projects/gantt.rfpdf", :layout => false
621 render :template => "projects/gantt.rfpdf", :layout => false
622 elsif params[:format]=='png' && respond_to?('gantt_image')
623 image = gantt_image(@events, @date_from, @months, @zoom)
624 image.format = 'PNG'
625 send_data(image.to_blob, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png")
621 else
626 else
622 render :template => "projects/gantt.rhtml"
627 render :template => "projects/gantt.rhtml"
623 end
628 end
624 end
629 end
625
630
626 def feeds
631 def feeds
627 @queries = @project.queries.find :all, :conditions => ["is_public=? or user_id=?", true, (logged_in_user ? logged_in_user.id : 0)]
632 @queries = @project.queries.find :all, :conditions => ["is_public=? or user_id=?", true, (logged_in_user ? logged_in_user.id : 0)]
628 @key = logged_in_user.get_or_create_rss_key.value if logged_in_user
633 @key = logged_in_user.get_or_create_rss_key.value if logged_in_user
629 end
634 end
630
635
631 private
636 private
632 # Find project of id params[:id]
637 # Find project of id params[:id]
633 # if not found, redirect to project list
638 # if not found, redirect to project list
634 # Used as a before_filter
639 # Used as a before_filter
635 def find_project
640 def find_project
636 @project = Project.find(params[:id])
641 @project = Project.find(params[:id])
637 @html_title = @project.name
642 @html_title = @project.name
638 rescue ActiveRecord::RecordNotFound
643 rescue ActiveRecord::RecordNotFound
639 render_404
644 render_404
640 end
645 end
641
646
642 def retrieve_selected_tracker_ids(selectable_trackers)
647 def retrieve_selected_tracker_ids(selectable_trackers)
643 if ids = params[:tracker_ids]
648 if ids = params[:tracker_ids]
644 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
649 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
645 else
650 else
646 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
651 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
647 end
652 end
648 end
653 end
649
654
650 # Retrieve query from session or build a new query
655 # Retrieve query from session or build a new query
651 def retrieve_query
656 def retrieve_query
652 if params[:query_id]
657 if params[:query_id]
653 @query = @project.queries.find(params[:query_id])
658 @query = @project.queries.find(params[:query_id])
654 @query.executed_by = logged_in_user
659 @query.executed_by = logged_in_user
655 session[:query] = @query
660 session[:query] = @query
656 else
661 else
657 if params[:set_filter] or !session[:query] or session[:query].project_id != @project.id
662 if params[:set_filter] or !session[:query] or session[:query].project_id != @project.id
658 # Give it a name, required to be valid
663 # Give it a name, required to be valid
659 @query = Query.new(:name => "_", :executed_by => logged_in_user)
664 @query = Query.new(:name => "_", :executed_by => logged_in_user)
660 @query.project = @project
665 @query.project = @project
661 if params[:fields] and params[:fields].is_a? Array
666 if params[:fields] and params[:fields].is_a? Array
662 params[:fields].each do |field|
667 params[:fields].each do |field|
663 @query.add_filter(field, params[:operators][field], params[:values][field])
668 @query.add_filter(field, params[:operators][field], params[:values][field])
664 end
669 end
665 else
670 else
666 @query.available_filters.keys.each do |field|
671 @query.available_filters.keys.each do |field|
667 @query.add_short_filter(field, params[field]) if params[field]
672 @query.add_short_filter(field, params[field]) if params[field]
668 end
673 end
669 end
674 end
670 session[:query] = @query
675 session[:query] = @query
671 else
676 else
672 @query = session[:query]
677 @query = session[:query]
673 end
678 end
674 end
679 end
675 end
680 end
676 end
681 end
@@ -1,28 +1,178
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 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 ProjectsHelper
18 module ProjectsHelper
19 def link_to_version(version, options = {})
19 def link_to_version(version, options = {})
20 return '' unless version && version.is_a?(Version)
20 return '' unless version && version.is_a?(Version)
21 link_to version.name, {:controller => 'projects',
21 link_to version.name, {:controller => 'projects',
22 :action => 'roadmap',
22 :action => 'roadmap',
23 :id => version.project_id,
23 :id => version.project_id,
24 :completed => (version.completed? ? 1 : nil),
24 :completed => (version.completed? ? 1 : nil),
25 :anchor => version.name
25 :anchor => version.name
26 }, options
26 }, options
27 end
27 end
28
29 # Generates a gantt image
30 # Only defined if RMagick is avalaible
31 def gantt_image(events, date_from, months, zoom)
32 date_to = (date_from >> months)-1
33 show_weeks = zoom > 1
34 show_days = zoom > 2
35
36 subject_width = 320
37 header_heigth = 18
38 # width of one day in pixels
39 zoom = zoom*2
40 g_width = (date_to - date_from + 1)*zoom
41 g_height = 20 * events.length + 20
42 headers_heigth = (show_weeks ? 2*header_heigth : header_heigth)
43 height = g_height + headers_heigth
44
45 imgl = Magick::ImageList.new
46 imgl.new_image(subject_width+g_width+1, height)
47 gc = Magick::Draw.new
48
49 # Subjects
50 top = headers_heigth + 20
51 gc.fill('black')
52 gc.stroke('transparent')
53 gc.stroke_width(1)
54 events.each do |i|
55 gc.text(4, top + 2, (i.is_a?(Issue) ? i.subject : i.name))
56 top = top + 20
57 end
58
59 # Months headers
60 month_f = date_from
61 left = subject_width
62 months.times do
63 width = ((month_f >> 1) - month_f) * zoom
64 gc.fill('white')
65 gc.stroke('grey')
66 gc.stroke_width(1)
67 gc.rectangle(left, 0, left + width, height)
68 gc.fill('black')
69 gc.stroke('transparent')
70 gc.stroke_width(1)
71 gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
72 left = left + width
73 month_f = month_f >> 1
74 end
75
76 # Weeks headers
77 if show_weeks
78 left = subject_width
79 height = header_heigth
80 if date_from.cwday == 1
81 # date_from is monday
82 week_f = date_from
83 else
84 # find next monday after date_from
85 week_f = date_from + (7 - date_from.cwday + 1)
86 width = (7 - date_from.cwday + 1) * zoom
87 gc.fill('white')
88 gc.stroke('grey')
89 gc.stroke_width(1)
90 gc.rectangle(left, header_heigth, left + width, 2*header_heigth + g_height-1)
91 left = left + width
92 end
93 while week_f <= date_to
94 width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
95 gc.fill('white')
96 gc.stroke('grey')
97 gc.stroke_width(1)
98 gc.rectangle(left.round, header_heigth, left.round + width, 2*header_heigth + g_height-1)
99 gc.fill('black')
100 gc.stroke('transparent')
101 gc.stroke_width(1)
102 gc.text(left.round + 2, header_heigth + 14, week_f.cweek.to_s)
103 left = left + width
104 week_f = week_f+7
105 end
106 end
107
108 # Days details (week-end in grey)
109 if show_days
110 left = subject_width
111 height = g_height + header_heigth - 1
112 wday = date_from.cwday
113 (date_to - date_from + 1).to_i.times do
114 width = zoom
115 gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
116 gc.stroke('grey')
117 gc.stroke_width(1)
118 gc.rectangle(left, 2*header_heigth, left + width, 2*header_heigth + g_height-1)
119 left = left + width
120 wday = wday + 1
121 wday = 1 if wday > 7
122 end
123 end
124
125 # border
126 gc.fill('transparent')
127 gc.stroke('grey')
128 gc.stroke_width(1)
129 gc.rectangle(0, 0, subject_width+g_width, headers_heigth)
130 gc.stroke('black')
131 gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_heigth-1)
132
133 # content
134 top = headers_heigth + 20
135 gc.stroke('transparent')
136 events.each do |i|
137 if i.is_a?(Issue)
138 i_start_date = (i.start_date >= date_from ? i.start_date : date_from )
139 i_end_date = (i.due_date <= date_to ? i.due_date : date_to )
140 i_done_date = i.start_date + ((i.due_date - i.start_date+1)*i.done_ratio/100).floor
141 i_done_date = (i_done_date <= date_from ? date_from : i_done_date )
142 i_done_date = (i_done_date >= date_to ? date_to : i_done_date )
143 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
144
145 i_left = subject_width + ((i_start_date - date_from)*zoom).floor
146 i_width = ((i_end_date - i_start_date + 1)*zoom).floor # total width of the issue
147 d_width = ((i_done_date - i_start_date)*zoom).floor # done width
148 l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor : 0 # delay width
149
150 gc.fill('grey')
151 gc.rectangle(i_left, top, i_left + i_width, top - 6)
152 gc.fill('red')
153 gc.rectangle(i_left, top, i_left + l_width, top - 6) if l_width > 0
154 gc.fill('blue')
155 gc.rectangle(i_left, top, i_left + d_width, top - 6) if d_width > 0
156 gc.fill('black')
157 gc.text(i_left + i_width + 5,top + 1, "#{i.status.name} #{i.done_ratio}%")
158 else
159 i_left = subject_width + ((i.start_date - date_from)*zoom).floor
160 gc.fill('green')
161 gc.rectangle(i_left, top, i_left + 6, top - 6)
162 gc.fill('black')
163 gc.text(i_left + 11, top + 1, i.name)
164 end
165 top = top + 20
166 end
167
168 # today red line
169 if Date.today >= @date_from and Date.today <= @date_to
170 gc.stroke('red')
171 x = (Date.today-@date_from+1)*zoom + subject_width
172 gc.line(x, headers_heigth, x, headers_heigth + g_height-1)
173 end
174
175 gc.draw(imgl)
176 imgl
177 end if Object.const_defined?(:Magick)
28 end
178 end
@@ -1,244 +1,246
1 <% zoom = 1
1 <% zoom = 1
2 @zoom.times { zoom = zoom * 2 }
2 @zoom.times { zoom = zoom * 2 }
3
3
4 subject_width = 330
4 subject_width = 330
5 header_heigth = 18
5 header_heigth = 18
6
6
7 headers_height = header_heigth
7 headers_height = header_heigth
8 show_weeks = false
8 show_weeks = false
9 show_days = false
9 show_days = false
10
10
11 if @zoom >1
11 if @zoom >1
12 show_weeks = true
12 show_weeks = true
13 headers_height = 2*header_heigth
13 headers_height = 2*header_heigth
14 if @zoom > 2
14 if @zoom > 2
15 show_days = true
15 show_days = true
16 headers_height = 3*header_heigth
16 headers_height = 3*header_heigth
17 end
17 end
18 end
18 end
19
19
20 g_width = (@date_to - @date_from + 1)*zoom
20 g_width = (@date_to - @date_from + 1)*zoom
21 g_height = [(20 * @events.length + 6)+150, 206].max
21 g_height = [(20 * @events.length + 6)+150, 206].max
22 t_height = g_height + headers_height
22 t_height = g_height + headers_height
23 %>
23 %>
24
24
25 <% cache(:year => @year_from, :month => @month_from, :months => @months, :zoom => @zoom, :tracker_ids => @selected_tracker_ids, :subprojects => params[:with_subprojects], :lang => current_language) do %>
26 <div class="contextual">
25 <div class="contextual">
27 <%= l(:label_export_to) %>
26 <%= l(:label_export_to) %>
28 <%= link_to 'PDF', {:zoom => @zoom, :year => @year_from, :month => @month_from, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects], :output => 'pdf'}, :class => 'icon icon-pdf' %>
27 <%= link_to 'PDF', {:zoom => @zoom, :year => @year_from, :month => @month_from, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects], :format => 'pdf'}, :class => 'icon icon-pdf' %>
28 <%= link_to 'PNG', {:zoom => @zoom, :year => @year_from, :month => @month_from, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects], :format => 'png'}, :class => 'icon icon-image' if respond_to?('gantt_image') %>
29 </div>
29 </div>
30
30
31 <h2><%= l(:label_gantt) %></h2>
31 <h2><%= l(:label_gantt) %></h2>
32
32
33 <% form_tag do %>
33 <% form_tag do %>
34 <table width="100%">
34 <table width="100%">
35 <tr>
35 <tr>
36 <td align="left">
36 <td align="left">
37 <input type="text" name="months" size="2" value="<%= @months %>" />
37 <input type="text" name="months" size="2" value="<%= @months %>" />
38 <%= l(:label_months_from) %>
38 <%= l(:label_months_from) %>
39 <%= select_month(@month_from, :prefix => "month", :discard_type => true) %>
39 <%= select_month(@month_from, :prefix => "month", :discard_type => true) %>
40 <%= select_year(@year_from, :prefix => "year", :discard_type => true) %>
40 <%= select_year(@year_from, :prefix => "year", :discard_type => true) %>
41 <%= hidden_field_tag 'zoom', @zoom %>
41 <%= hidden_field_tag 'zoom', @zoom %>
42 <%= submit_tag l(:button_submit), :class => "button-small" %>
42 <%= submit_tag l(:button_submit), :class => "button-small" %>
43 </td>
43 </td>
44 <td>
44 <td>
45 <%= toggle_link l(:label_options), "trackerselect" %>
45 <%= toggle_link l(:label_options), "trackerselect" %>
46 <div id="trackerselect" class="rightbox overlay" style="width:140px; display: none;">
46 <div id="trackerselect" class="rightbox overlay" style="width:140px; display: none;">
47 <p><strong><%=l(:label_tracker_plural)%></strong></p>
47 <p><strong><%=l(:label_tracker_plural)%></strong></p>
48 <% @trackers.each do |tracker| %>
48 <% @trackers.each do |tracker| %>
49 <%= check_box_tag "tracker_ids[]", tracker.id, (@selected_tracker_ids.include? tracker.id.to_s) %>
49 <%= check_box_tag "tracker_ids[]", tracker.id, (@selected_tracker_ids.include? tracker.id.to_s) %>
50 <%= tracker.name %><br />
50 <%= tracker.name %><br />
51 <% end %>
51 <% end %>
52 <% if @project.active_children.any? %>
52 <% if @project.active_children.any? %>
53 <p><strong><%=l(:label_subproject_plural)%></strong></p>
53 <p><strong><%=l(:label_subproject_plural)%></strong></p>
54 <%= check_box_tag "with_subprojects", 1, params[:with_subprojects] %> <%= l(:general_text_Yes) %>
54 <%= check_box_tag "with_subprojects", 1, params[:with_subprojects] %> <%= l(:general_text_Yes) %>
55 <% end %>
55 <% end %>
56 <p><center><%= submit_tag l(:button_apply), :class => 'button-small' %></center></p>
56 <p><center><%= submit_tag l(:button_apply), :class => 'button-small' %></center></p>
57 </div>
57 </div>
58 </td>
58 </td>
59 <td align="right">
59 <td align="right">
60 <%= if @zoom < 4
60 <%= if @zoom < 4
61 link_to image_tag('zoom_in.png'), {:zoom => (@zoom+1), :year => @year_from, :month => @month_from, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects]}
61 link_to image_tag('zoom_in.png'), {:zoom => (@zoom+1), :year => @year_from, :month => @month_from, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects]}
62 else
62 else
63 image_tag 'zoom_in_g.png'
63 image_tag 'zoom_in_g.png'
64 end %>
64 end %>
65 <%= if @zoom > 1
65 <%= if @zoom > 1
66 link_to image_tag('zoom_out.png'),{:zoom => (@zoom-1), :year => @year_from, :month => @month_from, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects]}
66 link_to image_tag('zoom_out.png'),{:zoom => (@zoom-1), :year => @year_from, :month => @month_from, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects]}
67 else
67 else
68 image_tag 'zoom_out_g.png'
68 image_tag 'zoom_out_g.png'
69 end %>
69 end %>
70 </td>
70 </td>
71 </tr>
71 </tr>
72 </table>
72 </table>
73 <% end %>
73 <% end %>
74
74
75 <% cache(:year => @year_from, :month => @month_from, :months => @months, :zoom => @zoom, :tracker_ids => @selected_tracker_ids, :subprojects => params[:with_subprojects], :lang => current_language) do %>
76
75 <table width="100%" style="border:0; border-collapse: collapse;">
77 <table width="100%" style="border:0; border-collapse: collapse;">
76 <tr>
78 <tr>
77 <td style="width:<%= subject_width %>px;">
79 <td style="width:<%= subject_width %>px;">
78
80
79 <div style="position:relative;height:<%= t_height + 24 %>px;width:<%= subject_width + 1 %>px;">
81 <div style="position:relative;height:<%= t_height + 24 %>px;width:<%= subject_width + 1 %>px;">
80 <div style="right:-2px;width:<%= subject_width %>px;height:<%= headers_height %>px;background: #eee;" class="gantt_hdr"></div>
82 <div style="right:-2px;width:<%= subject_width %>px;height:<%= headers_height %>px;background: #eee;" class="gantt_hdr"></div>
81 <div style="right:-2px;width:<%= subject_width %>px;height:<%= t_height %>px;border-left: 1px solid #c0c0c0;overflow:hidden;" class="gantt_hdr"></div>
83 <div style="right:-2px;width:<%= subject_width %>px;height:<%= t_height %>px;border-left: 1px solid #c0c0c0;overflow:hidden;" class="gantt_hdr"></div>
82 <%
84 <%
83 #
85 #
84 # Tasks subjects
86 # Tasks subjects
85 #
87 #
86 top = headers_height + 8
88 top = headers_height + 8
87 @events.each do |i| %>
89 @events.each do |i| %>
88 <div style="position: absolute;line-height:1.2em;height:16px;top:<%= top %>px;left:4px;overflow:hidden;"><small>
90 <div style="position: absolute;line-height:1.2em;height:16px;top:<%= top %>px;left:4px;overflow:hidden;"><small>
89 <% if i.is_a? Issue %>
91 <% if i.is_a? Issue %>
90 <%= link_to_issue i %><%= " (#{i.project.name})" unless @project && @project == i.project %>:
92 <%= link_to_issue i %><%= " (#{i.project.name})" unless @project && @project == i.project %>:
91 <%=h i.subject %>
93 <%=h i.subject %>
92 <% else %>
94 <% else %>
93 <%= link_to_version i, :class => "icon icon-package" %>
95 <%= link_to_version i, :class => "icon icon-package" %>
94 <% end %>
96 <% end %>
95 </small></div>
97 </small></div>
96 <% top = top + 20
98 <% top = top + 20
97 end %>
99 end %>
98 </div>
100 </div>
99 </td>
101 </td>
100 <td>
102 <td>
101
103
102 <div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;">
104 <div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;">
103 <div style="width:<%= g_width-1 %>px;height:<%= headers_height %>px;background: #eee;" class="gantt_hdr">&nbsp;</div>
105 <div style="width:<%= g_width-1 %>px;height:<%= headers_height %>px;background: #eee;" class="gantt_hdr">&nbsp;</div>
104 <%
106 <%
105 #
107 #
106 # Months headers
108 # Months headers
107 #
109 #
108 month_f = @date_from
110 month_f = @date_from
109 left = 0
111 left = 0
110 height = (show_weeks ? header_heigth : header_heigth + g_height)
112 height = (show_weeks ? header_heigth : header_heigth + g_height)
111 @months.times do
113 @months.times do
112 width = ((month_f >> 1) - month_f) * zoom - 1
114 width = ((month_f >> 1) - month_f) * zoom - 1
113 %>
115 %>
114 <div style="left:<%= left %>px;width:<%= width %>px;height:<%= height %>px;" class="gantt_hdr">
116 <div style="left:<%= left %>px;width:<%= width %>px;height:<%= height %>px;" class="gantt_hdr">
115 <%= link_to "#{month_f.year}-#{month_f.month}", { :year => month_f.year, :month => month_f.month, :zoom => @zoom, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects] }, :title => "#{month_name(month_f.month)} #{month_f.year}"%>
117 <%= link_to "#{month_f.year}-#{month_f.month}", { :year => month_f.year, :month => month_f.month, :zoom => @zoom, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects] }, :title => "#{month_name(month_f.month)} #{month_f.year}"%>
116 </div>
118 </div>
117 <%
119 <%
118 left = left + width + 1
120 left = left + width + 1
119 month_f = month_f >> 1
121 month_f = month_f >> 1
120 end %>
122 end %>
121
123
122 <%
124 <%
123 #
125 #
124 # Weeks headers
126 # Weeks headers
125 #
127 #
126 if show_weeks
128 if show_weeks
127 left = 0
129 left = 0
128 height = (show_days ? header_heigth-1 : header_heigth-1 + g_height)
130 height = (show_days ? header_heigth-1 : header_heigth-1 + g_height)
129 if @date_from.cwday == 1
131 if @date_from.cwday == 1
130 # @date_from is monday
132 # @date_from is monday
131 week_f = @date_from
133 week_f = @date_from
132 else
134 else
133 # find next monday after @date_from
135 # find next monday after @date_from
134 week_f = @date_from + (7 - @date_from.cwday + 1)
136 week_f = @date_from + (7 - @date_from.cwday + 1)
135 width = (7 - @date_from.cwday + 1) * zoom-1
137 width = (7 - @date_from.cwday + 1) * zoom-1
136 %>
138 %>
137 <div style="left:<%= left %>px;top:19px;width:<%= width %>px;height:<%= height %>px;" class="gantt_hdr">&nbsp;</div>
139 <div style="left:<%= left %>px;top:19px;width:<%= width %>px;height:<%= height %>px;" class="gantt_hdr">&nbsp;</div>
138 <%
140 <%
139 left = left + width+1
141 left = left + width+1
140 end %>
142 end %>
141 <%
143 <%
142 while week_f <= @date_to
144 while week_f <= @date_to
143 width = (week_f + 6 <= @date_to) ? 7 * zoom -1 : (@date_to - week_f + 1) * zoom-1
145 width = (week_f + 6 <= @date_to) ? 7 * zoom -1 : (@date_to - week_f + 1) * zoom-1
144 %>
146 %>
145 <div style="left:<%= left %>px;top:19px;width:<%= width %>px;height:<%= height %>px;" class="gantt_hdr">
147 <div style="left:<%= left %>px;top:19px;width:<%= width %>px;height:<%= height %>px;" class="gantt_hdr">
146 <small><%= week_f.cweek if width >= 16 %></small>
148 <small><%= week_f.cweek if width >= 16 %></small>
147 </div>
149 </div>
148 <%
150 <%
149 left = left + width+1
151 left = left + width+1
150 week_f = week_f+7
152 week_f = week_f+7
151 end
153 end
152 end %>
154 end %>
153
155
154 <%
156 <%
155 #
157 #
156 # Days headers
158 # Days headers
157 #
159 #
158 if show_days
160 if show_days
159 left = 0
161 left = 0
160 height = g_height + header_heigth - 1
162 height = g_height + header_heigth - 1
161 wday = @date_from.cwday
163 wday = @date_from.cwday
162 (@date_to - @date_from + 1).to_i.times do
164 (@date_to - @date_from + 1).to_i.times do
163 width = zoom - 1
165 width = zoom - 1
164 %>
166 %>
165 <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">
167 <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">
166 <%= day_name(wday).first %>
168 <%= day_name(wday).first %>
167 </div>
169 </div>
168 <%
170 <%
169 left = left + width+1
171 left = left + width+1
170 wday = wday + 1
172 wday = wday + 1
171 wday = 1 if wday > 7
173 wday = 1 if wday > 7
172 end
174 end
173 end %>
175 end %>
174
176
175 <%
177 <%
176 #
178 #
177 # Tasks
179 # Tasks
178 #
180 #
179 top = headers_height + 10
181 top = headers_height + 10
180 @events.each do |i|
182 @events.each do |i|
181 if i.is_a? Issue
183 if i.is_a? Issue
182 i_start_date = (i.start_date >= @date_from ? i.start_date : @date_from )
184 i_start_date = (i.start_date >= @date_from ? i.start_date : @date_from )
183 i_end_date = (i.due_date <= @date_to ? i.due_date : @date_to )
185 i_end_date = (i.due_date <= @date_to ? i.due_date : @date_to )
184
186
185 i_done_date = i.start_date + ((i.due_date - i.start_date+1)*i.done_ratio/100).floor
187 i_done_date = i.start_date + ((i.due_date - i.start_date+1)*i.done_ratio/100).floor
186 i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date )
188 i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date )
187 i_done_date = (i_done_date >= @date_to ? @date_to : i_done_date )
189 i_done_date = (i_done_date >= @date_to ? @date_to : i_done_date )
188
190
189 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
191 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
190
192
191 i_left = ((i_start_date - @date_from)*zoom).floor
193 i_left = ((i_start_date - @date_from)*zoom).floor
192 i_width = ((i_end_date - i_start_date + 1)*zoom).floor - 2 # total width of the issue (- 2 for left and right borders)
194 i_width = ((i_end_date - i_start_date + 1)*zoom).floor - 2 # total width of the issue (- 2 for left and right borders)
193 d_width = ((i_done_date - i_start_date)*zoom).floor - 2 # done width
195 d_width = ((i_done_date - i_start_date)*zoom).floor - 2 # done width
194 l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor - 2 : 0 # delay width
196 l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor - 2 : 0 # delay width
195 %>
197 %>
196 <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= i_width %>px;" class="task task_todo">&nbsp;</div>
198 <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= i_width %>px;" class="task task_todo">&nbsp;</div>
197 <% if l_width > 0 %>
199 <% if l_width > 0 %>
198 <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= l_width %>px;" class="task task_late">&nbsp;</div>
200 <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= l_width %>px;" class="task task_late">&nbsp;</div>
199 <% end %>
201 <% end %>
200 <% if d_width > 0 %>
202 <% if d_width > 0 %>
201 <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= d_width %>px;" class="task task_done">&nbsp;</div>
203 <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= d_width %>px;" class="task task_done">&nbsp;</div>
202 <% end %>
204 <% end %>
203 <div style="top:<%= top %>px;left:<%= i_left + i_width + 5 %>px;background:#fff;" class="task">
205 <div style="top:<%= top %>px;left:<%= i_left + i_width + 5 %>px;background:#fff;" class="task">
204 <%= i.status.name %>
206 <%= i.status.name %>
205 <%= (i.done_ratio).to_i %>%
207 <%= (i.done_ratio).to_i %>%
206 </div>
208 </div>
207 <% # === tooltip === %>
209 <% # === tooltip === %>
208 <div class="tooltip" style="position: absolute;top:<%= top %>px;left:<%= i_left %>px;width:<%= i_width %>px;height:12px;">
210 <div class="tooltip" style="position: absolute;top:<%= top %>px;left:<%= i_left %>px;width:<%= i_width %>px;height:12px;">
209 <span class="tip">
211 <span class="tip">
210 <%= render :partial => "issues/tooltip", :locals => { :issue => i }%>
212 <%= render :partial => "issues/tooltip", :locals => { :issue => i }%>
211 </span></div>
213 </span></div>
212 <% else
214 <% else
213 i_left = ((i.start_date - @date_from)*zoom).floor
215 i_left = ((i.start_date - @date_from)*zoom).floor
214 %>
216 %>
215 <div style="top:<%= top %>px;left:<%= i_left %>px;width:15px;" class="task milestone">&nbsp;</div>
217 <div style="top:<%= top %>px;left:<%= i_left %>px;width:15px;" class="task milestone">&nbsp;</div>
216 <div style="top:<%= top %>px;left:<%= i_left + 12 %>px;background:#fff;" class="task">
218 <div style="top:<%= top %>px;left:<%= i_left + 12 %>px;background:#fff;" class="task">
217 <strong><%= i.name %></strong>
219 <strong><%= i.name %></strong>
218 </div>
220 </div>
219 <% end %>
221 <% end %>
220 <% top = top + 20
222 <% top = top + 20
221 end %>
223 end %>
222
224
223 <% end # cache
225 <% end # cache
224 %>
226 %>
225
227
226 <%
228 <%
227 #
229 #
228 # Today red line (excluded from cache)
230 # Today red line (excluded from cache)
229 #
231 #
230 if Date.today >= @date_from and Date.today <= @date_to %>
232 if Date.today >= @date_from and Date.today <= @date_to %>
231 <div style="position: absolute;height:<%= g_height %>px;top:<%= headers_height + 1 %>px;left:<%= ((Date.today-@date_from+1)*zoom).floor()-1 %>px;width:10px;border-left: 1px dashed red;">&nbsp;</div>
233 <div style="position: absolute;height:<%= g_height %>px;top:<%= headers_height + 1 %>px;left:<%= ((Date.today-@date_from+1)*zoom).floor()-1 %>px;width:10px;border-left: 1px dashed red;">&nbsp;</div>
232 <% end %>
234 <% end %>
233
235
234 </div>
236 </div>
235 </td>
237 </td>
236 </tr>
238 </tr>
237 </table>
239 </table>
238
240
239 <table width="100%">
241 <table width="100%">
240 <tr>
242 <tr>
241 <td align="left"><%= link_to ('&#171; ' + l(:label_previous)), :year => (@date_from << @months).year, :month => (@date_from << @months).month, :zoom => @zoom, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects] %></td>
243 <td align="left"><%= link_to ('&#171; ' + l(:label_previous)), :year => (@date_from << @months).year, :month => (@date_from << @months).month, :zoom => @zoom, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects] %></td>
242 <td align="right"><%= link_to (l(:label_next) + ' &#187;'), :year => (@date_from >> @months).year, :month => (@date_from >> @months).month, :zoom => @zoom, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects] %></td>
244 <td align="right"><%= link_to (l(:label_next) + ' &#187;'), :year => (@date_from >> @months).year, :month => (@date_from >> @months).month, :zoom => @zoom, :months => @months, :tracker_ids => @selected_tracker_ids, :with_subprojects => params[:with_subprojects] %></td>
243 </tr>
245 </tr>
244 </table>
246 </table>
@@ -1,5 +1,11
1 require 'redmine/version'
1 require 'redmine/version'
2 require 'redmine/mime_type'
2 require 'redmine/mime_type'
3 require 'redmine/acts_as_watchable/init'
3 require 'redmine/acts_as_watchable/init'
4
4
5 begin
6 require_library_or_gem 'rmagick' unless Object.const_defined?(:Magick)
7 rescue LoadError
8 # RMagick is not available
9 end
10
5 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs )
11 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs )
@@ -1,694 +1,695
1 /* andreas08 - an open source xhtml/css website layout by Andreas Viklund - http://andreasviklund.com . Free to use in any way and for any purpose as long as the proper credits are given to the original designer. Version: 1.0, November 28, 2005 */
1 /* andreas08 - an open source xhtml/css website layout by Andreas Viklund - http://andreasviklund.com . Free to use in any way and for any purpose as long as the proper credits are given to the original designer. Version: 1.0, November 28, 2005 */
2 /* Edited by Jean-Philippe Lang *>
2 /* Edited by Jean-Philippe Lang *>
3 /**************** Body and tag styles ****************/
3 /**************** Body and tag styles ****************/
4
4
5 #header * {margin:0; padding:0;}
5 #header * {margin:0; padding:0;}
6 p, ul, ol, li {margin:0; padding:0;}
6 p, ul, ol, li {margin:0; padding:0;}
7
7
8 body{
8 body{
9 font:76% Verdana,Tahoma,Arial,sans-serif;
9 font:76% Verdana,Tahoma,Arial,sans-serif;
10 line-height:1.4em;
10 line-height:1.4em;
11 text-align:center;
11 text-align:center;
12 color:#303030;
12 color:#303030;
13 background:#e8eaec;
13 background:#e8eaec;
14 margin:0;
14 margin:0;
15 }
15 }
16
16
17 a{color:#467aa7;font-weight:bold;text-decoration:none;background-color:inherit;}
17 a{color:#467aa7;font-weight:bold;text-decoration:none;background-color:inherit;}
18 a:hover{color:#2a5a8a; text-decoration:none; background-color:inherit;}
18 a:hover{color:#2a5a8a; text-decoration:none; background-color:inherit;}
19 a img{border:none;}
19 a img{border:none;}
20
20
21 p{margin:0 0 1em 0;}
21 p{margin:0 0 1em 0;}
22 p form{margin-top:0; margin-bottom:20px;}
22 p form{margin-top:0; margin-bottom:20px;}
23
23
24 img.left,img.center,img.right{padding:4px; border:1px solid #a0a0a0;}
24 img.left,img.center,img.right{padding:4px; border:1px solid #a0a0a0;}
25 img.left{float:left; margin:0 12px 5px 0;}
25 img.left{float:left; margin:0 12px 5px 0;}
26 img.center{display:block; margin:0 auto 5px auto;}
26 img.center{display:block; margin:0 auto 5px auto;}
27 img.right{float:right; margin:0 0 5px 12px;}
27 img.right{float:right; margin:0 0 5px 12px;}
28
28
29 /**************** Header and navigation styles ****************/
29 /**************** Header and navigation styles ****************/
30
30
31 #container{
31 #container{
32 width:100%;
32 width:100%;
33 min-width: 800px;
33 min-width: 800px;
34 margin:0;
34 margin:0;
35 padding:0;
35 padding:0;
36 text-align:left;
36 text-align:left;
37 background:#ffffff;
37 background:#ffffff;
38 color:#303030;
38 color:#303030;
39 }
39 }
40
40
41 #header{
41 #header{
42 height:4.5em;
42 height:4.5em;
43 margin:0;
43 margin:0;
44 background:#467aa7;
44 background:#467aa7;
45 color:#ffffff;
45 color:#ffffff;
46 margin-bottom:1px;
46 margin-bottom:1px;
47 }
47 }
48
48
49 #header h1{
49 #header h1{
50 padding:10px 0 0 20px;
50 padding:10px 0 0 20px;
51 font-size:2em;
51 font-size:2em;
52 background-color:inherit;
52 background-color:inherit;
53 color:#fff;
53 color:#fff;
54 letter-spacing:-1px;
54 letter-spacing:-1px;
55 font-weight:bold;
55 font-weight:bold;
56 font-family: Trebuchet MS,Georgia,"Times New Roman",serif;
56 font-family: Trebuchet MS,Georgia,"Times New Roman",serif;
57 }
57 }
58
58
59 #header h2{
59 #header h2{
60 margin:3px 0 0 40px;
60 margin:3px 0 0 40px;
61 font-size:1.5em;
61 font-size:1.5em;
62 background-color:inherit;
62 background-color:inherit;
63 color:#f0f2f4;
63 color:#f0f2f4;
64 letter-spacing:-1px;
64 letter-spacing:-1px;
65 font-weight:normal;
65 font-weight:normal;
66 font-family: Trebuchet MS,Georgia,"Times New Roman",serif;
66 font-family: Trebuchet MS,Georgia,"Times New Roman",serif;
67 }
67 }
68
68
69 #header a {color:#fff;}
69 #header a {color:#fff;}
70
70
71 #navigation{
71 #navigation{
72 height:2.2em;
72 height:2.2em;
73 line-height:2.2em;
73 line-height:2.2em;
74 margin:0;
74 margin:0;
75 background:#578bb8;
75 background:#578bb8;
76 color:#ffffff;
76 color:#ffffff;
77 }
77 }
78
78
79 #navigation li{
79 #navigation li{
80 float:left;
80 float:left;
81 list-style-type:none;
81 list-style-type:none;
82 border-right:1px solid #ffffff;
82 border-right:1px solid #ffffff;
83 white-space:nowrap;
83 white-space:nowrap;
84 }
84 }
85
85
86 #navigation li.right {
86 #navigation li.right {
87 float:right;
87 float:right;
88 list-style-type:none;
88 list-style-type:none;
89 border-right:0;
89 border-right:0;
90 border-left:1px solid #ffffff;
90 border-left:1px solid #ffffff;
91 white-space:nowrap;
91 white-space:nowrap;
92 }
92 }
93
93
94 #navigation li a{
94 #navigation li a{
95 display:block;
95 display:block;
96 padding:0px 10px 0px 22px;
96 padding:0px 10px 0px 22px;
97 font-size:0.8em;
97 font-size:0.8em;
98 font-weight:normal;
98 font-weight:normal;
99 text-decoration:none;
99 text-decoration:none;
100 background-color:inherit;
100 background-color:inherit;
101 color: #ffffff;
101 color: #ffffff;
102 }
102 }
103
103
104 #navigation li.submenu {background:url(../images/arrow_down.png) 96% 80% no-repeat;}
104 #navigation li.submenu {background:url(../images/arrow_down.png) 96% 80% no-repeat;}
105 #navigation li.submenu a {padding:0px 16px 0px 22px;}
105 #navigation li.submenu a {padding:0px 16px 0px 22px;}
106 * html #navigation a {width:1%;}
106 * html #navigation a {width:1%;}
107
107
108 #navigation .selected,#navigation a:hover{
108 #navigation .selected,#navigation a:hover{
109 color:#ffffff;
109 color:#ffffff;
110 text-decoration:none;
110 text-decoration:none;
111 background-color: #80b0da;
111 background-color: #80b0da;
112 }
112 }
113
113
114 /**************** Icons *******************/
114 /**************** Icons *******************/
115 .icon {
115 .icon {
116 background-position: 0% 40%;
116 background-position: 0% 40%;
117 background-repeat: no-repeat;
117 background-repeat: no-repeat;
118 padding-left: 20px;
118 padding-left: 20px;
119 padding-top: 2px;
119 padding-top: 2px;
120 padding-bottom: 3px;
120 padding-bottom: 3px;
121 vertical-align: middle;
121 vertical-align: middle;
122 }
122 }
123
123
124 #navigation .icon {
124 #navigation .icon {
125 background-position: 4px 50%;
125 background-position: 4px 50%;
126 }
126 }
127
127
128 .icon22 {
128 .icon22 {
129 background-position: 0% 40%;
129 background-position: 0% 40%;
130 background-repeat: no-repeat;
130 background-repeat: no-repeat;
131 padding-left: 26px;
131 padding-left: 26px;
132 line-height: 22px;
132 line-height: 22px;
133 vertical-align: middle;
133 vertical-align: middle;
134 }
134 }
135
135
136 .icon-add { background-image: url(../images/add.png); }
136 .icon-add { background-image: url(../images/add.png); }
137 .icon-edit { background-image: url(../images/edit.png); }
137 .icon-edit { background-image: url(../images/edit.png); }
138 .icon-del { background-image: url(../images/delete.png); }
138 .icon-del { background-image: url(../images/delete.png); }
139 .icon-move { background-image: url(../images/move.png); }
139 .icon-move { background-image: url(../images/move.png); }
140 .icon-save { background-image: url(../images/save.png); }
140 .icon-save { background-image: url(../images/save.png); }
141 .icon-cancel { background-image: url(../images/cancel.png); }
141 .icon-cancel { background-image: url(../images/cancel.png); }
142 .icon-pdf { background-image: url(../images/pdf.png); }
142 .icon-pdf { background-image: url(../images/pdf.png); }
143 .icon-csv { background-image: url(../images/csv.png); }
143 .icon-csv { background-image: url(../images/csv.png); }
144 .icon-html { background-image: url(../images/html.png); }
144 .icon-html { background-image: url(../images/html.png); }
145 .icon-image { background-image: url(../images/image.png); }
145 .icon-txt { background-image: url(../images/txt.png); }
146 .icon-txt { background-image: url(../images/txt.png); }
146 .icon-file { background-image: url(../images/file.png); }
147 .icon-file { background-image: url(../images/file.png); }
147 .icon-folder { background-image: url(../images/folder.png); }
148 .icon-folder { background-image: url(../images/folder.png); }
148 .icon-package { background-image: url(../images/package.png); }
149 .icon-package { background-image: url(../images/package.png); }
149 .icon-home { background-image: url(../images/home.png); }
150 .icon-home { background-image: url(../images/home.png); }
150 .icon-user { background-image: url(../images/user.png); }
151 .icon-user { background-image: url(../images/user.png); }
151 .icon-mypage { background-image: url(../images/user_page.png); }
152 .icon-mypage { background-image: url(../images/user_page.png); }
152 .icon-admin { background-image: url(../images/admin.png); }
153 .icon-admin { background-image: url(../images/admin.png); }
153 .icon-projects { background-image: url(../images/projects.png); }
154 .icon-projects { background-image: url(../images/projects.png); }
154 .icon-logout { background-image: url(../images/logout.png); }
155 .icon-logout { background-image: url(../images/logout.png); }
155 .icon-help { background-image: url(../images/help.png); }
156 .icon-help { background-image: url(../images/help.png); }
156 .icon-attachment { background-image: url(../images/attachment.png); }
157 .icon-attachment { background-image: url(../images/attachment.png); }
157 .icon-index { background-image: url(../images/index.png); }
158 .icon-index { background-image: url(../images/index.png); }
158 .icon-history { background-image: url(../images/history.png); }
159 .icon-history { background-image: url(../images/history.png); }
159 .icon-feed { background-image: url(../images/feed.png); }
160 .icon-feed { background-image: url(../images/feed.png); }
160 .icon-time { background-image: url(../images/time.png); }
161 .icon-time { background-image: url(../images/time.png); }
161 .icon-stats { background-image: url(../images/stats.png); }
162 .icon-stats { background-image: url(../images/stats.png); }
162 .icon-warning { background-image: url(../images/warning.png); }
163 .icon-warning { background-image: url(../images/warning.png); }
163 .icon-fav { background-image: url(../images/fav.png); }
164 .icon-fav { background-image: url(../images/fav.png); }
164 .icon-fav-off { background-image: url(../images/fav_off.png); }
165 .icon-fav-off { background-image: url(../images/fav_off.png); }
165 .icon-reload { background-image: url(../images/reload.png); }
166 .icon-reload { background-image: url(../images/reload.png); }
166 .icon-lock { background-image: url(../images/locked.png); }
167 .icon-lock { background-image: url(../images/locked.png); }
167 .icon-unlock { background-image: url(../images/unlock.png); }
168 .icon-unlock { background-image: url(../images/unlock.png); }
168
169
169 .icon22-projects { background-image: url(../images/22x22/projects.png); }
170 .icon22-projects { background-image: url(../images/22x22/projects.png); }
170 .icon22-users { background-image: url(../images/22x22/users.png); }
171 .icon22-users { background-image: url(../images/22x22/users.png); }
171 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
172 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
172 .icon22-role { background-image: url(../images/22x22/role.png); }
173 .icon22-role { background-image: url(../images/22x22/role.png); }
173 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
174 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
174 .icon22-options { background-image: url(../images/22x22/options.png); }
175 .icon22-options { background-image: url(../images/22x22/options.png); }
175 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
176 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
176 .icon22-authent { background-image: url(../images/22x22/authent.png); }
177 .icon22-authent { background-image: url(../images/22x22/authent.png); }
177 .icon22-info { background-image: url(../images/22x22/info.png); }
178 .icon22-info { background-image: url(../images/22x22/info.png); }
178 .icon22-comment { background-image: url(../images/22x22/comment.png); }
179 .icon22-comment { background-image: url(../images/22x22/comment.png); }
179 .icon22-package { background-image: url(../images/22x22/package.png); }
180 .icon22-package { background-image: url(../images/22x22/package.png); }
180 .icon22-settings { background-image: url(../images/22x22/settings.png); }
181 .icon22-settings { background-image: url(../images/22x22/settings.png); }
181
182
182 /**************** Content styles ****************/
183 /**************** Content styles ****************/
183
184
184 html>body #content {
185 html>body #content {
185 height: auto;
186 height: auto;
186 min-height: 500px;
187 min-height: 500px;
187 }
188 }
188
189
189 #content{
190 #content{
190 width: auto;
191 width: auto;
191 height:500px;
192 height:500px;
192 font-size:0.9em;
193 font-size:0.9em;
193 padding:20px 10px 10px 20px;
194 padding:20px 10px 10px 20px;
194 margin-left: 120px;
195 margin-left: 120px;
195 border-left: 1px dashed #c0c0c0;
196 border-left: 1px dashed #c0c0c0;
196
197
197 }
198 }
198
199
199 #content h2, #content div.wiki h1 {
200 #content h2, #content div.wiki h1 {
200 display:block;
201 display:block;
201 margin:0 0 16px 0;
202 margin:0 0 16px 0;
202 font-size:1.7em;
203 font-size:1.7em;
203 font-weight:normal;
204 font-weight:normal;
204 letter-spacing:-1px;
205 letter-spacing:-1px;
205 color:#606060;
206 color:#606060;
206 background-color:inherit;
207 background-color:inherit;
207 font-family: Trebuchet MS,Georgia,"Times New Roman",serif;
208 font-family: Trebuchet MS,Georgia,"Times New Roman",serif;
208 }
209 }
209
210
210 #content h2 a{font-weight:normal;}
211 #content h2 a{font-weight:normal;}
211 #content h3{margin:0 0 12px 0; font-size:1.4em;color:#707070;font-family: Trebuchet MS,Georgia,"Times New Roman",serif;}
212 #content h3{margin:0 0 12px 0; font-size:1.4em;color:#707070;font-family: Trebuchet MS,Georgia,"Times New Roman",serif;}
212 #content h4{font-size: 1em; margin-bottom: 12px; margin-top: 20px; font-weight: normal; border-bottom: dotted 1px #c0c0c0;}
213 #content h4{font-size: 1em; margin-bottom: 12px; margin-top: 20px; font-weight: normal; border-bottom: dotted 1px #c0c0c0;}
213 #content a:hover,#subcontent a:hover{text-decoration:underline;}
214 #content a:hover,#subcontent a:hover{text-decoration:underline;}
214 #content ul,#content ol{margin:0 5px 16px 35px;}
215 #content ul,#content ol{margin:0 5px 16px 35px;}
215 #content dl{margin:0 5px 10px 25px;}
216 #content dl{margin:0 5px 10px 25px;}
216 #content dt{font-weight:bold; margin-bottom:5px;}
217 #content dt{font-weight:bold; margin-bottom:5px;}
217 #content dd{margin:0 0 10px 15px;}
218 #content dd{margin:0 0 10px 15px;}
218
219
219 #content .tabs{height: 2.6em;}
220 #content .tabs{height: 2.6em;}
220 #content .tabs ul{margin:0;}
221 #content .tabs ul{margin:0;}
221 #content .tabs ul li{
222 #content .tabs ul li{
222 float:left;
223 float:left;
223 list-style-type:none;
224 list-style-type:none;
224 white-space:nowrap;
225 white-space:nowrap;
225 margin-right:8px;
226 margin-right:8px;
226 background:#fff;
227 background:#fff;
227 }
228 }
228 #content .tabs ul li a{
229 #content .tabs ul li a{
229 display:block;
230 display:block;
230 font-size: 0.9em;
231 font-size: 0.9em;
231 text-decoration:none;
232 text-decoration:none;
232 line-height:1em;
233 line-height:1em;
233 padding:4px;
234 padding:4px;
234 border: 1px solid #c0c0c0;
235 border: 1px solid #c0c0c0;
235 }
236 }
236
237
237 #content .tabs ul li a.selected, #content .tabs ul li a:hover{
238 #content .tabs ul li a.selected, #content .tabs ul li a:hover{
238 background-color: #80b0da;
239 background-color: #80b0da;
239 border: 1px solid #80b0da;
240 border: 1px solid #80b0da;
240 color: #fff;
241 color: #fff;
241 text-decoration:none;
242 text-decoration:none;
242 }
243 }
243
244
244 /***********************************************/
245 /***********************************************/
245
246
246 form {display: inline;}
247 form {display: inline;}
247 blockquote {padding-left: 6px; border-left: 2px solid #ccc;}
248 blockquote {padding-left: 6px; border-left: 2px solid #ccc;}
248 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
249 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
249
250
250 input.button-small {font-size: 0.8em;}
251 input.button-small {font-size: 0.8em;}
251 textarea.wiki-edit { width: 99.5%; }
252 textarea.wiki-edit { width: 99.5%; }
252 .select-small {font-size: 0.8em;}
253 .select-small {font-size: 0.8em;}
253 label {font-weight: bold; font-size: 1em; color: #505050;}
254 label {font-weight: bold; font-size: 1em; color: #505050;}
254 fieldset {border:1px solid #c0c0c0; padding: 6px;}
255 fieldset {border:1px solid #c0c0c0; padding: 6px;}
255 legend {color: #505050;}
256 legend {color: #505050;}
256 .required {color: #bb0000;}
257 .required {color: #bb0000;}
257 .odd {background-color:#f6f7f8;}
258 .odd {background-color:#f6f7f8;}
258 .even {background-color: #fff;}
259 .even {background-color: #fff;}
259 hr { border:0; border-top: dotted 1px #fff; border-bottom: dotted 1px #c0c0c0; }
260 hr { border:0; border-top: dotted 1px #fff; border-bottom: dotted 1px #c0c0c0; }
260 table p {margin:0; padding:0;}
261 table p {margin:0; padding:0;}
261
262
262 .highlight { background-color: #FCFD8D;}
263 .highlight { background-color: #FCFD8D;}
263
264
264 div.square {
265 div.square {
265 border: 1px solid #999;
266 border: 1px solid #999;
266 float: left;
267 float: left;
267 margin: .4em .5em 0 0;
268 margin: .4em .5em 0 0;
268 overflow: hidden;
269 overflow: hidden;
269 width: .6em; height: .6em;
270 width: .6em; height: .6em;
270 }
271 }
271
272
272 ul.documents {
273 ul.documents {
273 list-style-type: none;
274 list-style-type: none;
274 padding: 0;
275 padding: 0;
275 margin: 0;
276 margin: 0;
276 }
277 }
277
278
278 ul.documents li {
279 ul.documents li {
279 background-image: url(../images/32x32/file.png);
280 background-image: url(../images/32x32/file.png);
280 background-repeat: no-repeat;
281 background-repeat: no-repeat;
281 background-position: 0 1px;
282 background-position: 0 1px;
282 padding-left: 36px;
283 padding-left: 36px;
283 margin-bottom: 10px;
284 margin-bottom: 10px;
284 margin-left: -37px;
285 margin-left: -37px;
285 }
286 }
286
287
287 /********** Table used to display lists of things ***********/
288 /********** Table used to display lists of things ***********/
288
289
289 table.list {
290 table.list {
290 width:100%;
291 width:100%;
291 border-collapse: collapse;
292 border-collapse: collapse;
292 border: 1px dotted #d0d0d0;
293 border: 1px dotted #d0d0d0;
293 margin-bottom: 6px;
294 margin-bottom: 6px;
294 }
295 }
295
296
296 table.with-cells td {
297 table.with-cells td {
297 border: 1px solid #d7d7d7;
298 border: 1px solid #d7d7d7;
298 }
299 }
299
300
300 table.list td {
301 table.list td {
301 padding:2px;
302 padding:2px;
302 }
303 }
303
304
304 table.list thead th {
305 table.list thead th {
305 text-align: center;
306 text-align: center;
306 background: #eee;
307 background: #eee;
307 border: 1px solid #d7d7d7;
308 border: 1px solid #d7d7d7;
308 color: #777;
309 color: #777;
309 }
310 }
310
311
311 table.list tbody th {
312 table.list tbody th {
312 font-weight: bold;
313 font-weight: bold;
313 background: #eed;
314 background: #eed;
314 border: 1px solid #d7d7d7;
315 border: 1px solid #d7d7d7;
315 color: #777;
316 color: #777;
316 }
317 }
317
318
318 /*========== Drop down menu ==============*/
319 /*========== Drop down menu ==============*/
319 div.menu {
320 div.menu {
320 background-color: #FFFFFF;
321 background-color: #FFFFFF;
321 border-style: solid;
322 border-style: solid;
322 border-width: 1px;
323 border-width: 1px;
323 border-color: #7F9DB9;
324 border-color: #7F9DB9;
324 position: absolute;
325 position: absolute;
325 top: 0px;
326 top: 0px;
326 left: 0px;
327 left: 0px;
327 padding: 0;
328 padding: 0;
328 visibility: hidden;
329 visibility: hidden;
329 z-index: 101;
330 z-index: 101;
330 }
331 }
331
332
332 div.menu a.menuItem {
333 div.menu a.menuItem {
333 font-size: 10px;
334 font-size: 10px;
334 font-weight: normal;
335 font-weight: normal;
335 line-height: 2em;
336 line-height: 2em;
336 color: #000000;
337 color: #000000;
337 background-color: #FFFFFF;
338 background-color: #FFFFFF;
338 cursor: default;
339 cursor: default;
339 display: block;
340 display: block;
340 padding: 0 1em;
341 padding: 0 1em;
341 margin: 0;
342 margin: 0;
342 border: 0;
343 border: 0;
343 text-decoration: none;
344 text-decoration: none;
344 white-space: nowrap;
345 white-space: nowrap;
345 }
346 }
346
347
347 div.menu a.menuItem:hover, div.menu a.menuItemHighlight {
348 div.menu a.menuItem:hover, div.menu a.menuItemHighlight {
348 background-color: #80b0da;
349 background-color: #80b0da;
349 color: #ffffff;
350 color: #ffffff;
350 }
351 }
351
352
352 div.menu a.menuItem span.menuItemText {}
353 div.menu a.menuItem span.menuItemText {}
353
354
354 div.menu a.menuItem span.menuItemArrow {
355 div.menu a.menuItem span.menuItemArrow {
355 margin-right: -.75em;
356 margin-right: -.75em;
356 }
357 }
357
358
358 /**************** Sidebar styles ****************/
359 /**************** Sidebar styles ****************/
359
360
360 #subcontent{
361 #subcontent{
361 position: absolute;
362 position: absolute;
362 left: 0px;
363 left: 0px;
363 width:95px;
364 width:95px;
364 padding:20px 20px 10px 5px;
365 padding:20px 20px 10px 5px;
365 overflow: hidden;
366 overflow: hidden;
366 }
367 }
367
368
368 #subcontent h2{
369 #subcontent h2{
369 display:block;
370 display:block;
370 margin:0 0 5px 0;
371 margin:0 0 5px 0;
371 font-size:1.0em;
372 font-size:1.0em;
372 font-weight:bold;
373 font-weight:bold;
373 text-align:left;
374 text-align:left;
374 color:#606060;
375 color:#606060;
375 background-color:inherit;
376 background-color:inherit;
376 font-family: Trebuchet MS,Georgia,"Times New Roman",serif;
377 font-family: Trebuchet MS,Georgia,"Times New Roman",serif;
377 }
378 }
378
379
379 #subcontent p{margin:0 0 16px 0; font-size:0.9em;}
380 #subcontent p{margin:0 0 16px 0; font-size:0.9em;}
380
381
381 /**************** Menublock styles ****************/
382 /**************** Menublock styles ****************/
382
383
383 .menublock{margin:0 0 20px 8px; font-size:0.8em;}
384 .menublock{margin:0 0 20px 8px; font-size:0.8em;}
384 .menublock li{list-style:none; display:block; padding:1px; margin-bottom:0px;}
385 .menublock li{list-style:none; display:block; padding:1px; margin-bottom:0px;}
385 .menublock li a{font-weight:bold; text-decoration:none;}
386 .menublock li a{font-weight:bold; text-decoration:none;}
386 .menublock li a:hover{text-decoration:none;}
387 .menublock li a:hover{text-decoration:none;}
387 .menublock li ul{margin:0; font-size:1em; font-weight:normal;}
388 .menublock li ul{margin:0; font-size:1em; font-weight:normal;}
388 .menublock li ul li{margin-bottom:0;}
389 .menublock li ul li{margin-bottom:0;}
389 .menublock li ul a{font-weight:normal;}
390 .menublock li ul a{font-weight:normal;}
390
391
391 /**************** Footer styles ****************/
392 /**************** Footer styles ****************/
392
393
393 #footer{
394 #footer{
394 clear:both;
395 clear:both;
395 padding:5px 0;
396 padding:5px 0;
396 margin:0;
397 margin:0;
397 font-size:0.9em;
398 font-size:0.9em;
398 color:#f0f0f0;
399 color:#f0f0f0;
399 background:#467aa7;
400 background:#467aa7;
400 }
401 }
401
402
402 #footer p{padding:0; margin:0; text-align:center;}
403 #footer p{padding:0; margin:0; text-align:center;}
403 #footer a{color:#f0f0f0; background-color:inherit; font-weight:bold;}
404 #footer a{color:#f0f0f0; background-color:inherit; font-weight:bold;}
404 #footer a:hover{color:#ffffff; background-color:inherit; text-decoration: underline;}
405 #footer a:hover{color:#ffffff; background-color:inherit; text-decoration: underline;}
405
406
406 /**************** Misc classes and styles ****************/
407 /**************** Misc classes and styles ****************/
407
408
408 .splitcontentleft{float:left; width:49%;}
409 .splitcontentleft{float:left; width:49%;}
409 .splitcontentright{float:right; width:49%;}
410 .splitcontentright{float:right; width:49%;}
410 .clear{clear:both;}
411 .clear{clear:both;}
411 .small{font-size:0.8em;line-height:1.4em;padding:0 0 0 0;}
412 .small{font-size:0.8em;line-height:1.4em;padding:0 0 0 0;}
412 .hide{display:none;}
413 .hide{display:none;}
413 .textcenter{text-align:center;}
414 .textcenter{text-align:center;}
414 .textright{text-align:right;}
415 .textright{text-align:right;}
415 .important{color:#f02025; background-color:inherit; font-weight:bold;}
416 .important{color:#f02025; background-color:inherit; font-weight:bold;}
416
417
417 .box{
418 .box{
418 margin:0 0 20px 0;
419 margin:0 0 20px 0;
419 padding:10px;
420 padding:10px;
420 border:1px solid #c0c0c0;
421 border:1px solid #c0c0c0;
421 background-color:#fafbfc;
422 background-color:#fafbfc;
422 color:#505050;
423 color:#505050;
423 line-height:1.5em;
424 line-height:1.5em;
424 }
425 }
425
426
426 a.close-icon {
427 a.close-icon {
427 display:block;
428 display:block;
428 margin-top:3px;
429 margin-top:3px;
429 overflow:hidden;
430 overflow:hidden;
430 width:12px;
431 width:12px;
431 height:12px;
432 height:12px;
432 background-repeat: no-repeat;
433 background-repeat: no-repeat;
433 cursor:pointer;
434 cursor:pointer;
434 background-image:url('../images/close.png');
435 background-image:url('../images/close.png');
435 }
436 }
436
437
437 a.close-icon:hover {
438 a.close-icon:hover {
438 background-image:url('../images/close_hl.png');
439 background-image:url('../images/close_hl.png');
439 }
440 }
440
441
441 .rightbox{
442 .rightbox{
442 background: #fafbfc;
443 background: #fafbfc;
443 border: 1px solid #c0c0c0;
444 border: 1px solid #c0c0c0;
444 float: right;
445 float: right;
445 padding: 8px;
446 padding: 8px;
446 position: relative;
447 position: relative;
447 margin: 0 5px 5px;
448 margin: 0 5px 5px;
448 }
449 }
449
450
450 div.attachments {padding-left: 6px; border-left: 2px solid #ccc; margin-bottom: 8px;}
451 div.attachments {padding-left: 6px; border-left: 2px solid #ccc; margin-bottom: 8px;}
451 div.attachments p {margin-bottom:2px;}
452 div.attachments p {margin-bottom:2px;}
452
453
453 .overlay{
454 .overlay{
454 position: absolute;
455 position: absolute;
455 margin-left:0;
456 margin-left:0;
456 z-index: 50;
457 z-index: 50;
457 }
458 }
458
459
459 .layout-active {
460 .layout-active {
460 background: #ECF3E1;
461 background: #ECF3E1;
461 }
462 }
462
463
463 .block-receiver {
464 .block-receiver {
464 border:1px dashed #c0c0c0;
465 border:1px dashed #c0c0c0;
465 margin-bottom: 20px;
466 margin-bottom: 20px;
466 padding: 15px 0 15px 0;
467 padding: 15px 0 15px 0;
467 }
468 }
468
469
469 .mypage-box {
470 .mypage-box {
470 margin:0 0 20px 0;
471 margin:0 0 20px 0;
471 color:#505050;
472 color:#505050;
472 line-height:1.5em;
473 line-height:1.5em;
473 }
474 }
474
475
475 .handle {
476 .handle {
476 cursor: move;
477 cursor: move;
477 }
478 }
478
479
479 .login {
480 .login {
480 width: 50%;
481 width: 50%;
481 text-align: left;
482 text-align: left;
482 }
483 }
483
484
484 img.calendar-trigger {
485 img.calendar-trigger {
485 cursor: pointer;
486 cursor: pointer;
486 vertical-align: middle;
487 vertical-align: middle;
487 margin-left: 4px;
488 margin-left: 4px;
488 }
489 }
489
490
490 #history p {
491 #history p {
491 margin-left: 34px;
492 margin-left: 34px;
492 }
493 }
493
494
494 .progress {
495 .progress {
495 border: 1px solid #D7D7D7;
496 border: 1px solid #D7D7D7;
496 border-collapse: collapse;
497 border-collapse: collapse;
497 border-spacing: 0pt;
498 border-spacing: 0pt;
498 empty-cells: show;
499 empty-cells: show;
499 padding: 3px;
500 padding: 3px;
500 width: 40em;
501 width: 40em;
501 text-align: center;
502 text-align: center;
502 }
503 }
503
504
504 .progress td { height: 1em; }
505 .progress td { height: 1em; }
505 .progress .closed { background: #BAE0BA none repeat scroll 0%; }
506 .progress .closed { background: #BAE0BA none repeat scroll 0%; }
506 .progress .open { background: #FFF none repeat scroll 0%; }
507 .progress .open { background: #FFF none repeat scroll 0%; }
507
508
508 /***** Contextual links div *****/
509 /***** Contextual links div *****/
509 .contextual {
510 .contextual {
510 float: right;
511 float: right;
511 font-size: 0.8em;
512 font-size: 0.8em;
512 line-height: 16px;
513 line-height: 16px;
513 padding: 2px;
514 padding: 2px;
514 }
515 }
515
516
516 .contextual select, .contextual input {
517 .contextual select, .contextual input {
517 font-size: 1em;
518 font-size: 1em;
518 }
519 }
519
520
520 /***** Gantt chart *****/
521 /***** Gantt chart *****/
521 .gantt_hdr {
522 .gantt_hdr {
522 position:absolute;
523 position:absolute;
523 top:0;
524 top:0;
524 height:16px;
525 height:16px;
525 border-top: 1px solid #c0c0c0;
526 border-top: 1px solid #c0c0c0;
526 border-bottom: 1px solid #c0c0c0;
527 border-bottom: 1px solid #c0c0c0;
527 border-right: 1px solid #c0c0c0;
528 border-right: 1px solid #c0c0c0;
528 text-align: center;
529 text-align: center;
529 overflow: hidden;
530 overflow: hidden;
530 }
531 }
531
532
532 .task {
533 .task {
533 position: absolute;
534 position: absolute;
534 height:8px;
535 height:8px;
535 font-size:0.8em;
536 font-size:0.8em;
536 color:#888;
537 color:#888;
537 padding:0;
538 padding:0;
538 margin:0;
539 margin:0;
539 line-height:0.8em;
540 line-height:0.8em;
540 }
541 }
541
542
542 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
543 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
543 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
544 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
544 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
545 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
545 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
546 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
546
547
547 /***** Tooltips ******/
548 /***** Tooltips ******/
548 .tooltip{position:relative;z-index:24;}
549 .tooltip{position:relative;z-index:24;}
549 .tooltip:hover{z-index:25;color:#000;}
550 .tooltip:hover{z-index:25;color:#000;}
550 .tooltip span.tip{display: none; text-align:left;}
551 .tooltip span.tip{display: none; text-align:left;}
551
552
552 div.tooltip:hover span.tip{
553 div.tooltip:hover span.tip{
553 display:block;
554 display:block;
554 position:absolute;
555 position:absolute;
555 top:12px; left:24px; width:270px;
556 top:12px; left:24px; width:270px;
556 border:1px solid #555;
557 border:1px solid #555;
557 background-color:#fff;
558 background-color:#fff;
558 padding: 4px;
559 padding: 4px;
559 font-size: 0.8em;
560 font-size: 0.8em;
560 color:#505050;
561 color:#505050;
561 }
562 }
562
563
563 /***** CSS FORM ******/
564 /***** CSS FORM ******/
564 .tabular p{
565 .tabular p{
565 margin: 0;
566 margin: 0;
566 padding: 5px 0 8px 0;
567 padding: 5px 0 8px 0;
567 padding-left: 180px; /*width of left column containing the label elements*/
568 padding-left: 180px; /*width of left column containing the label elements*/
568 height: 1%;
569 height: 1%;
569 clear:both;
570 clear:both;
570 }
571 }
571
572
572 .tabular label{
573 .tabular label{
573 font-weight: bold;
574 font-weight: bold;
574 float: left;
575 float: left;
575 margin-left: -180px; /*width of left column*/
576 margin-left: -180px; /*width of left column*/
576 margin-bottom: 10px;
577 margin-bottom: 10px;
577 width: 175px; /*width of labels. Should be smaller than left column to create some right
578 width: 175px; /*width of labels. Should be smaller than left column to create some right
578 margin*/
579 margin*/
579 }
580 }
580
581
581 .error {
582 .error {
582 color: #cc0000;
583 color: #cc0000;
583 }
584 }
584
585
585 #settings .tabular p{ padding-left: 300px; }
586 #settings .tabular p{ padding-left: 300px; }
586 #settings .tabular label{ margin-left: -300px; width: 295px; }
587 #settings .tabular label{ margin-left: -300px; width: 295px; }
587
588
588 /*.threepxfix class below:
589 /*.threepxfix class below:
589 Targets IE6- ONLY. Adds 3 pixel indent for multi-line form contents.
590 Targets IE6- ONLY. Adds 3 pixel indent for multi-line form contents.
590 to account for 3 pixel bug: http://www.positioniseverything.net/explorer/threepxtest.html
591 to account for 3 pixel bug: http://www.positioniseverything.net/explorer/threepxtest.html
591 */
592 */
592
593
593 * html .threepxfix{
594 * html .threepxfix{
594 margin-left: 3px;
595 margin-left: 3px;
595 }
596 }
596
597
597 /***** Wiki sections ****/
598 /***** Wiki sections ****/
598 #content div.wiki { font-size: 110%}
599 #content div.wiki { font-size: 110%}
599
600
600 #content div.wiki h2, div.wiki h3 { font-family: Trebuchet MS,Georgia,"Times New Roman",serif; color:#606060; }
601 #content div.wiki h2, div.wiki h3 { font-family: Trebuchet MS,Georgia,"Times New Roman",serif; color:#606060; }
601 #content div.wiki h2 { font-size: 1.4em;}
602 #content div.wiki h2 { font-size: 1.4em;}
602 #content div.wiki h3 { font-size: 1.2em;}
603 #content div.wiki h3 { font-size: 1.2em;}
603
604
604 div.wiki table {
605 div.wiki table {
605 border: 1px solid #505050;
606 border: 1px solid #505050;
606 border-collapse: collapse;
607 border-collapse: collapse;
607 }
608 }
608
609
609 div.wiki table, div.wiki td {
610 div.wiki table, div.wiki td {
610 border: 1px solid #bbb;
611 border: 1px solid #bbb;
611 padding: 4px;
612 padding: 4px;
612 }
613 }
613
614
614 div.wiki a {
615 div.wiki a {
615 background-position: 0% 60%;
616 background-position: 0% 60%;
616 background-repeat: no-repeat;
617 background-repeat: no-repeat;
617 padding-left: 12px;
618 padding-left: 12px;
618 background-image: url(../images/external.png);
619 background-image: url(../images/external.png);
619 }
620 }
620
621
621 div.wiki a.wiki-page, div.wiki a.issue, div.wiki a.changeset {
622 div.wiki a.wiki-page, div.wiki a.issue, div.wiki a.changeset {
622 padding-left: 0;
623 padding-left: 0;
623 background-image: none;
624 background-image: none;
624 }
625 }
625
626
626 div.wiki code {
627 div.wiki code {
627 font-size: 1.2em;
628 font-size: 1.2em;
628 }
629 }
629
630
630 div.wiki img {
631 div.wiki img {
631 margin: 6px;
632 margin: 6px;
632 }
633 }
633
634
634 .diff_out{
635 .diff_out{
635 background: #fcc;
636 background: #fcc;
636 }
637 }
637
638
638 .diff_in{
639 .diff_in{
639 background: #cfc;
640 background: #cfc;
640 }
641 }
641
642
642 #preview .preview { background: #fafbfc url(../images/draft.png); }
643 #preview .preview { background: #fafbfc url(../images/draft.png); }
643
644
644 #ajax-indicator {
645 #ajax-indicator {
645 position: absolute; /* fixed not supported by IE */
646 position: absolute; /* fixed not supported by IE */
646 background-color:#eee;
647 background-color:#eee;
647 border: 1px solid #bbb;
648 border: 1px solid #bbb;
648 top:35%;
649 top:35%;
649 left:40%;
650 left:40%;
650 width:20%;
651 width:20%;
651 font-weight:bold;
652 font-weight:bold;
652 text-align:center;
653 text-align:center;
653 padding:0.6em;
654 padding:0.6em;
654 z-index:100;
655 z-index:100;
655 filter:alpha(opacity=50);
656 filter:alpha(opacity=50);
656 -moz-opacity:0.5;
657 -moz-opacity:0.5;
657 opacity: 0.5;
658 opacity: 0.5;
658 -khtml-opacity: 0.5;
659 -khtml-opacity: 0.5;
659 }
660 }
660
661
661 html>body #ajax-indicator { position: fixed; }
662 html>body #ajax-indicator { position: fixed; }
662
663
663 #ajax-indicator span {
664 #ajax-indicator span {
664 background-position: 0% 40%;
665 background-position: 0% 40%;
665 background-repeat: no-repeat;
666 background-repeat: no-repeat;
666 background-image: url(../images/loading.gif);
667 background-image: url(../images/loading.gif);
667 padding-left: 26px;
668 padding-left: 26px;
668 vertical-align: bottom;
669 vertical-align: bottom;
669 }
670 }
670
671
671 /***** Flash & error messages ****/
672 /***** Flash & error messages ****/
672 #flash div, #errorExplanation {
673 #flash div, #errorExplanation {
673 padding: 4px 4px 4px 30px;
674 padding: 4px 4px 4px 30px;
674 margin-bottom: 16px;
675 margin-bottom: 16px;
675 font-size: 1.1em;
676 font-size: 1.1em;
676 border: 2px solid;
677 border: 2px solid;
677 }
678 }
678
679
679 #flash div.error, #errorExplanation {
680 #flash div.error, #errorExplanation {
680 background: url(../images/false.png) 8px 5px no-repeat;
681 background: url(../images/false.png) 8px 5px no-repeat;
681 background-color: #ffe3e3;
682 background-color: #ffe3e3;
682 border-color: #dd0000;
683 border-color: #dd0000;
683 color: #550000;
684 color: #550000;
684 }
685 }
685
686
686 #flash div.notice {
687 #flash div.notice {
687 background: url(../images/true.png) 8px 5px no-repeat;
688 background: url(../images/true.png) 8px 5px no-repeat;
688 background-color: #dfffdf;
689 background-color: #dfffdf;
689 border-color: #9fcf9f;
690 border-color: #9fcf9f;
690 color: #005f00;
691 color: #005f00;
691 }
692 }
692
693
693 #errorExplanation ul { margin-bottom: 0px; }
694 #errorExplanation ul { margin-bottom: 0px; }
694 #errorExplanation ul li { list-style: none; margin-left: -16px;}
695 #errorExplanation ul li { list-style: none; margin-left: -16px;}
General Comments 0
You need to be logged in to leave comments. Login now