changeset 443:350acce374a2 cannam

Merge from branch "cannam-pre-20110113-merge"
author Chris Cannam
date Mon, 06 Jun 2011 14:55:38 +0100
parents 753f1380d6bc (current diff) d3faf348b287 (diff)
children ec7c78040115 32dd9e02950a
files app/controllers/application_controller.rb app/controllers/attachments_controller.rb app/controllers/files_controller.rb app/controllers/my_controller.rb app/controllers/projects_controller.rb app/controllers/repositories_controller.rb app/helpers/projects_helper.rb app/helpers/repositories_helper.rb app/models/mailer.rb app/models/news.rb app/models/project.rb app/models/repository.rb app/models/repository/mercurial.rb app/views/layouts/base.rhtml app/views/projects/settings/_members.rhtml app/views/projects/settings/_repository.rhtml app/views/projects/show.rhtml app/views/search/index.rhtml app/views/users/edit.rhtml config/locales/en.yml config/routes.rb extra/soundsoftware/reposman-soundsoftware.rb lib/redmine.rb public/stylesheets/application.css
diffstat 55 files changed, 3293 insertions(+), 181 deletions(-) [+]
line wrap: on
line diff
--- a/app/controllers/application_controller.rb	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/controllers/application_controller.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -267,12 +267,17 @@
         uri = URI.parse(back_url)
         # do not redirect user to another host or to the login or register page
         if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
+          # soundsoftware: if back_url is the home page,
+          # change it to My Page (#125)
+          if (uri.path == home_path)
+            uri.path = uri.path + "/my"
+          end
           # soundsoftware: if login page is https but back_url http,
           # switch back_url to https to ensure cookie validity (#83)
           if (uri.scheme == "http") && (URI.parse(request.url).scheme == "https")
             uri.scheme = "https"
-            back_url = uri.to_s
           end
+          back_url = uri.to_s
           redirect_to(back_url)
           return
         end
--- a/app/controllers/attachments_controller.rb	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/controllers/attachments_controller.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -16,9 +16,11 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 class AttachmentsController < ApplicationController
+
   before_filter :find_project
   before_filter :file_readable, :read_authorize, :except => :destroy
   before_filter :delete_authorize, :only => :destroy
+  before_filter :active_authorize, :only => :toggle_active
 
   verify :method => :post, :only => :destroy
 
@@ -54,6 +56,12 @@
     redirect_to :controller => 'projects', :action => 'show', :id => @project
   end
 
+  def toggle_active
+    @attachment.active = !@attachment.active?
+    @attachment.save!
+    render :layout => false
+  end
+
 private
   def find_project
     @attachment = Attachment.find(params[:id])
@@ -77,6 +85,10 @@
     @attachment.deletable? ? true : deny_access
   end
 
+  def active_authorize
+    true
+  end
+
   def detect_content_type(attachment)
     content_type = attachment.content_type
     if content_type.blank?
--- a/app/controllers/files_controller.rb	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/controllers/files_controller.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -8,8 +8,9 @@
   include SortHelper
 
   def index
-    sort_init 'filename', 'asc'
+    sort_init 'active', 'desc'
     sort_update 'filename' => "#{Attachment.table_name}.filename",
+		'active' => "#{Attachment.table_name}.active",
                 'created_on' => "#{Attachment.table_name}.created_on",
                 'size' => "#{Attachment.table_name}.filesize",
                 'downloads' => "#{Attachment.table_name}.downloads"
@@ -33,4 +34,5 @@
     end
     redirect_to project_files_path(@project)
   end
+
 end
--- a/app/controllers/members_controller.rb	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/controllers/members_controller.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -28,12 +28,24 @@
       attrs = params[:member].dup
       if (user_ids = attrs.delete(:user_ids))
         user_ids.each do |user_id|
-          members << Member.new(attrs.merge(:user_id => user_id))
+          @new_member = Member.new(attrs.merge(:user_id => user_id))
+          members << @new_member
+
+          # send notification to member
+          Mailer.deliver_added_to_project(@new_member, @project)
+
         end
       else
-        members << Member.new(attrs)
+        @new_member = Member.new(attrs)
+        members << @new_member
+        
+        # send notification to member
+        Mailer.deliver_added_to_project(@new_member, @project)
+        
       end
+
       @project.members << members
+
     end
     respond_to do |format|
       if members.present? && members.all? {|m| m.valid? }
@@ -54,8 +66,8 @@
             errors = members.collect {|m|
               m.errors.full_messages
             }.flatten.uniq
-
-            page.alert(l(:notice_failed_to_save_members, :errors => errors.join(', ')))
+            
+            # page.alert(l(:notice_failed_to_save_members, :errors => errors.join(', ')))
           }
         }
         
--- a/app/controllers/my_controller.rb	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/controllers/my_controller.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -25,14 +25,16 @@
   BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
              'issuesreportedbyme' => :label_reported_issues,
              'issueswatched' => :label_watched_issues,
+             'activitymyprojects' => :label_activity_my_recent,
              'news' => :label_news_latest,
+             'tipoftheday' => :label_tipoftheday,
              'calendar' => :label_calendar,
              'documents' => :label_document_plural,
              'timelog' => :label_spent_time
            }.merge(Redmine::Views::MyPage::Block.additional_blocks).freeze
 
-  DEFAULT_LAYOUT = {  'left' => ['issuesassignedtome'], 
-                      'right' => ['issuesreportedbyme'] 
+  DEFAULT_LAYOUT = {  'left' => ['tipoftheday', 'activitymyprojects'], 
+                      'right' => ['issueswatched','calendar'] 
                    }.freeze
 
   verify :xhr => true,
--- a/app/controllers/projects_controller.rb	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/controllers/projects_controller.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -56,7 +56,9 @@
         @offset ||= @project_pages.current.offset
         @projects = Project.visible_roots.all(:offset => @offset, :limit => @limit, :order => sort_clause) 
         if User.current.logged?
-          @user_projects = User.current.projects.sort_by(&:name)
+          # seems sort_by gives us case-sensitive ordering, which we don't want
+#          @user_projects = User.current.projects.sort_by(&:name)
+          @user_projects = User.current.projects.all(:order => :name)
         end
         render :template => 'projects/index.rhtml', :layout => !request.xhr?
       }
@@ -215,6 +217,15 @@
   end
 
   verify :method => :post, :only => :modules, :render => {:nothing => true, :status => :method_not_allowed }
+  
+  def overview
+    @project.has_welcome_page = params[:has_welcome_page]
+    if @project.save
+      flash[:notice] = l(:notice_successful_update)
+    end
+    redirect_to :action => 'settings', :id => @project, :tab => 'overview'
+  end
+
   def modules
     @project.enabled_module_names = params[:enabled_module_names]
     flash[:notice] = l(:notice_successful_update)
--- a/app/controllers/repositories_controller.rb	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/controllers/repositories_controller.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -36,7 +36,11 @@
 
   def edit
     @repository = @project.repository
-    if !@repository && !params[:repository_scm].blank?
+
+    if !@repository
+
+      params[:repository_scm]='Mercurial'
+
       @repository = Repository.factory(params[:repository_scm])
       @repository.project = @project if @repository
     end
@@ -55,6 +59,7 @@
       @repository.merge_extra_info(p_extra)
       @repository.save
     end
+
     render(:update) do |page|
       page.replace_html "tab-content-repository",
                         :partial => 'projects/settings/repository'
--- a/app/controllers/sys_controller.rb	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/controllers/sys_controller.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -55,6 +55,30 @@
     render :nothing => true, :status => 404
   end
 
+  def get_external_repo_url
+    project = Project.find(params[:id])
+    if project.repository
+      repo = project.repository
+      if repo.is_external?
+        render :text => repo.external_url, :status => 200
+      else
+        render :nothing => true, :status => 200
+      end
+    end
+  rescue ActiveRecord::RecordNotFound
+    render :nothing => true, :status => 404
+  end
+
+  def clear_repository_cache
+    project = Project.find(params[:id])
+    if project.repository
+      project.repository.clear_cache
+    end
+    render :nothing => true, :status => 200
+  rescue ActiveRecord::RecordNotFound
+    render :nothing => true, :status => 404
+  end
+  
   def set_embedded_active
     project = Project.find(params[:id])
     mods = project.enabled_modules
--- a/app/controllers/welcome_controller.rb	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/controllers/welcome_controller.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -18,11 +18,16 @@
 class WelcomeController < ApplicationController
   caches_action :robots
 
+  include ProjectsHelper
+  helper :projects
+
   def index
-    @news = News.latest User.current
+    @site_project = Project.find_by_identifier "soundsoftware-site"
+    @site_news = []
+    @site_news = News.latest_for @site_project if @site_project
     @projects = Project.latest User.current
     
-    # tests if user is logged in to gfenerate the tips of the day list
+    # tests if user is logged in to generate the tips of the day list
     if User.current.logged?
       @tipsoftheday = Setting.tipoftheday_text
     else
--- a/app/helpers/projects_helper.rb	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/helpers/projects_helper.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -23,6 +23,7 @@
 
   def project_settings_tabs
     tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural},
+            {:name => 'overview', :action => :edit_project, :partial => 'projects/settings/overview', :label => :label_welcome_page},
             {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural},
             {:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural},
             {:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural},
@@ -49,6 +50,16 @@
     content_tag('select', options, :name => 'project[parent_id]', :id => 'project_parent_id')
   end
 
+  def render_project_short_description(project)
+    s = ''
+    if (project.short_description)
+      s << "<div class='description'>"
+      s << textilizable(project.short_description, :project => project).gsub(/<[^>]+>/, '')
+      s << "</div>"
+    end
+    s
+  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)
@@ -73,7 +84,7 @@
         classes = (ancestors.empty? ? 'root' : 'child')
         s << "<li class='#{classes}'><div class='#{classes}'>" +
                link_to_project(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 << render_project_short_description(project)
         s << "</div>\n"
         ancestors << project
       end
@@ -84,68 +95,73 @@
   end
 
 
+  def render_my_project_in_hierarchy(project)
+ 
+    s = ''
+
+    if User.current.member_of?(project)
+
+      # set the project environment to please macros.
+      @project = project
+
+      classes = (project.root? ? 'root' : 'child')
+      
+      s << "<li class='#{classes}'><div class='#{classes}'>" +
+        link_to_project(project, {}, :class => "project my-project")
+      if project.is_public?
+        s << " <span class='public'>" << l("field_is_public") << "</span>"
+      else
+        s << " <span class='private'>" << l("field_is_private") << "</span>"
+      end
+      s << render_project_short_description(project)
+      s << "</div>\n"
+
+      cs = ''
+      project.children.each do |child|
+        cs << render_my_project_in_hierarchy(child)
+      end
+
+      if cs != ''
+        s << "<ul class='projects'>\n" << cs << "</ul>\n";
+      end
+
+    end
+
+    s
+
+  end
+
   # Renders a tree of projects where the current user belongs
   # 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_my_project_hierarchy(projects)
+
     s = ''
 
-    a = ''
+    original_project = @project
 
-    # Flag to tell if user has any projects
-    t = FALSE
-    
-    if projects.any?
-      ancestors = []
-      original_project = @project
-      projects.each do |project|
-        # set the project environment to please macros.
-
-        @project = project
-
-        if User.current.member_of?(project):
-
-          t = TRUE
-
-          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_project(project, {}, :class => "project my-project")
-          if project.is_public?
-            s << " <span class='public'>" << l("field_is_public") << "</span>"
-          else
-            s << " <span class='private'>" << l("field_is_private") << "</span>"
-          end
-          s << "<div class='wiki description'>#{textilizable(project.short_description, :project => project)}</div>" unless project.description.blank?
-          s << "</div>\n"
-          ancestors << project
-        end
-       end
-        s << ("</li></ul>\n" * ancestors.size)
-        @project = original_project
+    projects.each do |project|
+      if project.root? || !projects.include?(project.parent)
+        s << render_my_project_in_hierarchy(project)
+      end
     end
 
-    if t == TRUE
+    @project = original_project
+
+    if s != ''
+      a = ''
       a << "<h2>"
       a <<  l("label_my_project_plural")
       a << "</h2>"
+      a << "<ul class='projects root'>\n"
       a << s
-    else
-      a = s
+      a << "</ul>\n"
+      s = a
     end
+
+    s
     
-    a
   end
 
   # Renders a tree of projects that the current user does not belong
@@ -198,11 +214,7 @@
     s << " no_description" if project.description.blank?
     s << "'>" << link_to_project(project, {}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}");
     s << "</div>"
-    unless project.description.blank?
-      s << "<div class='wiki description'>"
-      s << textilizable(project.short_description, :project => project)
-      s << "</div>"
-    end
+    s << render_project_short_description(project)
       
     s << "<td class='managers' align=top>"
 
@@ -230,7 +242,9 @@
     s << "</tr>"
 
     project.children.each do |child|
-      s << render_project_in_table(child, oddeven, level + 1)
+      if child.is_public? or User.current.member_of?(child)
+        s << render_project_in_table(child, oddeven, level + 1)
+      end
     end
     
     s
--- a/app/models/mailer.rb	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/models/mailer.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -32,6 +32,27 @@
     { :host => h, :protocol => Setting.protocol }
   end
 
+
+
+  # Builds a tmail object used to email the specified user that he was added to a project
+  #
+  # Example:
+  #   add_to_project(user) => tmail object
+  #   Mailer.deliver_add_to_project(user) => sends an email to the registered user
+  def added_to_project(member, project)
+
+    user = User.find(member.user_id)
+
+    set_language_if_valid user.language
+    recipients user.mail
+    subject l(:mail_subject_added_to_project, Setting.app_title)
+    body :project_url => url_for(:controller => 'projects', :action => 'show', :id => project.id),
+        :project_name => project.name
+    render_multipart('added_to_project', body)
+  end
+
+
+  
   # Builds a tmail object used to email recipients of the added issue.
   #
   # Example:
@@ -458,3 +479,7 @@
     end
   end
 end
+
+
+
+
--- a/app/models/news.rb	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/models/news.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -51,4 +51,9 @@
   def add_author_as_watcher
     Watcher.create(:watchable => self, :user => author)
   end
+
+  # returns latest news for a specific project
+  def self.latest_for(project, count = 5)
+    find(:all, :limit => count, :conditions => [ "#{News.table_name}.project_id = #{project.id}", Project.allowed_to_condition(User.current, :view_news) ], :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC") 
+  end
 end
--- a/app/models/project.rb	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/models/project.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -468,13 +468,20 @@
   
   # Returns a short description of the projects (first lines)
   def short_description(length = 255)
+
+    ## The short description is used in lists, e.g. Latest projects,
+    ## My projects etc.  It should be no more than a line or two with
+    ## no text formatting.
+
     ## Original Redmine code: this truncates to the CR that is more
     ## than "length" characters from the start.
     # description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
-    ## That's too much for us, and also we want to omit images and the
-    ## like.  Truncate instead to the first CR that follows _any_
-    ## non-blank text, and to the next word break beyond "length"
-    ## characters if the result is still longer than that.
+
+    ## That can leave too much text for us, and also we want to omit
+    ## images and the like.  Truncate instead to the first CR that
+    ## follows _any_ non-blank text, and to the next word break beyond
+    ## "length" characters if the result is still longer than that.
+    ##
     description.gsub(/![^\s]+!/, '').gsub(/^(\s*[^\n\r]*).*$/m, '\1').gsub(/^(.{#{length}}\b).*$/m, '\1 ...').strip if description
   end
 
--- a/app/models/repository.rb	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/models/repository.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -268,6 +268,10 @@
     nil
   end
 
+  def clear_cache
+    clear_changesets
+  end
+    
   def self.scm_adapter_class
     nil
   end
--- a/app/models/repository/mercurial.rb	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/models/repository/mercurial.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -22,7 +22,7 @@
   has_many :changesets, :order => "#{Changeset.table_name}.id DESC", :foreign_key => 'repository_id'
 
   attr_protected :root_url
-  validates_presence_of :url
+  # validates_presence_of :url
 
   FETCH_AT_ONCE = 100  # number of changesets to fetch at once
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/views/activities/_recent.rhtml	Mon Jun 06 14:55:38 2011 +0100
@@ -0,0 +1,65 @@
+<% events = @events_by_day %>
+<% max = 5 %>
+<% if (events.nil?) 
+     activity = Redmine::Activity::Fetcher.new(User.current, :project => @project)
+     
+     if @project
+        # Don't show news (duplicated with News box) or wiki edits (too
+	# tedious) in project front page
+        activity.scope = [ "changesets", "files", "issues", "documents" ]
+     end
+     
+     events = activity.events(Date.today - 28, Date.today + 1)
+     
+     if defined? user
+       events = events.select { |e| user.member_of? e.project }
+     end
+     
+     events = events.first(max)
+
+   end
+%>
+
+<div id="activity">
+
+<% if @project.nil? %>
+   <%= content_tag('h3', l(:label_activity_my_recent)) %>
+   <div class="activity box">
+<% end %>
+
+<% if events.empty? %>
+
+   <% if @project.nil? %>
+     <p><%= l(:label_activity_my_recent_none) %></p>
+   <% end %>
+
+<% else %>
+
+   <% if !@project.nil? %>
+     <div class="activity box">
+     <%= content_tag('h3', l(:label_activity_recent)) %>
+   <% end %>
+
+   <dl>
+   <% events.sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%>
+     <dt class="<%= User.current.logged? && e.respond_to?(:event_author) && User.current == e.event_author ? 'me' : nil %>">
+	<%= avatar(e.event_author, :size => "24") if e.respond_to?(:event_author) %>
+     <span class="time"><%= format_time(e.event_datetime) %></span>
+     <%= content_tag('span', link_to_project(e.project), :class => 'project') if @project.nil? || @project != e.project %>
+     <% if e.respond_to?(:event_author) %>
+       <span class="author"><%= e.event_author %></span>
+     <% end %>
+     </dt>
+     <dd><%= link_to format_activity_title(e.event_title), e.event_url %>
+     <span class="description"><%= format_activity_description(e.event_description) %></span>
+     </dd>
+   <% end -%>
+   </dl>
+
+   </div>
+
+<% end %>
+
+<% if events.empty? and @project.nil? %></div><% end %>
+
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/views/attachments/toggle_active.rhtml	Mon Jun 06 14:55:38 2011 +0100
@@ -0,0 +1,7 @@
+<%=
+file = Attachment.find(params[:id])
+active_id = "active-" + file.id.to_s
+link_to_remote image_tag(file.active? ? 'fav.png' : 'fav_off.png'),
+  :url => {:controller => 'attachments', :action => 'toggle_active', :project_id => @project.id, :id => file},
+  :update => active_id
+%>
--- a/app/views/files/index.html.erb	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/views/files/index.html.erb	Mon Jun 06 14:55:38 2011 +0100
@@ -5,29 +5,51 @@
 <h2><%=l(:label_attachment_plural)%></h2>
 
 <% delete_allowed = User.current.allowed_to?(:manage_files, @project) %>
+<% active_change_allowed = delete_allowed %>
 
 <table class="list files">
   <thead><tr>
+    <%= sort_header_tag('active', :caption => l(:field_active)) %>
     <%= sort_header_tag('filename', :caption => l(:field_filename)) %>
     <%= sort_header_tag('created_on', :caption => l(:label_date), :default_order => 'desc') %>
     <%= sort_header_tag('size', :caption => l(:field_filesize), :default_order => 'desc') %>
-    <%= sort_header_tag('downloads', :caption => l(:label_downloads_abbr), :default_order => 'desc') %>
+    <%= sort_header_tag('downloads', :caption => l(:field_downloads), :default_order => 'desc') %>
     <th>MD5</th>
     <th></th>
   </tr></thead>
   <tbody>
+<% have_file = false %>
 <% @containers.each do |container| %>	
   <% next if container.attachments.empty? -%>
 	<% if container.is_a?(Version) -%>
   <tr>
-  	<th colspan="6" align="left">
+  	<th colspan="7" align="left">
   		<%= link_to(h(container), {:controller => 'versions', :action => 'show', :id => container}, :class => "icon icon-package") %>
 		</th>
 	</tr>
 	<% end -%>
   <% container.attachments.each do |file| %>		
-  <tr class="file <%= cycle("odd", "even") %>">
-    <td class="filename"><%= link_to_attachment file, :download => true, :title => file.description %></td>
+  <tr class="file <%= cycle("odd", "even") %> <%= "active" if file.active? %>">
+    <td class="active">
+      <% have_file = true %>
+      <% if active_change_allowed
+           active_id = "active-" + file.id.to_s -%>
+        <div id="<%= active_id %>">
+        <%= link_to_remote image_tag(file.active? ? 'fav.png' : 'fav_off.png'),
+              :url => {:controller => 'attachments', :action => 'toggle_active', :project_id => @project.id, :id => file},
+              :update => active_id
+        %>
+        </div>
+      <% else -%>
+        <%= image_tag('fav.png') if file.active? %>
+      <% end -%>
+    </td>
+    <% if file.active? %>
+      <td class="filename active"><%= link_to_attachment file, :download => true %><br><span class="description"><%= h(file.description) %></span></td>
+    <% else %>
+      <td class="filename"><%= link_to_attachment file, :download => true, :title => file.description %>
+    <% end %>
+    </td>
     <td class="created_on"><%= format_time(file.created_on) %></td>
     <td class="filesize"><%= number_to_human_size(file.filesize) %></td>
     <td class="downloads"><%= file.downloads %></td>
@@ -43,4 +65,6 @@
   </tbody>
 </table>
 
+<%= l(:text_files_active_change) if active_change_allowed and have_file %>
+
 <% html_title(l(:label_attachment_plural)) -%>
--- a/app/views/layouts/base.rhtml	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/views/layouts/base.rhtml	Mon Jun 06 14:55:38 2011 +0100
@@ -54,11 +54,16 @@
       <h3 id="project-ancestors-title"><%= page_header_title[1] %></h3>
     <% end %>  
 
-    <h1  id="project-title"
+    <h1 id="project-title"
       <% unless page_header_title[1].empty? %>
         style="margin-top: 0px; "
       <% end %>  
-    ><%= page_header_title[0] %></h1>
+    ><% if display_main_menu?(@project) %>
+       <%= link_to_project(@project) %>
+     <% else %> 
+       <%= page_header_title[0] %>
+     <% end %>
+    </h1>
     
     <% if display_main_menu?(@project) %>
     <div id="main-menu">
@@ -85,7 +90,7 @@
 	
 <div id="footer">
   <div class="bgl"><div class="bgr">
-    Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %> &copy; 2006-2011 Jean-Philippe Lang
+    <small>Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %><br>&copy; 2006-2011 Jean-Philippe Lang</small>
   </div></div>
 </div>
 </div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/views/mailer/added_to_project.text.html.rhtml	Mon Jun 06 14:55:38 2011 +0100
@@ -0,0 +1,3 @@
+<p><%= l(:notice_added_to_project, :project_name => @project_name) %></p>
+<p><%= l(:notice_project_homepage, :project_url => @project_url) %></p>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/views/mailer/added_to_project.text.plain.rhtml	Mon Jun 06 14:55:38 2011 +0100
@@ -0,0 +1,2 @@
+<%= l(:notice_added_to_project, :project_name => @project_name) %>
+<%= l(:notice_project_homepage, :project_url => @project_url)  %>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/views/my/blocks/_activitymyprojects.rhtml	Mon Jun 06 14:55:38 2011 +0100
@@ -0,0 +1,4 @@
+
+<%= render :partial => 'activities/recent', :locals => { :user => User.current } %>
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/views/my/blocks/_tipoftheday.rhtml	Mon Jun 06 14:55:38 2011 +0100
@@ -0,0 +1,4 @@
+    <h3><%=l(:label_tipoftheday)%></h3>
+    <div class="tipoftheday box">
+          <div class="tip"><%= textilizable Setting.tipoftheday_text %></div>
+    </div>
--- a/app/views/news/_news.rhtml	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/views/news/_news.rhtml	Mon Jun 06 14:55:38 2011 +0100
@@ -1,6 +1,13 @@
-<p><%= link_to_project(news.project) + ': ' unless @project %>
-<%= link_to h(news.title), news_path(news) %>
-<%= "(#{l(:label_x_comments, :count => news.comments_count)})" if news.comments_count > 0 %>
-<br />
+<div id="news">
+<dt>
+<span class="time"><%= format_time(news.created_on) %></span>
+<% project ||= @project %>
+<% if !project %>
+<span class="project"><%= link_to_project(news.project) %></span>
+<% end %>
+<span class="headline"><%= link_to h(news.title), news_path(news) %></span>
+<span class="comments"><%= "(#{l(:label_x_comments, :count => news.comments_count)})" if news.comments_count > 0 %></span>
+</dt><dd>
 <% unless news.summary.blank? %><span class="summary"><%=h news.summary %></span><br /><% end %>
-<span class="author"><%= authoring news.created_on, news.author %></span></p>
+</dd>
+</div>
--- a/app/views/projects/settings/_members.rhtml	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/views/projects/settings/_members.rhtml	Mon Jun 06 14:55:38 2011 +0100
@@ -2,6 +2,7 @@
 <% roles = Role.find_all_givable
    members = @project.member_principals.find(:all, :include => [:roles, :principal]).sort %>
 
+
 <div class="splitcontentleft">
 <% if members.any? %>
 <table class="list members">
@@ -73,10 +74,10 @@
 		<% end %>
 		</div>
 		
-    <p><%= l(:label_role_plural) %>:
+    <p><%= l(:label_set_role_plural) %>:</p>
     <% roles.each do |role| %>
-    	<label><%= check_box_tag 'member[role_ids][]', role.id %> <%=h role %></label>
-   	<% end %></p>
+    	<label><%= check_box_tag 'member[role_ids][]', role.id %> <%=h role %> </label><div style="margin-left: 2em; margin-bottom: 0.5em"><i><%=l( 'label_' + role.name.downcase + "_description").to_sym %></i></div>
+   	<% end %>
    	
     <p><%= submit_tag l(:button_add), :id => 'member-add-submit' %></p>
 		</fieldset>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/views/projects/settings/_overview.rhtml	Mon Jun 06 14:55:38 2011 +0100
@@ -0,0 +1,30 @@
+
+<% form_for :project, @project,
+            :url => { :action => 'overview', :id => @project },
+            :html => {:id => 'overview-form'} do |f| %>
+
+<div class="box tabular">
+
+<p><%= l(:text_has_welcome_page_info, { :overview_link => link_to(l(:label_overview), { :controller => 'projects', :action => 'show' } ) } ) %></p>
+
+<% if @project.module_enabled? :wiki %>
+
+<p><%= link_to(l(:button_welcome_page_edit), {:controller => 'wiki', :action => 'edit', :project_id => @project, :id => Wiki.titleize("Overview")}, :class => 'icon icon-edit') %>
+
+<% else %>
+
+<p><%= l(:text_has_welcome_page_wiki_disabled, { :modules_link => link_to(l(:label_module_plural), { :controller => 'projects', :action => 'settings', :tab => 'modules' } ) } ) %></p>
+
+<% end %>
+
+<p><label for="has_welcome_page"><%= l(:label_has_welcome_page) %></label>
+<%= check_box_tag 'has_welcome_page', 1, @project.has_welcome_page? -%>
+<br/><em><%= l(:setting_has_welcome_page) %></em>
+
+</p>
+
+</div>
+
+<%= submit_tag l(:button_save) %>
+
+<% end %>
--- a/app/views/projects/settings/_repository.rhtml	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/views/projects/settings/_repository.rhtml	Mon Jun 06 14:55:38 2011 +0100
@@ -1,3 +1,6 @@
+
+<%= javascript_include_tag 'repository' %>
+
 <% remote_form_for :repository, @repository,
                    :url => { :controller => 'repositories', :action => 'edit', :id => @project },
                    :builder => TabularFormBuilder,
@@ -6,11 +9,36 @@
 <%= error_messages_for 'repository' %>
 
 <div class="box tabular">
-<% if !@repository || !@repository.url %>
-<ul><li><%= l(:text_settings_repo_creation) %></li></ul>
-<% end %>
-<p><%= label_tag('repository_scm', l(:label_scm)) %><%= scm_select_tag(@repository) %></p>
-<%= repository_field_tags(f, @repository) if @repository %>
+
+<p>
+<% if @repository %>
+   <%= l(:text_settings_repo_explanation) %></ br>
+   <% if @repository.is_external %>
+     <p><%= l(:text_settings_repo_is_external) %></ br>
+   <% else %>
+     <p><%= l(:text_settings_repo_is_internal) %></ br>
+   <% end %>
+</p>
+
+
+
+
+
+<p>
+	<%= label_tag('repository_is_external', l(:label_is_external_repository)) %>
+	<%= check_box :repository, :is_external, :onclick => "toggle_ext_url()" %> 
+	<br/><em><%= l(:setting_external_repository) %></em>
+</p>
+
+
+<p>
+	<%= label_tag('repository_external_url', l(:label_repository_external_url)) %>
+	<%= text_field :repository, :external_url, :disabled => !(@repository and @repository.is_external) %> 
+	<br/><em><%= l(:setting_external_repository_url) %></em>
+</p>
+
+<p><%= l(:text_settings_repo_need_help) %></p>
+
 </div>
 
 <div class="contextual">
@@ -22,12 +50,13 @@
                 :id         => @project
                 },
              :class => 'icon icon-user') %>
-<%= link_to(l(:button_delete), {:controller => 'repositories', :action => 'destroy', :id => @project},
-            :confirm => l(:text_are_you_sure),
-            :method  => :post,
-            :class   => 'icon icon-del') %>
 <% end %>
 </div>
 
-<%= submit_tag((@repository.nil? || @repository.new_record?) ? l(:button_create) : l(:button_save), :disabled => @repository.nil?) %>
+<%= submit_tag(l(:button_save), :onclick => remote_function(:url => { :controller => 'repositories', :action => 'edit', :id => @project }, :method => :get, :with => "Form.serialize(this.form)")) %>
+
+<% else %>
+   <%= l(:text_settings_repo_creation) %></ br>
 <% end %>
+
+<% end %>
--- a/app/views/projects/show.rhtml	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/views/projects/show.rhtml	Mon Jun 06 14:55:38 2011 +0100
@@ -4,6 +4,29 @@
 	<% end %>
 </div>
 
+<% if @project.has_welcome_page %>
+<% page = @project.wiki.find_page("Overview") %>
+<% end %>
+
+<% if page %>
+
+<div class="contextual" style="clear: right">
+<ul>
+<% @users_by_role.keys.sort.each do |role| %>
+<li><%=h role %>: <%= @users_by_role[role].sort.collect{|u| link_to_user u}.join(", ") %></li>
+<% end %>
+<% unless @project.homepage.blank? %><li><%=l(:field_homepage)%>: <%= auto_link(h(@project.homepage)) %></li><% end %>
+<% if @subprojects.any? %>
+	<li><%=l(:label_subproject_plural)%>:
+	    <%= @subprojects.collect{|p| link_to(h(p), :action => 'show', :id => p)}.join(", ") %></li>
+<% end %>
+</ul>
+</div>
+
+<%= render(:partial => "wiki/content", :locals => {:content => page.content_for_version()}) %>
+
+<% else %>
+
 <h2><%=l(:label_overview)%></h2> 
 	
 <div class="splitcontentleft">
@@ -23,7 +46,8 @@
 	<% end %>
 	</ul>	
 
-  <% if User.current.allowed_to?(:view_issues, @project) %>
+  <% if User.current.allowed_to?(:view_issues, @project) and @open_issues_by_tracker.values.any? %>
+
   <div class="issues box">    
     <h3><%=l(:label_issue_tracking)%></h3>
     <ul>
@@ -46,7 +70,9 @@
 			<% end %>
 		</p>
   </div>
+
   <% end %>
+
   <%= call_hook(:view_projects_show_left, :project => @project) %>
 </div>
 
@@ -60,6 +86,9 @@
     <p><%= link_to l(:label_news_view_all), :controller => 'news', :action => 'index', :project_id => @project %></p>
   </div>  
   <% end %>
+
+  <%= render :partial => 'activities/recent' %>
+
   <%= call_hook(:view_projects_show_right, :project => @project) %>
 </div>
 
@@ -73,6 +102,8 @@
     <%= call_hook(:view_projects_show_sidebar_bottom, :project => @project) %>
 <% end %>
 
+<% end %>
+
 <% content_for :header_tags do %>
 <%= auto_discovery_link_tag(:atom, {:controller => 'activities', :action => 'index', :id => @project, :format => 'atom', :key => User.current.rss_key}) %>
 <% end %>
--- a/app/views/search/index.rhtml	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/views/search/index.rhtml	Mon Jun 06 14:55:38 2011 +0100
@@ -19,11 +19,11 @@
 </div>
 
 <% if @results %>
+    
+    <h3><%= l(:label_result_plural) %> (<%= @results_by_type.values.sum %>)</h3>
     <div id="search-results-counts">
     <%= render_results_by_type(@results_by_type) unless @scope.size == 1 %>
     </div>
-    
-    <h3><%= l(:label_result_plural) %> (<%= @results_by_type.values.sum %>)</h3>
     <dl id="search-results">
       <% @results.each do |e| %>
         <dt class="<%= e.event_type %>"><%= content_tag('span', h(e.project), :class => 'project') unless @project == e.project %> <%= link_to highlight_tokens(truncate(e.event_title, :length => 255), @tokens), e.event_url %></dt>
--- a/app/views/users/edit.rhtml	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/views/users/edit.rhtml	Mon Jun 06 14:55:38 2011 +0100
@@ -9,3 +9,4 @@
 <%= render_tabs user_settings_tabs %>
 
 <% html_title(l(:label_user), @user.login, l(:label_administration)) -%>
+
--- a/app/views/welcome/index.rhtml	Mon Jun 06 14:41:04 2011 +0100
+++ b/app/views/welcome/index.rhtml	Mon Jun 06 14:55:38 2011 +0100
@@ -9,37 +9,33 @@
 <div class="splitcontentleft">
   <%= textilizable Setting.welcome_text %>
   
-  <% if @news.any? %>
-  <div class="news box">
-	<h3><%=l(:label_news_latest)%></h3>
-		<%= render :partial => 'news/news', :collection => @news %>
-		<%= link_to l(:label_news_view_all), :controller => 'news' %>
-  </div>
-  <% end %>
   <%= call_hook(:view_welcome_index_left, :projects => @projects) %>
 </div>
 
 <div class="splitcontentright">
-  <% if not @tipsoftheday.empty? %>
-    <div class="newsoftheday box">
-          <h3><%=l(:label_tipoftheday)%></h3>
-          <%= textilizable @tipsoftheday %>
-    </div>
+  <% if @site_news.any? %>
+  <div class="news box">
+	<h3><%=l(:label_news_site_latest)%></h3>
+	<%= render :partial => 'news/news', :locals => { :project => @site_project }, :collection => @site_news %>
+	
+	<%= link_to l(:label_news_more), { :controller => 'projects', :action => @site_project.identifier, :id => 'news' } %>
+  </div>
   <% end %>
-
     <% if @projects.any? %>
 	<div class="projects box">
 	<h3><%=l(:label_project_latest)%></h3>
 		<ul>
 		<% for project in @projects %>
 		  <% @project = project %>
-			<li>
-			<%= link_to_project project %> (<%= format_time(project.created_on) %>)
-			<%= textilizable project.short_description, :project => project %>
+			<li class="latest">
+			<span class="title"><%= link_to_project project %></span>
+			<span class="time"><%= format_time(project.created_on)%></span>
+			<%= render_project_short_description project %>
 			</li>
 		<% end %>
     <% @project = nil %>
 		</ul>
+	<%= link_to l(:label_projects_more), :controller => 'projects' %>
 	</div>
 	<% end %>
     <%= call_hook(:view_welcome_index_right, :projects => @projects) %>
--- a/config/locales/en.yml	Mon Jun 06 14:41:04 2011 +0100
+++ b/config/locales/en.yml	Mon Jun 06 14:55:38 2011 +0100
@@ -1,14 +1,13 @@
 en:
-  # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl)
   direction: ltr
   date:
     formats:
       # Use the strftime parameters for formats.
       # When no format has been given, it uses default.
       # You can provide other formats here if you like!
-      default: "%m/%d/%Y"
-      short: "%b %d"
-      long: "%B %d, %Y"
+      default: "%d/%m/%Y"
+      short: "%d %b"
+      long: "%d %B, %Y"
       
     day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
     abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
@@ -24,10 +23,10 @@
 
   time:
     formats:
-      default: "%m/%d/%Y %I:%M %p"
+      default: "%d/%m/%Y %I:%M %p"
       time: "%I:%M %p"
       short: "%d %b %H:%M"
-      long: "%B %d, %Y %H:%M"
+      long: "%d %B, %Y %H:%M"
     am: "am"
     pm: "pm"
       
@@ -70,10 +69,14 @@
 
   number:
     format:
-      separator: "." 
-      delimiter: ""
+      separator: "."
+      delimiter: " "
       precision: 3
 
+    currency:
+      format:
+        format: "%u%n"
+        unit: "£"
     human:
       format:
         delimiter: ""
@@ -89,6 +92,7 @@
           gb: "GB"
           tb: "TB"
 
+        
 # Used in array.to_sentence.
   support:
     array:
@@ -126,7 +130,7 @@
         not_same_project: "doesn't belong to the same project"
         circular_dependency: "This relation would create a circular dependency"
         cant_link_an_issue_with_a_descendant: "An issue cannot be linked to one of its subtasks"
-        must_accept_terms_and_conditions: "You must accept the Terms and Conditions"
+        must_accept_terms_and_conditions: "You must accept the Terms and Conditions"        
 
   actionview_instancetag_blank_option: Please select
   
@@ -139,7 +143,7 @@
   general_csv_decimal_separator: '.'
   general_csv_encoding: ISO-8859-1
   general_pdf_encoding: UTF-8
-  general_first_day_of_week: '7'
+  general_first_day_of_week: '1'
   
   notice_account_updated: Account was successfully updated.
   notice_account_invalid_creditentials: Invalid user or password
@@ -323,6 +327,9 @@
   field_cvsroot: CVSROOT
   field_cvs_module: Module
 
+  setting_external_repository: "Select this if the project's main repository is hosted somewhere else"
+  setting_external_repository_url: "The URL of the existing external repository. Must be publicly accessible without a password"
+  label_repository_external_url: "External repository URL"
   field_terms_and_conditions: 'Terms and Conditions:'
   accept_terms_and_conditions: 'I have read and agree with the '
   setting_app_title: Application title
@@ -379,6 +386,7 @@
   setting_rest_api_enabled: Enable REST web service
   setting_cache_formatted_text: Cache formatted text
   setting_default_notification_option: Default notification option
+  setting_has_welcome_page: Select this to replace the project overview page with your welcome page
   setting_commit_logtime_enabled: Enable time logging
   setting_commit_logtime_activity_id: Activity for logged time
   setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
@@ -417,8 +425,8 @@
   permission_comment_news: Comment news
   permission_manage_documents: Manage documents
   permission_view_documents: View documents
-  permission_manage_files: Manage Downloads
-  permission_view_files: View Downloads
+  permission_manage_files: Manage downloads
+  permission_view_files: View downloads
   permission_manage_wiki: Manage wiki
   permission_rename_wiki_pages: Rename wiki pages
   permission_delete_wiki_pages: Delete wiki pages
@@ -451,8 +459,12 @@
   project_module_boards: Forums
   project_module_calendar: Calendar
   project_module_gantt: Gantt chart
-  project_module_embedded: Embedded documentation (Javadoc or Doxygen)
+  project_module_embedded: Embedded documentation (Javadoc, Doxygen or MATLAB)
   label_tipoftheday: Tip of the day
+  label_notifications: Important Message
+  field_terms_and_conditions: 'Terms and Conditions:'
+  accept_terms_and_conditions: 'I have read and agree with the '
+  label_ssamr_description: Research description
   label_ssamr_details: Other Details
   label_ssamr_institution: Institution
   label_ssamr_description: Research description
@@ -471,6 +483,7 @@
     other: "%{count} projects"
   label_project_all: All Projects
   label_project_latest: Latest projects
+  label_projects_more: More projects
   label_managers: Managed by
   label_issue: Issue
   label_issue_new: New issue
@@ -531,6 +544,9 @@
   label_last_login: Last connection
   label_registered_on: Registered on
   label_activity: Activity
+  label_activity_recent: Recent activity
+  label_activity_my_recent: Recent activity in my projects
+  label_activity_my_recent_none: No recent activity
   label_overall_activity: Overall activity
   label_user_activity: "%{value}'s activity"
   label_new: New
@@ -569,6 +585,8 @@
   label_news_new: Add news
   label_news_plural: News
   label_news_latest: Latest news
+  label_news_site_latest: Site news
+  label_news_more: More news
   label_news_view_all: View all news
   label_news_added: News added
   label_news_comment_added: Comment added to a news
@@ -617,7 +635,7 @@
   label_internal: Internal
   label_last_changes: "last %{count} changes"
   label_change_view_all: View all changes
-  label_personalize_page: Personalize this page
+  label_personalize_page: Personalise this page
   label_comment: Comment
   label_comment_plural: Comments
   label_x_comments:
@@ -657,6 +675,7 @@
   label_not_contains: doesn't contain
   label_day_plural: days
   label_repository: Repository
+  label_is_external_repository: Track an external repository
   label_repository_plural: Repositories
   label_browse: Browse
   label_modification: "%{count} change"
@@ -763,6 +782,7 @@
   label_added_time_by: "Added by %{author} %{age} ago"
   label_updated_time_by: "Updated by %{author} %{age} ago"
   label_updated_time: "Updated %{value} ago"
+  label_time_ago: "{{age}} ago"
   label_jump_to_a_project: Jump to a project...
   label_file_plural: Downloads
   label_changeset_plural: Changesets
@@ -831,6 +851,8 @@
   label_project_copy_notifications: Send email notifications during the project copy
   label_principal_search: "Search by name:"
   label_user_search: "Search for user:"
+  label_welcome_page: "Welcome page"
+  label_has_welcome_page: "Use your own welcome page"
   label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author
   label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee
   label_issues_visibility_all: All issues
@@ -884,6 +906,7 @@
   button_quote: Quote
   button_duplicate: Duplicate
   button_show: Show
+  button_welcome_page_edit: Create or edit welcome page
 
   status_active: active
   status_registered: registered
@@ -894,6 +917,7 @@
   version_status_closed: closed
 
   field_active: Active
+  field_current: Current
 
   text_select_mail_notifications: Select actions for which email notifications should be sent.
   text_regexp_info: eg. ^[A-Z0-9]+$
@@ -908,15 +932,17 @@
   text_journal_set_to: "%{label} set to %{value}"
   text_journal_deleted: "%{label} deleted (%{old})"
   text_journal_added: "%{label} %{value} added"
-  text_tip_issue_begin_day: issue beginning this day
-  text_tip_issue_end_day: issue ending this day
-  text_tip_issue_begin_end_day: issue beginning and ending this day
+  text_tip_issue_begin_day: task beginning this day
+  text_tip_issue_end_day: task ending this day
+  text_tip_issue_begin_end_day: task beginning and ending this day
   text_project_identifier_info: 'Only lower case letters (a-z), numbers and dashes are allowed.<br />Once saved, the identifier cannot be changed.'
+  text_project_homepage_info: 'Link to an external project page.'
   text_caracters_maximum: "%{count} characters maximum."
   text_caracters_minimum: "Must be at least %{count} characters long."
   text_length_between: "Length between %{min} and %{max} characters."
+  text_user_ssamr_description_info: 'Please describe your current research or development interests.<br/>This information will be used at registration to determine that you are a real person &ndash; so please be descriptive, or your application may be delayed or rejected.<br/>After registration, the description is publicly visible in your profile and you can edit it at any time.'
   text_issue_parent_issue_info: 'If this is a subtask, please insert its parent task number or write the main task name.'
-  text_user_ssamr_description_info: 'Please describe your current research or development interests, within the fields of audio and music.<br/>This information is publicly visible in your profile and you can edit it at any time.'
+
   text_tracker_no_workflow: No workflow defined for this tracker
   text_unallowed_characters: Unallowed characters
   text_comma_separated: Multiple values allowed (comma separated).
@@ -949,7 +975,7 @@
   text_enumeration_destroy_question: "%{count} objects are assigned to this value."
   text_enumeration_category_reassign_to: 'Reassign them to this value:'
   text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.yml and restart the application to enable them."
-  text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
+  text_repository_usernames_mapping: "Select the project member associated with each username found in the repository log.\nUsers whose name or email matches that in the repository are mapped automatically."
   text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
   text_custom_field_possible_values_info: 'One line for each value'
   text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?"
@@ -960,7 +986,8 @@
   text_zoom_in: Zoom in
   text_zoom_out: Zoom out
   text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page."
-  text_settings_repo_creation: The repository for a project should be set up automatically within a few minutes of the project being created.<br>You should not have to adjust any settings here.<br>Please check again in ten minutes, and <a href="/projects/soundsoftware-site/wiki/Help">contact us</a> if there is any problem.
+  text_files_active_change: <br>Click the star to switch active status for a download on or off.<br>Active files will be shown more prominently in the download page.
+  text_settings_repo_creation: <b>Creating repository...</b></p><p>The source code repository for a project will be set up automatically within a few minutes of the project being created.</p><p>Please check again in five minutes, and <a href="/projects/soundsoftware-site/wiki/Help">contact us</a> if there is any problem.</p><p>If you wish to use this project to track a repository that is already hosted somewhere else, please wait until the repository has been created here and then return to this settings page to configure it.</p><p>If you don't want a repository at all, go to the Modules tab and switch it off there.
   text_scm_path_encoding_note: "Default: UTF-8"
   text_git_repository_note: "Bare and local repository (e.g. /gitrepo, c:\gitrepo)"
   text_mercurial_repository_note: "Local repository (e.g. /hgrepo, c:\hgrepo)"
@@ -968,6 +995,13 @@
   text_scm_command_version: Version
   text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it.
   text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
+  text_settings_repo_explanation: <b>External repositories</b><p>Normally your project's primary repository will be the Mercurial repository hosted at this site.<p>However, if you already have your project hosted somewhere else, you can specify your existing external repository's URL here &ndash; then this site will track that repository in a read-only &ldquo;mirror&rdquo; copy.  External Mercurial, git and Subversion repositories can be tracked. Note that you cannot switch to an external repository if you have already made any commits to the repository hosted here.
+  text_settings_repo_is_internal: Currently the repository hosted at this site is the primary repository for this project.
+  text_settings_repo_is_external: Currently the repository hosted at this site is a read-only copy of an external repository.
+  text_settings_repo_need_help: Please <a href="/projects/soundsoftware-site/wiki/Help">contact us</a> if you need help deciding how best to set this up.<br>We can also import complete revision history from other systems into a new primary repository for you if you wish.
+  text_has_welcome_page_info: <b>Welcome page</b><p>You can replace the standard {{overview_link}} page for this project with your own welcome page.<br>This page will be editable using the project Wiki.
+  text_has_welcome_page_wiki_disabled: <b>Note:</b> You must enable the Wiki module in the {{modules_link}} tab before you can create or edit this page.
+
 
   default_role_manager: Manager
   default_role_developer: Developer
@@ -995,3 +1029,13 @@
   enumeration_doc_categories: Document categories
   enumeration_activities: Activities (time tracking)
   enumeration_system_activity: System Activity
+
+  label_manager_description: All powers including adding and removing members and adjusting project settings
+  label_developer_description: Can commit to repository and carry out most project editing tasks
+  label_reporter_description: Can submit bug reports; has read access for private projects
+
+  label_set_role_plural: Choose roles for new member
+
+  notice_added_to_project: 'You have been added to the project "{{project_name}}".'
+  notice_project_homepage: "You can visit the project using the following link: {{project_url}}"
+  mail_subject_added_to_project: "You've been added to a project on {{value}}"
\ No newline at end of file
--- a/config/routes.rb	Mon Jun 06 14:41:04 2011 +0100
+++ b/config/routes.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -151,6 +151,7 @@
     :copy => [:get, :post],
     :settings => :get,
     :modules => :post,
+    :overview => :post,
     :archive => :post,
     :unarchive => :post
   } do |project|
@@ -250,7 +251,9 @@
   map.with_options :controller => 'sys' do |sys|
     sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
     sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
+    sys.connect 'sys/projects/:id/external_repository.:format', :action => 'get_external_repo_url', :conditions => {:method => :get}
     sys.connect 'sys/projects/:id/embedded.:format', :action => 'set_embedded_active', :conditions => { :method => :post }
+    sys.connect 'sys/projects/:id/repository_cache.:format', :action => 'clear_repository_cache', :conditions => {:method => :post}
   end
  
   # Install the default route as the lowest priority.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/db/migrate/20110207142856_add_ext_rep_to_repositories.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -0,0 +1,11 @@
+class AddExtRepToRepositories < ActiveRecord::Migration
+  def self.up
+    add_column :repositories, :is_external, :bool
+    add_column :repositories, :external_url, :string
+  end
+
+  def self.down
+    remove_column :repositories, :is_external
+    remove_column :repositories, :external_url
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/db/migrate/20110303152903_add_active_column_to_attachments.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -0,0 +1,9 @@
+class AddActiveColumnToAttachments < ActiveRecord::Migration
+  def self.up
+    add_column :attachments, :active, :boolean
+  end
+
+  def self.down
+    remove_column :attachments, :active
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/db/migrate/20110331152140_add_has_welcome_page_to_projects.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -0,0 +1,9 @@
+class AddHasWelcomePageToProjects < ActiveRecord::Migration
+  def self.up
+    add_column :projects, :has_welcome_page, :boolean
+  end
+
+  def self.down
+    remove_column :projects, :has_welcome_page
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extra/soundsoftware/SoundSoftware-salted.pm	Mon Jun 06 14:55:38 2011 +0100
@@ -0,0 +1,470 @@
+package Apache::Authn::SoundSoftware;
+
+=head1 Apache::Authn::SoundSoftware
+
+SoundSoftware - a mod_perl module for Apache authentication against a
+Redmine database and optional LDAP implementing the access control
+rules required for the SoundSoftware.ac.uk repository site.
+
+=head1 SYNOPSIS
+
+This module is closely based on the Redmine.pm authentication module
+provided with Redmine.  It is intended to be used for authentication
+in front of a repository service such as hgwebdir.
+
+Requirements:
+
+1. Clone/pull from repo for public project: Any user, no
+authentication required
+
+2. Clone/pull from repo for private project: Project members only
+
+3. Push to repo for public project: "Permitted" users only (this
+probably means project members who are also identified in the hgrc web
+section for the repository and so will be approved by hgwebdir?)
+
+4. Push to repo for private project: "Permitted" users only (as above)
+
+5. Push to any repo that is tracking an external repo: Refused always
+
+=head1 INSTALLATION
+
+Debian/ubuntu:
+
+  apt-get install libapache-dbi-perl libapache2-mod-perl2 \
+    libdbd-mysql-perl libauthen-simple-ldap-perl libio-socket-ssl-perl
+
+Note that LDAP support is hardcoded "on" in this script (it is
+optional in the original Redmine.pm).
+
+=head1 CONFIGURATION
+
+   ## This module has to be in your perl path
+   ## eg:  /usr/local/lib/site_perl/Apache/Authn/SoundSoftware.pm
+   PerlLoadModule Apache::Authn::SoundSoftware
+
+   # Example when using hgwebdir
+   ScriptAlias / "/var/hg/hgwebdir.cgi/"
+
+   <Location />
+       AuthName "Mercurial"
+       AuthType Basic
+       Require valid-user
+       PerlAccessHandler Apache::Authn::SoundSoftware::access_handler
+       PerlAuthenHandler Apache::Authn::SoundSoftware::authen_handler
+       SoundSoftwareDSN "DBI:mysql:database=redmine;host=localhost"
+       SoundSoftwareDbUser "redmine"
+       SoundSoftwareDbPass "password"
+       Options +ExecCGI
+       AddHandler cgi-script .cgi
+       ## Optional where clause (fulltext search would be slow and
+       ## database dependant).
+       # SoundSoftwareDbWhereClause "and members.role_id IN (1,2)"
+       ## Optional prefix for local repository URLs
+       # SoundSoftwareRepoPrefix "/var/hg/"
+  </Location>
+
+See the original Redmine.pm for further configuration notes.
+
+=cut
+
+use strict;
+use warnings FATAL => 'all', NONFATAL => 'redefine';
+
+use DBI;
+use Digest::SHA1;
+use Authen::Simple::LDAP;
+use Apache2::Module;
+use Apache2::Access;
+use Apache2::ServerRec qw();
+use Apache2::RequestRec qw();
+use Apache2::RequestUtil qw();
+use Apache2::Const qw(:common :override :cmd_how);
+use APR::Pool ();
+use APR::Table ();
+
+my @directives = (
+  {
+    name => 'SoundSoftwareDSN',
+    req_override => OR_AUTHCFG,
+    args_how => TAKE1,
+    errmsg => 'Dsn in format used by Perl DBI. eg: "DBI:Pg:dbname=databasename;host=my.db.server"',
+  },
+  {
+    name => 'SoundSoftwareDbUser',
+    req_override => OR_AUTHCFG,
+    args_how => TAKE1,
+  },
+  {
+    name => 'SoundSoftwareDbPass',
+    req_override => OR_AUTHCFG,
+    args_how => TAKE1,
+  },
+  {
+    name => 'SoundSoftwareDbWhereClause',
+    req_override => OR_AUTHCFG,
+    args_how => TAKE1,
+  },
+  {
+    name => 'SoundSoftwareRepoPrefix',
+    req_override => OR_AUTHCFG,
+    args_how => TAKE1,
+  },
+);
+
+sub SoundSoftwareDSN { 
+    my ($self, $parms, $arg) = @_;
+    $self->{SoundSoftwareDSN} = $arg;
+    my $query = "SELECT 
+                 hashed_password, salt, auth_source_id, permissions
+              FROM members, projects, users, roles, member_roles
+              WHERE 
+                projects.id=members.project_id
+                AND member_roles.member_id=members.id
+                AND users.id=members.user_id 
+                AND roles.id=member_roles.role_id
+                AND users.status=1 
+                AND login=? 
+                AND identifier=? ";
+    $self->{SoundSoftwareQuery} = trim($query);
+}
+
+sub SoundSoftwareDbUser { set_val('SoundSoftwareDbUser', @_); }
+sub SoundSoftwareDbPass { set_val('SoundSoftwareDbPass', @_); }
+sub SoundSoftwareDbWhereClause { 
+    my ($self, $parms, $arg) = @_;
+    $self->{SoundSoftwareQuery} = trim($self->{SoundSoftwareQuery}.($arg ? $arg : "")." ");
+}
+
+sub SoundSoftwareRepoPrefix { 
+    my ($self, $parms, $arg) = @_;
+    if ($arg) {
+	$self->{SoundSoftwareRepoPrefix} = $arg;
+    }
+}
+
+sub trim {
+    my $string = shift;
+    $string =~ s/\s{2,}/ /g;
+    return $string;
+}
+
+sub set_val {
+    my ($key, $self, $parms, $arg) = @_;
+    $self->{$key} = $arg;
+}
+
+Apache2::Module::add(__PACKAGE__, \@directives);
+
+
+my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
+
+sub access_handler {
+    my $r = shift;
+
+    print STDERR "SoundSoftware.pm: In access handler at " . scalar localtime() . "\n";
+
+    unless ($r->some_auth_required) {
+	$r->log_reason("No authentication has been configured");
+	return FORBIDDEN;
+    }
+
+    my $method = $r->method;
+
+    print STDERR "SoundSoftware.pm: Method: $method, uri " . $r->uri . ", location " . $r->location . "\n";
+    print STDERR "SoundSoftware.pm: Accept: " . $r->headers_in->{Accept} . "\n";
+
+    my $dbh = connect_database($r);
+    unless ($dbh) {
+	print STDERR "SoundSoftware.pm: Database connection failed!: " . $DBI::errstr . "\n";
+	return FORBIDDEN;
+    }
+
+    print STDERR "Connected to db, dbh is " . $dbh . "\n";
+
+    my $project_id = get_project_identifier($dbh, $r);
+
+    if (!defined $read_only_methods{$method}) {
+        print STDERR "SoundSoftware.pm: Method is not read-only\n";
+        if (project_repo_is_readonly($dbh, $project_id, $r)) {
+            print STDERR "SoundSoftware.pm: Project repo is read-only, refusing access\n";
+	    return FORBIDDEN;
+        } else {
+	    print STDERR "SoundSoftware.pm: Project repo is read-write, authentication handler required\n";
+            return OK;
+        }
+    }
+
+    my $status = get_project_status($dbh, $project_id, $r);
+
+    $dbh->disconnect();
+    undef $dbh;
+
+    if ($status == 0) { # nonexistent
+	print STDERR "SoundSoftware.pm: Project does not exist, refusing access\n";
+	return FORBIDDEN;
+    } elsif ($status == 1) { # public
+	print STDERR "SoundSoftware.pm: Project is public, no restriction here\n";
+	$r->set_handlers(PerlAuthenHandler => [\&OK])
+    } else { # private
+	print STDERR "SoundSoftware.pm: Project is private, authentication handler required\n";
+    }
+
+    return OK
+}
+
+sub authen_handler {
+    my $r = shift;
+    
+    print STDERR "SoundSoftware.pm: In authentication handler at " . scalar localtime() . "\n";
+
+    my $dbh = connect_database($r);
+    unless ($dbh) {
+        print STDERR "SoundSoftware.pm: Database connection failed!: " . $DBI::errstr . "\n";
+        return AUTH_REQUIRED;
+    }
+    
+    my $project_id = get_project_identifier($dbh, $r);
+    my $realm = get_realm($dbh, $project_id, $r);
+    $r->auth_name($realm);
+
+    my ($res, $redmine_pass) =  $r->get_basic_auth_pw();
+    unless ($res == OK) {
+	$dbh->disconnect();
+	undef $dbh;
+	return $res;
+    }
+    
+    print STDERR "SoundSoftware.pm: User is " . $r->user . ", got password\n";
+
+    my $permitted = is_permitted($dbh, $project_id, $r->user, $redmine_pass, $r);
+    
+    $dbh->disconnect();
+    undef $dbh;
+
+    if ($permitted) {
+	return OK;
+    } else {
+	print STDERR "SoundSoftware.pm: Not permitted\n";
+	$r->note_auth_failure();
+	return AUTH_REQUIRED;
+    }
+}
+
+sub get_project_status {
+    my $dbh = shift;
+    my $project_id = shift;
+    my $r = shift;
+
+    if (!defined $project_id or $project_id eq '') {
+	return 0; # nonexistent
+    }
+    
+    my $sth = $dbh->prepare(
+        "SELECT is_public FROM projects WHERE projects.identifier = ?;"
+    );
+
+    $sth->execute($project_id);
+    my $ret = 0; # nonexistent
+    if (my @row = $sth->fetchrow_array) {
+    	if ($row[0] eq "1" || $row[0] eq "t") {
+	    $ret = 1; # public
+    	} else {
+	    $ret = 2; # private
+	}
+    }
+    $sth->finish();
+    undef $sth;
+
+    $ret;
+}
+
+sub project_repo_is_readonly {
+    my $dbh = shift;
+    my $project_id = shift;
+    my $r = shift;
+
+    if (!defined $project_id or $project_id eq '') {
+        return 0; # nonexistent
+    }
+
+    my $sth = $dbh->prepare(
+        "SELECT repositories.is_external FROM repositories, projects WHERE projects.identifier = ? AND repositories.project_id = projects.id;"
+    );
+
+    $sth->execute($project_id);
+    my $ret = 0; # nonexistent
+    if (my @row = $sth->fetchrow_array) {
+        if (defined($row[0]) && ($row[0] eq "1" || $row[0] eq "t")) {
+            $ret = 1; # read-only (i.e. external)
+        } else {
+            $ret = 0; # read-write
+        }
+    }
+    $sth->finish();
+    undef $sth;
+
+    $ret;
+}
+
+sub is_permitted {
+    my $dbh = shift;
+    my $project_id = shift;
+    my $redmine_user = shift;
+    my $redmine_pass = shift;
+    my $r = shift;
+
+    my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
+
+    my $cfg = Apache2::Module::get_config
+	(__PACKAGE__, $r->server, $r->per_dir_config);
+
+    my $query = $cfg->{SoundSoftwareQuery};
+    my $sth = $dbh->prepare($query);
+    $sth->execute($redmine_user, $project_id);
+
+    my $ret;
+    while (my ($hashed_password, $salt, $auth_source_id, $permissions) = $sth->fetchrow_array) {
+
+	# Test permissions for this user before we verify credentials
+	# -- if the user is not permitted this action anyway, there's
+	# not much point in e.g. contacting the LDAP
+
+	my $method = $r->method;
+
+	if ((defined $read_only_methods{$method} && $permissions =~ /:browse_repository/)
+	    || $permissions =~ /:commit_access/) {
+
+	    # User would be permitted this action, if their
+	    # credentials checked out -- test those now
+
+	    print STDERR "SoundSoftware.pm: User $redmine_user has required role, checking credentials\n";
+
+	    unless ($auth_source_id) {
+                my $salted_password = Digest::SHA1::sha1_hex($salt.$pass_digest);
+		if ($hashed_password eq $salted_password) {
+		    print STDERR "SoundSoftware.pm: User $redmine_user authenticated via password\n";
+		    $ret = 1;
+		    last;
+		}
+	    } else {
+		my $sthldap = $dbh->prepare(
+		    "SELECT host,port,tls,account,account_password,base_dn,attr_login FROM auth_sources WHERE id = ?;"
+		    );
+		$sthldap->execute($auth_source_id);
+		while (my @rowldap = $sthldap->fetchrow_array) {
+		    my $ldap = Authen::Simple::LDAP->new(
+			host    => ($rowldap[2] eq "1" || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]" : $rowldap[0],
+			port    => $rowldap[1],
+			basedn  => $rowldap[5],
+			binddn  => $rowldap[3] ? $rowldap[3] : "",
+			bindpw  => $rowldap[4] ? $rowldap[4] : "",
+			filter  => "(".$rowldap[6]."=%s)"
+			);
+		    if ($ldap->authenticate($redmine_user, $redmine_pass)) {
+			print STDERR "SoundSoftware.pm: User $redmine_user authenticated via LDAP\n";
+			$ret = 1;
+		    }
+		}
+		$sthldap->finish();
+		undef $sthldap;
+	    }
+	} else {
+	    print STDERR "SoundSoftware.pm: User $redmine_user lacks required role for this project\n";
+	}
+    }
+
+    $sth->finish();
+    undef $sth;
+
+    $ret;
+}
+
+sub get_project_identifier {
+    my $dbh = shift;
+    my $r = shift;
+
+    my $location = $r->location;
+    my ($repo) = $r->uri =~ m{$location/*([^/]+)};
+
+    return $repo if (!$repo);
+
+    $repo =~ s/[^a-zA-Z0-9\._-]//g;
+
+    # The original Redmine.pm returns the string just calculated as
+    # the project identifier.  That won't do for us -- we may have
+    # (and in fact already do have, in our test instance) projects
+    # whose repository names differ from the project identifiers.
+
+    # This is a rather fundamental change because it means that almost
+    # every request needs more than one database query -- which
+    # prompts us to start passing around $dbh instead of connecting
+    # locally within each function as is done in Redmine.pm.
+
+    my $sth = $dbh->prepare(
+        "SELECT projects.identifier FROM projects, repositories WHERE repositories.project_id = projects.id AND repositories.url LIKE ?;"
+    );
+
+    my $cfg = Apache2::Module::get_config
+	(__PACKAGE__, $r->server, $r->per_dir_config);
+
+    my $prefix = $cfg->{SoundSoftwareRepoPrefix};
+    if (!defined $prefix) { $prefix = '%/'; }
+
+    my $identifier = '';
+
+    $sth->execute($prefix . $repo);
+    my $ret = 0;
+    if (my @row = $sth->fetchrow_array) {
+	$identifier = $row[0];
+    }
+    $sth->finish();
+    undef $sth;
+
+    print STDERR "SoundSoftware.pm: Repository '$repo' belongs to project '$identifier'\n";
+
+    $identifier;
+}
+
+sub get_realm {
+    my $dbh = shift;
+    my $project_id = shift;
+    my $r = shift;
+
+    my $sth = $dbh->prepare(
+        "SELECT projects.name FROM projects WHERE projects.identifier = ?;"
+    );
+
+    my $name = $project_id;
+
+    $sth->execute($project_id);
+    my $ret = 0;
+    if (my @row = $sth->fetchrow_array) {
+	$name = $row[0];
+    }
+    $sth->finish();
+    undef $sth;
+
+    # be timid about characters not permitted in auth realm and revert
+    # to project identifier if any are found
+    if ($name =~ m/[^\w\d\s\._-]/) {
+	$name = $project_id;
+    }
+
+    my $realm = '"Mercurial repository for ' . "'$name'" . '"';
+
+    $realm;
+}
+
+sub connect_database {
+    my $r = shift;
+    
+    my $cfg = Apache2::Module::get_config
+	(__PACKAGE__, $r->server, $r->per_dir_config);
+
+    return DBI->connect($cfg->{SoundSoftwareDSN},
+	                $cfg->{SoundSoftwareDbUser},
+		        $cfg->{SoundSoftwareDbPass});
+}
+
+1;
--- a/extra/soundsoftware/SoundSoftware.pm	Mon Jun 06 14:41:04 2011 +0100
+++ b/extra/soundsoftware/SoundSoftware.pm	Mon Jun 06 14:55:38 2011 +0100
@@ -25,6 +25,8 @@
 
 4. Push to repo for private project: "Permitted" users only (as above)
 
+5. Push to any repo that is tracking an external repo: Refused always
+
 =head1 INSTALLATION
 
 Debian/ubuntu:
@@ -172,21 +174,27 @@
     print STDERR "SoundSoftware.pm: Method: $method, uri " . $r->uri . ", location " . $r->location . "\n";
     print STDERR "SoundSoftware.pm: Accept: " . $r->headers_in->{Accept} . "\n";
 
-    if (!defined $read_only_methods{$method}) {
-	print STDERR "SoundSoftware.pm: Method is not read-only, authentication handler required\n";
-	return OK;
-    }
-
     my $dbh = connect_database($r);
     unless ($dbh) {
 	print STDERR "SoundSoftware.pm: Database connection failed!: " . $DBI::errstr . "\n";
 	return FORBIDDEN;
     }
 
-
-print STDERR "Connected to db, dbh is " . $dbh . "\n";
+    print STDERR "Connected to db, dbh is " . $dbh . "\n";
 
     my $project_id = get_project_identifier($dbh, $r);
+
+    if (!defined $read_only_methods{$method}) {
+        print STDERR "SoundSoftware.pm: Method is not read-only\n";
+        if (project_repo_is_readonly($dbh, $project_id, $r)) {
+            print STDERR "SoundSoftware.pm: Project repo is read-only, refusing access\n";
+	    return FORBIDDEN;
+        } else {
+	    print STDERR "SoundSoftware.pm: Project repo is read-write, authentication handler required\n";
+            return OK;
+        }
+    }
+
     my $status = get_project_status($dbh, $project_id, $r);
 
     $dbh->disconnect();
@@ -271,6 +279,34 @@
     $ret;
 }
 
+sub project_repo_is_readonly {
+    my $dbh = shift;
+    my $project_id = shift;
+    my $r = shift;
+
+    if (!defined $project_id or $project_id eq '') {
+        return 0; # nonexistent
+    }
+
+    my $sth = $dbh->prepare(
+        "SELECT repositories.is_external FROM repositories, projects WHERE projects.identifier = ? AND repositories.project_id = projects.id;"
+    );
+
+    $sth->execute($project_id);
+    my $ret = 0; # nonexistent
+    if (my @row = $sth->fetchrow_array) {
+        if (defined($row[0]) && ($row[0] eq "1" || $row[0] eq "t")) {
+            $ret = 1; # read-only (i.e. external)
+        } else {
+            $ret = 0; # read-write
+        }
+    }
+    $sth->finish();
+    undef $sth;
+
+    $ret;
+}
+
 sub is_permitted {
     my $dbh = shift;
     my $project_id = shift;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extra/soundsoftware/convert-external-repos.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -0,0 +1,183 @@
+#!/usr/bin/env ruby
+
+# == Synopsis
+#
+# convert-external-repos: Update local Mercurial mirrors of external repos,
+# by running an external command for each project requiring an update.
+#
+# == Usage
+#
+#    convert-external-repos [OPTIONS...] -s [DIR] -r [HOST]
+#     
+# == Arguments (mandatory)
+#
+#   -s, --scm-dir=DIR         use DIR as base directory for repositories
+#   -r, --redmine-host=HOST   assume Redmine is hosted on HOST. Examples:
+#                             -r redmine.example.net
+#                             -r http://redmine.example.net
+#                             -r https://example.net/redmine
+#   -k, --key=KEY             use KEY as the Redmine API key
+#   -c, --command=COMMAND     use this command to update each external
+#                             repository: command is called with the name
+#                             of the project, the path to its repo, and
+#                             its external repo url as its three args
+#
+# == Options
+#
+#   --http-user=USER          User for HTTP Basic authentication with Redmine WS
+#   --http-pass=PASSWORD      Password for Basic authentication with Redmine WS
+#   -t, --test                only show what should be done
+#   -h, --help                show help and exit
+#   -v, --verbose             verbose
+#   -V, --version             print version and exit
+#   -q, --quiet               no log
+
+
+require 'getoptlong'
+require 'rdoc/usage'
+require 'find'
+require 'etc'
+
+Version = "1.0"
+
+opts = GetoptLong.new(
+                      ['--scm-dir',      '-s', GetoptLong::REQUIRED_ARGUMENT],
+                      ['--redmine-host', '-r', GetoptLong::REQUIRED_ARGUMENT],
+                      ['--key',          '-k', GetoptLong::REQUIRED_ARGUMENT],
+                      ['--http-user',          GetoptLong::REQUIRED_ARGUMENT],
+                      ['--http-pass',          GetoptLong::REQUIRED_ARGUMENT],
+                      ['--command' ,     '-c', GetoptLong::REQUIRED_ARGUMENT],
+                      ['--test',         '-t', GetoptLong::NO_ARGUMENT],
+                      ['--verbose',      '-v', GetoptLong::NO_ARGUMENT],
+                      ['--version',      '-V', GetoptLong::NO_ARGUMENT],
+                      ['--help'   ,      '-h', GetoptLong::NO_ARGUMENT],
+                      ['--quiet'  ,      '-q', GetoptLong::NO_ARGUMENT]
+                      )
+
+$verbose      = 0
+$quiet        = false
+$redmine_host = ''
+$repos_base   = ''
+$http_user    = ''
+$http_pass    = ''
+$test         = false
+
+$mirrordir    = '/var/mirror'
+
+def log(text, options={})
+  level = options[:level] || 0
+  puts text unless $quiet or level > $verbose
+  exit 1 if options[:exit]
+end
+
+def system_or_raise(command)
+  raise "\"#{command}\" failed" unless system command
+end
+
+begin
+  opts.each do |opt, arg|
+    case opt
+    when '--scm-dir';        $repos_base   = arg.dup
+    when '--redmine-host';   $redmine_host = arg.dup
+    when '--key';            $api_key      = arg.dup
+    when '--http-user';      $http_user    = arg.dup
+    when '--http-pass';      $http_pass    = arg.dup
+    when '--command';        $command      = arg.dup
+    when '--verbose';        $verbose += 1
+    when '--test';           $test = true
+    when '--version';        puts Version; exit
+    when '--help';           RDoc::usage
+    when '--quiet';          $quiet = true
+    end
+  end
+rescue
+  exit 1
+end
+
+if $test
+  log("running in test mode")
+end
+
+if ($redmine_host.empty? or $repos_base.empty? or $command.empty?)
+  RDoc::usage
+end
+
+unless File.directory?($repos_base)
+  log("directory '#{$repos_base}' doesn't exist", :exit => true)
+end
+
+begin
+  require 'active_resource'
+rescue LoadError
+  log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true)
+end
+
+class Project < ActiveResource::Base
+  self.headers["User-agent"] = "SoundSoftware external repository converter/#{Version}"
+end
+
+log("querying Redmine for projects...", :level => 1);
+
+$redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
+$redmine_host.gsub!(/\/$/, '')
+
+Project.site = "#{$redmine_host}/sys";
+Project.user = $http_user;
+Project.password = $http_pass;
+
+begin
+  # Get all active projects that have the Repository module enabled
+  projects = Project.find(:all, :params => {:key => $api_key})
+rescue => e
+  log("Unable to connect to #{Project.site}: #{e}", :exit => true)
+end
+
+if projects.nil?
+  log('no project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
+end
+
+log("retrieved #{projects.size} projects", :level => 1)
+
+projects.each do |project|
+  log("treating project #{project.name}", :level => 1)
+
+  if project.identifier.empty?
+    log("\tno identifier for project #{project.name}")
+    next
+  elsif not project.identifier.match(/^[a-z0-9\-]+$/)
+    log("\tinvalid identifier for project #{project.name} : #{project.identifier}");
+    next
+  end
+
+  if !project.respond_to?(:repository) or !project.repository.is_external?
+    log("\tproject #{project.identifier} does not use an external repository");
+    next
+  end
+
+  external_url = project.repository.external_url;
+  log("\tproject #{project.identifier} has external repository url #{external_url}");
+
+  if !external_url.match(/^[a-z][a-z+]{0,8}[a-z]:\/\//)
+    log("\tthis doesn't look like a plausible url to me, skipping")
+    next
+  end
+
+  repos_path = File.join($repos_base, project.identifier).gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
+
+  unless File.directory?(repos_path)
+    log("\tproject repo directory '#{repos_path}' doesn't exist")
+    next
+  end
+
+  system($command, project.identifier, repos_path, external_url)
+  
+  $cache_clearance_file = File.join($mirrordir, project.identifier, 'url_changed')
+  if File.file?($cache_clearance_file)
+    log("\tproject repo url has changed, requesting cache clearance")
+    if project.post(:repository_cache, :key => $api_key)
+      File.delete($cache_clearance_file)
+    end
+  end
+
+end
+  
--- a/extra/soundsoftware/extract-docs.sh	Mon Jun 06 14:41:04 2011 +0100
+++ b/extra/soundsoftware/extract-docs.sh	Mon Jun 06 14:55:38 2011 +0100
@@ -12,6 +12,7 @@
 redgrp="redmine"
 
 apikey=""
+apischeme="https"
 apihost=""
 apiuser=""
 apipass=""
@@ -22,7 +23,7 @@
     *) progdir="$(pwd)/$progdir" ;;
 esac
 
-types="doxygen javadoc" # Do Doxygen first (it can be used for Java too)
+types="doxygen javadoc matlabdocs" # Do Doxygen first (it can be used for Java too)
 
 for x in $types; do
     if [ ! -x "$progdir/extract-$x.sh" ]; then
@@ -36,9 +37,9 @@
     p="$1"
     if [ -n "$apikey" ]; then
 	if [ -n "$apiuser" ]; then
-	    sudo -u docgen curl -u "$apiuser":"$apipass" "http://$apihost/sys/projects/$p/embedded.xml?enable=1&key=$apikey" -d ""
+	    sudo -u docgen curl -u "$apiuser":"$apipass" "$apischeme://$apihost/sys/projects/$p/embedded.xml?enable=1&key=$apikey" -d ""
 	else
-	    sudo -u docgen curl "http://$apihost/sys/projects/$p/embedded.xml?enable=1&key=$apikey" -d ""
+	    sudo -u docgen curl "$apischeme://$apihost/sys/projects/$p/embedded.xml?enable=1&key=$apikey" -d ""
 	fi
     else 
 	echo "Can't enable Embedded, API not configured" 1>&2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extra/soundsoftware/extract-matlabdocs.sh	Mon Jun 06 14:55:38 2011 +0100
@@ -0,0 +1,47 @@
+#!/bin/bash
+
+docdir="/var/doc"
+
+progdir=$(dirname $0)
+case "$progdir" in
+    /*) ;;
+    *) progdir="$(pwd)/$progdir" ;;
+esac
+
+project="$1"
+projectdir="$2"
+targetdir="$3"
+
+if [ -z "$project" ] || [ -z "$targetdir" ] || [ -z "$projectdir" ]; then
+    echo "Usage: $0 <project> <projectdir> <targetdir>"
+    exit 2
+fi
+
+if [ ! -d "$projectdir" ]; then
+    echo "Project directory $projectdir not found"
+    exit 1
+fi
+
+if [ ! -d "$targetdir" ]; then
+    echo "Target dir $targetdir not found"
+    exit 1
+fi
+
+if [ -f "$targetdir/index.html" ]; then
+    echo "Target dir $targetdir already contains index.html"
+    exit 1
+fi
+
+mfile=$(find "$projectdir" -type f -name \*.m -print0 | xargs -0 grep -l '^% ' | head -1)
+
+if [ -z "$mfile" ]; then
+    echo "No MATLAB files with comments found for project $project"
+    exit 1
+fi
+
+echo "Project $project contains at least one MATLAB file with comments"
+
+cd "$projectdir" || exit 1
+
+perl "$progdir/matlab-docs.pl" -c "$progdir/matlab-docs.conf" -d "$targetdir"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extra/soundsoftware/matlab-docs-credit.html	Mon Jun 06 14:55:38 2011 +0100
@@ -0,0 +1,1 @@
+<div style="clear: both; float: right"><small><i>Produced by mtree2html by Hartmut Pohlheim</i></small></div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extra/soundsoftware/matlab-docs.conf	Mon Jun 06 14:55:38 2011 +0100
@@ -0,0 +1,166 @@
+# configuration file for generation of html-docu from m-files
+#
+# Author:   Hartmut Pohlheim
+# History:  05.11.2000  file created (parameters for mtree2html2001)
+#
+# The following options/variables must be changed/adapted:
+#   dirmfiles
+#   dirhtml
+#   csslink
+#   texttitleframelayout
+#   texttitlefiles
+#
+# The following options/variables should be adapted:
+#   authorfile
+#   filenametopframe
+#   codeheadmeta
+
+#========================================================================
+# Variables (possible keywords: set)
+# to use the built-in settings, comment the line using # in first column
+#========================================================================
+
+#------------------------------------------------------------------------
+# dirmfiles: name of directory containing Matlab m-files
+# dirhtml: name of directory to place the html-files into
+# exthtml: extension used for the html files (.html or .htm)
+#          don't forget the point in front of the extension
+#------------------------------------------------------------------------
+set dirmfiles = .
+set dirhtml = doc-output
+set exthtml = .html
+
+#------------------------------------------------------------------------
+# authorfile:   name of file containing info about author (in html)
+#               if defined, this text is included at the bottom of the 
+#               html files
+#------------------------------------------------------------------------
+set authorfile = matlab-docs-credit.html
+
+#------------------------------------------------------------------------
+# csslink:   text for linking to css file (style sheets)
+#            the text defined here is directly included into the head 
+#            of the html file
+#------------------------------------------------------------------------
+#set csslink = <link rel=stylesheet type="text/css" href="CSSFILENAME.css" />
+
+#------------------------------------------------------------------------
+# links2filescase: this is a bit difficult
+#                  Matlab is case sensitive on UNIX, but case insensitive
+#                  on Windows. Under UNIX Matlab function calls work 
+#                  only, when the case of file name and function call are 
+#                  identical, under Windows you can do what you want.
+#                  This scripts help you, to keep an exact case in your 
+#                  project.
+#          exact - internal links are only generated, when case of file 
+#                  name and in source code are identical
+#            all - case doesn't matter
+#     exactupper - same as exact, additionally links are also vreated to 
+#                  all upper case function names in source code (often 
+#                  used by Mathworks)
+#      exactvery - same as exact, additionally info about not matching
+#                  case is written to screen (stdout), this can be very 
+#                  helpful in cleaning up the case in a project
+#------------------------------------------------------------------------
+set links2filescase = all
+
+#------------------------------------------------------------------------
+# texttitleframelayout:    text of title for frame layout file (whole docu)
+#------------------------------------------------------------------------
+set texttitleframelayout = MATLAB Function Documentation
+
+#------------------------------------------------------------------------
+# texttitle/headerindexalldirs: text of title and header for directory index
+#------------------------------------------------------------------------
+set texttitleindexalldirs = Index of Directories
+set textheaderindexalldirs = Index of Directories
+
+#------------------------------------------------------------------------
+# texttitle/headerindex:    text of title and header for index file
+#------------------------------------------------------------------------
+set texttitleindex = A-Z Index of Functions
+set textheaderindex = A-Z Index of Functions
+
+#------------------------------------------------------------------------
+# texttitle/headerfiles:    text of title and header for files
+#                           name of file will be added at the end
+#------------------------------------------------------------------------
+set texttitlefiles = Function
+set textheaderfiles = Documentation of
+
+#------------------------------------------------------------------------
+# frames: whether to use frames in layout (yes or no)
+#------------------------------------------------------------------------
+set frames = no
+
+#------------------------------------------------------------------------
+# filenametopframe: name of file including frame layout (highest level file)
+# [default: index]
+#------------------------------------------------------------------------
+set filenametopframe = index
+
+#------------------------------------------------------------------------
+# textjumpindexglobal: text displayed for jump to index of all files
+#                      (global)
+# textjumpindexlocal:  text displayed for jump to index of files in actual
+#                      directory (local)
+#------------------------------------------------------------------------
+set textjumpindexglobal = <b>Index of</b> all files:
+set textjumpindexlocal = this subdirectory only:
+
+#------------------------------------------------------------------------
+# includesource: include source of m-files in documentation [YES|no]
+#------------------------------------------------------------------------
+set includesource = yes
+
+#------------------------------------------------------------------------
+# usecontentsm: use contents.m files as well for structured
+#               (hopefully) index [YES|no]
+#------------------------------------------------------------------------
+set usecontentsm = no
+
+#------------------------------------------------------------------------
+# includesource: write/update contents.m files [yes|NO]
+#------------------------------------------------------------------------
+set writecontentsm = no
+
+#------------------------------------------------------------------------
+# processtree:  parse whole directory tree recursively [YES|no]
+#------------------------------------------------------------------------
+set processtree = yes
+
+#------------------------------------------------------------------------
+# producetree:  produce tree for html-files in same structure than
+#		          tree of m-files [yes|NO]
+#               if no, all files are saved in the same directory, often 
+#               easier for outside linking to files
+#------------------------------------------------------------------------
+set producetree = yes
+
+#------------------------------------------------------------------------
+# codebodyindex/files: HTML-code for adding to BODY tag
+#                      can be used for defining colors and
+#                      backgroundimages of the files
+#                      No longer recommended, use the css file
+#------------------------------------------------------------------------
+set codebodyindex =
+set codebodyfiles =
+
+#------------------------------------------------------------------------
+# codeheadmeta: HTML-code added in HEAD area, use for supplying META info
+#------------------------------------------------------------------------
+set codeheadmeta = 
+
+#------------------------------------------------------------------------
+# codehr: HTML-code used to define a <HR>, do what you want
+#------------------------------------------------------------------------
+set codehr = <hr>
+
+#------------------------------------------------------------------------
+# codeheader: HTML-code added to <H*> tags, use for centering header text
+#             or changing the colour/size/font of the header text
+#------------------------------------------------------------------------
+set codeheader = 
+
+
+# End of parameter file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extra/soundsoftware/matlab-docs.pl	Mon Jun 06 14:55:38 2011 +0100
@@ -0,0 +1,1585 @@
+@rem = '--*-Perl-*--';
+@rem = '
+@echo off
+perl -w -S %0.bat %1 %2 %3 %4 %5 %6 %7 %8 %9
+goto endofperl
+@rem ';
+# perl -w -S %0.bat "$@"
+#!/usr/bin/perl
+#
+# mtree2html_2000 - produce html files from Matlab m-files.
+#                   use configuration file for flexibility
+#                   can process tree of directories
+#
+# Copyright (C) 1996-2000 Hartmut Pohlheim.  All rights reserved.
+# includes small parts of m2html from Jeffrey C. Kantor 1995
+#
+# Author:  Hartmut Pohlheim
+# History: 06.03.1996  file created
+#          07.03.1996  first working version
+#          08.03.1996  modularized, help text only once included
+#          11.03.1996  clean up, some functions rwritten
+#          18.04.1996  silent output with writing in one line only
+#                      version 0.20 fixed
+#          14.05.1996  start of adding tree structure, could create tree
+#          15.05.1996  creating of index files for every directory
+#          17.05.1996  first working version except compact A-Z index
+#          20.05.1996  cleanup of actual version, more variables and 
+#                      configurable settings
+#          21.05.1996  reading, update and creation of contents.m added 
+#          22.05.1996  creation of short index started
+#          28.05.1996  jump letters for short index,
+#                      3 different directory indexes (short/long/contents)
+#          29.05.1996  major cleanup, short and long index created from one function
+#                      links for HTML and Indexes from 1 function,
+#                      version 0.9
+#          30.05.1996  contents.m changed to Contents.m (because unix likes it)
+#                      function definition can be in first line of m file before comments
+#                      version 0.91 fixed
+#          03.06.1996  contents file can be written as wanted, the links will be correct
+#                      cross references in help block of m-file will be found and
+#                      converted, even if the name of the function is written upper case
+#                      version 0.92 fixed
+#          05.06.1996  construction of dependency matrix changed, is able now to process
+#                      even the whole matlab tree (previous version needed to much memory)
+#                      removed warning for contents files in different directories
+#                      version 0.94 fixed
+#          06.06.1996  new link name matrices for ConstructHTMLFile created,
+#                      everything is done in ConstructDependencyMatrix,
+#                      both dependencies (calls and called) and matrix 
+#                      with all mentioned names in this m-file, thus, much
+#                      less scanning in html construction
+#                      script is now (nearly) linear scalable, thus, matlab-toolbox
+#                      tree takes less than 1 hour on a Pentium120, with source
+#                      version 0.96 fixed
+#          10.06.1996  order of creation changed, first all indexes (includes 
+#                      update/creation of contents.m) and then ConstructDepency
+#                      thus, AutoAdd section will be linked as well
+#                      excludenames extended, some more common word function names added
+#                      version 0.97 fixed
+#          17.02.1998  writecontentsm as command line parameter added
+#                      error of file not found will even appear when silent
+#                      version 1.02
+#          21.05.2000  mark comments in source code specially (no fully correct, 
+#                      can't handle % in strings)
+#                      version 1.11
+#          05.11.2000  link also to upper and mixed case m-files
+#                      searching for .m files now really works (doesn't find grep.com any longer)
+#                      file renamed to mtree2html2001
+#                      generated html code now all lower case 
+#                      inclusion of meta-description and meta-keywords in html files
+#                      HTML4 compliance done (should be strict HTML4.0, quite near XHTML)
+#                      version 1.23
+#
+#	   29.03.2011  (Chris Cannam) add frames option.
+
+$VERSION  = '1.23';
+($PROGRAM = $0) =~ s@.*/@@; $PROGRAM = "\U$PROGRAM\E";
+$debug = 1;
+
+#------------------------------------------------------------------------
+# Define platform specific things
+#------------------------------------------------------------------------
+# suffix for files to search is defined twice
+# the first ($suffix) is for string creation and contains the . as well
+# the second ($suffixforsearch) is for regular expression, handling of . is quite special
+$suffix = ".m";
+$suffixforsearch = "m";
+# the directory separator
+$dirsep = "/";
+# what is the current directory
+$diract = ".";
+
+#------------------------------------------------------------------------
+#  Define all variables and their standard settings
+#  documentation of variables is contained in accompanying rc file
+#------------------------------------------------------------------------
+%var =
+(
+   'authorfile',                '',
+   'codebodyfiles',             '',
+   'codebodyindex',             '',
+   'codeheadmeta',              '<meta name="author of conversion perl script" content="Hartmut Pohlheim" />',
+   'codehr',                    '<hr size="3" noshade="noshade" />',
+   'codeheader',                '',
+   'configfile',                'matlab-docs.conf',
+   'csslink',                   '',
+   'dirmfiles',                 $diract,
+   'dirhtml',                   $diract,
+   'exthtml',                   '.html',
+   'frames',                    'yes',
+   'filenametopframe',          'index',
+   'filenameindexlongglobal',   'indexlg',
+   'filenameindexlonglocal',    'indexll',
+   'filenameindexshortglobal',  'indexsg',
+   'filenameindexshortlocal',   'indexsl',
+   'filenameextensionframe',    'f',
+   'filenameextensionindex',    'i',
+   'filenameextensionjump',     'j',
+   'filenamedirshort',          'dirtops',
+   'filenamedirlong',           'dirtopl',
+   'filenamedircontents',       'dirtopc',
+   'includesource',             'yes',
+   'links2filescase',           'all',
+   'processtree',               'yes',
+   'producetree',               'yes',
+   'textjumpindexlocal',        'Local Index',
+   'textjumpindexglobal',       'Global Index',
+   'texttitleframelayout',      'Documentation of Matlab Files',
+   'texttitleindexalldirs',     'Index of Directories',
+   'textheaderindexalldirs',    'Index of Directories',
+   'texttitleindex',            '',
+   'textheaderindex',           '',
+   'texttitlefiles',            'Documentation of ',
+   'textheaderfiles',           'Documentation of ',
+   'usecontentsm',              'yes',
+   'writecontentsm',            'no'
+);
+
+
+# define all m-file names, that should be excluded from linking
+# however, files will still be converted
+@excludenames = ( 'all','ans','any','are',
+                  'cs',
+                  'demo','dos',
+                  'echo','edit','else','elseif','end','exist',
+                  'flag','for','function',
+                  'global',
+                  'help',
+                  'i','if','inf','info',
+                  'j',
+                  'more',
+                  'null',
+                  'return',
+                  'script','strings',
+                  'what','which','while','who','whos','why',
+                );
+
+# Text for inclusion in created HTML/Frame files: Doctype and Charset
+$TextDocTypeHTML  = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">';
+$TextDocTypeFrame = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN" "http://www.w3.org/TR/REC-html40/frameset.dtd">'; 
+$TextMetaCharset = '<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />';
+
+#------------------------------------------------------------------------
+# Read the command line arguments
+#------------------------------------------------------------------------
+if (@ARGV == 0) {
+   &DisplayHelp()  if &CheckFileName($var{'configfile'}, 'configuration file');
+}
+
+# Print provided command line arguments on screen
+foreach (@ARGV) { print "   $_\n      "; }
+
+# Get the options
+use Getopt::Long;
+@options = ('help|h', 'todo|t', 'version|v',
+            'authorfile|a=s', 'configfile|c=s', 'dirhtml|html|d=s',
+            'dirmfiles|mfiles|m=s', 'includesource|i=s',
+            'processtree|r=s', 'producetree|p=s',
+            'silent|quiet|q', 'writecontentsm|w=s');
+&GetOptions(@options) || die "use -h switch to display help statement\n";
+
+
+# Display help or todo list, when requested
+&DisplayHelp()                         if $opt_help;
+&DisplayTodo()                         if $opt_todo;
+die "$PROGRAM v$VERSION\n"             if $opt_version;
+
+$exit_status = 0;
+
+#------------------------------------------------------------------------
+# Read the config file
+#------------------------------------------------------------------------
+$var{'configfile'} = $opt_configfile         if $opt_configfile;
+&GetConfigFile($var{'configfile'});
+
+
+#------------------------------------------------------------------------
+# Process/Check the command line otions
+#------------------------------------------------------------------------
+$var{'dirhtml'}   = $opt_dirhtml              if $opt_dirhtml;
+if (!(substr($var{'dirhtml'}, -1, 1) eq $dirsep)) { $var{'dirhtml'} = $var{'dirhtml'}.$dirsep; }
+$var{'dirmfiles'} = $opt_dirmfiles            if $opt_dirmfiles;
+if (!(substr($var{'dirmfiles'}, -1, 1) eq $dirsep)) { $var{'dirmfiles'} = $var{'dirmfiles'}.$dirsep; }
+
+$var{'authorfile'} = $opt_author              if $opt_author;
+$var{'includesource'} = $opt_includesource    if $opt_includesource;
+if ($var{'includesource'} ne 'no') { $var{'includesource'} = 'yes'; }
+$var{'processtree'} = $opt_processtree        if $opt_processtree;
+if ($var{'processtree'} ne 'no') { $var{'processtree'} = 'yes'; }
+$var{'producetree'} = $opt_producetree        if $opt_producetree;
+if ($var{'producetree'} ne 'no') { $var{'producetree'} = 'yes'; }
+if ($var{'processtree'} eq 'no') { $var{'producetree'} = 'no'; }
+if ($var{'frames'} ne 'no') { $var{'frames'} = 'yes'; }
+# if (($var{'processtree'} eq 'yes') && ($var{'producetree'} eq 'no')) { $var{'usecontentsm'} = 'no'; }
+
+$var{'writecontentsm'} = $opt_writecontentsm  if $opt_writecontentsm;
+
+#------------------------------------------------------------------------
+# Do the real stuff
+#------------------------------------------------------------------------
+
+# Print variables on screen, when not silent
+&ListVariables                          if !$opt_silent;
+
+# Check the author file
+if ($var{'authorfile'} ne '') {
+    if (!($var{'authorfile'} =~ m,^/,)) {
+	# relative path: treat as relative to config file
+	my $cfd = $var{'configfile'};
+	$cfd =~ s,/[^/]*$,/,;
+	$cfd =~ s,^[^/]*$,.,;
+	$var{'authorfile'} = "$cfd/" . $var{'authorfile'};
+    }
+    if (&CheckFileName($var{'authorfile'}, 'author file')) {
+	$var{'authorfile'} = '';
+	if (!$opt_silent) { print "   Proceeding without author information!\n"; }
+    }
+}
+
+# Call the function doing all the real work
+&ConstructNameMatrix;
+
+&ConstructDependencyMatrix;
+
+&ConstructAllIndexFiles;
+
+&ConstructHTMLFiles;
+
+exit $exit_status;
+
+#------------------------------------------------------------------------
+# Construct list of all mfile names and initialize various data arrays.
+#------------------------------------------------------------------------
+sub ConstructNameMatrix
+{
+   local(*MFILE);
+   local($file, $dirname);
+   local(@newdirectories);
+   local(%localnames);
+   
+   $RecDeep = 0;
+   &ParseTreeReadFiles($var{'dirmfiles'}, $RecDeep);
+
+   foreach $dirname (@directories) { 
+      if ($dirnumbermfiles{$dirname} > 0) {
+         push(@newdirectories, $dirname);
+         if (! defined($contentsname{$dirname})) {
+            $contentsname{$dirname} = 'Contents';
+            if (($var{'writecontentsm'} eq 'no') && ($var{'usecontentsm'} eq 'yes')) {
+               print "\r ParseTree - for directory  $dirname  no contents file found!\n";
+               print   "             create one or enable writing of contents file (writecontentsm = yes)!\n";
+            }
+         }
+      }
+   }
+   @alldirectories = @directories;
+   @directories = @newdirectories;
+
+   foreach $dirname (@directories) { 
+      if ($debug > 0) { print "Dir: $dirname \t\t $dirnumbermfiles{$dirname} \t$contentsname{$dirname}\n"; }
+   }
+   
+   @names = sort(keys %mfile);
+
+   # check, if name of directory is identical to name of file
+   @dirsinglenames = values(%dirnamesingle);
+   grep($localnames{$_}++, @dirsinglenames);
+   @dirandfilename = grep($localnames{$_}, @names);
+   if (@dirandfilename) { 
+      print "\r   Name clash between directory and file name: @dirandfilename\n";
+      print   "      These files will be excluded from linking!\n";
+      push(@excludenames, @dirandfilename);
+   }
+   
+   # construct names matrix for help text linking
+   #    exclude some common words (and at the same time m-functions) from linking in help text
+   grep($localnames{$_}++, @excludenames);
+   @linknames = grep(!$localnames{$_}, @names);
+
+   if ($debug > 2) { print "linknames (names of found m-files):\n    @linknames\n"; }
+   
+}
+
+#------------------------------------------------------------------------
+# Parse tree and collect all Files
+#------------------------------------------------------------------------
+sub ParseTreeReadFiles
+{
+   local($dirname, $localRecDeep) = @_;
+   local($file, $name, $filewosuffix);
+   local($dirhtmlname, $dirmode);
+   local($relpath, $relpathtoindex, $replacevardir);
+   local(*CHECKDIR, *AKTDIR);
+   local(@ALLEFILES);
+   
+   opendir(AKTDIR, $dirname) || die "ParseTree - Can't open directory $dirname: $!";
+   if ($debug > 1) { print "\nDirectory: $dirname\n"; }
+
+   # create relative path
+   $_ = $dirname; $replacevardir = $var{'dirmfiles'};
+   s/$replacevardir//; $relpath = $_;
+   s/[^\/]+/../g; $relpathtoindex = $_;
+
+   # producetree no
+   if ($var{'producetree'} eq 'no') { $relpath = ''; $relpathtoindex = ''; }
+
+   # names of directories (top-level and below top-level m-file-directory)
+   push(@directories, $dirname);
+   $dirnumbermfiles{$dirname} = 0;    # set number of m-files for this dir to zero
+   # relative path from top-level directory, depends on directory name
+   $dirnamerelpath{$dirname} = $relpath;
+   # relative path from actual directory to top-level directory, depends on directory name
+   $dirnamerelpathtoindex{$dirname} = $relpathtoindex;
+   # recursion level for directory, depends on directory name
+   $dirnamerecdeep{$dirname} = $localRecDeep;
+   
+   # only the name of the directory, without path
+   $rindexprint = rindex($dirname, $dirsep, length($dirname)-2);
+   $rindsub = substr($dirname, $rindexprint+1, length($dirname)-$rindexprint-2);
+   $dirnamesingle{$dirname} = $rindsub;
+
+   # create name of html-directories 
+   $_ = $dirname;
+   s/$var{'dirmfiles'}/$var{'dirhtml'}/;
+   $dirhtmlname = $_;
+   if ($var{'producetree'} eq 'no') { $dirhtmlname = $var{'dirhtml'}; }
+   # try to open html directory, if error, then create directory,
+   # use same mode as for corresponding m-file directory
+   opendir(CHECKDIR,"$dirhtmlname") || do {
+      $dirmode = (stat($dirname))[2]; # print "$dirmode\n";
+      mkdir("$dirhtmlname", $dirmode) || die ("Cannot create directory $dirhtmlname: $! !");
+   };
+   closedir(CHECKDIR);
+
+
+   # read everything from this directory and process them
+   @ALLEFILES = readdir(AKTDIR);
+
+   foreach $file (@ALLEFILES) {
+      # exclude . and .. directories
+      next if $file eq '.';  next if $file eq '..';
+
+      # test for existense of entry (redundant, used for debugging)
+      if (-e $dirname.$file) {
+         # if it's a directory, call this function recursively
+         if (-d $dirname.$file) {
+            if ($var{'processtree'} eq 'yes') {
+               &ParseTreeReadFiles($dirname.$file.$dirsep, $localRecDeep+1);
+            }
+         }
+         # if it's a file - test for m-file, save name and create some arrays
+         elsif (-f $dirname.$file) {
+            if ($file =~ /\.$suffixforsearch$/i) {
+               # Remove the file suffix to establish the matlab identifiers
+               $filewosuffix = $file;
+               $filewosuffix =~ s/\.$suffixforsearch$//i;
+               # $filename = $name;
+
+               # Contents file in unix must start with a capital letter (Contents.m)
+               # ensure, that m-file name is lower case, except the contents file
+               if (! ($filewosuffix =~ /^contents$/i)) {
+		   # if ($var{'links2filescase'}  eq 'low') { $filewosuffix = "\L$filewosuffix\E"; }
+                  $filewosuffixlow = "\L$filewosuffix\E";
+               }
+               else { $contentsname{$dirname} = $filewosuffix; }
+
+               # internal handle name is always lower case
+               $name     = $filewosuffixlow;
+               # file name is not lower case
+               $filename = $filewosuffix;
+
+               # if don't use C|contents.m, then forget all C|contents.m
+               if ($var{'usecontentsm'} eq 'no') { if ($name =~ /contents/i) { next; } }
+
+               # if m-file with this name already exists, use directory and name for name
+               # only the first occurence of name will be used for links
+               if (defined $mfile{$name}) { 
+                  if (! ($name =~ /^contents$/i) ) {
+                     print "\r ParseTree - Name conflict:  $name in $dirname already exists: $mfile{$name} !\n";
+                     print   "             $mfile{$name}  will be used for links!\n";
+                  }
+                  $name = $dirname.$name;
+               }
+               # mfile name with path
+               $mfile{$name} = $dirname.$file;
+               # mfile name (without path)
+               $mfilename{$name} = $filename;
+               # mfile directory
+               $mfiledir{$name} = $dirname;
+               
+               # html file name and full path, special extension of Contents files
+               if ($name =~ /contents/i) { $extrahtmlfilename = $dirnamesingle{$dirname}; }
+               else { $extrahtmlfilename = ''; }
+               $hfile{$name} = $dirhtmlname.$mfilename{$name}.$extrahtmlfilename.$var{'exthtml'};
+
+               # save relative html path
+               # if ($var{'producetree'} eq 'yes') {
+               $hfilerelpath{$name} = $relpath;
+               # } else { # if no tree to produce, relative path is empty
+               #    $hfilerelpath{$name} = '';
+               # }
+
+               # create relative path from html file to directory with global index file
+               $hfileindexpath{$name} = $relpathtoindex;
+
+               # Function declaration, if one exists, set default to script
+               $synopsis{$name} = "";
+               $mtype{$name} = "script";
+
+               # First comment line
+               $apropos{$name} = "";
+
+               # count number of m-files in directories
+               $dirnumbermfiles{$dirname}++;
+
+               if ($debug > 1) {
+                  if ($opt_silent) { print "\r"; }
+                  print "   ParseTree: $name \t\t $mfile{$name} \t\t $hfile{$name}\t\t";
+                  if (!$opt_silent) { print "\n"; }
+               }
+            }
+         }
+         else {
+            print "Unknown type of file in $dirname: $file\n";
+         }
+      }
+      else { print "Error: Not existing file in $dirname: $file\n"; }
+   }
+
+   closedir(AKTDIR)
+   
+}
+
+#------------------------------------------------------------------------
+# Construct Dependency matrix
+#    $dep{$x,$y} > 0 if $x includes a reference to $y.
+#------------------------------------------------------------------------
+sub ConstructDependencyMatrix
+{
+   &ConstructDependencyMatrixReadFiles('all');
+   &ConstructDependencyMatrixReally;
+}
+
+
+#------------------------------------------------------------------------
+# Construct Dependency matrix
+#    $dep{$x,$y} > 0 if $x includes a reference to $y.
+#------------------------------------------------------------------------
+sub ConstructDependencyMatrixReadFiles
+{
+   local($whatstring) = @_;
+   local(*MFILE);
+   local($name, $inames);
+   local(%symbolsdep, %symbolsall);
+
+   # Initialize as all zeros.
+   # foreach $name (@names) { grep($dep{$name,$_}=0,@names); if ($debug > 0) { print "\r   DepMatrix anlegen: $name\t$#names\t"; } }
+
+   # Compute the dependency matrix
+   $inames = -1;
+   foreach $name (@names) {
+      # Read each file and tabulate the distinct alphanumeric identifiers in
+      # an array of symbols. Also scan for:
+      #   synopsis: The function declaration line
+      #   apropos:  The first line of the help text
+
+      # look for whatstring, if all: process every file, if contents: process only contents files 
+      if ($whatstring eq 'contents') { if (! ($name =~ /contents$/i) ) { next; } }
+      elsif ($whatstring eq 'all') { }    # do nothing
+      else { print "\r   ConstructDependency: Unknown parameter whatstring: $whatstring \n"; }
+
+      undef %symbolsall; undef %symbolsdep;
+      open(MFILE,"<$mfile{$name}") || die("Can't open $mfile{$name}: $!\n");
+      while (<MFILE>) {
+         chop;
+
+         # Split on nonalphanumerics, then look for all words, used for links later
+         # this one for all references
+         @wordsall = grep(/[a-zA-Z]\w*/, split('\W',$_));
+         # set all words to lower case for link checking
+         undef @wordsall2;
+         # do case conversion not, case checking is done later
+         foreach (@wordsall) { push(@wordsall2, "\L$_\E"); }
+         # @wordsall2 = @wordsall;
+         grep($symbolsall{$_}++, @wordsall2);
+
+         # Store first comment line, skip all others.
+         if (/^\s*%/) {
+            if (!$apropos{$name}) {
+               s/^\s*%\s*//;   # remove % and leading white spaces on line
+               $_ = &SubstituteHTMLEntities($_);
+               $apropos{$name} = $_;
+            }
+            next;
+         }
+
+         # If it's the function declaration line, then store it and skip
+         # but only, when first function definition (multiple function lines when private subfunctions in file
+         if ($synopsis{$name} eq '') {
+            if (/^\s*function/) {
+               s/^\s*function\s*//;
+               $synopsis{$name} = $_;
+               $mtype{$name} = "function";
+               next;
+            }
+         }
+
+         # Split off any trailing comments
+         if ($_ ne '') {
+            # this one for references in program code only
+            # when syntax parsing, here is a working place
+            ($statement) = split('%',$_,1);
+            @wordsdep = grep(/[a-zA-Z]\w*/,split('\W',$statement));
+            # do case conversion not, case checking is done later
+            undef @wordsdep2;
+            foreach (@wordsdep) { push(@wordsdep2, "\L$_\E"); }
+            grep($symbolsdep{$_}++, @wordsdep2);
+         }
+      }
+      close MFILE;
+
+      # compute intersection between %symbolsall and @linknames
+      delete($symbolsall{$name});
+      # foreach $localsumall ($symbolsall) {
+      #    $localsumall = "\L$localsumall\E";
+      # }
+      @{'all'.$name} = grep($symbolsall{$_}, @linknames);
+
+      # compute intersection between %symbolsdep and @linknames
+      delete($symbolsdep{$name});
+      @{'depcalls'.$name} = grep($symbolsdep{$_}, @linknames);
+
+      $inames++; print "\r   DepCallsMatrix: $inames/$#names\t $name\t";
+      if ($debug > 2) { print "\n      depnames: @{'depcalls'.$name}\n      all: @{'all'.$name}\n"; } 
+   }
+}
+
+
+#------------------------------------------------------------------------
+# Construct Dependency matrix
+#    $dep{$x,$y} > 0 if $x includes a reference to $y.
+#------------------------------------------------------------------------
+sub ConstructDependencyMatrixReally
+{
+   local($inames, $name);
+
+   $inames = -1;
+   foreach $name (@names) { undef %{'depint'.$name}; }
+   foreach $name (@names) {
+      grep(${'depint'.$_}{$name}++, @{'depcalls'.$name});
+      $inames++; print "\r   DepCalledMatrix1: $inames/$#names\t $name\t";
+   }
+   $inames = -1;
+   foreach $name (@names) {
+      # compute intersection between %depint.name{$_} and @linknames
+      if (defined (%{'depint'.$name})) { @{'depcalled'.$name} = grep(${'depint'.$name}{$_}, @linknames); }
+      $inames++; print "\r   DepCalledMatrix2: $inames/$#names\t $name\t";
+      if ($debug > 2) { print "\n      depcalled: @{'depcalled'.$name}\n"; }
+   }
+
+}
+
+
+#========================================================================
+# Construct all index files
+#========================================================================
+sub ConstructAllIndexFiles
+{
+   local(@localnames);
+   local($ActDir);
+   local($name);
+   
+   # define variables and names for frame target
+   $GlobalNameFrameMainLeft = 'Cont_Main';
+   $GlobalNameFrameMainRight = 'Cont_Lower';
+   $GlobalNameFrameAZIndexsmall = 'IndexAZindex';
+   $GlobalNameFrameAZIndexjump = 'IndexAZjump';
+
+   $indexcreated = 0;
+
+   &ConstructHighestIndexFile;
+   $indexcreated++;
+
+   # if ($var{'producetree'} eq 'yes') {
+      # moved next 2 lines out of if for producetree no
+      # &ConstructHighestIndexFile;
+      # $indexcreated++;
+   
+      foreach $ActDir (@directories) {
+         undef @localnames;
+         foreach $name (@names) {
+            local($pathsubstr) = substr($mfile{$name}, 0, rindex($mfile{$name}, "/")+1);
+            if ($ActDir eq $pathsubstr) {
+               if ($debug > 1) { print "IndexFile: $pathsubstr    ActDir: $ActDir   Hfilerelpath: $hfilerelpath{$name}\n"; }
+               push(@localnames, $name);
+            }
+         }
+         if ($debug > 2) { print "localnames: @localnames\n"; }
+         # create contents file and short|long index of files in local directory
+         &ConstructContentsmFile($ActDir, @localnames);
+         &ConstructAZIndexFile($ActDir, 'short', 'local', @localnames);
+         &ConstructAZIndexFile($ActDir, 'long', 'local', @localnames);
+         $indexcreated+=2;
+      }   
+   # } else {
+   #    &ConstructContentsmFile($var{'dirmfiles'}, @names);
+   # }
+
+   # create short|long index of files in all directory
+   &ConstructAZIndexFile($var{'dirmfiles'}, 'short', 'global', @names);
+   &ConstructAZIndexFile($var{'dirmfiles'}, 'long', 'global', @names);
+   $indexcreated+=2;
+
+   # if contents.m were created or updated, the dependency matrices should 
+   # be updated as well
+   if ($var{'writecontentsm'} eq 'yes') { &ConstructDependencyMatrixReadFiles('contents');; }
+}
+
+
+#========================================================================
+# Construct the highest level index file
+#========================================================================
+sub ConstructHighestIndexFile
+{
+   local(*IFILE);
+   local($indexfile, $filename);
+
+   # Build the frame layout file, this files includes the layout of the frames
+   # Build the frame layout file name (highest one)
+   $indexfile = $var{'dirhtml'}.$var{'filenametopframe'}.$var{'exthtml'};
+
+   if ($var{'frames'} eq 'yes') {
+
+       open(IFILE,">$indexfile") || die("Cannot open frame layout file $indexfile\n");
+
+       # Write the header of frame file
+       print IFILE "$TextDocTypeFrame\n<html>\n<head>\n$var{'codeheadmeta'}\n$TextMetaCharset\n";
+       print IFILE "   <title>$var{'texttitleframelayout'}</title>\n";
+       print IFILE "</head>\n";
+
+       # definition of 2 frames, left the tree of directories,
+       # right the index of that directory or the docu of a file
+       print IFILE "<frameset  cols=\"25%,75%\">\n";
+       print IFILE "   <frame src=\"$var{'filenamedirshort'}$var{'exthtml'}\" name=\"$GlobalNameFrameMainLeft\" />\n";
+       print IFILE "   <frame src=\"$var{'filenameindexshortglobal'}$var{'filenameextensionframe'}$var{'exthtml'}\" name=\"$GlobalNameFrameMainRight\" />\n";   print IFILE "</frameset>\n";
+
+       print IFILE "</html>\n";
+
+       close(IFILE);
+
+       if ($opt_silent) { print "\r"; }
+       print "   Frame layout file created: $indexfile\t";
+       if (!$opt_silent) { print "\n"; }
+   }
+
+   for($irun=0; $irun <= 2; $irun++) {
+      # Build the top directory index file, these files include the directory tree
+      # Build the directory tree index file name
+      
+      # Create no directory file for contents, when no contents to use
+      if (($irun == 2) && ($var{'usecontentsm'} eq 'no')) { next; }
+
+      # Assign the correct index file name
+      if ($irun == 0) { $filename = $var{'filenamedirshort'}; }
+      elsif ($irun == 1) { $filename = $var{'filenamedirlong'}; }
+      elsif ($irun == 2) { $filename = $var{'filenamedircontents'}; }
+      
+      $indexfile = $var{'dirhtml'}.$filename.$var{'exthtml'};
+
+      open(IFILE,">$indexfile") || die("Cannot open directory tree index file $indexfile\n");
+      # Write header of HTML file
+      print IFILE "$TextDocTypeHTML\n<html>\n<head>\n$var{'codeheadmeta'}\n$TextMetaCharset\n$var{'csslink'}\n";
+
+      if ($var{'texttitleindexalldirs'} eq '') {
+         print IFILE "<title>Index of Directories of $var{'dirmfiles'}</title>\n";
+      } else {
+         print IFILE "<title>$var{'texttitleindexalldirs'}</title>\n";
+      }
+      
+      if ($var{'frames'} eq 'yes') {
+	  print IFILE "<base target=\"$GlobalNameFrameMainRight\" />\n";
+      }
+
+      print IFILE "</head>\n";
+      print IFILE "<body $var{'codebodyindex'}>\n";
+      print IFILE "<div id=\"matlabdoc\">\n";
+      if ($var{'textheaderindexalldirs'} eq '') {
+         print IFILE "<h1 $var{'codeheader'}>Index of Directories of <em>$var{'dirmfiles'}</em></h1>\n";
+      } else {
+         print IFILE "<h1 $var{'codeheader'}>$var{'textheaderindexalldirs'}</h1>\n";
+      }
+      print IFILE "<p>\n";
+
+      if ($var{'frames'} eq 'yes') {
+	  if ($irun == 0) { print IFILE "<strong>short</strong>\n"; }
+	  else { print IFILE "<a href=\"$var{'filenamedirshort'}$var{'exthtml'}\" target=\"$GlobalNameFrameMainLeft\">short</a>\n"; }
+	  if ($irun == 1) { print IFILE " | <strong>long</strong>\n"; }
+	  else { print IFILE " | <a href=\"$var{'filenamedirlong'}$var{'exthtml'}\" target=\"$GlobalNameFrameMainLeft\">long</a>\n"; }
+	  if ($var{'usecontentsm'} eq 'yes') {
+	      if ($irun == 2) { print IFILE " | <strong>contents</strong>\n"; }
+	      else { print IFILE " | <a href=\"$var{'filenamedircontents'}$var{'exthtml'}\" target=\"$GlobalNameFrameMainLeft\">contents</a>\n"; }
+	  }
+      } else {
+	  if ($irun == 0) { print IFILE "<strong>short</strong>\n"; }
+	  else { print IFILE "<a href=\"$var{'filenamedirshort'}$var{'exthtml'}\">short</a>\n"; }
+	  if ($irun == 1) { print IFILE " | <strong>long</strong>\n"; }
+	  else { print IFILE " | <a href=\"$var{'filenamedirlong'}$var{'exthtml'}\">long</a>\n"; }
+	  if ($var{'usecontentsm'} eq 'yes') {
+	      if ($irun == 2) { print IFILE " | <strong>contents</strong>\n"; }
+	      else { print IFILE " | <a href=\"$var{'filenamedircontents'}$var{'exthtml'}\">contents</a>\n"; }
+	  }
+      }
+   
+      print IFILE "</p><br />\n\n";
+      print IFILE "<ul>\n";
+
+      # go through all directories and create a list entry for each one,
+      # depending on recursion level create sublists
+      $prevrecdeeplevel = 0;
+      foreach $name (@alldirectories) {
+         $actrecdeeplevel = $dirnamerecdeep{$name};
+         for( ; $prevrecdeeplevel < $actrecdeeplevel; $prevrecdeeplevel++ ) { print IFILE "<ul>\n"; }
+         for( ; $prevrecdeeplevel > $actrecdeeplevel; $prevrecdeeplevel-- ) { print IFILE "</ul>\n"; }
+         if ($irun == 0) { $indexfilenameused = $var{'filenameindexshortlocal'}.$var{'filenameextensionframe'}; }
+         elsif ($irun == 1) { $indexfilenameused = $var{'filenameindexlonglocal'}.$var{'filenameextensionframe'}; }
+         elsif ($irun == 2) { $indexfilenameused = $contentsname{$name}; }
+         else { die "ConstructHighestIndexFile: Unknown value of irun"; }
+         if ($dirnumbermfiles{$name} > 0) {
+            # producetree no
+            # if ($var{'producetree'} eq 'no') { $dirnamehere = ''; }
+            # else { $dirnamehere = '$dirnamerelpath{$name}'; }
+            # print IFILE "<LI><A HREF=\"$dirnamehere$indexfilenameused_$dirnamesingle{$name}$var{'exthtml'}\">$dirnamesingle{$name}</A>\n";
+            print IFILE "<li><a href=\"$dirnamerelpath{$name}$indexfilenameused$dirnamesingle{$name}$var{'exthtml'}\">$dirnamesingle{$name}</a></li>\n";
+         } else { 
+            # print directories with no m-files inside not
+            # print IFILE "<li>$dirnamesingle{$name}</li>\n";
+         }
+      }
+      $actrecdeeplevel = 0;
+      for( ; $prevrecdeeplevel > $actrecdeeplevel; $prevrecdeeplevel-- ) { print IFILE "</ul>\n"; }
+      print IFILE "</ul>\n<br />$var{'codehr'}\n";
+
+      # Include info about author from authorfile
+      &WriteFile2Handle($var{'authorfile'}, IFILE);
+
+      print IFILE "<!--navigate-->\n";
+      print IFILE "<!--copyright-->\n";
+      print IFILE "</div>\n</body>\n</html>\n";
+
+      close(IFILE);
+
+      if ($opt_silent) { print "\r"; }
+      print "   Directory - Indexfile created: $indexfile\t";
+      if (!$opt_silent) { print "\n"; }
+   }
+}
+
+
+#========================================================================
+# Construct the A-Z index file (global/local and/or short/long)
+#========================================================================
+sub ConstructAZIndexFile
+{
+   local($LocalActDir, $LocalShortLong, $LocalGlobalLocal, @localnames) = @_;
+   local(*IFILE);
+   local($name, $indexfilename, $dirpath);
+   local($firstletter, $firstone);
+   
+   if ($debug > 2) { print "localnames in AZ small: @localnames\n"; print "     ActDir in A-Z: $LocalActDir\n"; }
+   
+   # extract filename of index file from parameters of function
+   if ($LocalShortLong eq 'short') {
+      if ($LocalGlobalLocal eq 'global') { $indexfilename = $var{'filenameindexshortglobal'}; }
+      elsif ($LocalGlobalLocal eq 'local') { $indexfilename = $var{'filenameindexshortlocal'}; }
+      else { die "wrong parameter for LocalGlobalLocal in ConstructAZIndexFile: $LocalGlobalLocal."; }
+   } elsif ($LocalShortLong eq 'long') {
+      if ($LocalGlobalLocal eq 'global') { $indexfilename = $var{'filenameindexlongglobal'}; }
+      elsif ($LocalGlobalLocal eq 'local') { $indexfilename = $var{'filenameindexlonglocal'}; }
+      else { die "wrong parameter for LocalGlobalLocal in ConstructAZIndexFile: $LocalGlobalLocal."; }
+   } else { die "wrong parameter for LocalShortLong in ConstructAZIndexFile: $LocalShortLong."; }
+   
+   # producetree no
+   # if ($var{'producetree'} eq 'no') { $dirnamehere = ''; }
+   # else { $dirnamehere = '$dirnamerelpath{$LocalActDir}'; }
+   # Build the index file name
+   # handle the global index file case separately (no extra directory name in file)
+   #    the local index file name must be extended by the name of the directory
+   if ($LocalGlobalLocal eq 'global') { $extradirfilename = ''; }
+   else { $extradirfilename = $dirnamesingle{$LocalActDir}; }
+   $indexfile = $var{'dirhtml'}.$dirnamerelpath{$LocalActDir}.$indexfilename.$var{'filenameextensionindex'}.$extradirfilename.$var{'exthtml'};
+
+   if ($LocalShortLong eq 'short' and $extradirfilename eq '' and $var{'frames'} ne 'yes') {
+       # With no frames and no subdir path, this must go in the
+       # top-level index file instead
+       $indexfile = $var{'dirhtml'}.$var{'filenametopframe'}.$var{'exthtml'};
+   }
+
+   if ($debug > 2) { print "   indexfilename (a-z small): $indexfile\n"; }
+
+   open(IFILE,">$indexfile") || die("Cannot open index file $indexfile: $!\n");
+
+   # Write the header of HTML file
+   print IFILE "$TextDocTypeHTML\n<html>\n<head>\n$var{'codeheadmeta'}\n$TextMetaCharset\n$var{'csslink'}\n";
+   
+   my $dirToPrint = $LocalActDir;
+   $dirToPrint =~ s,^./,,;
+
+   if ($var{'texttitleindex'} eq '') {
+      print IFILE "<title>Index of Matlab Files in Directory $dirToPrint</title>\n";
+   } else {
+      if ($LocalGlobalLocal eq 'global') { print IFILE "<title>$var{'texttitleindex'}</title>\n"; }
+      else { print IFILE "<title>$var{'texttitleindex'} in Directory $dirToPrint</title>\n"; }
+   }
+
+   if ($var{'frames'} eq 'yes') {
+       print IFILE "<base target=\"$GlobalNameFrameMainRight\" />\n";
+   }
+   print IFILE "</head>\n";
+
+   print IFILE "<body $var{'codebodyindex'}>\n";
+   print IFILE "<div id=\"matlabdoc\">\n";
+   if ($var{'textheaderindex'} eq '') {
+      print IFILE "<h1 $var{'codeheader'}>Index of Matlab Files in Directory $dirToPrint</h1>\n";
+   } else {
+      if ($LocalGlobalLocal eq 'global') { print IFILE "<h1 $var{'codeheader'}>$var{'textheaderindex'}</h1>\n"; }
+      else { print IFILE "<h1 $var{'codeheader'}>$var{'textheaderindex'} in Directory $dirToPrint</h1>\n"; }
+   }
+
+   # include links to indexes
+   &ConstructLinks2Index(IFILE, $dirnamerelpathtoindex{$LocalActDir}, $LocalActDir, $LocalGlobalLocal);
+
+   # Collect the starting letters of m files in this directory or all m-files
+   for('a'..'z') { undef @{$_}; }
+   foreach $name (@localnames) {
+      if (! ($mfilename{$name} =~ /contents/i)) {
+         $firstletter = substr($mfilename{$name}, 0, 1);
+         # convert first letter always to lower case
+         # needed for reference to lower and upper case m-files
+         $firstletter = "\L$firstletter\E";
+         push(@{$firstletter}, $name);
+      }
+   }
+   
+   if ($LocalShortLong eq 'short') {
+      # begin create short index
+      print IFILE "<table width=\"100%\">\n";
+
+      for('a'..'z') {
+         # print "   $_: @{$_}\n";
+         $numberofletter = $#{$_}+1;
+	 $cols = 3;
+         if ($numberofletter > 0) {
+            print IFILE "\n<tr><td><br/><strong><a name=\"\U$_\E$_\"></a><span class=\"an\">\U$_\E</span></strong></td></tr>\n";
+            for ($count = 0; $count < $numberofletter; $count++) {
+		if (($count % $cols) == 0) {
+		    if ($count > 0) {
+			print IFILE "</tr><tr>\n";
+		    }
+		    print IFILE "<tr><td></td>";
+		}
+		$name = @{$_}[$count];
+		if ($LocalGlobalLocal eq 'global') { $dirpath = $hfilerelpath{$name}; } else { $dirpath = ""; }
+		print IFILE "<td><a href=\"$dirpath$mfilename{$name}$var{'exthtml'}\">$mfilename{$name}</a></td>";
+	    }
+
+	    print IFILE "</tr>\n";
+		    
+            # $numberhalf = ($numberofletter + 1 - (($numberofletter+1) % 2))/2;
+            # if ($debug > 2) { print "   $_: @{$_} \t $numberhalf \t $numberofletter\n"; }
+            # for($count = 0; $count < $numberhalf; $count++) {
+            #    $name = @{$_}[$count];
+            #    if ($LocalGlobalLocal eq 'global') { $dirpath = $hfilerelpath{$name}; } else { $dirpath = ""; }
+            #    print IFILE "<tr><td width=\"50%\"><a href=\"$dirpath$mfilename{$name}$var{'exthtml'}\">$mfilename{$name}</a></td>";
+            #    if (($count + $numberhalf) < $numberofletter) {
+            #       $name = @{$_}[$count + $numberhalf];
+            #       if ($LocalGlobalLocal eq 'global') { $dirpath = $hfilerelpath{$name}; } else { $dirpath = ""; }
+            #       print IFILE "<td width=\"50%\"><a href=\"$dirpath$mfilename{$name}$var{'exthtml'}\">$mfilename{$name}</a></td></tr>\n";
+            #    } else {
+            #       print IFILE "<td width=\"50%\"></td></tr>\n";
+            #    }
+            # }
+         }
+      }
+      print IFILE "</table>\n<br />$var{'codehr'}\n";
+
+   } elsif ($LocalShortLong eq 'long') {
+      # begin create long index
+      print IFILE "<table width=\"100%\">\n";
+      print IFILE "<tr><th>Name</th><th>Synopsis</th></tr>\n";
+
+      for('a'..'z') {
+         # print "   $_: @{$_}\n";
+         $numberofletter = $#{$_}+1;
+         if ($numberofletter > 0) {
+            $firstone = 1;
+            foreach $name (@{$_}) {
+               if ($debug > 1) { print "   AZinforeach1: $name \t\t $hfilerelpath{$name} \t\t $dirnamerelpath{$LocalActDir}\n"; }
+               if ($LocalGlobalLocal eq 'global') { $dirpath = $hfilerelpath{$name}; } else { $dirpath = ""; }
+               if (! ($mfilename{$name} =~ /contents/i)) {
+                  if ($firstone == 1) { print IFILE "\n<tr><td colspan=\"2\"><br /><strong><a name=\"\U$_\E$_\"></a><span class=\"an\">\U$_\E</span></strong></td></tr>\n"; $firstone = 0; } 
+                  print IFILE "<tr><td valign=\"top\"><a href=\"$dirpath$mfilename{$name}$var{'exthtml'}\">$mfilename{$name}</a></td><td>$apropos{$name}</td></tr>\n";
+               }
+            }
+         }
+      }
+      print IFILE "</table>\n<br />$var{'codehr'}\n";
+   } else { die "wrong parameter for LocalShortLong in ConstructAZIndexFile: $LocalShortLong."; }
+
+   # Include info about author from authorfile
+   &WriteFile2Handle($var{'authorfile'}, IFILE);
+
+   print IFILE "<!--navigate-->\n";
+   print IFILE "<!--copyright-->\n";
+   print IFILE "</div>\n</body>\n</html>\n";
+
+   close(IFILE);
+
+   if ($opt_silent) { print "\r"; }
+   print "   Indexfile small (A-Z) created: $indexfile\t";
+   if (!$opt_silent) { print "\n"; }
+
+
+   # Build the A-Z jump index file name
+   # handle the global index file case separately (no extra directory name in file)
+   if ($LocalGlobalLocal eq 'global') { $extradirfilename = ''; }
+   else { $extradirfilename = $dirnamesingle{$LocalActDir}; }
+
+   if ($var{'frames'} eq 'yes') {
+
+       $indexfile = $var{'dirhtml'}.$dirnamerelpath{$LocalActDir}.$indexfilename.$var{'filenameextensionjump'}.$extradirfilename.$var{'exthtml'};
+       if ($debug > 2) { print "   indexfilename (a-z jump): $indexfile\n"; }
+       open(IFILE,">$indexfile") || die("Cannot open jump index file $indexfile: $!\n");
+
+       # Write the header of HTML file
+       print IFILE "$TextDocTypeHTML\n<html>\n<head>\n$var{'codeheadmeta'}\n$TextMetaCharset\n$var{'csslink'}\n";
+       
+       if ($var{'texttitleindex'} eq '') {
+	   print IFILE "<title>A-Z jump index in directory $dirToPrint</title>\n";
+       } else {
+	   if ($LocalGlobalLocal eq 'global') { print IFILE "<title>$var{'texttitleindex'}</title>\n"; }
+	   else { print IFILE "<title>$var{'texttitleindex'} in Directory $dirToPrint</title>\n"; }
+       }
+
+       if ($var{'frames'} eq 'yes') {
+	   print IFILE "<base target=\"$GlobalNameFrameAZIndexsmall\" />\n";
+       }
+       print IFILE "</head>\n";
+       print IFILE "<body $var{'codebodyindex'}>\n";
+       print IFILE "<div id=\"matlabdoc\">\n";
+
+       # Write the A-Z jump line, generate link for letters with files starting with this letter
+       # and only letters for no files starting with this letter
+       # use previously generated arrays with names of files sorted by starting letter
+       for('a'..'z') {
+	   $numberofletter = $#{$_}+1;
+	   if ($numberofletter > 0) {
+	       print IFILE "<strong><a href=\"$indexfilename$var{'filenameextensionindex'}$extradirfilename$var{'exthtml'}#\U$_\E$_\">\U$_\E</a> </strong>\n";
+	   } else {
+	       print IFILE "\U$_\E \n";
+	   }
+       }
+
+       print IFILE "</div></body>\n</html>\n";
+
+       close(IFILE);
+
+       if ($opt_silent) { print "\r"; }
+       print "   Indexfile small (A-Z jump) created: $indexfile\t";
+       if (!$opt_silent) { print "\n"; }
+   }
+
+
+   # Build the frame layout file, this file includes the layout of the frames
+   # Build the frame layout file name (for small/compact A-Z index)
+   # handle the global index file case separately (no extra directory name in file)
+   if ($LocalGlobalLocal eq 'global') { $extradirfilename = ''; }
+   else { $extradirfilename = $dirnamesingle{$LocalActDir}; }
+
+   if ($var{'frames'} eq 'yes') {
+
+       $indexfile = $var{'dirhtml'}.$dirnamerelpath{$LocalActDir}.$indexfilename.$var{'filenameextensionframe'}.$extradirfilename.$var{'exthtml'};
+       if ($debug > 2) { print "   indexfilename (a-z frame): $indexfile\n"; }
+
+       open(IFILE,">$indexfile") || die("Cannot open jump index frame file $indexfile: $!\n");
+
+       # Write the header of Frame file
+       print IFILE "$TextDocTypeHTML\n<html>\n<head>\n$var{'codeheadmeta'}\n$TextMetaCharset\n$var{'csslink'}\n";
+
+       if ($var{'texttitleindex'} eq '') {
+	   print IFILE "<title>Index of Matlab Files in Directory $dirToPrint</title>\n";
+       } else {
+	   if ($LocalGlobalLocal eq 'global') { print IFILE "<title>$var{'texttitleindex'}</title>\n"; }
+	   else { print IFILE "<title>$var{'texttitleindex'} in Directory $dirToPrint</title>\n"; }
+       }
+       print IFILE "</head>\n";
+
+       # definition of 2 frames, top the A-Z index, below the jump letter line
+       print IFILE "<frameset  rows=\"90%,10%\">\n";
+       print IFILE "   <frame src=\"$indexfilename$var{'filenameextensionindex'}$extradirfilename$var{'exthtml'}\" name=\"$GlobalNameFrameAZIndexsmall\" />\n";
+       print IFILE "   <frame src=\"$indexfilename$var{'filenameextensionjump'}$extradirfilename$var{'exthtml'}\" name=\"$GlobalNameFrameAZIndexjump\" />\n";
+       print IFILE "</frameset>\n";
+
+       print IFILE "</html>\n";
+
+       close(IFILE);
+
+       if ($opt_silent) { print "\r"; }
+       print "   Frame layout file created: $indexfile\t";
+       if (!$opt_silent) { print "\n"; }
+   }
+}
+   
+
+#========================================================================
+# Construct the links to all indexes
+#========================================================================
+sub ConstructLinks2Index
+{
+   local(*WRITEFILE, $LocalPath2Index, $PathContents, $LocalGlobalLocal) = @_;
+   
+   # include links to short/long - local/global index and C|contents.m
+   print WRITEFILE "\n<p>";
+   print WRITEFILE "$var{'textjumpindexglobal'} ";
+
+   if ($var{'frames'} eq 'yes') {
+       print WRITEFILE "<a href=\"$LocalPath2Index$var{'filenameindexshortglobal'}$var{'filenameextensionframe'}$var{'exthtml'}\">short</a> | ";
+       print WRITEFILE "<a href=\"$LocalPath2Index$var{'filenameindexlongglobal'}$var{'filenameextensionframe'}$var{'exthtml'}\">long</a>\n";
+   } else {
+       print WRITEFILE "<a href=\"$LocalPath2Index$var{'filenametopframe'}$var{'exthtml'}\">short</a> | ";
+       print WRITEFILE "<a href=\"$LocalPath2Index$var{'filenameindexlongglobal'}$var{'filenameextensionindex'}$var{'exthtml'}\">long</a>\n";
+   }
+
+   if ($LocalGlobalLocal eq 'local') {
+      if ($var{'usecontentsm'} eq 'yes') {
+         print WRITEFILE " | <a href=\"$contentsname{$PathContents}$dirnamesingle{$PathContents}$var{'exthtml'}\">Local contents</a>\n";
+      }
+      if ($var{'frames'} eq 'yes') {
+         print WRITEFILE " | $var{'textjumpindexlocal'} ";
+         print WRITEFILE "<a href=\"$var{'filenameindexshortlocal'}$var{'filenameextensionframe'}$dirnamesingle{$PathContents}$var{'exthtml'}\">short</a> | ";
+         print WRITEFILE "<a href=\"$var{'filenameindexlonglocal'}$var{'filenameextensionframe'}$dirnamesingle{$PathContents}$var{'exthtml'}\">long</a>\n";
+      } else {
+         print WRITEFILE " | $var{'textjumpindexlocal'} ";
+         print WRITEFILE "<a href=\"$var{'filenameindexshortlocal'}$var{'filenameextensionindex'}$dirnamesingle{$PathContents}$var{'exthtml'}\">short</a> | ";
+         print WRITEFILE "<a href=\"$var{'filenameindexlonglocal'}$var{'filenameextensionindex'}$dirnamesingle{$PathContents}$var{'exthtml'}\">long</a>\n";
+      }
+   }
+   print WRITEFILE "</p>\n\n";
+   print WRITEFILE "$var{'codehr'}\n";
+}
+
+
+#========================================================================
+# Construct the contents.m files or update
+#========================================================================
+sub ConstructContentsmFile
+{
+   local($LocalActDir, @localnames) = @_;
+   local(*CFILE, $name,$newline);
+   local($contentsfile, $isincontentsonly);
+   local(@lines, @autoaddlines, @emptylines);
+   local($autoadd) = 'AutoAdd';
+   local($autoaddsection) = 0;
+   local($emptylineflag) = 0;
+   local(%nameincontents);
+   
+   # Build the contents file name
+   $contentsfile = $LocalActDir.$contentsname{$LocalActDir}.$suffix;
+   
+   if (-e $contentsfile) {
+      open(CFILE,"<$contentsfile") || die("Cannot open contents file $contentsfile: $!\n");
+      while (<CFILE>) {
+         # Search for the specified string pattern
+         @words = split;
+         if ((@words >= 3) && ($words[2] eq '-')) {
+            $isincontentsonly = 0;
+            foreach $name (@localnames) {
+               if ($name eq $words[1]) {    # old
+               # if ($mfilename{$name} eq $words[1]) {
+                  $isincontentsonly = 1;
+                  $nameincontents{$name} = 1;
+                  $newline = sprintf("%% %-13s - %s\n", $mfilename{$name}, $apropos{$name});
+                  push(@lines, $newline);
+               }
+            }
+            # issue a warning, if file is in contents, but not as file in the directory
+            if ($isincontentsonly == 0) {
+               print "\rConstructContents: Obsolete entry  $words[1]  in  $contentsfile ! Entry not used.\n";
+            }
+         } else {
+            # look for the AutoAdd section, should be the second word
+            if ((@words >= 2) && ($words[1] eq $autoadd)) { $autoaddsection = 1; }
+            # push the red line in an array
+            push(@lines, $_); 
+         }
+      }   
+      close(CFILE);
+   } else {
+      $newline = "% MATLAB Files in directory  $LocalActDir\n%\n";
+      push(@lines, $newline);
+      
+   }
+   
+   # collect the file names, that were not included in original C|contents.m
+   foreach $name (@localnames) {
+      if (! defined $nameincontents{$name}) {
+         if (! ($mfilename{$name} =~ /contents/i)) {
+            $newline = sprintf("%% %-13s - %s\n", $mfilename{$name}, $apropos{$name});
+            push(@autoaddlines, $newline);
+         }   
+      }
+   }
+
+   # write/update C|contents.m only if variable is set
+   if ($var{'writecontentsm'} eq 'yes') {
+      unlink($contentsfile);
+      open(CFILE,">$contentsfile") || die("Cannot open contents file $contentsfile: $!\n");
+      # write old C|contents.m or header of new file, as long as comment lines
+      foreach $line (@lines) {
+         if ($emptylineflag == 0) {
+            if ($line =~ /^\s*%/) { print CFILE $line; }
+            else { $emptylineflag = 1; push(@emptylines, $line); }
+         } else { push(@emptylines, $line); }
+      }
+      # add header of AutoAdd section
+      if (($autoaddsection == 0) && (@autoaddlines > 0)) { print CFILE "%\n% $autoadd\n"; }
+      # add autoadd section lines (previously undocumented files
+      foreach $line (@autoaddlines) { print CFILE $line; }
+      # add tail of original C|contents.m (everything behind first non-comment line)
+      foreach $line (@emptylines)   { print CFILE $line; }
+      print CFILE "\n";
+      close CFILE;
+      if ($opt_silent) { print "\r"; }
+      print "   Contents file created/updated: $contentsfile\t";
+      if (!$opt_silent) { print "\n"; }
+   }
+}
+
+
+#========================================================================
+# Replace found special characters with their HTMl Entities
+#========================================================================
+sub SubstituteHTMLEntities {
+   local($_) = @_;
+   
+   # Replace & <-> &amp;  < <-> &lt;  > <-> &gt;  " <-> &quot;
+   s/&/&amp;/g; s/\</&lt;/g; s/\>/&gt;/g; s/\"/&quot;/g;
+   return $_;
+}
+
+#========================================================================
+# Replace found m-filenamestring with full link.
+#========================================================================
+sub SubstituteName2Link {
+   local($_, $funname) = @_;
+   local($refstr1, $refstr2, $reffound);
+   
+   # Look for something matching in the line
+   if ( /(\W+)($funname)(\W+)/i ) {
+      $reffound = $2;
+      $refstr1 = "<a class=\"mfun\" href=\"$hfileindexpath{$name}$hfilerelpath{$funname}$mfilename{$funname}$var{'exthtml'}\">";
+      $refstr2 = "<\/a>";
+      # Do links only for exact case match
+      if ( ($var{'links2filescase'}  eq 'exact') || ($var{'links2filescase'}  eq 'exactvery') ) {
+         if ( /(\W+)($funname)(\W+)/g ) {
+            s/(\W+)($funname)(\W+)/$1$refstr1$funname$refstr2$3/g;
+         }
+         else {
+            # Print info for not matching case in references, good for check up of files
+            if ( ($var{'links2filescase'}  eq 'exactvery') ) {
+               print "Diff in case found: $funname  (case of file name)   <->  $reffound  (case in source code)\n";  
+               print "     (source line)  $_ \n";
+            }
+         }
+      }
+      # Do links for exact match and additionally for all upper case (often used in original matlab help text)
+      elsif ( ($var{'links2filescase'}  eq 'exactupper') ) {
+         s/(\W+)($funname)(\W+)/$1$refstr1$2$refstr2$3/g;
+         $funname2 = "\U$funname\E";
+         s/(\W+)($funname2)(\W+)/$1$refstr1$2$refstr2$3/g;
+      }
+      # Do links for all case mixes, this calls for trouble under LINUX/UNIX
+      else {  #elsif ( ($var{'links2filescase'}  eq 'all') )
+         s/(\W+)($funname)(\W+)/$1$refstr1$2$refstr2$3/ig;
+      }
+   }
+   
+   return $_;
+}
+
+#========================================================================
+# Construct the html files for each matlab file.
+#    Need to reread each matlab file to find the help text.
+#    Note that we can't do this in a single loop because sometimes
+#    the help text maybe before the function declaration.
+#========================================================================
+sub ConstructHTMLFiles
+{
+   local(*MFILE);
+   local(*HFILE);
+
+   local($filescreated) = 0;
+   local($functionline);
+   
+   foreach $name (@names) {
+      # Create cross reference information already here, used for keywords as well
+      # Construct list of referenced functions
+      @xref = @{'depcalls'.$name};    # the functions, that this m-file calls
+      @yref = @{'depcalled'.$name};   # the functions, that this m-file is called from
+      # print "   depcalls: @{'depcalls'.$name}\n   depcalled: @{'depcalled'.$name}\n";
+      # foreach $cname (@names) { next if $cname eq $name; push(@yref,$cname) if grep(/$name/,@{'depcalls'.$cname}); }
+
+
+      # Open m-file and html-file
+      open(MFILE,"<$mfile{$name}");
+      open(HFILE,">$hfile{$name}");
+
+      # Write the header of HTML file
+      print HFILE "$TextDocTypeHTML\n<html>\n<head>\n$var{'codeheadmeta'}\n$TextMetaCharset\n$var{'csslink'}\n";
+
+      # Write meta tags: use apropos (one line function description) for description
+      # and cross reference function names for keywords (any better ideas?)
+      print HFILE "<meta name=\"description\" content=\" $apropos{$name} \" />\n";
+      print HFILE "<meta name=\"keywords\" content=\" @xref @yref \" />\n";
+
+      # Write Title and start body of html-file
+      print HFILE "<title>$var{'texttitlefiles'} $mfilename{$name}</title>\n</head>\n";
+      print HFILE "<body $var{'codebodyfiles'}>\n";
+      print HFILE "<div id=\"matlabdoc\">\n";
+      print HFILE "<h1 $var{'codeheader'}>$var{'textheaderfiles'} $mfilename{$name}</h1>\n";
+
+# http://test.soundsoftware.ac.uk/cannam/projects/smallbox/repository/annotate/DL/RLS-DLA/SolveFISTA.m
+#      print HFILE "<a href=\"" . $hfileindexpath{$name} . "../../projects/smallbox/repository/annotate/" . $mfiledir{$name}  . $mfilename{$name} . ".m\">View in repository</a>\n";
+
+      print HFILE "$var{'codehr'}\n";
+
+      # include links to short/long - local/global index and C|contents.m
+      &ConstructLinks2Index(HFILE, $hfileindexpath{$name}, $mfiledir{$name}, 'local');
+
+      # If this is a function, then write out the first line as a synopsis
+      if ($mtype{$name} eq "function") {
+         print HFILE "<h2 $var{'codeheader'}>Function Synopsis</h2>\n";
+         print HFILE "<pre>$synopsis{$name}</pre>\n$var{'codehr'}\n";
+      }
+
+      # Look for the matlab help text block
+      $functionline = "\n";
+      do {
+         $_ = <MFILE>;
+         # remember functionline, if before help text block
+         if (/^\s*function/) { $functionline = $_; }
+      } until (/^\s*%/ || eof);
+      if (! (eof(MFILE))) {
+         print HFILE "<h2 $var{'codeheader'}>Help text</h2>\n";
+         print HFILE "<pre>\n";
+         while (/^\s*%/) {
+            # First remove leading % and white space, then Substitute special characlers
+            s/^\s*%//;
+            $_ = &SubstituteHTMLEntities($_);
+
+            # check/create cross references
+            foreach $funname (@{'all'.$name}) {
+               if ($funname =~ /simulink/) { print "\n Simulink - Filename: $name;  scanname: $funname\n"; }
+               next if $funname eq $name;
+               $_ = &SubstituteName2Link($_, $funname);
+            }
+            print HFILE $_;
+            if (! eof) { $_ = <MFILE>; }
+         }
+         print HFILE "</pre>\n$var{'codehr'}\n";
+      }
+
+      # Write the cross reference information
+      if (@xref || @yref) {
+         print HFILE "<h2 $var{'codeheader'}>Cross-Reference Information</H2>\n";
+         print HFILE "<table border=\"0\" width=\"100%\">\n<tr align=\"left\">\n<th width=\"50%\">";
+         if (@xref) {
+            print HFILE "This $mtype{$name} calls";
+         }
+         print HFILE "</th>\n<th width=\"50%\">";
+         if (@yref) {
+            print HFILE "This $mtype{$name} is called by";
+         }
+         print HFILE "</th>\n</tr>\n<tr valign=\"top\"><td>";
+         if (@xref) {
+            print HFILE "\n<ul>\n";
+            foreach $cname (sort @xref) {
+               print HFILE "<li><a class=\"mfun\" href=\"$hfileindexpath{$name}$hfilerelpath{$cname}$mfilename{$cname}$var{'exthtml'}\">$mfilename{$cname}</a></li>\n";
+            }
+            print HFILE "</ul>\n";
+         }
+         print HFILE "</td><td>";
+         if (@yref) {
+            print HFILE "\n<ul>\n";
+            foreach $cname (sort @yref) {
+               print HFILE "<li><a class=\"mfun\" href=\"$hfileindexpath{$name}$hfilerelpath{$cname}$mfilename{$cname}$var{'exthtml'}\">$mfilename{$cname}</a></li>\n";
+            }
+            print HFILE "</ul>\n";
+         }
+         print HFILE "</td>\n</tr>\n</table>\n";
+         print HFILE "$var{'codehr'}\n";
+      }
+
+      # Include source text if requested
+      if (($var{'includesource'} eq 'yes') && (! ($mfilename{$name} =~ /^contents$/i))) {
+         print HFILE "<h2 $var{'codeheader'}>Listing of $mtype{$name} $mfilename{$name}</h2>\n";
+         seek(MFILE,0,0);
+         print HFILE "<pre>\n";
+         $IsStillHelp = 2;
+         print HFILE $functionline;    # functionline from scanning of help
+         while (<MFILE>) {
+            if ($IsStillHelp == 2) {
+               next     if (/^\s*$/);
+               next     if (/^\s*function/);
+               if (/^\s*%/) { $IsStillHelp = 1; next; }
+            } elsif ($IsStillHelp == 1) {
+               next     if (/^\s*%/);
+               $IsStillHelp = 0;
+            }
+            
+            # Substritute special characters
+            $_ = &SubstituteHTMLEntities($_);
+            
+            # check for comment in line and format with css em
+            s/^(.*)%(.*?)([\s\r\n]+)$/$1<em class=\"mcom\">%$2<\/em>$3/;
+
+            # check/create cross references
+            foreach $funname (@{'all'.$name}) {
+               next if $funname eq $name;
+               $_ = &SubstituteName2Link($_, $funname);
+            }
+            print HFILE $_;
+         }
+         print HFILE "</pre>\n$var{'codehr'}\n";
+      }
+
+      # Include info about author from authorfile
+      &WriteFile2Handle($var{'authorfile'}, HFILE)   ;
+
+      print HFILE "<!--navigate-->\n";
+      print HFILE "<!--copyright-->\n";
+      print HFILE "</div>\n</body>\n</html>\n";
+      close(MFILE);
+      close(HFILE);
+
+      # Print name of finished file
+      if ($opt_silent) { print "\r"; }
+      print "   HTML-File created: $hfile{$name}\t";
+      if (!$opt_silent) { print "\n"; }
+      $filescreated++;
+   }
+
+   print "\n$PROGRAM: $indexcreated index and $filescreated files created.\n"; 
+}
+
+#========================================================================
+# Function:	CheckFileName
+# Purpose:	.
+#========================================================================
+sub CheckFileName {
+   local($filename, $description) = @_;
+   local(*CHECKFILE);
+   
+   open(CHECKFILE,"<$filename") || do {
+      if ($description eq '') {$description = 'file';}
+      # if (!$opt_silent) { print "Cannot open $description $filename: $!\n"; }
+      print "Cannot open $description $filename: $!\n";
+      return 1;
+   };
+   close(CHECKFILE);
+   return 0;
+
+}
+
+#========================================================================
+# Function:	CheckDirName
+# Purpose:	.
+#========================================================================
+sub CheckDirName {
+   local($dirname, $description) = @_;
+   local(*CHECKDIR);
+   
+   opendir(CHECKDIR,"$dirname") || die ("Cannot open $description directory $dirname: $!\n");
+   closedir(CHECKDIR);
+}
+
+#========================================================================
+# Function:	WriteFile2Handle
+# Purpose:	.
+#========================================================================
+sub WriteFile2Handle {
+   local($filename, *WRITEFILE) = @_;
+   local(*READFILE);
+   
+   if ($filename ne '') {
+      open(READFILE,"<$filename");  
+      @filecontents = <READFILE>;
+      close(READFILE);
+      print WRITEFILE "@filecontents\n";
+      # if (!$opt_silent) {print "      Contents of $filename added\n"};
+   }
+}
+
+
+#========================================================================
+# Function:	GetConfigFile
+# Purpose:	Read user's configuration file, if such exists.
+#========================================================================
+sub GetConfigFile
+{
+   local($filename) = @_;
+   local(*CONFIG);
+   local($value);
+
+   if (&CheckFileName($filename, 'configuration file')) {
+      # if (!$opt_silent) { print "   Proceeding using built-in defaults for configuration.\n"; }
+      print "   Proceeding using built-in defaults for configuration.\n";
+      return 0;
+   };
+
+   open(CONFIG,"< $filename");
+   while (<CONFIG>) {
+      s/#.*$//;
+      next if /^\s*$/o;
+
+      # match keyword: process one or more arguments
+      # keyword set
+      if (/^\s*set\s+(\S+)\s*=\s*(.*)/) {
+         # setting a configuration variable
+         if (defined $var{$1}) {
+            $var{$1} = $2;
+            if ($debug > 3) { print "$1:   $var{$1}\n"; }
+         }
+         else {
+            print "$PROGRAM: unknown variable `$1' in configuration file\n"
+         }
+      } else {
+         chop($_);
+         print "$PROGRAM: unknown keyword in configuration file in line: `$_'\n"
+      }
+   }
+   close CONFIG;
+   1;
+}
+
+
+#------------------------------------------------------------------------
+# DisplayHelp - display help text using -h or -help command-line switch
+#------------------------------------------------------------------------
+sub DisplayHelp
+{
+   $help=<<EofHelp;
+   $PROGRAM v$VERSION - generate html documentation from Matlab m-files
+
+   Usage: $PROGRAM [-h] [-c config_file] [-m|dirmfiles matlab_dir] [-d|dirhtml html_dir]
+                   [-i yes|no] [-r yes|no] [-p yes|no] [-quiet|q] [-a authorfile]
+
+   $PROGRAM is a perl script that reads each matlab .m file in a directory
+   to produce a corresponding .html file of help documentation and cross
+   reference information. An index file is written with links to all of 
+   the html files produced. The options are:
+
+      -quiet         or -q : be silent, no status information during generation
+      -help          or -h : display this help message
+      -todo          or -t : print the todo list for $PROGRAM
+      -version       or -v : display version
+
+      -configfile    or -c : name of configuration file (default to $var{'configfile'}).
+      -dirmfiles     or -m : top level directory containing matlab files to generate html for;
+                             default to actual directory.
+      -dirhtml       or -d : top level directory for generated html files;
+                             default to actual directory.
+
+      -includesource or -i : Include matlab source in the html documentation [yes|no]
+                             default to yes.
+      -processtree   or -r : create docu for m-file directory and all subdirectories [yes|no];
+                             default to yes.
+      -producetree   or -p : create multi-level docu identical to directory structure
+                             of m-files [yes|no]; default to yes.
+      -writecontentsm or -w: update or write contents.m files into the matlab source
+                             directories [yes|no]; default to no.
+
+      -authorfile    or -a : name of file including author information, last element in html;
+                             default to empty.
+      
+   The command line setting overwrite all other settings (built-in and configuration file).
+   The configuration file settings overwrite the built-in settings (and not the command
+   line settings).
+
+   Typical usages are:
+     $PROGRAM   
+        (use default parameters from perl script, if configuration 
+         file is found -> generation of docu, else display of help)
+
+     $PROGRAM -dirmfiles matlab -dirhtml html
+        (generate html documentation for all m-files in directory matlab,
+         place html files in directory html, use built-in defaults for
+         all other parameters, this way all m-files in the directory
+         matlab and below are converted and the generated html-files are
+         placed in the directory html and below producing the same 
+         directory structure than below matlab)
+
+     $PROGRAM -quiet
+        (use built-in parameters from perl script, if configuration 
+         file is found use these settings as well, do generation,
+         no display except critical errors, status of conversion and result)
+         
+     $PROGRAM -m toolbox -dirhtml doc/html -r yes -p no
+        (convert all m-files in directory toolbox and below and place
+         the generated html files in directory doc/html, read all m-files
+         recursively, however, the generated html files are placed in one
+         directory)
+
+     $PROGRAM -m toolbox -dirhtml doc/html -i no -r no
+        (convert all m-files in directory toolbox and place
+         the generated html files in directory doc/html, do not read m-files
+         recursively, do not include source code in documentation)
+
+EofHelp
+
+   die "$help";
+}
+
+#------------------------------------------------------------------------
+# DisplayTodo - display ToDo list using -t or -todo command-line switch
+#------------------------------------------------------------------------
+sub DisplayTodo
+{
+   $todo=<<EofToDo;
+      $PROGRAM v$VERSION - ToDo list
+
+       o	use more than one high level directory
+       
+       o	what should/could be done here???
+
+EofToDo
+
+   die "$todo";
+}
+
+
+#------------------------------------------------------------------------
+# ListVariables - list all defined variables and their values
+#------------------------------------------------------------------------
+sub ListVariables
+{
+   local($value);
+   
+   if ($debug > 0) {
+      print "List of all variables and their values\n";
+      foreach (sort keys %var)
+      {
+         if ($var{$_} eq '') {
+            $value = "empty";
+         } else {
+            $value = $var{$_};
+         }
+         print "   $_\n      $value\n";
+      }
+      print "\n\n";
+   }
+}
+
+
+__END__
+:endofperl
--- a/extra/soundsoftware/reposman-soundsoftware.rb	Mon Jun 06 14:41:04 2011 +0100
+++ b/extra/soundsoftware/reposman-soundsoftware.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -9,12 +9,12 @@
 #    reposman [OPTIONS...] -s [DIR] -r [HOST]
 #
 #  Examples:
-#    reposman --svn-dir=/var/svn --redmine-host=redmine.example.net --scm subversion
+#    reposman --scm-dir=/var/svn --redmine-host=redmine.example.net --scm subversion
 #    reposman -s /var/git -r redmine.example.net -u http://svn.example.net --scm git
 #
 # == Arguments (mandatory)
 #
-#   -s, --svn-dir=DIR         use DIR as base directory for svn repositories
+#   -s, --scm-dir=DIR         use DIR as base directory for repositories
 #   -r, --redmine-host=HOST   assume Redmine is hosted on HOST. Examples:
 #                             -r redmine.example.net
 #                             -r http://redmine.example.net
@@ -70,7 +70,7 @@
 SUPPORTED_SCM = %w( Subversion Darcs Mercurial Bazaar Git Filesystem )
 
 opts = GetoptLong.new(
-                      ['--svn-dir',      '-s', GetoptLong::REQUIRED_ARGUMENT],
+                      ['--scm-dir',      '-s', GetoptLong::REQUIRED_ARGUMENT],
                       ['--redmine-host', '-r', GetoptLong::REQUIRED_ARGUMENT],
                       ['--key',          '-k', GetoptLong::REQUIRED_ARGUMENT],
                       ['--owner',        '-o', GetoptLong::REQUIRED_ARGUMENT],
@@ -133,7 +133,7 @@
 begin
   opts.each do |opt, arg|
     case opt
-    when '--svn-dir';        $repos_base   = arg.dup
+    when '--scm-dir';        $repos_base   = arg.dup
     when '--redmine-host';   $redmine_host = arg.dup
     when '--key';            $api_key      = arg.dup
     when '--owner';          $svn_owner    = arg.dup; $use_groupid = false;
@@ -174,7 +174,7 @@
 end
 
 unless File.directory?($repos_base)
-  log("directory '#{$repos_base}' doesn't exists", :exit => true)
+  log("directory '#{$repos_base}' doesn't exist", :exit => true)
 end
 
 begin
@@ -184,7 +184,7 @@
 end
 
 class Project < ActiveResource::Base
-  self.headers["User-agent"] = "Redmine repository manager/#{Version}"
+  self.headers["User-agent"] = "SoundSoftware repository manager/#{Version}"
 end
 
 log("querying Redmine for projects...", :level => 1);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extra/soundsoftware/update-external-repo.sh	Mon Jun 06 14:55:38 2011 +0100
@@ -0,0 +1,132 @@
+#!/bin/sh
+
+mirrordir="/var/mirror"
+hg="/usr/local/bin/hg"
+
+project="$1"
+local_repo="$2"
+remote_repo="$3"
+
+if [ -z "$project" ] || [ -z "$local_repo" ] || [ -z "$remote_repo" ]; then
+    echo "Usage: $0 <project> <local-repo-path> <remote-repo-url>"
+    exit 2
+fi
+
+  # We need to handle different source repository types separately.
+  # 
+  # The convert extension cannot convert directly from a remote git
+  # repo; we'd have to mirror to a local repo first.  Incremental
+  # conversions do work though.  The hg-git plugin will convert
+  # directly from remote repositories, but not via all schemes
+  # (e.g. https is not currently supported).  It's probably easier to
+  # use git itself to clone locally and then convert or hg-git from
+  # there.
+  # 
+  # We can of course convert directly from remote Subversion repos,
+  # but we need to keep track of that -- you can ask to convert into a
+  # repo that has already been used (for Mercurial) and it'll do so
+  # happily; we don't want that.
+  #
+  # Converting from a remote Hg repo should be fine!
+  #
+  # One other thing -- we can't actually tell the difference between
+  # the various SCM types based on URL alone.  We have to try them
+  # (ideally in an order determined by a guess based on the URL) and
+  # see what happens.
+
+project_mirror="$mirrordir/$project"
+mkdir -p "$project_mirror"
+project_repo_mirror="$project_mirror/repo"
+
+  # Some test URLs:
+  # 
+  # http://aimc.googlecode.com/svn/trunk/
+  # http://aimc.googlecode.com/svn/
+  # http://vagar.org/git/flam
+  # https://github.com/wslihgt/IMMF0salience.git
+  # http://hg.breakfastquay.com/dssi-vst/
+  # git://github.com/schacon/hg-git.git
+  # http://svn.drobilla.net/lad (externals!)
+
+# If we are importing from another distributed system, then our aim is
+# to create either a Hg repo or a git repo at $project_mirror, which
+# we can then pull from directly to the Hg repo at $local_repo (using
+# hg-git, in the case of a git repo).
+
+# Importing from SVN, we should use hg convert directly to the target
+# hg repo (or should we?) but keep a record of the last changeset ID
+# we brought in, and test each time whether it matches the last
+# changeset ID actually in the repo
+
+success=""
+
+# If we have a record of the last successfully updated remote repo
+# URL, check it against our current remote URL: if it has changed, we
+# will need to start again with a new clone rather than pulling
+# updates into the existing local mirror
+
+successfile="$project_mirror/last_successful_url"
+if [ -f "$successfile" ]; then
+    last=$(cat "$successfile")
+    if [ x"$last" = x"$remote_repo" ]; then
+	echo "$$: Remote URL is unchanged from last successful update"
+    else
+	echo "$$: Remote URL has changed since last successful update:"
+	echo "$$: Last URL was $last, current is $remote_repo"
+	suffix="$$.$(date +%s)"
+	echo "$$: Moving existing repos to $suffix suffix and starting afresh"
+	mv "$project_repo_mirror" "$project_repo_mirror"."$suffix"
+	mv "$local_repo" "$local_repo"."$suffix"
+	mv "$successfile" "$successfile"."$suffix"
+	touch "$project_mirror/url_changed"
+    fi
+fi
+
+if [ -d "$project_repo_mirror" ]; then
+
+    # Repo mirror exists: update it
+    echo "$$: Mirror for project $project exists at $project_repo_mirror, updating" 1>&2
+
+    if [ -d "$project_repo_mirror/.hg" ]; then
+	"$hg" --config extensions.convert= convert --datesort "$remote_repo" "$project_repo_mirror" && success=true
+	if [ -z "$success" ]; then
+	    ( cd "$project_repo_mirror" && "$hg" pull "$remote_repo" ) && success=true
+	fi
+    elif [ -d "$project_repo_mirror/.git" ]; then
+	( cd "$project_repo_mirror" && git pull "$remote_repo" master ) && success=true
+    else 
+	echo "$$: ERROR: Repo mirror dir $project_repo_mirror exists but is not an Hg or git repo" 1>&2
+    fi
+
+else
+
+    # Repo mirror does not exist yet
+    echo "$$: Mirror for project $project does not yet exist at $project_repo_mirror, trying to convert or clone" 1>&2
+
+    case "$remote_repo" in
+	*git*) 
+	    git clone "$remote_repo" "$project_repo_mirror" ||
+	    "$hg" --config extensions.convert= convert --datesort "$remote_repo" "$project_repo_mirror"
+	    ;;
+	*)
+	    "$hg" --config extensions.convert= convert --datesort "$remote_repo" "$project_repo_mirror" ||
+	    git clone "$remote_repo" "$project_repo_mirror" ||
+	    "$hg" clone "$remote_repo" "$project_repo_mirror"
+	    ;;
+    esac && success=true
+
+fi
+	
+echo "Success=$success"
+
+if [ -n "$success" ]; then
+    echo "$$: Update successful, pulling into local repo at $local_repo"
+    if [ ! -d "$local_repo" ]; then
+	"$hg" init "$local_repo"
+    fi
+    if [ -d "$project_repo_mirror/.git" ]; then
+	( cd "$local_repo" && "$hg" --config extensions.hggit= pull "$project_repo_mirror" ) && echo "$remote_repo" > "$successfile"
+    else 
+	( cd "$local_repo" && "$hg" pull "$project_repo_mirror" ) && echo "$remote_repo" > "$successfile"
+    fi
+fi
--- a/lib/redmine.rb	Mon Jun 06 14:41:04 2011 +0100
+++ b/lib/redmine.rb	Mon Jun 06 14:55:38 2011 +0100
@@ -50,7 +50,7 @@
   map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true
   map.permission :search_project, {:search => :index}, :public => true
   map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin
-  map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member
+  map.permission :edit_project, {:projects => [:settings, :edit, :update, :overview]}, :require => :member
   map.permission :select_project_modules, {:projects => :modules}, :require => :member
   map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member
   map.permission :manage_versions, {:projects => :settings, :versions => [:new, :create, :edit, :update, :close_completed, :destroy]}, :require => :member
@@ -189,6 +189,7 @@
 
 Redmine::MenuManager.map :project_menu do |menu|
   menu.push :overview, { :controller => 'projects', :action => 'show' }
+  menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
   menu.push :activity, { :controller => 'activities', :action => 'index' }
   menu.push :roadmap, { :controller => 'versions', :action => 'index' }, :param => :project_id,
               :if => Proc.new { |p| p.shared_versions.any? }
@@ -197,7 +198,6 @@
               :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
   menu.push :gantt, { :controller => 'gantts', :action => 'show' }, :param => :project_id, :caption => :label_gantt
   menu.push :calendar, { :controller => 'calendars', :action => 'show' }, :param => :project_id, :caption => :label_calendar
-  menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
   menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
   menu.push :wiki, { :controller => 'wiki', :action => 'show', :id => nil }, :param => :project_id,
               :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/public/javascripts/repository.js	Mon Jun 06 14:55:38 2011 +0100
@@ -0,0 +1,7 @@
+function toggle_ext_url(){
+	if($('repository_is_external').checked)
+	    $('repository_external_url').enable();
+	else
+	    $('repository_external_url').disable();
+}
+
--- a/public/javascripts/ssamr_registration.js	Mon Jun 06 14:41:04 2011 +0100
+++ b/public/javascripts/ssamr_registration.js	Mon Jun 06 14:55:38 2011 +0100
@@ -4,13 +4,12 @@
 
 /* institution related functions */
 Event.observe(window, 'load',
-  function() {
-		
+  function() {		
 		if(!$('ssamr_user_details_institution_type_true').checked && $('ssamr_user_details_institution_type_true').checked){
-  			$('ssamr_user_details_other_institution').disable();
-  			$('ssamr_user_details_institution_id').enable();
-  			$('ssamr_user_details_institution_type_true').checked = true;
-  			$('ssamr_user_details_institution_type_false').checked = false;
+		    $('ssamr_user_details_other_institution').disable();
+		    $('ssamr_user_details_institution_id').enable();
+		    $('ssamr_user_details_institution_type_true').checked = true;
+		    $('ssamr_user_details_institution_type_false').checked = false;
 		}
 	}
 );
--- a/public/stylesheets/application.css	Mon Jun 06 14:41:04 2011 +0100
+++ b/public/stylesheets/application.css	Mon Jun 06 14:55:38 2011 +0100
@@ -161,7 +161,7 @@
 tr.changeset td.committed_on { text-align: center; width: 15%; }
 
 table.files tr.file td { text-align: center; }
-table.files tr.file td.filename { text-align: left; padding-left: 24px; }
+table.files tr.file td.filename { text-align: left; }
 table.files tr.file td.digest { font-size: 80%; }
 
 table.members td.roles, table.memberships td.roles { width: 45%; }
@@ -225,6 +225,7 @@
 h3.version { background: url(../images/package.png) no-repeat 0% 50%; padding-left: 20px; }
 
 div.issues h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; }
+div.activity h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; }
 div.members h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; }
 div.news h3 { background: url(../images/news.png) no-repeat 0% 50%; padding-left: 20px; }
 div.projects h3 { background: url(../images/projects.png) no-repeat 0% 50%; padding-left: 20px; }
@@ -304,20 +305,24 @@
 div#issue-changesets div.changeset { border-bottom: 1px solid #ddd; }
 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
 
-div#activity dl, #search-results { margin-left: 2em; }
-div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
-div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
-div#activity dt.me .time { border-bottom: 1px solid #999; }
-div#activity dt .time { color: #777; font-size: 80%; }
+div#activity dl, div#news dl, #search-results { margin-left: 2em; }
+div#activity .box dl { margin-left: 0; }
+div#activity dd, div#news dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
+div#activity dt, div#news dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
+div#activity dt.me .time, div#news dt.me .time { border-bottom: 1px solid #999; }
+div#activity dt .time, div#news dt .time, .projects .latest .time { color: #777; font-size: 80%; }
 div#activity dd .description, #search-results dd .description { font-style: italic; }
-div#activity span.project:after, #search-results span.project:after { content: " -"; }
+div#activity span.project:after, div#news span.project:after, #search-results span.project:after { content: " -"; }
 div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; }
 
+.projects .latest .title { margin-right: 0.5em; }
+.tipoftheday .tip { margin-left: 2em; margin-top: 0.5em; }
+
 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
 
-div#search-results-counts {float:right;}
-div#search-results-counts ul { margin-top: 0.5em; }
-div#search-results-counts  li { list-style-type:none; float: left; margin-left: 1em; }
+div#search-results-counts { display: block; padding-left: 0; margin-left: 0; }
+div#search-results-counts ul { margin-top: 0.5em; padding-left: 0; margin-left: 0; }
+div#search-results-counts li { display: inline; list-style-type: none; margin-right: 1em; }
   
 dt.issue { background-image: url(../images/ticket.png); }
 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
@@ -365,6 +370,8 @@
 ul.projects li.child { list-style-type:none; margin-top: 1em;}
 ul.projects div.root a.project { font-weight: bold; font-size: 16px; margin: 0 0 10px 0; }
 
+li.latest { margin-bottom: 0.5em; }
+
 #tracker_project_ids ul { margin: 0;  padding-left: 1em; }
 #tracker_project_ids li { list-style-type:none; } 
 
--- a/public/themes/soundsoftware/stylesheets/application.css	Mon Jun 06 14:41:04 2011 +0100
+++ b/public/themes/soundsoftware/stylesheets/application.css	Mon Jun 06 14:55:38 2011 +0100
@@ -37,16 +37,41 @@
 
 body,p,h2,h3,h4,li,table,.wiki h1 { 
   font-family: DroidSans, 'Liberation Sans', tahoma, verdana, sans-serif; 
+  line-height: 1.34;
 }
 
 h2,h3,h4,.wiki h1 {
   color: #3e442c;
+  font-weight: bold;
+}
+
+.wiki h2,.wiki h3,.wiki h4 {
+  color: #000;
 }
 
 h2,.wiki h1 {
-    font-size: 1.8em;
+  font-size: 1.8em;
 }
 
+.wiki h2 {
+  margin-top: 1em;
+}
+
+.splitcontentleft p:first-child {
+  margin-top: 0;
+}
+
+div.attachments {
+  margin-top: 2em;
+}
+#wiki_add_attachment {
+  margin-top: 1.5em;
+}
+
+/* Hide these (the paragraph markers that show anchors) -- they confuse more than they help */
+a.wiki-anchor:hover { display: none; }
+h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: none; }
+
 .box {
     padding: 6px;
     margin-bottom: 10px;
@@ -81,24 +106,29 @@
 
 ul.projects .public, ul.projects .private { padding-left: 0.5em; color: #3e442c; font-size: 0.95em }
 
+table.files tr.active td { padding-top: 0.5em; padding-bottom: 0.5em; }
+table.files .file .active { font-weight: bold; }
+table.files .file .description { font-weight: normal; color: #3e442c; }
+
 #top-menu { position: absolute; top: 0; z-index: 1; left: 0px; width: 100%; font-size: 90%; /* height: 2em; */ margin: 0; padding: 0; padding-top: 0.5em; background-color: #3e442c; }
 #top-menu ul { margin-left: 10px; }
 #top-menu a { font-weight: bold; }
 #header {  position: absolute; z-index: 0; top: 0; width: 100%; background: #fdfbf5; border-bottom: 2px solid #a9b680; /* height:80px; */ padding: 20px 0 0.5em 0; margin-bottom: 0; }
 #header a { color: #be5700; }
 #header h1 { color: #525a38; margin-top: 25px; font-size: 3em; font-weight: normal; margin-left: 10px; }
+#header #project-title a, #header #project-title a:hover { color: #525a38; text-decoration: none; }
 .header-general h1 {
   background: url('soundsoftware-logo-title-only-transparent-beta.png') no-repeat 0 0;
   text-indent: -9999px;
   width: 500px;
   height: 34px;
  }
- 
+
 #quick-search { margin-right: 6px; margin-top: 1em; color: #000; }
 #project-jump-box { float: right;  margin-right: 6px; margin-top: 5px; color: #000; }
 #project-ancestors-title {
     margin-bottom: 0px;
-    margin-left: 10px;
+    margin-left: 12px;
     margin-top: 6px;
     font-family: GilliusADFNo2, 'Gill Sans', Tahoma, sans-serif;
     font-weight: normal;
@@ -201,5 +231,9 @@
 
 .embedded .contents .center { text-align: center; } /* undo javadoc hack above */
 
+/* For MATLAB documentation */
 
+.embedded #matlabdoc th { text-align: left; }
 
+
+
--- a/vendor/plugins/redmine_checkout/app/views/redmine_checkout_hooks/_view_repositories_show_contextual.rhtml	Mon Jun 06 14:41:04 2011 +0100
+++ b/vendor/plugins/redmine_checkout/app/views/redmine_checkout_hooks/_view_repositories_show_contextual.rhtml	Mon Jun 06 14:55:38 2011 +0100
@@ -17,8 +17,19 @@
        <div id="clipboard_button"><%= image_tag 'paste.png', :plugin => 'redmine_checkout' %></div>
     </div>
     <% end -%>
-    <% if default_protocol %><p><%=l :label_access_type, :type => l(default_protocol.access_label(User.current)) %></p><% end %>
-  
+
+    <p>
+    <% if User.current.logged? %>
+      <% if repository.is_external? %>
+      <%=l :label_access_type_all, :type => l(:label_access_read_only) %>
+      <% else %>
+      <% if default_protocol %><%=l :label_access_type, :type => l(default_protocol.access_label(User.current)) %><% end %>
+      <% end %>
+    <% else %>
+      &nbsp;
+    <% end %>
+    </p>
+
     <% javascript_tag do %>
       var checkout_access   = $H({<%= protocols.inject([]){|r,p| r << "'checkout_protocol_#{p.protocol.to_s.underscore}': '#{l(p.access_label(User.current))}'"}.join(', ') %>});
       var checkout_commands = $H({<%= protocols.inject([]){|r,p| r << "'checkout_protocol_#{p.protocol.to_s.underscore}': '#{escape_javascript(p.full_command(checkout_path))}'"}.join(', ') %>});
@@ -26,6 +37,11 @@
     <% end %>
   </div>
   <% end%>
+  <% if repository.is_external? %>
+    <div style="clear: left">
+    </div>
+    <p class="topline" style="padding-top: 1em"><%= l(:text_repository_external, :location => repository.external_url) %></p>
+  <% end %>
 </div>
 <div style="clear: left"></div>
 
@@ -33,4 +49,4 @@
   <%= stylesheet_link_tag 'checkout', :plugin => 'redmine_checkout' %>
   <%= javascript_include_tag 'checkout', :plugin => 'redmine_checkout' %>
   <%= (javascript_include_tag 'ZeroClipboard', :plugin => 'redmine_checkout') if Setting.checkout_use_zero_clipboard? %>
-<% end %>
\ No newline at end of file
+<% end %>
--- a/vendor/plugins/redmine_checkout/assets/stylesheets/checkout.css	Mon Jun 06 14:41:04 2011 +0100
+++ b/vendor/plugins/redmine_checkout/assets/stylesheets/checkout.css	Mon Jun 06 14:55:38 2011 +0100
@@ -20,6 +20,10 @@
   border-bottom: 1px solid #ccc;
 }
 
+.topline {
+  border-top: 1px solid #ccc;
+}
+
 #checkout_box {
   margin: 10px 0;
 }
@@ -159,4 +163,4 @@
 
 #clipboard_button img {
   padding-top: 2px;
-}
\ No newline at end of file
+}
--- a/vendor/plugins/redmine_checkout/config/locales/en-GB.yml	Mon Jun 06 14:41:04 2011 +0100
+++ b/vendor/plugins/redmine_checkout/config/locales/en-GB.yml	Mon Jun 06 14:55:38 2011 +0100
@@ -16,9 +16,9 @@
   label_protocol_plural: "Protocols"
   button_add_protocol: "Add Protocol"
 
-  label_access_type: 'This URL has <span id="checkout_access">{{type}}</span> access.'
-  label_access_read_only: 'Read-Only'
-  label_access_read_write: "Read+Write"
+  label_access_type: 'You have <span id="checkout_access">{{type}}</span> access to this URL.'
+  label_access_read_only: 'Read Only'
+  label_access_read_write: "Read and Write"
   label_access_permission: "Depending on user's permissions"
 
   label_append_path: "Append path"
@@ -37,3 +37,5 @@
     Leave the Checkout URL field empty to use the defined repository URL.
   help_moved_settings: "The settings page has been moved to {{link}}."
   label_settings_location: "Administration -> Settings -> Checkout"
+
+  text_repository_external: "The primary repository for this project is hosted at <code>{{location}}</code> .<br>This repository is a read-only copy which is updated automatically."
--- a/vendor/plugins/redmine_checkout/config/locales/en.yml	Mon Jun 06 14:41:04 2011 +0100
+++ b/vendor/plugins/redmine_checkout/config/locales/en.yml	Mon Jun 06 14:55:38 2011 +0100
@@ -16,9 +16,10 @@
   label_protocol_plural: "Protocols"
   button_add_protocol: "Add Protocol"
 
-  label_access_type: 'This URL has <span id="checkout_access">{{type}}</span> access.'
-  label_access_read_only: 'Read-Only'
-  label_access_read_write: "Read+Write"
+  label_access_type: 'You have <span id="checkout_access">{{type}}</span> access to this URL.'
+  label_access_type_all: 'All access to this URL is <span id="checkout_access">{{type}}</span>.'
+  label_access_read_only: 'Read Only'
+  label_access_read_write: "Read and Write"
   label_access_permission: "Depending on user's permissions"
 
   label_append_path: "Append path"
@@ -37,3 +38,6 @@
     Leave the Checkout URL field empty to use the defined repository URL.
   help_moved_settings: "The settings page has been moved to {{link}}."
   label_settings_location: "Administration -> Settings -> Checkout"
+
+  text_repository_external: "The primary repository for this project is hosted at <code>{{location}}</code> .<br>This repository is a read-only copy which is updated automatically every hour."
+