Project

General

Profile

RE: Better Gantt Chart ยป gantt_edit_5_v1-1-1.patch

Hiroyuki Yoshioka, 2011-04-07 15:57

View differences:

app/controllers/gantts_controller.rb (working copy)
33 33
    show
34 34
  end
35 35

  
36
  def edit_gantt
37
    date_from = Date.parse(params[:date_from])
38
    date_to = Date.parse(params[:date_to])
39
    months = date_to.month - date_from.month + 1
40
    params[:year] = date_from.year
41
    params[:month] = date_from.month
42
    params[:months] = months
43
    @gantt = Redmine::Helpers::Gantt.new(params)
44
    @gantt.project = @project
45
    text, status = @gantt.edit(params)
46
    render :text=>text, :status=>status
47
  end
48

  
49
  def find_optional_project
50
    begin
51
      if params[:action] && params[:action].to_s == "edit_gantt"
52
        @project = Project.find(params[:project_id]) unless params[:project_id].blank?
53
        allowed = User.current.allowed_to?(:edit_issues, @project, :global => true)
54
        if allowed
55
          return true
56
        else
57
          render :text=>"lack of permission to edit issues", :status=>403
58
        end
59
      else
60
        super
61
      end
62
    rescue => e
63
      return e.to_s + "\n===\n" + [$!,$@.join("\n")].join("\n")
64
    end
65
  end
36 66
end
app/helpers/application_helper.rb (working copy)
828 828
    javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
829 829
  end
830 830

  
831
  def g_calendar_for(field_id)
832
    include_calendar_headers_tags
833
    image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
834
    javascript_tag("Calendar.setup({inputField : '#{field_id}', electric : false, ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
835
  end
836

  
831 837
  def include_calendar_headers_tags
832 838
    unless @calendar_headers_tags_included
833 839
      @calendar_headers_tags_included = true
app/helpers/issues_helper.rb (working copy)
49 49
    link_to_issue(issue) + "<br /><br />" +
50 50
      "<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />" +
51 51
      "<strong>#{@cached_label_status}</strong>: #{issue.status.name}<br />" +
52
      "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" +
53
      "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
52
      "<strong>#{@cached_label_start_date}</strong>: <span id='tooltip_start_date_i#{issue.id}'>#{format_date(issue.start_date)}</span><br />" +
53
      "<strong>#{@cached_label_due_date}</strong>: <span id='tooltip_due_date_i#{issue.id}'>#{format_date(issue.due_date)}</span><br />" +
54 54
      "<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" +
55 55
      "<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
56 56
  end
app/models/issue.rb (working copy)
465 465
    dependencies
466 466
  end
467 467
  
468
  def all_precedes_issues
469
    dependencies = []
470
    relations_from.each do |relation|
471
      next unless relation.relation_type == IssueRelation::TYPE_PRECEDES
472
      dependencies << relation.issue_to
473
      dependencies += relation.issue_to.all_dependent_issues
474
    end
475
    dependencies
476
  end
477

  
468 478
  # Returns an array of issues that duplicate this one
469 479
  def duplicates
470 480
    relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
app/views/gantts/show.html.erb (working copy)
1
<% include_calendar_headers_tags %>
1 2
<% @gantt.view = self %>
2 3
<h2><%= l(:label_gantt) %></h2>
3 4

  
......
41 42
<% zoom = 1
42 43
@gantt.zoom.times { zoom = zoom * 2 }
43 44

  
44
subject_width = 330
45
subject_width = 280
45 46
header_heigth = 18
46 47

  
47 48
headers_height = header_heigth
......
67 68

  
68 69

  
69 70
%>
71
<input type="hidden" name="_date_from" id="i_date_from" value="<%=h(@gantt.date_from)%>" />
72
<input type="hidden" name="_date_to" id="i_date_to" value="<%=h(@gantt.date_to)%>" />
73
<input type="hidden" name="zm" id="i_zm" value="<%=zoom%>" />
74
<input type="hidden" name="pzm" id="i_pzm" value="<%=@gantt.zoom%>" />
75
<script type='text/javascript'>
76
  function issue_moved(elem) {
77
    var id_str = elem.id.substring(3, elem.id.length);
78
    var v_date_from = document.getElementById('i_date_from').getAttribute("value");
79
    var v_date_to = document.getElementById('i_date_to').getAttribute("value");
80
    var v_zm = document.getElementById('i_zm').getAttribute("value");
81
    var v_pzm = document.getElementById('i_pzm').getAttribute("value");
82
    var url_str = '<%=  url_for(:controller=>:gantts, :action => :edit_gantt) %>';
83
    url_str = url_str + "/" + id_str;
84
    var day = parseInt(elem.style.left)/parseInt(v_zm);
85
    new Ajax.Request(url_str, {asynchronous:true, evalScripts:true,
86
      parameters: 'day='+day+'&date_from='+v_date_from+'&date_to='+v_date_to+'&zoom='+v_pzm+"&project_id=<%= @project.to_param %>",
87
      onSuccess:function(obj) {
88
        change_dates(obj.responseText);
89
      },
90
      onFailure:function(obj) {
91
        handle_failure(obj.responseText);
92
      }
93
    });
70 94

  
95
  }
96

  
97
  function handle_failure(res_text) {
98
    var text = res_text.split('|');
99
    alert(text[0]);
100
    if (text.length == 1) {
101
      return;
102
    }
103
    change_dates(text[1]);//revert
104
  }
105

  
106
  function change_dates(issue_infos) {
107
    if (!issue_infos) {
108
      return;
109
    }
110
    var issue_list = issue_infos.split("|");
111
    for (i = 0; i < issue_list.length; i++) {
112
      change_date(issue_list[i]);
113
    }
114
  }
115

  
116
  function change_date(text) {
117
    if (!text) {
118
      return;
119
    }
120
    var issue_info = text.split("=");
121
    var elem_id = issue_info[0];
122
    var kind = elem_id.substring(0,1);
123
    var preClassName = "";
124
    if (kind == 'v') {
125
      preClassName = "version ";
126
    } else if (kind == 'p') {
127
      preClassName = "project ";
128
    }
129
    var vals = issue_info[1].split(',');
130
    var start_date_elem = document.getElementById(elem_id + '_start_date_str');
131
    if (!start_date_elem) {
132
      //target not exists
133
      return;
134
    }
135
    start_date_elem.innerHTML = vals[0];
136
    var tooltip_start_date_elem = document.getElementById('tooltip_start_date_' + elem_id);
137
    if (tooltip_start_date_elem) {
138
      tooltip_start_date_elem.innerHTML = vals[0];
139
    }
140
    var due_date_elem = document.getElementById(elem_id + '_due_date_str');
141
    if (due_date_elem) {
142
      due_date_elem.innerHTML = vals[2];
143
    }
144
    
145
    var tooltip_due_date_elem = document.getElementById('tooltip_due_date_' + elem_id);
146
    if (tooltip_due_date_elem) {
147
      tooltip_due_date_elem.innerHTML = vals[2];
148
    }
149
    
150
    var ev_elem = document.getElementById('ev_' + elem_id);
151
    if (ev_elem) {
152
      ev_elem.style.left = vals[4] + 'px';
153
      ev_elem.style.width = (parseInt(vals[5])+100)+'px';
154
    }
155
    var todo_elem = document.getElementById('task_todo_' + elem_id);
156
    if (todo_elem) {
157
      todo_elem.style.width = vals[5] + 'px';
158
    }
159
    
160
    var late_elem = document.getElementById('task_late_' + elem_id);
161
    if (late_elem) {
162
      late_elem.style.width = vals[6] + 'px';
163
      if (vals[6] == '0') {
164
        late_elem.className = preClassName + 'task task_none';
165
      } else {
166
        late_elem.className = preClassName + 'task task_late';
167
      }
168
    }
169
    var done_elem = document.getElementById('task_done_' + elem_id);
170
    if (done_elem) {
171
      done_elem.style.width = vals[7] + 'px';
172
      if (vals[7] == '0') {
173
        done_elem.className = preClassName + 'task task_none';
174
      } else {
175
        done_elem.className = preClassName + 'task task_done';
176
      }
177
    }
178
    //var task_elem = document.getElementById('task_task_' + elem_id);
179
    //if (task_elem) {
180
    //  task_elem.style.left = (parseInt(vals[5])+5) + 'px';
181
    //}
182
    var tooltip = document.getElementById("tt_" + elem_id);
183
    if (tooltip) {
184
      tooltip.style.left = ev_elem.style.left;
185
    }
186

  
187
    var label = document.getElementById("label_" + elem_id);
188
    if (label) {
189
      label.style.left = (parseInt(vals[4]) + parseInt(vals[5]) + 8) + 'px';
190
    }
191
    var marker_start = document.getElementById("marker_start_" + elem_id);
192
    if (marker_start && vals[8]) {
193
      marker_start.style.left = vals[8] + 'px';
194
    }
195
    var marker_end = document.getElementById("marker_end_" + elem_id);
196
    if (marker_end && vals[9]) {
197
      marker_end.style.left = vals[9] + 'px';
198
    }
199

  
200
    //change calendar date
201
    var elm1 = document.getElementById(elem_id+"_hidden_start_date");
202
    if (elm1) elm1.value = vals[1];
203
    var elm2 = document.getElementById(elem_id+"_start_date");
204
    if (elm2) elm2.value = vals[1];
205
    var elm3 = document.getElementById(elem_id+"_hidden_due_date");
206
    if (elm3) elm3.value = vals[3];
207
    var elm4 = document.getElementById(elem_id+"_due_date");
208
    if (elm4) elm4.value = vals[3];
209
  }
210
</script>
211

  
71 212
<% if @gantt.truncated %>
72 213
	<p class="warning"><%= l(:notice_gantt_chart_truncated, :max => @gantt.max_rows) %></p>
73 214
<% end %>
......
86 227

  
87 228
</div>
88 229
</td>
230
<td style="width:225px; padding:0px;">
231
<div style="position:relative;height:<%= t_height + 24 %>px;width:225px;">
232
<div style="width:225px;height:<%= headers_height %>px;background: #eee;" class="gantt_hdr"></div>
233
<div style="width:225px;height:<%= t_height %>px;border-left: 1px solid #c0c0c0;overflow:hidden;" class="gantt_hdr"></div>
234

  
235
<div class="gantt_subjects">
236
<%= @gantt.calendars %>
237
</div>
238

  
239
</div>
240
</td>
89 241
<td>
90 242

  
91
<div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;">
243
<div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;" id="gantt-container">
92 244
<div style="width:<%= g_width-1 %>px;height:<%= headers_height %>px;background: #eee;" class="gantt_hdr">&nbsp;</div>
93 245
<% 
94 246
#
......
148 300
	left = 0
149 301
	height = g_height + header_heigth - 1
150 302
	wday = @gantt.date_from.cwday
303
        dt = @gantt.date_from
151 304
	(@gantt.date_to - @gantt.date_from + 1).to_i.times do 
152 305
	width =  zoom - 1
153 306
	%>
154 307
	<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">
155
	<%= day_name(wday).first %>
308
	<%=  "#{dt.day}<br>"if @gantt.zoom == 4 %><%= day_name(wday).first %>
156 309
	</div>
157 310
	<% 
158 311
	left = left + width+1
159 312
	wday = wday + 1
313
        dt = dt + 1
160 314
	wday = 1 if wday > 7
161 315
	end
162 316
end %>
163

  
164 317
<%= @gantt.lines %>
165 318

  
166 319
<%
config/routes.rb (working copy)
80 80
  map.quoted_issue '/issues/:id/quoted', :controller => 'journals', :action => 'new', :id => /\d+/, :conditions => { :method => :post }
81 81
  map.connect '/issues/:id/destroy', :controller => 'issues', :action => 'destroy', :conditions => { :method => :post } # legacy
82 82

  
83
  map.resource :gantt, :path_prefix => '/issues', :controller => 'gantts', :only => [:show, :update]
84
  map.resource :gantt, :path_prefix => '/projects/:project_id/issues', :controller => 'gantts', :only => [:show, :update]
83
  map.resource :gantt, :path_prefix => '/issues', :controller => 'gantts', :only => [:show, :update, :edit_gantt]
84
  map.resource :gantt, :path_prefix => '/projects/:project_id/issues', :controller => 'gantts', :only => [:show, :update, :edit_gantt]
85 85
  map.resource :calendar, :path_prefix => '/issues', :controller => 'calendars', :only => [:show, :update]
86 86
  map.resource :calendar, :path_prefix => '/projects/:project_id/issues', :controller => 'calendars', :only => [:show, :update]
87 87

  
lib/redmine/helpers/gantt.rb (working copy)
70 70
        
71 71
        @subjects = ''
72 72
        @lines = ''
73
        @calendars = ''
73 74
        @number_of_rows = nil
74 75
        
75 76
        @issue_ancestors = []
......
154 155
        render(options.merge(:only => :lines)) unless @lines_rendered
155 156
        @lines
156 157
      end
158

  
159
      # Renders the calendars of the Gantt chart, the right side
160
      def calendars(options={})
161
        render(options.merge(:only => :calendars)) unless @calendars_rendered
162
        @calendars
163
      end
157 164
      
158 165
      def render(options={})
159 166
        options = {:indent => 4, :render => :subject, :format => :html}.merge(options)
160 167
        
161
        @subjects = '' unless options[:only] == :lines
162
        @lines = '' unless options[:only] == :subjects
168
        @subjects = '' unless options[:only] == :lines && options[:only] == :calendars
169
        @lines = '' unless options[:only] == :subjects && options[:only] == :calendars
170
        @calendars = '' unless options[:only] == :lines && options[:only] == :subjects
163 171
        @number_of_rows = 0
164 172
        
165 173
        if @project
......
171 179
          end
172 180
        end
173 181
        
174
        @subjects_rendered = true unless options[:only] == :lines
175
        @lines_rendered = true unless options[:only] == :subjects
182
        @subjects_rendered = true unless options[:only] == :lines && options[:only] == :calendars
183
        @lines_rendered = true unless options[:only] == :subjects && options[:only] == :calendars
184
        @calendars_rendered = true unless options[:only] == :lines && options[:only] == :subjects
176 185
        
177 186
        render_end(options)
178 187
      end
......
182 191
        options[:indent_increment] = 20 unless options.key? :indent_increment
183 192
        options[:top_increment] = 20 unless options.key? :top_increment
184 193

  
185
        subject_for_project(project, options) unless options[:only] == :lines
186
        line_for_project(project, options) unless options[:only] == :subjects
194
        subject_for_project(project, options) unless options[:only] == :lines && options[:only] == :calendars
195
        line_for_project(project, options) unless options[:only] == :subjects && options[:only] == :calendars
196
        calendar_for_project(project, options) unless options[:only] == :lines && options[:only] == :subjects
187 197
        
188 198
        options[:top] += options[:top_increment]
189 199
        options[:indent] += options[:indent_increment]
......
218 228
        @issue_ancestors = []
219 229
        
220 230
        issues.each do |i|
221
          subject_for_issue(i, options) unless options[:only] == :lines
222
          line_for_issue(i, options) unless options[:only] == :subjects
231
          subject_for_issue(i, options) unless options[:only] == :lines && options[:only] == :calendars
232
          line_for_issue(i, options) unless options[:only] == :subjects && options[:only] == :calendars
233
          calendar_for_issue(i, options) unless options[:only] == :lines && options[:only] == :subjects
223 234
          
224 235
          options[:top] += options[:top_increment]
225 236
          @number_of_rows += 1
......
231 242

  
232 243
      def render_version(version, options={})
233 244
        # Version header
234
        subject_for_version(version, options) unless options[:only] == :lines
235
        line_for_version(version, options) unless options[:only] == :subjects
236
        
245
        subject_for_version(version, options) unless options[:only] == :lines && options[:only] == :calendars
246
        line_for_version(version, options) unless options[:only] == :subjects && options[:only] == :calendars
247
        calendar_for_version(version, options) unless options[:only] == :lines && options[:only] == :subjects
248

  
237 249
        options[:top] += options[:top_increment]
238 250
        @number_of_rows += 1
239 251
        return if abort?
......
286 298
          
287 299
          case options[:format]
288 300
          when :html
289
            html_task(options, coords, :css => "project task", :label => label, :markers => true)
301
            html_task(options, coords, :css => "project task", :label => label, :markers => true, :id => project.id, :kind => "p")
290 302
          when :image
291 303
            image_task(options, coords, :label => label, :markers => true, :height => 3)
292 304
          when :pdf
......
325 337

  
326 338
          case options[:format]
327 339
          when :html
328
            html_task(options, coords, :css => "version task", :label => label, :markers => true)
340
            html_task(options, coords, :css => "version task", :label => label, :markers => true, :id => version.id, :kind => "v")
329 341
          when :image
330 342
            image_task(options, coords, :label => label, :markers => true, :height => 3)
331 343
          when :pdf
......
378 390
        if issue.is_a?(Issue) && issue.due_before
379 391
          coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom])
380 392
          label = "#{ issue.status.name } #{ issue.done_ratio }%"
393
          if !issue.due_date && issue.fixed_version
394
            label += "-&nbsp;<strong>#{h(issue.fixed_version.name)}</strong>"
395
          end
381 396
          
382 397
          case options[:format]
383 398
          when :html
384
            html_task(options, coords, :css => "task " + (issue.leaf? ? 'leaf' : 'parent'), :label => label, :issue => issue, :markers => !issue.leaf?)
399
            html_task(options, coords, :css => "task " + (issue.leaf? ? 'leaf' : 'parent'), :label => label, :issue => issue, :markers => !issue.leaf?, :id => issue.id, :kind => "i")
385 400
          when :image
386 401
            image_task(options, coords, :label => label)
387 402
          when :pdf
......
393 408
        end
394 409
      end
395 410

  
396
      # Generates a gantt image
411
    # Generates a gantt image
397 412
      # Only defined if RMagick is avalaible
398 413
      def to_image(format='PNG')
399 414
        date_to = (@date_from >> @months)-1    
......
621 636
        pdf.Output
622 637
      end
623 638
      
639
      def edit(pms)
640
        id = pms[:id]
641
        kind = id.slice!(0).chr
642
        begin
643
          case kind
644
          when 'i'
645
            @issue = Issue.find(pms[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
646
          when 'p'
647
            @issue = Project.find(pms[:id])
648
          when 'v'
649
            @issue = Version.find(pms[:id], :include => [:project])
650
          end
651
        rescue ActiveRecord::RecordNotFound
652
          return "issue not found : #{pms[:id]}", 400
653
        end
654

  
655
        if !@issue.start_date || !@issue.due_before
656
          #render :text=>l(:notice_locking_conflict), :status=>400
657
          return l(:notice_locking_conflict), 400
658
        end
659
        @issue.init_journal(User.current)
660
        date_from = Date.parse(pms[:date_from])
661
        old_start_date = @issue.start_date
662
        o = get_issue_position(@issue, pms[:zoom])
663
        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]}"
664

  
665
        if pms[:day]
666
          #bar moved
667
          day = pms[:day].to_i
668
          duration = @issue.due_before - @issue.start_date
669
          @issue.start_date = date_from + day
670
          @issue.due_date = @issue.start_date + duration.to_i if @issue.due_date
671
        elsif pms[:start_date]
672
          #start date changed
673
          start_date = Date.parse(pms[:start_date])
674
          if @issue.start_date == start_date
675
            #render :text=>""
676
            return "", 200 #nothing has changed
677
          end
678
          @issue.start_date = start_date
679
          @issue.due_date = start_date if @issue.due_date && start_date > @issue.due_date
680
        elsif pms[:due_date]
681
          #due date changed
682
          due_date = Date.parse(pms[:due_date])
683
          if @issue.due_date == due_date
684
            #render :text=>""
685
            return "", 200 #nothing has changed
686
          end
687
          @issue.due_date = due_date
688
          @issue.start_date = due_date if due_date < @issue.start_date
689
        end
690
        fv = @issue.fixed_version
691
        if fv && fv.effective_date && !@issue.due_date && fv.effective_date < @issue.start_date
692
          @issue.start_date = old_start_date
693
        end
694

  
695
        begin
696
          @issue.save!
697
          o = get_issue_position(@issue, pms[:zoom])
698
          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]}"
699

  
700
          prj_map = {}
701
          text = set_project_data(@issue.project, pms[:zoom], text, prj_map)
702
          version_map = {}
703
          text = set_version_data(@issue.fixed_version, pms[:zoom], text, version_map)
704

  
705
          #check dependencies
706
          issues = @issue.all_precedes_issues
707
          issues.each do |i|
708
            o = get_issue_position(i, pms[:zoom])
709
            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]}"
710
            text = set_project_data(i.project, pms[:zoom], text, prj_map)
711
            text = set_version_data(i.fixed_version, pms[:zoom], text, version_map)
712
          end
713
          #render :text=>text
714
          return text, 200
715
        rescue => e
716
          #render :text=>@issue.errors.full_messages.join("\n") + "|" + text_for_revert  , :status=>400
717
          if @issue.errors.full_messages.to_s == ""
718
            return e.to_s + "\n" + [$!,$@.join("\n")].join("\n") + "\n" + @issue.errors.full_messages.join("\n") + "|" + text_for_revert, 400
719
          else
720
            return @issue.errors.full_messages.join("\n") + "|" + text_for_revert, 400
721
          end
722
        end
723
      end
724

  
624 725
      private
625 726
      
626 727
      def coordinates(start_date, end_date, progress, zoom=nil)
......
739 840
        output = ''
740 841
        # Renders the task bar, with progress and late
741 842
        if coords[:bar_start] && coords[:bar_end]
742
          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>"
843
          i_width = coords[:bar_end] - coords[:bar_start] - 2
844
          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'" : ""}>"
845
          output << "<div id='task_todo_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:#{ i_width}px;' class='#{options[:css]} task_todo'>&nbsp;</div>"
743 846
          
744 847
          if coords[:bar_late_end]
745
            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>"
848
            l_width = coords[:bar_late_end] - coords[:bar_start] - 2
849
            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>"
850
          else
851
            output << "<div id='task_late_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:0px;' class='#{ options[:css] + " task_none"}'>&nbsp;</div>"
746 852
          end
747 853
          if coords[:bar_progress_end]
748
            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>"
854
            d_width = coords[:bar_progress_end] - coords[:bar_start] - 2
855
            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>"
856
          else
857
            output << "<div id='task_done_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:0px;' class='#{ options[:css] + " task_none"}'>&nbsp;</div>"
749 858
          end
859
          output << "</div>"
860
        else
861
          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'" : ""}>"
862
          output << "<div id='task_todo_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:0px;' class='#{ options[:css]} task_todo'>&nbsp;</div>"
863
          output << "<div id='task_late_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:0px;' class='#{ options[:css] + " task_none"}'>&nbsp;</div>"
864
          output << "<div id='task_done_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:0px;' class='#{ options[:css] + " task_none"}'>&nbsp;</div>"
865
          output << "</div>"
750 866
        end
751 867
        # Renders the markers
752 868
        if options[:markers]
753 869
          if coords[:start]
754
            output << "<div style='top:#{ params[:top] }px;left:#{ coords[:start] }px;width:15px;' class='#{options[:css]} marker starting'>&nbsp;</div>"
870
            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>"
755 871
          end
756 872
          if coords[:end]
757
            output << "<div style='top:#{ params[:top] }px;left:#{ coords[:end] + params[:zoom] }px;width:15px;' class='#{options[:css]} marker ending'>&nbsp;</div>"
873
            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>"
758 874
          end
759 875
        end
760 876
        # Renders the label on the right
761 877
        if options[:label]
762
          output << "<div style='top:#{ params[:top] }px;left:#{ (coords[:bar_end] || 0) + 8 }px;' class='#{options[:css]} label'>"
878
          output << "<div id='label_#{options[:kind]}#{options[:id]}' style='top:#{ params[:top] }px;left:#{ (coords[:bar_end] || 0) + 8 }px;' class='#{options[:css]} label'>"
763 879
          output << options[:label]
764 880
          output << "</div>"
765 881
        end
766 882
        # Renders the tooltip
767 883
        if options[:issue] && coords[:bar_start] && coords[:bar_end]
768
          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;'>"
884
          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;'>"
769 885
          output << '<span class="tip">'
770 886
          output << view.render_issue_tooltip(options[:issue])
771 887
          output << "</span></div>"
888

  
889
          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);}')
772 890
        end
773 891
        @lines << output
774 892
        output
......
857 975
          params[:image].text(params[:subject_width] + (coords[:bar_end] || 0) + 5,params[:top] + 1, options[:label])
858 976
        end
859 977
      end
978

  
979
      ##
980
      ##  for edit gantt
981
      ##
982
      def set_project_data(prj, zoom, text, prj_map = {})
983
        if !prj
984
          return text
985
        end
986
        if !prj_map[prj.id]
987
          o = get_project_position(prj, zoom)
988
          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]}"
989
          prj_map[prj.id] = prj
990
        end
991
        text = set_project_data(prj.parent, zoom, text, prj_map)
992
      end
993

  
994
      def set_version_data(version, zoom, text, version_map = {})
995
        if !version
996
          return text
997
        end
998
        if !version_map[version.id]
999
          o = get_version_position(version, zoom)
1000
          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]}"
1001
          version_map[version.id] = version
1002
        end
1003
        return text
1004
      end
1005

  
1006
      def get_pos(coords)
1007
        i_left = 0
1008
        i_width = 0
1009
        l_width = 0
1010
        d_width = 0
1011
        if coords[:bar_start]
1012
          i_left = coords[:bar_start]
1013
          if coords[:bar_end]
1014
            i_width = coords[:bar_end] - coords[:bar_start] - 2
1015
            i_width = 0 if i_width < 0
1016
          end
1017
          if coords[:bar_late_end]
1018
            l_width = coords[:bar_late_end] - coords[:bar_start] - 2
1019
          end
1020
          if coords[:bar_progress_end]
1021
            d_width = coords[:bar_progress_end] - coords[:bar_start] - 2
1022
          end
1023
        end
1024
        return i_left, i_width, l_width, d_width
1025
      end
1026

  
1027
      def get_issue_position(issue, zoom_str)
1028
        z = zoom_str.to_i
1029
        zoom = 1
1030
        z.times { zoom = zoom * 2}
1031
        id = issue.due_before
1032
        if id && @date_to < id
1033
          id = @date_to
1034
        end
1035
        coords = coordinates(issue.start_date, id, issue.done_ratio, zoom)
1036

  
1037
        return get_pos(coords)
1038
      end
1039

  
1040
      def get_project_position(project, zoom_str)
1041
        z = zoom_str.to_i
1042
        zoom = 1
1043
        z.times { zoom = zoom * 2}
1044
        pd = project.due_date
1045
        if pd && @date_to < pd
1046
          pd = @date_to
1047
        end
1048
        coords = coordinates(project.start_date, pd, nil, zoom)
1049
        i_left, i_width, l_width, d_width = get_pos(coords)
1050
        if coords[:end]
1051
          return i_left, i_width, l_width, d_width, coords[:start], coords[:end] + zoom
1052
        else
1053
          return i_left, i_width, l_width, d_width, coords[:start], nil
1054
        end
1055
      end
1056

  
1057
      def get_version_position(version, zoom_str)
1058
        z = zoom_str.to_i
1059
        zoom = 1
1060
        z.times { zoom = zoom * 2}
1061
        vd = version.due_date
1062
        if vd &&  @date_to < vd
1063
          vd = @date_to
1064
        end
1065
        coords = coordinates(version.start_date, vd, version.completed_pourcent, zoom)
1066
        i_left, i_width, l_width, d_width = get_pos(coords)
1067
        if coords[:end]
1068
          return i_left, i_width, l_width, d_width, coords[:start], coords[:end] + zoom
1069
        else
1070
          return i_left, i_width, l_width, d_width, coords[:start], nil
1071
        end
1072
      end
1073

  
1074
      def calendar_for_issue(issue, options)
1075
        # Skip issues that don't have a due_before (due_date or version's due_date)
1076
        if issue.is_a?(Issue) && issue.due_before
1077

  
1078
          case options[:format]
1079
          when :html
1080
            start_date = issue.start_date
1081
            if start_date
1082
              @calendars << "<div style='position: absolute;line-height:1.2em;height:16px;top:#{options[:top]}px;left:4px;overflow:hidden;'>"
1083
              @calendars << "<span id='i#{issue.id}_start_date_str'>"
1084
              @calendars << format_date(start_date)
1085
              @calendars << "</span>"
1086
              @calendars << "<input type='hidden' size='12' id='i#{issue.id}_hidden_start_date' value='#{start_date}' />"
1087
              @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')}"
1088
              @calendars << observe_date_field("i#{issue.id}", 'start')
1089
            end
1090
            due_date = issue.due_date
1091
            if due_date
1092
              @calendars << "<span id='i#{issue.id}_due_date_str'>"
1093
              @calendars << format_date(due_date)
1094
              @calendars << "</span>"
1095
              @calendars << "<input type='hidden' size='12' id='i#{issue.id}_hidden_due_date' value='#{due_date}' />"
1096
              @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')}"
1097
              @calendars << observe_date_field("i#{issue.id}", 'due')
1098
              @calendars << "</div>"
1099
            end
1100
          when :image
1101
            #nop
1102
          when :pdf
1103
            #nop
1104
          end
1105
        else
1106
          ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
1107
          ''
1108
        end
1109
      end
1110

  
1111
      def calendar_for_version(version, options)
1112
        # Skip version that don't have a due_before (due_date or version's due_date)
1113
        if version.is_a?(Version) && version.start_date && version.due_date
1114

  
1115
          case options[:format]
1116
          when :html
1117
            @calendars << "<div style='position: absolute;line-height:1.2em;height:16px;top:#{options[:top]}px;left:4px;overflow:hidden;'>"
1118
            @calendars << "<span id='v#{version.id}_start_date_str'>"
1119
            @calendars << format_date(version.effective_date)
1120
            @calendars << "</span>"
1121
            @calendars << "</div>"
1122
          when :image
1123
            #nop
1124
          when :pdf
1125
            #nop
1126
          end
1127
        else
1128
          ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
1129
          ''
1130
        end
1131
      end
1132

  
1133
      def calendar_for_project(project, options)
1134
        case options[:format]
1135
        when :html
1136
          @calendars << "<div style='position: absolute;line-height:1.2em;height:16px;top:#{options[:top]}px;left:4px;overflow:hidden;'>"
1137
          @calendars << "<span id='p#{project.id}_start_date_str'>"
1138
          @calendars << format_date(project.start_date) if project.start_date
1139
          @calendars << "</span>"
1140
          @calendars << "&nbsp;&nbsp;&nbsp;"
1141
          @calendars << "<span id='p#{project.id}_due_date_str'>"
1142
          @calendars << format_date(project.due_date) if project.due_date
1143
          @calendars << "</span>"
1144
          @calendars << "</div>"
1145
        when :image
1146
          # nop
1147
        when :pdf
1148
          # nop
1149
        end
1150
      end
1151

  
1152
      def observe_date_field(id, type)
1153
        output = ''
1154
        prj_id = ''
1155
        prj_id = @project.to_param if @project
1156
        output << "<script type='text/javascript'>\n"
1157
        output << "//<![CDATA[\n"
1158
        output << "new Form.Element.Observer('#{id}_#{type}_date', 0.25,\n"
1159
        output << "  function(element, value) {\n"
1160
        output << "    if (value == document.getElementById('#{id}_hidden_#{type}_date').value) {\n"
1161
        output << "      return ;\n"
1162
        output << "    }\n"
1163
        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)});"
1164
        output << "  })\n"
1165
        output << "//]]>\n"
1166
        output << "</script>"
1167
      end
860 1168
    end
861 1169
  end
862 1170
end
public/stylesheets/application.css (working copy)
801 801
.task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
802 802
.task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; }  
803 803
.task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
804
.task_none { background:transparent; border-style: none; }
804 805

  
805 806
.task_todo.parent { background: #888; border: 1px solid #888; height: 3px;}
806 807
.task_late.parent, .task_done.parent { height: 3px;}
    (1-1/1)