# HG changeset patch
# User Toshi MARUYAMA
# Date 1353885966 -32400
# Node ID 24423aba7d99d94cb8d5ae2f7cfa70e431b989a5
# Parent  28d9b1f2ffa16f6bb78d84fe912051b27b1c10fa
gantt: progress line (#12122)

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
@@ -12,6 +12,15 @@
     <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
   </div>
 </fieldset>
+<fieldset id="filters" class="collapsible">
+  <legend onclick="toggleFieldset(this);"><%= l(:label_display) %></legend>
+  <div>
+    <label>
+      <%= check_box_tag "draw_progress_line", 0, params[:draw_progress_line] %>
+      <%= l(:label_gantt_progress_line) %>
+    </label>
+  </div>
+</fieldset>
 
 <p class="contextual">
   <%= gantt_zoom_link(@gantt, :in) %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -889,6 +889,7 @@ en:
   label_cross_project_tree: With project tree
   label_cross_project_hierarchy: With project hierarchy
   label_cross_project_system: With all projects
+  label_gantt_progress_line: Progress line
 
   button_login: Login
   button_submit: Submit
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -840,6 +840,7 @@ ja:
   label_git_report_last_commit: ファイルとディレクトリの最新コミットを表示する
   label_parent_revision: 親
   label_child_revision: 子
+  label_gantt_progress_line: イナズマ線
 
   button_login: ログイン
   button_submit: 送信
# HG changeset patch
# User Toshi MARUYAMA
# Date 1353970717 -32400
# Node ID 8fa49c82f4f88f8c27b3f671dd91910c8d744887
# Parent  24423aba7d99d94cb8d5ae2f7cfa70e431b989a5
gantt: progress line (#12122)

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
@@ -289,10 +289,18 @@ module Redmine
           html_class << 'icon icon-package '
           html_class << (version.behind_schedule? ? 'version-behind-schedule' : '') << " "
           html_class << (version.overdue? ? 'version-overdue' : '')
+          html_class << ' version-closed' unless version.open?
+          if version.start_date && version.due_date && version.completed_pourcent
+            progress_date = calc_progress_date(version.start_date,
+                                               version.due_date, version.completed_pourcent)
+            html_class << ' behind-start-date' if progress_date < self.date_from
+            html_class << ' over-end-date' if progress_date > self.date_to
+          end
           s = view.link_to_version(version).html_safe
           subject = view.content_tag(:span, s,
                                      :class => html_class).html_safe
-          html_subject(options, subject, :css => "version-name")
+          html_subject(options, subject, :css => "version-name",
+                       :id => "version-#{version.id}")
         when :image
           image_subject(options, version.to_s_with_project)
         when :pdf
@@ -313,7 +321,8 @@ module Redmine
           label = h("#{version.project} -") + label unless @project && @project == version.project
           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, :version => version)
           when :image
             image_task(options, coords, :label => label, :markers => true, :height => 3)
           when :pdf
@@ -336,6 +345,13 @@ 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
+          css_classes << ' issue-closed' if issue.closed?
+          if issue.start_date && issue.due_before && issue.done_ratio
+            progress_date = calc_progress_date(issue.start_date,
+                                               issue.due_before, issue.done_ratio)
+            css_classes << ' behind-start-date' if progress_date < self.date_from
+            css_classes << ' over-end-date' if progress_date > self.date_to
+          end
           s = "".html_safe
           if issue.assigned_to.present?
             assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
@@ -347,7 +363,7 @@ module Redmine
           s << view.link_to_issue(issue).html_safe
           subject = view.content_tag(:span, s, :class => css_classes).html_safe
           html_subject(options, subject, :css => "issue-subject",
-                       :title => issue.subject) + "\n"
+                       :title => issue.subject, :id => "issue-#{issue.id}") + "\n"
         when :image
           image_subject(options, issue.subject)
         when :pdf
@@ -610,7 +626,7 @@ module Redmine
             coords[:bar_end] = self.date_to - self.date_from + 1
           end
           if progress
-            progress_date = start_date + (end_date - start_date + 1) * (progress / 100.0)
+            progress_date = calc_progress_date(start_date, end_date, progress)
             if progress_date > self.date_from && progress_date > start_date
               if progress_date < self.date_to
                 coords[:bar_progress_end] = progress_date - self.date_from
@@ -637,6 +653,10 @@ module Redmine
         coords
       end
 
+      def calc_progress_date(start_date, end_date, progress)
+        start_date + (end_date - start_date + 1) * (progress / 100.0)
+      end
+
       # Sorts a collection of issues by start_date, due_date, id for gantt rendering
       def sort_issues!(issues)
         issues.sort! { |a, b| gantt_issue_compare(a, b) }
@@ -677,9 +697,10 @@ module Redmine
       def html_subject(params, subject, options={})
         style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;"
         style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width]
-        output = view.content_tag('div', subject,
+        output = view.content_tag(:div, subject,
                                   :class => options[:css], :style => style,
-                                  :title => options[:title])
+                                  :title => options[:title],
+                                  :id => options[:id])
         @subjects << output
         output
       end
@@ -713,9 +734,12 @@ module Redmine
           style << "top:#{params[:top]}px;"
           style << "left:#{coords[:bar_start]}px;"
           style << "width:#{width}px;"
+          html_id = "task-todo-issue-#{options[:issue].id}" if options[:issue]
+          html_id = "task-todo-version-#{options[:version].id}" if options[:version]
           output << view.content_tag(:div, '&nbsp;'.html_safe,
                                      :style => style,
-                                     :class => "#{options[:css]} task_todo")
+                                     :class => "#{options[:css]} task_todo",
+                                     :id => html_id)
           if coords[:bar_late_end]
             width = coords[:bar_late_end] - coords[:bar_start] - 2
             style = ""
@@ -732,9 +756,12 @@ module Redmine
             style << "top:#{params[:top]}px;"
             style << "left:#{coords[:bar_start]}px;"
             style << "width:#{width}px;"
+            html_id = "task-done-issue-#{options[:issue].id}" if options[:issue]
+            html_id = "task-done-version-#{options[:version].id}" if options[:version]
             output << view.content_tag(:div, '&nbsp;'.html_safe,
                                        :style => style,
-                                       :class => "#{options[:css]} task_done")
+                                       :class => "#{options[:css]} task_done",
+                                       :id => html_id)
           end
         end
         # Renders the markers
# HG changeset patch
# User Toshi MARUYAMA
# Date 1353882249 -32400
# Node ID b5a9f37034e3554a2245511b28fc92237ab67f06
# Parent  8fa49c82f4f88f8c27b3f671dd91910c8d744887
gantt: progress line (#12122)

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,15 @@
+<% content_for :header_tags do %>
+  <%= javascript_include_tag 'raphael' %>
+  <%= javascript_include_tag 'gantt' %>
+<% end %>
+<%= javascript_tag do %>
+  $(document).ready(drawGanttLinesHandler);
+  $(window).resize(drawGanttLinesHandler);
+  $(function() {
+    $("#draw_progress_line").change(drawGanttLinesHandler);
+  });
+
+<% end %>
 <% @gantt.view = self %>
 <h2><%= @query.new_record? ? l(:label_gantt) : h(@query.name) %></h2>
 
@@ -238,9 +250,17 @@
     style += "width:10px;"
     style += "border-left: 1px dashed red;"
   %>
-  <%= content_tag(:div, '&nbsp;'.html_safe, :style => style) %>
+  <%= content_tag(:div, '&nbsp;'.html_safe, :style => style, :id => 'today_line') %>
 <% end %>
-
+<%
+  style  = ""
+  style += "position: absolute;"
+  style += "height: #{g_height}px;"
+  style += "top: #{headers_height + 1}px;"
+  style += "left: 0px;"
+  style += "width: #{g_width - 1}px;"
+%>
+<%= content_tag(:div, '', :style => style, :id => "gantt_draw_area") %>
 </div>
 </td>
 </tr>
# HG changeset patch
# User Toshi MARUYAMA
# Date 1353971293 -32400
# Node ID 0de173ee4be2a5c1b2671de9ff99ed1ba590d7dd
# Parent  b5a9f37034e3554a2245511b28fc92237ab67f06
gantt: progress line (#12122)

diff --git a/public/javascripts/gantt.js b/public/javascripts/gantt.js
new file mode 100644
--- /dev/null
+++ b/public/javascripts/gantt.js
@@ -0,0 +1,75 @@
+var draw_gantt = null;
+var draw_top;
+var draw_right;
+
+function setDrawArea() {
+  draw_top   = $("#gantt_draw_area").position().top;
+  draw_right = $("#gantt_draw_area").width();
+}
+
+function getProgressLinesArray() {
+  var arr = new Array();
+  var today_left = $('#today_line').position().left;
+  arr.push({left: today_left, top: 0});
+  $.each($('div.issue-subject, div.version-name'), function(index, element) {
+    var t = $(element).position().top - draw_top ;
+    var h = ($(element).height() / 9);
+    var element_top_upper  = t - h;
+    var element_top_center = t + (h * 3);
+    var element_top_lower  = t + (h * 8);
+    var issue_closed   = $(element).children('span').hasClass('issue-closed');
+    var version_closed = $(element).children('span').hasClass('version-closed');
+    if (issue_closed || version_closed) {
+      arr.push({left: today_left, top: element_top_center});
+    } else {
+      var issue_done = $("#task-done-" + $(element).attr("id"));
+      var is_behind_start = $(element).children('span').hasClass('behind-start-date');
+      var is_over_end     = $(element).children('span').hasClass('over-end-date');
+      if (is_over_end) {
+        arr.push({left: draw_right, top: element_top_upper, is_right_edge: true});
+        arr.push({left: draw_right, top: element_top_lower, is_right_edge: true, none_stroke: true});
+      } else if (issue_done.size() > 0) {
+        var done_left = issue_done.first().position().left +
+                           issue_done.first().width();
+        arr.push({left: done_left, top: element_top_center});
+      } else if (is_behind_start) {
+        arr.push({left: 0 , top: element_top_upper, is_left_edge: true});
+        arr.push({left: 0 , top: element_top_lower, is_left_edge: true, none_stroke: true});
+      } else {
+        var todo_left = today_left;
+        var issue_todo = $("#task-todo-" + $(element).attr("id"));
+        if (issue_todo.size() > 0){
+          todo_left = issue_todo.first().position().left;
+        }
+        arr.push({left: Math.min(today_left, todo_left), top: element_top_center});
+      }
+    }
+  });
+  return arr;
+}
+
+function drawGanttLines() {
+  var arr = getProgressLinesArray();
+  var i;
+  for(i = 1 ; i < arr.length ; i++) {
+    if (!("none_stroke" in arr[i]) &&
+        (!("is_right_edge" in arr[i - 1] && "is_right_edge" in arr[i]) &&
+         !("is_left_edge"  in arr[i - 1] && "is_left_edge"  in arr[i]))
+        ) {
+      draw_gantt.path(["M", arr[i - 1].left, arr[i - 1].top,
+                       "L", arr[i].left, arr[i].top])
+                   .attr({stroke: "#ff0000", "stroke-width": 2});
+    }
+  }
+}
+
+function drawGanttLinesHandler() {
+  var folder = document.getElementById('gantt_draw_area');
+  if(draw_gantt != null)
+    draw_gantt.clear();
+  else
+    draw_gantt = Raphael(folder);
+  setDrawArea();
+  if ($("#draw_progress_line").attr('checked'))
+    drawGanttLines();
+}
