# HG changeset patch
# User Toshi MARUYAMA
# Date 1339815276 -32400
# Node ID 2f7b9abfc0d6cb8edbc707c0462c173f48df0a73
# Parent  7a90df65220f1e0cbb5af6821151ab16a4a4cea3
gantt_edit

diff --git a/app/controllers/gantts_controller.rb b/app/controllers/gantts_controller.rb
--- a/app/controllers/gantts_controller.rb
+++ b/app/controllers/gantts_controller.rb
@@ -45,4 +45,35 @@ class GanttsController < ApplicationCont
       format.pdf  { send_data(@gantt.to_pdf, :type => 'application/pdf', :filename => "#{basename}.pdf") }
     end
   end
+
+  def edit_gantt
+    date_from = Date.parse(params[:date_from])
+    date_to = Date.parse(params[:date_to])
+    months = date_to.month - date_from.month + 1
+    params[:year] = date_from.year
+    params[:month] = date_from.month
+    params[:months] = months
+    @gantt = Redmine::Helpers::Gantt.new(params)
+    @gantt.project = @project
+    text, status = @gantt.edit(params)
+    render :text=>text, :status=>status
+  end
+
+  def find_optional_project
+    begin
+      if params[:action] && params[:action].to_s == "edit_gantt"
+        @project = Project.find(params[:project_id]) unless params[:project_id].blank?
+        allowed = User.current.allowed_to?(:edit_issues, @project, :global => true)
+        if allowed
+          return true
+        else
+          render :text=>"lack of permission to edit issues", :status=>403
+        end
+      else
+        super
+      end
+    rescue => e
+      return e.to_s + "\n===\n" + [$!,$@.join("\n")].join("\n")
+    end
+  end
 end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1034,6 +1034,12 @@ module ApplicationHelper
     javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
   end
 
+  def g_calendar_for(field_id)
+    include_calendar_headers_tags
+    image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
+    javascript_tag("Calendar.setup({inputField : '#{field_id}', electric : false, ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
+  end
+
   def include_calendar_headers_tags
     unless @calendar_headers_tags_included
       @calendar_headers_tags_included = true
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -51,8 +51,8 @@ module IssuesHelper
     link_to_issue(issue) + "<br /><br />".html_safe +
       "<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />".html_safe +
       "<strong>#{@cached_label_status}</strong>: #{h(issue.status.name)}<br />".html_safe +
-      "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />".html_safe +
-      "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />".html_safe +
+      "<strong>#{@cached_label_start_date}</strong>: <span id='tooltip_start_date_i#{issue.id}'>#{format_date(issue.start_date)}</span><br />".html_safe +
+      "<strong>#{@cached_label_due_date}</strong>: <span id='tooltip_due_date_i#{issue.id}'>#{format_date(issue.due_date)}</span><br />".html_safe +
       "<strong>#{@cached_label_assigned_to}</strong>: #{h(issue.assigned_to)}<br />".html_safe +
       "<strong>#{@cached_label_priority}</strong>: #{h(issue.priority.name)}".html_safe
   end
diff --git a/app/models/issue.rb b/app/models/issue.rb
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -617,6 +617,16 @@ class Issue < ActiveRecord::Base
     dependencies
   end
 
+  def all_precedes_issues
+    dependencies = []
+    relations_from.each do |relation|
+      next unless relation.relation_type == IssueRelation::TYPE_PRECEDES
+      dependencies << relation.issue_to
+      dependencies += relation.issue_to.all_dependent_issues
+    end
+    dependencies
+  end
+
   # Returns an array of issues that duplicate this one
   def duplicates
     relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
diff --git a/app/views/gantts/show.html.erb b/app/views/gantts/show.html.erb
--- a/app/views/gantts/show.html.erb
+++ b/app/views/gantts/show.html.erb
@@ -1,3 +1,4 @@
+<% include_calendar_headers_tags %>
 <% @gantt.view = self %>
 <h2><%= @query.new_record? ? l(:label_gantt) : h(@query.name) %></h2>
 
@@ -35,7 +36,7 @@
 <% zoom = 1
 @gantt.zoom.times { zoom = zoom * 2 }
 
-subject_width = 330
+subject_width = 280
 header_heigth = 18
 
 headers_height = header_heigth
@@ -62,6 +63,147 @@ t_height = g_height + headers_height
 
 %>
 
+<input type="hidden" name="_date_from" id="i_date_from" value="<%=h(@gantt.date_from)%>" />
+<input type="hidden" name="_date_to" id="i_date_to" value="<%=h(@gantt.date_to)%>" />
+<input type="hidden" name="zm" id="i_zm" value="<%=zoom%>" />
+<input type="hidden" name="pzm" id="i_pzm" value="<%=@gantt.zoom%>" />
+<script type='text/javascript'>
+  function issue_moved(elem) {
+    var id_str = elem.id.substring(3, elem.id.length);
+    var v_date_from = document.getElementById('i_date_from').getAttribute("value");
+    var v_date_to = document.getElementById('i_date_to').getAttribute("value");
+    var v_zm = document.getElementById('i_zm').getAttribute("value");
+    var v_pzm = document.getElementById('i_pzm').getAttribute("value");
+    var url_str = '<%=  url_for(:controller=>:gantts, :action => :edit_gantt) %>';
+    url_str = url_str + "/" + id_str;
+    var day = parseInt(elem.style.left)/parseInt(v_zm);
+    new Ajax.Request(url_str, {asynchronous:true, evalScripts:true,
+      parameters: 'day='+day+'&date_from='+v_date_from+'&date_to='+v_date_to+'&zoom='+v_pzm+"&project_id=<%= @project.to_param %>",
+      onSuccess:function(obj) {
+        change_dates(obj.responseText);
+      },
+      onFailure:function(obj) {
+        handle_failure(obj.responseText);
+      }
+    });
+
+  }
+
+  function handle_failure(res_text) {
+    var text = res_text.split('|');
+    alert(text[0]);
+    if (text.length == 1) {
+      return;
+    }
+    change_dates(text[1]);//revert
+  }
+
+  function change_dates(issue_infos) {
+    if (!issue_infos) {
+      return;
+    }
+    var issue_list = issue_infos.split("|");
+    for (i = 0; i < issue_list.length; i++) {
+      change_date(issue_list[i]);
+    }
+  }
+
+  function change_date(text) {
+    if (!text) {
+      return;
+    }
+    var issue_info = text.split("=");
+    var elem_id = issue_info[0];
+    var kind = elem_id.substring(0,1);
+    var preClassName = "";
+    if (kind == 'v') {
+      preClassName = "version ";
+    } else if (kind == 'p') {
+      preClassName = "project ";
+    }
+    var vals = issue_info[1].split(',');
+    var start_date_elem = document.getElementById(elem_id + '_start_date_str');
+    if (!start_date_elem) {
+      //target not exists
+      return;
+    }
+    start_date_elem.innerHTML = vals[0];
+    var tooltip_start_date_elem = document.getElementById('tooltip_start_date_' + elem_id);
+    if (tooltip_start_date_elem) {
+      tooltip_start_date_elem.innerHTML = vals[0];
+    }
+    var due_date_elem = document.getElementById(elem_id + '_due_date_str');
+    if (due_date_elem) {
+      due_date_elem.innerHTML = vals[2];
+    }
+    
+    var tooltip_due_date_elem = document.getElementById('tooltip_due_date_' + elem_id);
+    if (tooltip_due_date_elem) {
+      tooltip_due_date_elem.innerHTML = vals[2];
+    }
+    
+    var ev_elem = document.getElementById('ev_' + elem_id);
+    if (ev_elem) {
+      ev_elem.style.left = vals[4] + 'px';
+      ev_elem.style.width = (parseInt(vals[5])+100)+'px';
+    }
+    var todo_elem = document.getElementById('task_todo_' + elem_id);
+    if (todo_elem) {
+      todo_elem.style.width = vals[5] + 'px';
+    }
+    
+    var late_elem = document.getElementById('task_late_' + elem_id);
+    if (late_elem) {
+      var parentStr = "";
+      if (late_elem.className.indexOf("parent") > 0) parentStr = "parent ";
+      late_elem.style.width = vals[6] + 'px';
+      if (vals[6] == '0') {
+        late_elem.className = preClassName + 'task ' + parentStr + 'task_none';
+      } else {
+        late_elem.className = preClassName + 'task ' + parentStr + 'task_late';
+      }
+    }
+    var done_elem = document.getElementById('task_done_' + elem_id);
+    if (done_elem) {
+      var parentStr = "";
+      if (done_elem.className.indexOf("parent") > 0) parentStr = "parent ";
+      done_elem.style.width = vals[7] + 'px';
+      if (vals[7] == '0') {
+        done_elem.className = preClassName + 'task ' + parentStr + 'task_none';
+      } else {
+        done_elem.className = preClassName + 'task ' + parentStr + 'task_done';
+      }
+    }
+    var tooltip = document.getElementById("tt_" + elem_id);
+    if (tooltip) {
+      tooltip.style.left = ev_elem.style.left;
+    }
+
+    var label = document.getElementById("label_" + elem_id);
+    if (label) {
+      label.style.left = (parseInt(vals[4]) + parseInt(vals[5]) + 8) + 'px';
+    }
+    var marker_start = document.getElementById("marker_start_" + elem_id);
+    if (marker_start && vals[8]) {
+      marker_start.style.left = vals[8] + 'px';
+    }
+    var marker_end = document.getElementById("marker_end_" + elem_id);
+    if (marker_end && vals[9]) {
+      marker_end.style.left = vals[9] + 'px';
+    }
+
+    //change calendar date
+    var elm1 = document.getElementById(elem_id+"_hidden_start_date");
+    if (elm1) elm1.value = vals[1];
+    var elm2 = document.getElementById(elem_id+"_start_date");
+    if (elm2) elm2.value = vals[1];
+    var elm3 = document.getElementById(elem_id+"_hidden_due_date");
+    if (elm3) elm3.value = vals[3];
+    var elm4 = document.getElementById(elem_id+"_due_date");
+    if (elm4) elm4.value = vals[3];
+  }
+</script>
+
 <% if @gantt.truncated %>
   <p class="warning"><%= l(:notice_gantt_chart_truncated, :max => @gantt.max_rows) %></p>
 <% end %>
@@ -80,9 +222,19 @@ t_height = g_height + headers_height
 
 </div>
 </td>
+
+<td style="width:225px; padding:0px;">
+<div style="position:relative;height:<%= t_height + 24 %>px;width:225px;">
+<div style="width:225px;height:<%= headers_height %>px;background: #eee;" class="gantt_hdr"></div>
+<div style="width:225px;height:<%= t_height %>px;border-left: 1px solid #c0c0c0;overflow:hidden;" class="gantt_hdr"></div>
+<div class="gantt_subjects">
+  <%= @gantt.calendars.html_safe %>
+</div>
+</div>
+</td>
 <td>
 
-<div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;">
+<div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;" id="gantt-container">
 <div style="width:<%= g_width - 1 %>px;height:<%= headers_height %>px;background: #eee;" class="gantt_hdr">&nbsp;</div>
 <%
 #
@@ -142,15 +294,18 @@ if show_days
   left = 0
   height = g_height + header_heigth - 1
   wday = @gantt.date_from.cwday
+  dt = @gantt.date_from
   (@gantt.date_to - @gantt.date_from + 1).to_i.times do
   width =  zoom - 1
   %>
   <div style="left:<%= left %>px;top:37px;width:<%= width %>px;height:<%= height %>px;font-size:0.7em;<%= "background:#f1f1f1;" if wday > 5 %>" class="gantt_hdr">
   <%= day_name(wday).first %>
+    <%= "#{dt.day}<br>".html_safe if @gantt.zoom == 4 %><%= day_name(wday).first %>
   </div>
   <%
   left = left + width + 1
   wday = wday + 1
+  dt = dt + 1
   wday = 1 if wday > 7
   end
 end %>
diff --git a/config/routes.rb b/config/routes.rb
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -53,6 +53,10 @@ RedmineApp::Application.routes.draw do
 
   match '/projects/:project_id/issues/gantt', :to => 'gantts#show'
   match '/issues/gantt', :to => 'gantts#show'
+  match '/gantts/edit_gantt', :controller => 'gantts', :action => 'edit_gantt',
+         :via => [:get]
+  match '/gantts/edit_gantt/:id', :controller => 'gantts', :action => 'edit_gantt',
+         :via => [:post]
 
   match '/projects/:project_id/issues/calendar', :to => 'calendars#show'
   match '/issues/calendar', :to => 'calendars#show'
diff --git a/lib/redmine/helpers/gantt.rb b/lib/redmine/helpers/gantt.rb
--- a/lib/redmine/helpers/gantt.rb
+++ b/lib/redmine/helpers/gantt.rb
@@ -70,6 +70,7 @@ module Redmine
 
         @subjects = ''
         @lines = ''
+        @calendars = ''
         @number_of_rows = nil
 
         @issue_ancestors = []
@@ -129,6 +130,12 @@ module Redmine
         @lines
       end
 
+      # Renders the calendars of the Gantt chart, the right side
+      def calendars(options={})
+        render(options.merge(:only => :calendars)) unless @calendars_rendered
+        @calendars
+      end
+
       # Returns issues that will be rendered
       def issues
         @issues ||= @query.issues(
@@ -175,8 +182,15 @@ module Redmine
         options = {:top => 0, :top_increment => 20, :indent_increment => 20, :render => :subject, :format => :html}.merge(options)
         indent = options[:indent] || 4
 
-        @subjects = '' unless options[:only] == :lines
-        @lines = '' unless options[:only] == :subjects
+        if options[:format] == :html
+          @subjects = '' unless options[:only] == :lines && options[:only] == :calendars
+          @lines = '' unless options[:only] == :subjects && options[:only] == :calendars
+          @calendars = '' unless options[:only] == :lines && options[:only] == :subjects
+        else
+          @subjects = '' unless options[:only] == :lines
+          @lines = '' unless options[:only] == :subjects
+        end
+
         @number_of_rows = 0
 
         Project.project_tree(projects) do |project, level|
@@ -185,16 +199,26 @@ module Redmine
           break if abort?
         end
 
-        @subjects_rendered = true unless options[:only] == :lines
-        @lines_rendered = true unless options[:only] == :subjects
-
+        if options[:format] == :html
+          @subjects_rendered = true unless options[:only] == :lines && options[:only] == :calendars
+          @lines_rendered = true unless options[:only] == :subjects && options[:only] == :calendars
+          @calendars_rendered = true unless options[:only] == :lines && options[:only] == :subjects
+        else
+          @subjects_rendered = true unless options[:only] == :lines
+          @lines_rendered = true unless options[:only] == :subjects
+        end
         render_end(options)
       end
 
       def render_project(project, options={})
-        subject_for_project(project, options) unless options[:only] == :lines
-        line_for_project(project, options) unless options[:only] == :subjects
-
+        if options[:format] == :html
+          subject_for_project(project, options) unless options[:only] == :lines && options[:only] == :calendars
+          line_for_project(project, options) unless options[:only] == :subjects && options[:only] == :calendars
+          calendar_for_project(project, options) unless options[:only] == :lines && options[:only] == :subjects
+        else
+          subject_for_project(project, options) unless options[:only] == :lines
+          line_for_project(project, options) unless options[:only] == :subjects
+        end
         options[:top] += options[:top_increment]
         options[:indent] += options[:indent_increment]
         @number_of_rows += 1
@@ -218,11 +242,15 @@ module Redmine
 
       def render_issues(issues, options={})
         @issue_ancestors = []
-
         issues.each do |i|
-          subject_for_issue(i, options) unless options[:only] == :lines
-          line_for_issue(i, options) unless options[:only] == :subjects
-
+          if options[:format] == :html
+            subject_for_issue(i, options) unless options[:only] == :lines && options[:only] == :calendars
+            line_for_issue(i, options) unless options[:only] == :subjects && options[:only] == :calendars
+            calendar_for_issue(i, options) unless options[:only] == :lines && options[:only] == :subjects
+          else
+            subject_for_issue(i, options) unless options[:only] == :lines
+            line_for_issue(i, options) unless options[:only] == :subjects
+          end
           options[:top] += options[:top_increment]
           @number_of_rows += 1
           break if abort?
@@ -233,9 +261,14 @@ module Redmine
 
       def render_version(project, version, options={})
         # Version header
-        subject_for_version(version, options) unless options[:only] == :lines
-        line_for_version(version, options) unless options[:only] == :subjects
-
+        if options[:format] == :html
+            subject_for_version(version, options) unless options[:only] == :lines && options[:only] == :calendars
+            line_for_version(version, options) unless options[:only] == :subjects && options[:only] == :calendars
+            calendar_for_version(version, options) unless options[:only] == :lines && options[:only] == :subjects
+        else
+            subject_for_version(version, options) unless options[:only] == :lines
+            line_for_version(version, options) unless options[:only] == :subjects
+        end
         options[:top] += options[:top_increment]
         @number_of_rows += 1
         return if abort?
@@ -283,7 +316,7 @@ module Redmine
 
           case options[:format]
           when :html
-            html_task(options, coords, :css => "project task", :label => label, :markers => true)
+            html_task(options, coords, :css => "project task", :label => label, :markers => true, :id => project.id, :kind => "p")
           when :image
             image_task(options, coords, :label => label, :markers => true, :height => 3)
           when :pdf
@@ -322,7 +355,7 @@ module Redmine
 
           case options[:format]
           when :html
-            html_task(options, coords, :css => "version task", :label => label, :markers => true)
+            html_task(options, coords, :css => "version task", :label => label, :markers => true, :id => version.id, :kind => "v")
           when :image
             image_task(options, coords, :label => label, :markers => true, :height => 3)
           when :pdf
@@ -346,7 +379,6 @@ module Redmine
           css_classes << ' issue-overdue' if issue.overdue?
           css_classes << ' issue-behind-schedule' if issue.behind_schedule?
           css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
-
           subject = "<span class='#{css_classes}'>".html_safe
           if issue.assigned_to.present?
             assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
@@ -375,10 +407,17 @@ module Redmine
         if issue.is_a?(Issue) && issue.due_before
           coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom])
           label = "#{ issue.status.name } #{ issue.done_ratio }%"
-
+          if !issue.due_date && issue.fixed_version
+            if options[:format] == :html
+              label += "-&nbsp;<strong>#{h(issue.fixed_version.name)}</strong>"
+            else
+              label += "-#{h(issue.fixed_version.name)}"
+            end
+          end
           case options[:format]
           when :html
-            html_task(options, coords, :css => "task " + (issue.leaf? ? 'leaf' : 'parent'), :label => label, :issue => issue, :markers => !issue.leaf?)
+            html_task(options, coords, :css => "task " + (issue.leaf? ? 'leaf' : 'parent'),
+                     :label => label, :issue => issue, :markers => !issue.leaf?, :id => issue.id, :kind => "i")
           when :image
             image_task(options, coords, :label => label)
           when :pdf
@@ -618,6 +657,105 @@ module Redmine
         pdf.Output
       end
 
+      def edit(pms)
+        id = pms[:id]
+        kind = id.slice!(0).chr
+        begin
+          case kind
+          when 'i'
+            @issue = Issue.find(pms[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
+          when 'p'
+            @issue = Project.find(pms[:id])
+          when 'v'
+            @issue = Version.find(pms[:id], :include => [:project])
+          end
+        rescue ActiveRecord::RecordNotFound
+          return "issue not found : #{pms[:id]}", 400
+        end
+
+        if !@issue.start_date || !@issue.due_before
+          #render :text=>l(:notice_locking_conflict), :status=>400
+          return l(:notice_locking_conflict), 400
+        end
+        @issue.init_journal(User.current)
+        date_from = Date.parse(pms[:date_from])
+        old_start_date = @issue.start_date
+        o = get_issue_position(@issue, pms[:zoom])
+        text_for_revert = "#{kind}#{id}=#{format_date(@issue.start_date)},#{@issue.start_date},#{format_date(@issue.due_before)},#{@issue.due_before},#{o[0]},#{o[1]},#{o[2]},#{o[3]}".html_safe
+
+        if pms[:day]
+          #bar moved
+          day = pms[:day].to_i
+          duration = @issue.due_before - @issue.start_date
+          @issue.start_date = date_from + day
+          @issue.due_date = @issue.start_date + duration.to_i if @issue.due_date
+        elsif pms[:start_date]
+          #start date changed
+          start_date = Date.parse(pms[:start_date])
+          if @issue.start_date == start_date
+            #render :text=>""
+            return "", 200 #nothing has changed
+          end
+          @issue.start_date = start_date
+          @issue.due_date = start_date if @issue.due_date && start_date > @issue.due_date
+        elsif pms[:due_date]
+          #due date changed
+          due_date = Date.parse(pms[:due_date])
+          if @issue.due_date == due_date
+            #render :text=>""
+            return "", 200 #nothing has changed
+          end
+          @issue.due_date = due_date
+          @issue.start_date = due_date if due_date < @issue.start_date
+        end
+        fv = @issue.fixed_version
+        if fv && fv.effective_date && !@issue.due_date && fv.effective_date < @issue.start_date
+          @issue.start_date = old_start_date
+        end
+
+        begin
+          @issue.save!
+          o = get_issue_position(@issue, pms[:zoom])
+          text = "#{kind}#{id}=#{format_date(@issue.start_date)},#{@issue.start_date},#{format_date(@issue.due_before)},#{@issue.due_before},#{o[0]},#{o[1]},#{o[2]},#{o[3]}".html_safe
+
+          prj_map = {}
+          text = set_project_data(@issue.project, pms[:zoom], text, prj_map)
+          version_map = {}
+          text = set_version_data(@issue.fixed_version, pms[:zoom], text, version_map)
+
+          #check dependencies
+          issues = @issue.all_precedes_issues
+          issues.each do |i|
+            o = get_issue_position(i, pms[:zoom])
+            text += "|i#{i.id}=#{format_date(i.start_date)},#{i.start_date},#{format_date(i.due_before)},#{i.due_before},#{o[0]},#{o[1]},#{o[2]},#{o[3]}".html_safe
+            text = set_project_data(i.project, pms[:zoom], text, prj_map)
+            text = set_version_data(i.fixed_version, pms[:zoom], text, version_map)
+          end
+
+          #check parent
+          is = @issue
+          while
+            pid = is.parent_issue_id
+            break if !pid
+            i = Issue.find(pid)
+            o = get_issue_position(i, pms[:zoom])
+            text += "|i#{i.id}=#{format_date(i.start_date)},#{i.start_date},#{format_date(i.due_before)},#{i.due_before},#{o[0]},#{o[1]},#{o[2]},#{o[3]},#{o[4]},#{o[5]}".html_safe
+            text = set_project_data(i.project, pms[:zoom], text, prj_map)
+            text = set_version_data(i.fixed_version, pms[:zoom], text, version_map)
+            is = i
+          end
+          #render :text=>text
+          return text, 200
+        rescue => e
+          #render :text=>@issue.errors.full_messages.join("\n") + "|" + text_for_revert  , :status=>400
+          if @issue.errors.full_messages.to_s == ""
+            return e.to_s + "\n" + [$!,$@.join("\n")].join("\n") + "\n" + @issue.errors.full_messages.join("\n") + "|" + text_for_revert, 400
+          else
+            return @issue.errors.full_messages.join("\n") + "|" + text_for_revert, 400
+          end
+        end
+      end
+
       private
 
       def coordinates(start_date, end_date, progress, zoom=nil)
@@ -737,33 +875,50 @@ module Redmine
         output = ''
         # Renders the task bar, with progress and late
         if coords[:bar_start] && coords[:bar_end]
-          output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_todo'>&nbsp;</div>".html_safe
+          i_width = coords[:bar_end] - coords[:bar_start] - 2
+          output << "<div id='ev_#{options[:kind]}#{options[:id]}' style='position:absolute;left:#{coords[:bar_start]}px;top:#{params[:top]}px;padding-top:3px;height:18px;width:#{ i_width + 100}px;' #{options[:kind] == 'i' ? "class='handle'" : ""}>".html_safe
+          output << "<div id='task_todo_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:#{ i_width}px;' class='#{options[:css]} task_todo'>&nbsp;</div>".html_safe
 
           if coords[:bar_late_end]
-            output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_late_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_late'>&nbsp;</div>".html_safe
+            l_width = coords[:bar_late_end] - coords[:bar_start] - 2
+            output << "<div id='task_late_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:#{ l_width}px;' class='#{ l_width == 0 ? options[:css] + " task_none" : options[:css] + " task_late"}'>&nbsp;</div>".html_safe
+          else
+            output << "<div id='task_late_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:0px;' class='#{ options[:css] + " task_none"}'>&nbsp;</div>".html_safe
           end
           if coords[:bar_progress_end]
-            output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_progress_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_done'>&nbsp;</div>".html_safe
+            d_width = coords[:bar_progress_end] - coords[:bar_start] - 2
+            output << "<div id='task_done_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:#{ d_width}px;' class='#{ d_width == 0 ? options[:css] + " task_none" : options[:css] + " task_done"}'>&nbsp;</div>".html_safe
+          else
+            output << "<div id='task_done_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:0px;' class='#{ options[:css] + " task_none"}'>&nbsp;</div>".html_safe
           end
+          output << "</div>".html_safe
+        else
+          output << "<div id='ev_#{options[:kind]}#{options[:id]}' style='position:absolute;left:0px;top:#{params[:top]}px;padding-top:3px;height:18px;width:0px;' #{options[:kind] == 'i' ? "class='handle'" : ""}>".html_safe
+          output << "<div id='task_todo_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:0px;' class='#{ options[:css]} task_todo'>&nbsp;</div>".html_safe
+          output << "<div id='task_late_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:0px;' class='#{ options[:css] + " task_none"}'>&nbsp;</div>".html_safe
+          output << "<div id='task_done_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:0px;' class='#{ options[:css] + " task_none"}'>&nbsp;</div>".html_safe
+          output << "</div>".html_safe
         end
         # Renders the markers
         if options[:markers]
           if coords[:start]
-            output << "<div style='top:#{ params[:top] }px;left:#{ coords[:start] }px;width:15px;' class='#{options[:css]} marker starting'>&nbsp;</div>".html_safe
+            output << "<div id='marker_start_#{options[:kind]}#{options[:id]}' style='top:#{ params[:top] }px;left:#{ coords[:start] }px;width:15px;' class='#{options[:css]} marker starting'>&nbsp;</div>".html_safe
           end
           if coords[:end]
-            output << "<div style='top:#{ params[:top] }px;left:#{ coords[:end] + params[:zoom] }px;width:15px;' class='#{options[:css]} marker ending'>&nbsp;</div>".html_safe
+            output << "<div id='marker_end_#{options[:kind]}#{options[:id]}' style='top:#{ params[:top] }px;left:#{ coords[:end] + params[:zoom] }px;width:15px;' class='#{options[:css]} marker ending'>&nbsp;</div>".html_safe
           end
+        else
+          output << view.draggable_element("ev_#{options[:kind]}#{options[:id]}", :revert =>false, :scroll=>"'gantt-container'", :constraint => "'horizontal'", :snap=>params[:zoom],:onEnd=>'function( draggable, event )  {issue_moved(draggable.element);}')         #pend
         end
         # Renders the label on the right
         if options[:label]
-          output << "<div style='top:#{ params[:top] }px;left:#{ (coords[:bar_end] || 0) + 8 }px;' class='#{options[:css]} label'>".html_safe
+          output << "<div id='label_#{options[:kind]}#{options[:id]}' style='top:#{ params[:top] }px;left:#{ (coords[:bar_end] || 0) + 8 }px;' class='#{options[:css]} label'>".html_safe
           output << options[:label]
           output << "</div>".html_safe
         end
         # Renders the tooltip
         if options[:issue] && coords[:bar_start] && coords[:bar_end]
-          output << "<div class='tooltip' style='position: absolute;top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_end] - coords[:bar_start] }px;height:12px;'>".html_safe
+          output << "<div id='tt_#{options[:kind]}#{options[:id]}' class='tooltip' style='position: absolute;top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_end] - coords[:bar_start] }px;height:12px;'>".html_safe
           output << '<span class="tip">'.html_safe
           output << view.render_issue_tooltip(options[:issue]).html_safe
           output << "</span></div>".html_safe
@@ -855,6 +1010,207 @@ module Redmine
           params[:image].text(params[:subject_width] + (coords[:bar_end] || 0) + 5,params[:top] + 1, options[:label])
         end
       end
+
+      ##  for edit gantt
+      def set_project_data(prj, zoom, text, prj_map = {})
+        if !prj
+          return text
+        end
+        if !prj_map[prj.id]
+          o = get_project_position(prj, zoom)
+          text += "|p#{prj.id}=#{format_date(prj.start_date)},#{prj.start_date},#{format_date(prj.due_date)},#{prj.due_date},#{o[0]},#{o[1]},#{o[2]},#{o[3]},#{o[4]},#{o[5]}"
+          prj_map[prj.id] = prj
+        end
+        text = set_project_data(prj.parent, zoom, text, prj_map)
+      end
+
+      def set_version_data(version, zoom, text, version_map = {})
+        if !version
+          return text
+        end
+        if !version_map[version.id]
+          o = get_version_position(version, zoom)
+          text += "|v#{version.id}=#{format_date(version.start_date)},#{version.start_date},#{format_date(version.due_date)},#{version.due_date},#{o[0]},#{o[1]},#{o[2]},#{o[3]},#{o[4]},#{o[5]}"
+          version_map[version.id] = version
+        end
+        return text
+      end
+
+      def get_pos(coords)
+        i_left = 0
+        i_width = 0
+        l_width = 0
+        d_width = 0
+        if coords[:bar_start]
+          i_left = coords[:bar_start]
+          if coords[:bar_end]
+            i_width = coords[:bar_end] - coords[:bar_start] - 2
+            i_width = 0 if i_width < 0
+          end
+          if coords[:bar_late_end]
+            l_width = coords[:bar_late_end] - coords[:bar_start] - 2
+          end
+          if coords[:bar_progress_end]
+            d_width = coords[:bar_progress_end] - coords[:bar_start] - 2
+          end
+        end
+        return i_left, i_width, l_width, d_width
+      end
+
+      def get_issue_position(issue, zoom_str)
+        z = zoom_str.to_i
+        zoom = 1
+        z.times { zoom = zoom * 2}
+        id = issue.due_before
+        if id && @date_to < id
+          id = @date_to
+        end
+        coords = coordinates(issue.start_date, id, issue.done_ratio, zoom)
+
+        i_left, i_width, l_width, d_width = get_pos(coords)
+        if coords[:end]
+          return i_left, i_width, l_width, d_width, coords[:start], coords[:end] + zoom
+        else
+          return i_left, i_width, l_width, d_width, coords[:start], nil
+        end
+      end
+
+      def get_project_position(project, zoom_str)
+        z = zoom_str.to_i
+        zoom = 1
+        z.times { zoom = zoom * 2}
+        pd = project.due_date
+        if pd && @date_to < pd
+          pd = @date_to
+        end
+        coords = coordinates(project.start_date, pd, nil, zoom)
+        i_left, i_width, l_width, d_width = get_pos(coords)
+        if coords[:end]
+          return i_left, i_width, l_width, d_width, coords[:start], coords[:end] + zoom
+        else
+          return i_left, i_width, l_width, d_width, coords[:start], nil
+        end
+      end
+
+      def get_version_position(version, zoom_str)
+        z = zoom_str.to_i
+        zoom = 1
+        z.times { zoom = zoom * 2}
+        vd = version.due_date
+        if vd &&  @date_to < vd
+          vd = @date_to
+        end
+        coords = coordinates(version.start_date, vd, version.completed_pourcent, zoom)
+        i_left, i_width, l_width, d_width = get_pos(coords)
+        if coords[:end]
+          return i_left, i_width, l_width, d_width, coords[:start], coords[:end] + zoom
+        else
+          return i_left, i_width, l_width, d_width, coords[:start], nil
+        end
+      end
+
+      def calendar_for_issue(issue, options)
+        # Skip issues that don't have a due_before (due_date or version's due_date)
+        if issue.is_a?(Issue) && issue.due_before
+
+          case options[:format]
+          when :html
+            @calendars << "<div style='position: absolute;line-height:1.2em;height:16px;top:#{options[:top]}px;left:4px;overflow:hidden;'>"
+            start_date = issue.start_date
+            if start_date
+              @calendars << "<span id='i#{issue.id}_start_date_str'>"
+              @calendars << format_date(start_date)
+              @calendars << "</span>"
+              @calendars << "<input type='hidden' size='12' id='i#{issue.id}_hidden_start_date' value='#{start_date}' />"
+              if issue.leaf?
+                @calendars << "<input type='hidden' size='12' id='i#{issue.id}_start_date' value='#{start_date}' />#{view.g_calendar_for('i' + issue.id.to_s + '_start_date')}"
+              else
+                @calendars << "<input type='hidden' size='12' id='i#{issue.id}_start_date' value='#{start_date}' />&nbsp;&nbsp;&nbsp;"
+              end
+              @calendars << observe_date_field("i#{issue.id}", 'start')
+            end
+            due_date = issue.due_date
+            if due_date
+              @calendars << "<span id='i#{issue.id}_due_date_str'>"
+              @calendars << format_date(due_date)
+              @calendars << "</span>"
+              @calendars << "<input type='hidden' size='12' id='i#{issue.id}_hidden_due_date' value='#{due_date}' />"
+              if issue.leaf?
+                @calendars << "<input type='hidden' size='12' id='i#{issue.id}_due_date' value='#{due_date}' />#{view.g_calendar_for('i' + issue.id.to_s + '_due_date')}"
+              else
+                @calendars << "<input type='hidden' size='12' id='i#{issue.id}_due_date' value='#{due_date}' />"
+              end
+              @calendars << observe_date_field("i#{issue.id}", 'due')
+            end
+            @calendars << "</div>"
+          when :image
+            #nop
+          when :pdf
+            #nop
+          end
+        else
+          ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
+          ''
+        end
+      end
+
+      def calendar_for_version(version, options)
+        # Skip version that don't have a due_before (due_date or version's due_date)
+        if version.is_a?(Version) && version.start_date && version.due_date
+
+          case options[:format]
+          when :html
+            @calendars << "<div style='position: absolute;line-height:1.2em;height:16px;top:#{options[:top]}px;left:4px;overflow:hidden;'>"
+            @calendars << "<span id='v#{version.id}_start_date_str'>"
+            @calendars << format_date(version.effective_date)
+            @calendars << "</span>"
+            @calendars << "</div>"
+          when :image
+            #nop
+          when :pdf
+            #nop
+          end
+        else
+          ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
+          ''
+        end
+      end
+
+      def calendar_for_project(project, options)
+        case options[:format]
+        when :html
+          @calendars << "<div style='position: absolute;line-height:1.2em;height:16px;top:#{options[:top]}px;left:4px;overflow:hidden;'>"
+          @calendars << "<span id='p#{project.id}_start_date_str'>"
+          @calendars << format_date(project.start_date) if project.start_date
+          @calendars << "</span>"
+          @calendars << "&nbsp;&nbsp;&nbsp;"
+          @calendars << "<span id='p#{project.id}_due_date_str'>"
+          @calendars << format_date(project.due_date) if project.due_date
+          @calendars << "</span>"
+          @calendars << "</div>"
+        when :image
+          # nop
+        when :pdf
+          # nop
+        end
+      end
+
+      def observe_date_field(id, type)
+        output = ''
+        prj_id = ''
+        prj_id = @project.to_param if @project
+        output << "<script type='text/javascript'>\n"
+        output << "//<![CDATA[\n"
+        output << "new Form.Element.Observer('#{id}_#{type}_date', 0.25,\n"
+        output << "  function(element, value) {\n"
+        output << "    if (value == document.getElementById('#{id}_hidden_#{type}_date').value) {\n"
+        output << "      return ;\n"
+        output << "    }\n"
+        output << "    new Ajax.Request('#{view.url_for(:controller=>:gantts, :action => :edit_gantt, :id=>id, :date_from=>self.date_from.strftime("%Y-%m-%d"), :date_to=>self.date_to.strftime("%Y-%m-%d"), :zoom=>self.zoom, :escape => false, :project_id=>prj_id)}', {asynchronous:true, evalScripts:true, onFailure:function(request){handle_failure(request.responseText)}, onSuccess:function(request){change_dates(request.responseText)}, parameters:'#{type}_date=' + encodeURIComponent(value)});"
+        output << "  })\n"
+        output << "//]]>\n"
+        output << "</script>"
+      end
     end
   end
 end
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
--- a/public/stylesheets/application.css
+++ b/public/stylesheets/application.css
@@ -914,6 +914,7 @@ background-image:url('../images/close_hl
 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
 .task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; }
 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
+.task_none { background:transparent; border-style: none; }
 
 .task_todo.parent { background: #888; border: 1px solid #888; height: 3px;}
 .task_late.parent, .task_done.parent { height: 3px;}
diff --git a/test/integration/routing/gantts_test.rb b/test/integration/routing/gantts_test.rb
--- a/test/integration/routing/gantts_test.rb
+++ b/test/integration/routing/gantts_test.rb
@@ -37,5 +37,14 @@ class RoutingGanttsTest < ActionControll
         { :controller => 'gantts', :action => 'show',
           :project_id => 'project-name', :format => 'pdf' }
       )
+    assert_routing(
+        { :method => 'get', :path => "/gantts/edit_gantt" },
+        { :controller => 'gantts', :action => 'edit_gantt' }
+      )
+    assert_routing(
+        { :method => 'post', :path => "/gantts/edit_gantt/123" },
+        { :controller => 'gantts', :action => 'edit_gantt',
+          :id => '123' }
+      )
   end
 end
