summaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorJean-Philippe Lang <jp_lang@yahoo.fr>2009-01-24 11:31:15 +0000
committerJean-Philippe Lang <jp_lang@yahoo.fr>2009-01-24 11:31:15 +0000
commitc9906480d3f279977720dc3f61534f4a5b77d1d2 (patch)
tree23d1ad1fc0a9435bcb703033dd7b5069ac565005 /app
parent51b745470c1d71b92072210a008f29c5d5a945d1 (diff)
downloadredmine-c9906480d3f279977720dc3f61534f4a5b77d1d2.tar.gz
redmine-c9906480d3f279977720dc3f61534f4a5b77d1d2.zip
Merged nested projects branch. Removes limit on subproject nesting (#594).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2304 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'app')
-rw-r--r--app/controllers/admin_controller.rb13
-rw-r--r--app/controllers/projects_controller.rb22
-rw-r--r--app/controllers/reports_controller.rb8
-rw-r--r--app/controllers/search_controller.rb2
-rw-r--r--app/controllers/users_controller.rb2
-rw-r--r--app/helpers/admin_helper.rb8
-rw-r--r--app/helpers/application_helper.rb39
-rw-r--r--app/helpers/projects_helper.rb35
-rw-r--r--app/helpers/search_helper.rb2
-rw-r--r--app/helpers/users_helper.rb11
-rw-r--r--app/models/project.rb64
-rw-r--r--app/models/query.rb10
-rw-r--r--app/views/admin/projects.rhtml20
-rw-r--r--app/views/layouts/_project_selector.rhtml12
-rw-r--r--app/views/layouts/base.rhtml2
-rw-r--r--app/views/projects/_form.rhtml4
-rw-r--r--app/views/projects/activity.rhtml2
-rw-r--r--app/views/projects/destroy.rhtml4
-rw-r--r--app/views/projects/index.rhtml13
-rw-r--r--app/views/projects/show.rhtml12
-rw-r--r--app/views/users/_memberships.rhtml2
21 files changed, 184 insertions, 103 deletions
diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb
index 45a20bf05..61d1eada7 100644
--- a/app/controllers/admin_controller.rb
+++ b/app/controllers/admin_controller.rb
@@ -26,9 +26,6 @@ class AdminController < ApplicationController
end
def projects
- sort_init 'name', 'asc'
- sort_update %w(name is_public created_on)
-
@status = params[:status] ? params[:status].to_i : 1
c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status])
@@ -37,14 +34,8 @@ class AdminController < ApplicationController
c << ["LOWER(identifier) LIKE ? OR LOWER(name) LIKE ?", name, name]
end
- @project_count = Project.count(:conditions => c.conditions)
- @project_pages = Paginator.new self, @project_count,
- per_page_option,
- params['page']
- @projects = Project.find :all, :order => sort_clause,
- :conditions => c.conditions,
- :limit => @project_pages.items_per_page,
- :offset => @project_pages.current.offset
+ @projects = Project.find :all, :order => 'lft',
+ :conditions => c.conditions
render :action => "projects", :layout => false if request.xhr?
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 2610ca6bc..64040e3ba 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -43,17 +43,14 @@ class ProjectsController < ApplicationController
# Lists visible projects
def index
- projects = Project.find :all,
- :conditions => Project.visible_by(User.current),
- :include => :parent
respond_to do |format|
format.html {
- @project_tree = projects.group_by {|p| p.parent || p}
- @project_tree.keys.each {|p| @project_tree[p] -= [p]}
+ @projects = Project.visible.find(:all, :order => 'lft')
}
format.atom {
- render_feed(projects.sort_by(&:created_on).reverse.slice(0, Setting.feeds_limit.to_i),
- :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
+ projects = Project.visible.find(:all, :order => 'created_on DESC',
+ :limit => Setting.feeds_limit.to_i)
+ render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
}
end
end
@@ -62,9 +59,6 @@ class ProjectsController < ApplicationController
def add
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.all
- @root_projects = Project.find(:all,
- :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
- :order => 'name')
@project = Project.new(params[:project])
if request.get?
@project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
@@ -74,6 +68,7 @@ class ProjectsController < ApplicationController
else
@project.enabled_module_names = params[:enabled_modules]
if @project.save
+ @project.set_parent!(params[:project]['parent_id']) if User.current.admin? && params[:project].has_key?('parent_id')
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'admin', :action => 'projects'
end
@@ -88,7 +83,8 @@ class ProjectsController < ApplicationController
end
@members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
- @subprojects = @project.children.find(:all, :conditions => Project.visible_by(User.current))
+ @subprojects = @project.children.visible
+ @ancestors = @project.ancestors.visible
@news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
@trackers = @project.rolled_up_trackers
@@ -110,9 +106,6 @@ class ProjectsController < ApplicationController
end
def settings
- @root_projects = Project.find(:all,
- :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id],
- :order => 'name')
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@issue_category ||= IssueCategory.new
@member ||= @project.members.new
@@ -126,6 +119,7 @@ class ProjectsController < ApplicationController
if request.post?
@project.attributes = params[:project]
if @project.save
+ @project.set_parent!(params[:project]['parent_id']) if User.current.admin? && params[:project].has_key?('parent_id')
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'settings', :id => @project
else
diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb
index dd3ece930..bfe515778 100644
--- a/app/controllers/reports_controller.rb
+++ b/app/controllers/reports_controller.rb
@@ -61,7 +61,7 @@ class ReportsController < ApplicationController
render :template => "reports/issue_report_details"
when "subproject"
@field = "project_id"
- @rows = @project.active_children
+ @rows = @project.descendants.active
@data = issues_by_subproject
@report_title = l(:field_subproject)
render :template => "reports/issue_report_details"
@@ -72,7 +72,7 @@ class ReportsController < ApplicationController
@categories = @project.issue_categories
@assignees = @project.members.collect { |m| m.user }
@authors = @project.members.collect { |m| m.user }
- @subprojects = @project.active_children
+ @subprojects = @project.descendants.active
issues_by_tracker
issues_by_version
issues_by_priority
@@ -229,8 +229,8 @@ private
#{Issue.table_name} i, #{IssueStatus.table_name} s
where
i.status_id=s.id
- and i.project_id IN (#{@project.active_children.collect{|p| p.id}.join(',')})
- group by s.id, s.is_closed, i.project_id") if @project.active_children.any?
+ and i.project_id IN (#{@project.descendants.active.collect{|p| p.id}.join(',')})
+ group by s.id, s.is_closed, i.project_id") if @project.descendants.active.any?
@issues_by_subproject ||= []
end
end
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index e6e66f05c..485d2349d 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -34,7 +34,7 @@ class SearchController < ApplicationController
when 'my_projects'
User.current.memberships.collect(&:project)
when 'subprojects'
- @project ? ([ @project ] + @project.active_children) : nil
+ @project ? (@project.self_and_descendants.active) : nil
else
@project
end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 4c9302824..ced17d667 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -83,7 +83,7 @@ class UsersController < ApplicationController
end
@auth_sources = AuthSource.find(:all)
@roles = Role.find_all_givable
- @projects = Project.find(:all, :order => 'name', :conditions => "status=#{Project::STATUS_ACTIVE}") - @user.projects
+ @projects = Project.active.find(:all, :order => 'lft')
@membership ||= Member.new
@memberships = @user.memberships
end
diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb
index 8f81f66ba..b49a5674c 100644
--- a/app/helpers/admin_helper.rb
+++ b/app/helpers/admin_helper.rb
@@ -20,4 +20,12 @@ module AdminHelper
options_for_select([[l(:label_all), ''],
[l(:status_active), 1]], selected)
end
+
+ def css_project_classes(project)
+ s = 'project'
+ s << ' root' if project.root?
+ s << ' child' if project.child?
+ s << (project.leaf? ? ' leaf' : ' parent')
+ s
+ end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 35d1670ae..375d030ca 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -156,6 +156,45 @@ module ApplicationHelper
end
s
end
+
+ # Renders the project quick-jump box
+ def render_project_jump_box
+ # Retrieve them now to avoid a COUNT query
+ projects = User.current.projects.all
+ if projects.any?
+ s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
+ "<option selected='selected'>#{ l(:label_jump_to_a_project) }</option>" +
+ '<option disabled="disabled">---</option>'
+ s << project_tree_options_for_select(projects) do |p|
+ { :value => url_for(:controller => 'projects', :action => 'show', :id => p) }
+ end
+ s << '</select>'
+ s
+ end
+ end
+
+ def project_tree_options_for_select(projects, options = {})
+ s = ''
+ project_tree(projects) do |project, level|
+ name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
+ tag_options = {:value => project.id, :selected => ((project == options[:selected]) ? 'selected' : nil)}
+ tag_options.merge!(yield(project)) if block_given?
+ s << content_tag('option', name_prefix + h(project), tag_options)
+ end
+ s
+ end
+
+ # Yields the given block for each project with its level in the tree
+ def project_tree(projects, &block)
+ ancestors = []
+ projects.sort_by(&:lft).each do |project|
+ while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
+ ancestors.pop
+ end
+ yield project, ancestors.size
+ ancestors << project
+ end
+ end
# Truncates and returns the string as a single line
def truncate_single_line(string, *args)
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 6f5e03349..912450c1c 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -33,4 +33,39 @@ module ProjectsHelper
]
tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}
end
+
+ def parent_project_select_tag(project)
+ options = '<option></option>' + project_tree_options_for_select(project.possible_parents, :selected => project.parent)
+ content_tag('select', options, :name => 'project[parent_id]')
+ end
+
+ # Renders a tree of projects as a nested set of unordered lists
+ # The given collection may be a subset of the whole project tree
+ # (eg. some intermediate nodes are private and can not be seen)
+ def render_project_hierarchy(projects)
+ s = ''
+ if projects.any?
+ ancestors = []
+ projects.each do |project|
+ if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
+ s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
+ else
+ ancestors.pop
+ s << "</li>"
+ while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
+ ancestors.pop
+ s << "</ul></li>\n"
+ end
+ end
+ classes = (ancestors.empty? ? 'root' : 'child')
+ s << "<li class='#{classes}'><div class='#{classes}'>" +
+ link_to(h(project), {:controller => 'projects', :action => 'show', :id => project}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}")
+ s << "<div class='wiki description'>#{textilizable(project.short_description, :project => project)}</div>" unless project.description.blank?
+ s << "</div>\n"
+ ancestors << project
+ end
+ s << ("</li></ul>\n" * ancestors.size)
+ end
+ s
+ end
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index cd96dbd3f..32ff16f67 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -44,7 +44,7 @@ module SearchHelper
def project_select_tag
options = [[l(:label_project_all), 'all']]
options << [l(:label_my_projects), 'my_projects'] unless User.current.memberships.empty?
- options << [l(:label_and_its_subprojects, @project.name), 'subprojects'] unless @project.nil? || @project.active_children.empty?
+ options << [l(:label_and_its_subprojects, @project.name), 'subprojects'] unless @project.nil? || @project.descendants.active.empty?
options << [@project.name, ''] unless @project.nil?
select_tag('scope', options_for_select(options, params[:scope].to_s)) if options.size > 1
end
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index 5b113e880..b9e990d6e 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -25,15 +25,10 @@ module UsersHelper
end
# Options for the new membership projects combo-box
- def projects_options_for_select(projects)
+ def options_for_membership_project_select(user, projects)
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
- projects_by_root = projects.group_by(&:root)
- projects_by_root.keys.sort.each do |root|
- options << content_tag('option', h(root.name), :value => root.id, :disabled => (!projects.include?(root)))
- projects_by_root[root].sort.each do |project|
- next if project == root
- options << content_tag('option', '&#187; ' + h(project.name), :value => project.id)
- end
+ options << project_tree_options_for_select(projects) do |p|
+ {:disabled => (user.projects.include?(p))}
end
options
end
diff --git a/app/models/project.rb b/app/models/project.rb
index c792b9c3b..8e4bd78e1 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -43,7 +43,7 @@ class Project < ActiveRecord::Base
:join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
:association_foreign_key => 'custom_field_id'
- acts_as_tree :order => "name", :counter_cache => true
+ acts_as_nested_set :order => 'name', :dependent => :destroy
acts_as_attachable :view_permission => :view_files,
:delete_permission => :manage_files
@@ -66,6 +66,8 @@ class Project < ActiveRecord::Base
before_destroy :delete_all_members
named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
+ named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
+ named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
def identifier=(identifier)
super unless identifier_frozen?
@@ -78,7 +80,7 @@ class Project < ActiveRecord::Base
def issues_with_subprojects(include_subprojects=false)
conditions = nil
if include_subprojects
- ids = [id] + child_ids
+ ids = [id] + descendants.collect(&:id)
conditions = ["#{Project.table_name}.id IN (#{ids.join(',')}) AND #{Project.visible_by}"]
end
conditions ||= ["#{Project.table_name}.id = ?", id]
@@ -118,7 +120,7 @@ class Project < ActiveRecord::Base
end
if options[:project]
project_statement = "#{Project.table_name}.id = #{options[:project].id}"
- project_statement << " OR #{Project.table_name}.parent_id = #{options[:project].id}" if options[:with_subprojects]
+ project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
base_statement = "(#{project_statement}) AND (#{base_statement})"
end
if user.admin?
@@ -141,7 +143,7 @@ class Project < ActiveRecord::Base
def project_condition(with_subprojects)
cond = "#{Project.table_name}.id = #{id}"
- cond = "(#{cond} OR #{Project.table_name}.parent_id = #{id})" if with_subprojects
+ cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
cond
end
@@ -164,6 +166,7 @@ class Project < ActiveRecord::Base
self.status == STATUS_ACTIVE
end
+ # Archives the project and its descendants recursively
def archive
# Archive subprojects if any
children.each do |subproject|
@@ -172,13 +175,54 @@ class Project < ActiveRecord::Base
update_attribute :status, STATUS_ARCHIVED
end
+ # Unarchives the project
+ # All its ancestors must be active
def unarchive
- return false if parent && !parent.active?
+ return false if ancestors.detect {|a| !a.active?}
update_attribute :status, STATUS_ACTIVE
end
- def active_children
- children.select {|child| child.active?}
+ # Returns an array of projects the project can be moved to
+ def possible_parents
+ @possible_parents ||= (Project.active.find(:all) - self_and_descendants)
+ end
+
+ # Sets the parent of the project
+ # Argument can be either a Project, a String, a Fixnum or nil
+ def set_parent!(p)
+ unless p.nil? || p.is_a?(Project)
+ if p.to_s.blank?
+ p = nil
+ else
+ p = Project.find_by_id(p)
+ return false unless p
+ end
+ end
+ if p == parent && !p.nil?
+ # Nothing to do
+ true
+ elsif p.nil? || (p.active? && move_possible?(p))
+ # Insert the project so that target's children or root projects stay alphabetically sorted
+ sibs = (p.nil? ? self.class.roots : p.children)
+ to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
+ if to_be_inserted_before
+ move_to_left_of(to_be_inserted_before)
+ elsif p.nil?
+ if sibs.empty?
+ # move_to_root adds the project in first (ie. left) position
+ move_to_root
+ else
+ move_to_right_of(sibs.last) unless self == sibs.last
+ end
+ else
+ # move_to_child_of adds the project in last (ie.right) position
+ move_to_child_of(p)
+ end
+ true
+ else
+ # Can not move to the given target
+ false
+ end
end
# Returns an array of the trackers used by the project and its sub projects
@@ -186,7 +230,7 @@ class Project < ActiveRecord::Base
@rolled_up_trackers ||=
Tracker.find(:all, :include => :projects,
:select => "DISTINCT #{Tracker.table_name}.*",
- :conditions => ["#{Project.table_name}.id = ? OR #{Project.table_name}.parent_id = ?", id, id],
+ :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ?", lft, rgt],
:order => "#{Tracker.table_name}.position")
end
@@ -225,7 +269,7 @@ class Project < ActiveRecord::Base
# Returns a short description of the projects (first lines)
def short_description(length = 255)
- description.gsub(/^(.{#{length}}[^\n]*).*$/m, '\1').strip if description
+ description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
end
def allows_to?(action)
@@ -257,8 +301,6 @@ class Project < ActiveRecord::Base
protected
def validate
- errors.add(parent_id, " must be a root project") if parent and parent.parent
- errors.add_to_base("A project with subprojects can't be a subproject") if parent and children.size > 0
errors.add(:identifier, :activerecord_error_invalid) if !identifier.blank? && identifier.match(/^\d*$/)
end
diff --git a/app/models/query.rb b/app/models/query.rb
index 0016cb24b..493144bf7 100644
--- a/app/models/query.rb
+++ b/app/models/query.rb
@@ -174,8 +174,8 @@ class Query < ActiveRecord::Base
unless @project.versions.empty?
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
end
- unless @project.active_children.empty?
- @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } }
+ unless @project.descendants.active.empty?
+ @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } }
end
add_custom_fields_filters(@project.all_issue_custom_fields)
else
@@ -257,7 +257,7 @@ class Query < ActiveRecord::Base
def project_statement
project_clauses = []
- if project && !@project.active_children.empty?
+ if project && !@project.descendants.active.empty?
ids = [project.id]
if has_filter?("subproject_id")
case operator_for("subproject_id")
@@ -268,10 +268,10 @@ class Query < ActiveRecord::Base
# main project only
else
# all subprojects
- ids += project.child_ids
+ ids += project.descendants.collect(&:id)
end
elsif Setting.display_subprojects_issues?
- ids += project.child_ids
+ ids += project.descendants.collect(&:id)
end
project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
elsif project
diff --git a/app/views/admin/projects.rhtml b/app/views/admin/projects.rhtml
index 6c7a21fb5..40177a63b 100644
--- a/app/views/admin/projects.rhtml
+++ b/app/views/admin/projects.rhtml
@@ -17,22 +17,20 @@
<table class="list">
<thead><tr>
- <%= sort_header_tag('name', :caption => l(:label_project)) %>
+ <th><%=l(:label_project)%></th>
<th><%=l(:field_description)%></th>
- <th><%=l(:label_subproject_plural)%></th>
- <%= sort_header_tag('is_public', :caption => l(:field_is_public), :default_order => 'desc') %>
- <%= sort_header_tag('created_on', :caption => l(:field_created_on), :default_order => 'desc') %>
+ <th><%=l(:field_is_public)%></th>
+ <th><%=l(:field_created_on)%></th>
<th></th>
<th></th>
</tr></thead>
<tbody>
<% for project in @projects %>
- <tr class="<%= cycle("odd", "even") %>">
- <td><%= project.active? ? link_to(h(project.name), :controller => 'projects', :action => 'settings', :id => project) : h(project.name) %>
- <td><%= textilizable project.short_description, :project => project %>
- <td align="center"><%= project.children.size %>
- <td align="center"><%= image_tag 'true.png' if project.is_public? %>
- <td align="center"><%= format_date(project.created_on) %>
+ <tr class="<%= cycle("odd", "even") %> <%= css_project_classes(project) %>">
+ <td class="name" style="padding-left: <%= project.level %>em;"><%= project.active? ? link_to(h(project.name), :controller => 'projects', :action => 'settings', :id => project) : h(project.name) %></td>
+ <td><%= textilizable project.short_description, :project => project %></td>
+ <td align="center"><%= image_tag 'true.png' if project.is_public? %></td>
+ <td align="center"><%= format_date(project.created_on) %></td>
<td align="center" style="width:10%">
<small>
<%= link_to(l(:button_archive), { :controller => 'projects', :action => 'archive', :id => project }, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-lock') if project.active? %>
@@ -47,6 +45,4 @@
</tbody>
</table>
-<p class="pagination"><%= pagination_links_full @project_pages, @project_count %></p>
-
<% html_title(l(:label_project_plural)) -%>
diff --git a/app/views/layouts/_project_selector.rhtml b/app/views/layouts/_project_selector.rhtml
deleted file mode 100644
index 545884042..000000000
--- a/app/views/layouts/_project_selector.rhtml
+++ /dev/null
@@ -1,12 +0,0 @@
-<% user_projects_by_root = User.current.projects.find(:all, :include => :parent).group_by(&:root) %>
-<select onchange="if (this.value != '') { window.location = this.value; }">
-<option selected="selected" value=""><%= l(:label_jump_to_a_project) %></option>
-<option disabled="disabled" value="">---</option>
-<% user_projects_by_root.keys.sort.each do |root| %>
- <%= content_tag('option', h(root.name), :value => url_for(:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item)) %>
- <% user_projects_by_root[root].sort.each do |project| %>
- <% next if project == root %>
- <%= content_tag('option', ('&#187; ' + h(project.name)), :value => url_for(:controller => 'projects', :action => 'show', :id => project, :jump => current_menu_item)) %>
- <% end %>
-<% end %>
-</select>
diff --git a/app/views/layouts/base.rhtml b/app/views/layouts/base.rhtml
index 0de3b0e3d..6e24d9ccf 100644
--- a/app/views/layouts/base.rhtml
+++ b/app/views/layouts/base.rhtml
@@ -34,7 +34,7 @@
<%= link_to l(:label_search), {:controller => 'search', :action => 'index', :id => @project}, :accesskey => accesskey(:search) %>:
<%= text_field_tag 'q', @question, :size => 20, :class => 'small', :accesskey => accesskey(:quick_search) %>
<% end %>
- <%= render :partial => 'layouts/project_selector' if User.current.memberships.any? %>
+ <%= render_project_jump_box %>
</div>
<h1><%= h(@project && !@project.new_record? ? @project.name : Setting.app_title) %></h1>
diff --git a/app/views/projects/_form.rhtml b/app/views/projects/_form.rhtml
index f0c9fda55..24b1d9c1f 100644
--- a/app/views/projects/_form.rhtml
+++ b/app/views/projects/_form.rhtml
@@ -4,8 +4,8 @@
<!--[form:project]-->
<p><%= f.text_field :name, :required => true %><br /><em><%= l(:text_caracters_maximum, 30) %></em></p>
-<% if User.current.admin? and !@root_projects.empty? %>
- <p><%= f.select :parent_id, (@root_projects.collect {|p| [p.name, p.id]}), { :include_blank => true } %></p>
+<% if User.current.admin? && !@project.possible_parents.empty? %>
+ <p><label><%= l(:field_parent) %></label><%= parent_project_select_tag(@project) %></p>
<% end %>
<p><%= f.text_area :description, :rows => 5, :class => 'wiki-edit' %></p>
diff --git a/app/views/projects/activity.rhtml b/app/views/projects/activity.rhtml
index b0e536690..67c991359 100644
--- a/app/views/projects/activity.rhtml
+++ b/app/views/projects/activity.rhtml
@@ -48,7 +48,7 @@
<p><% @activity.event_types.each do |t| %>
<label><%= check_box_tag "show_#{t}", 1, @activity.scope.include?(t) %> <%= l("label_#{t.singularize}_plural")%></label><br />
<% end %></p>
-<% if @project && @project.active_children.any? %>
+<% if @project && @project.descendants.active.any? %>
<p><label><%= check_box_tag 'with_subprojects', 1, @with_subprojects %> <%=l(:label_subproject_plural)%></label></p>
<%= hidden_field_tag 'with_subprojects', 0 %>
<% end %>
diff --git a/app/views/projects/destroy.rhtml b/app/views/projects/destroy.rhtml
index a1913c115..09d7d2a1c 100644
--- a/app/views/projects/destroy.rhtml
+++ b/app/views/projects/destroy.rhtml
@@ -3,8 +3,8 @@
<p><strong><%=h @project_to_destroy %></strong><br />
<%=l(:text_project_destroy_confirmation)%>
-<% if @project_to_destroy.children.any? %>
-<br /><%= l(:text_subprojects_destroy_warning, content_tag('strong', h(@project_to_destroy.children.sort.collect{|p| p.to_s}.join(', ')))) %>
+<% if @project_to_destroy.descendants.any? %>
+<br /><%= l(:text_subprojects_destroy_warning, content_tag('strong', h(@project_to_destroy.descendants.collect{|p| p.to_s}.join(', ')))) %>
<% end %>
</p>
<p>
diff --git a/app/views/projects/index.rhtml b/app/views/projects/index.rhtml
index 4c68717f5..40ea4b862 100644
--- a/app/views/projects/index.rhtml
+++ b/app/views/projects/index.rhtml
@@ -6,20 +6,11 @@
<h2><%=l(:label_project_plural)%></h2>
-<% @project_tree.keys.sort.each do |project| %>
-<h3><%= link_to h(project.name), {:action => 'show', :id => project}, :class => (User.current.member_of?(project) ? "icon icon-fav" : "") %></h3>
-<%= textilizable(project.short_description, :project => project) %>
-
-<% if @project_tree[project].any? %>
- <p><%= l(:label_subproject_plural) %>:
- <%= @project_tree[project].sort.collect {|subproject|
- link_to(h(subproject.name), {:action => 'show', :id => subproject}, :class => (User.current.member_of?(subproject) ? "icon icon-fav" : ""))}.join(', ') %></p>
-<% end %>
-<% end %>
+<%= render_project_hierarchy(@projects)%>
<% if User.current.logged? %>
<p style="text-align:right;">
-<span class="icon icon-fav"><%= l(:label_my_projects) %></span>
+<span class="my-project"><%= l(:label_my_projects) %></span>
</p>
<% end %>
diff --git a/app/views/projects/show.rhtml b/app/views/projects/show.rhtml
index fa6571306..6d5a1536b 100644
--- a/app/views/projects/show.rhtml
+++ b/app/views/projects/show.rhtml
@@ -4,11 +4,13 @@
<%= textilizable @project.description %>
<ul>
<% unless @project.homepage.blank? %><li><%=l(:field_homepage)%>: <%= link_to(h(@project.homepage), @project.homepage) %></li><% end %>
- <% if @subprojects.any? %>
- <li><%=l(:label_subproject_plural)%>: <%= @subprojects.collect{|p| link_to(h(p.name), :action => 'show', :id => p)}.join(", ") %></li>
- <% end %>
- <% if @project.parent %>
- <li><%=l(:field_parent)%>: <%= link_to h(@project.parent.name), :controller => 'projects', :action => 'show', :id => @project.parent %></li>
+ <% if @subprojects.any? %>
+ <li><%=l(:label_subproject_plural)%>:
+ <%= @subprojects.collect{|p| link_to(h(p), :action => 'show', :id => p)}.join(", ") %></li>
+ <% end %>
+ <% if @ancestors.any? %>
+ <li><%=l(:field_parent)%>:
+ <%= @ancestors.collect {|p| link_to(h(p), :action => 'show', :id => p)}.join(" &#187; ") %></li>
<% end %>
<% @project.custom_values.each do |custom_value| %>
<% if !custom_value.value.empty? %>
diff --git a/app/views/users/_memberships.rhtml b/app/views/users/_memberships.rhtml
index 94b49159e..d1657fb98 100644
--- a/app/views/users/_memberships.rhtml
+++ b/app/views/users/_memberships.rhtml
@@ -31,7 +31,7 @@
<p>
<label><%=l(:label_project_new)%></label><br/>
<% form_tag({ :action => 'edit_membership', :id => @user }) do %>
-<%= select_tag 'membership[project_id]', projects_options_for_select(@projects) %>
+<%= select_tag 'membership[project_id]', options_for_membership_project_select(@user, @projects) %>
<%= l(:label_role) %>:
<%= select_tag 'membership[role_id]', options_from_collection_for_select(@roles, "id", "name") %>
<%= submit_tag l(:button_add) %>