gantt_edit_v1-1-2.patch

Hiroyuki Yoshioka, 2011-05-29 13:13

Download (53.9 KB)

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
      var parentStr = "";
163
      if (late_elem.className.indexOf("parent") > 0) parentStr = "parent ";
164
      late_elem.style.width = vals[6] + 'px';
165
      if (vals[6] == '0') {
166
        late_elem.className = preClassName + 'task ' + parentStr + 'task_none';
167
      } else {
168
        late_elem.className = preClassName + 'task ' + parentStr + 'task_late';
169
      }
170
    }
171
    var done_elem = document.getElementById('task_done_' + elem_id);
172
    if (done_elem) {
173
      var parentStr = "";
174
      if (done_elem.className.indexOf("parent") > 0) parentStr = "parent ";
175
      done_elem.style.width = vals[7] + 'px';
176
      if (vals[7] == '0') {
177
        done_elem.className = preClassName + 'task ' + parentStr + 'task_none';
178
      } else {
179
        done_elem.className = preClassName + 'task ' + parentStr + 'task_done';
180
      }
181
    }
182
    //var task_elem = document.getElementById('task_task_' + elem_id);
183
    //if (task_elem) {
184
    //  task_elem.style.left = (parseInt(vals[5])+5) + 'px';
185
    //}
186
    var tooltip = document.getElementById("tt_" + elem_id);
187
    if (tooltip) {
188
      tooltip.style.left = ev_elem.style.left;
189
    }
190

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

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

  
71 216
<% if @gantt.truncated %>
72 217
	<p class="warning"><%= l(:notice_gantt_chart_truncated, :max => @gantt.max_rows) %></p>
73 218
<% end %>
......
86 231

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

  
239
<div class="gantt_subjects">
240
<%= @gantt.calendars %>
241
</div>
242

  
243
</div>
244
</td>
89 245
<td>
90 246

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

  
164 321
<%= @gantt.lines %>
165 322

  
166 323
<%
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)
5 5
# modify it under the terms of the GNU General Public License
6 6
# as published by the Free Software Foundation; either version 2
7 7
# of the License, or (at your option) any later version.
8
# 
8
#
9 9
# This program is distributed in the hope that it will be useful,
10 10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
......
38 38
      attr_accessor :query
39 39
      attr_accessor :project
40 40
      attr_accessor :view
41
      
41

  
42 42
      def initialize(options={})
43 43
        options = options.dup
44
        
44

  
45 45
        if options[:year] && options[:year].to_i >0
46 46
          @year_from = options[:year].to_i
47 47
          if options[:month] && options[:month].to_i >=1 && options[:month].to_i <= 12
......
53 53
          @month_from ||= Date.today.month
54 54
          @year_from ||= Date.today.year
55 55
        end
56
        
56

  
57 57
        zoom = (options[:zoom] || User.current.pref[:gantt_zoom]).to_i
58
        @zoom = (zoom > 0 && zoom < 5) ? zoom : 2    
58
        @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
59 59
        months = (options[:months] || User.current.pref[:gantt_months]).to_i
60 60
        @months = (months > 0 && months < 25) ? months : 6
61
        
61

  
62 62
        # Save gantt parameters as user preference (zoom and months count)
63 63
        if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
64 64
          User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
65 65
          User.current.preference.save
66 66
        end
67
        
67

  
68 68
        @date_from = Date.civil(@year_from, @month_from, 1)
69 69
        @date_to = (@date_from >> @months) - 1
70
        
70

  
71 71
        @subjects = ''
72 72
        @lines = ''
73
        @calendars = ''
73 74
        @number_of_rows = nil
74
        
75

  
75 76
        @issue_ancestors = []
76
        
77

  
77 78
        @truncated = false
78 79
        if options.has_key?(:max_rows)
79 80
          @max_rows = options[:max_rows]
......
85 86
      def common_params
86 87
        { :controller => 'gantts', :action => 'show', :project_id => @project }
87 88
      end
88
      
89

  
89 90
      def params
90 91
        common_params.merge({  :zoom => zoom, :year => year_from, :month => month_from, :months => months })
91 92
      end
92
      
93

  
93 94
      def params_previous
94 95
        common_params.merge({:year => (date_from << months).year, :month => (date_from << months).month, :zoom => zoom, :months => months })
95 96
      end
96
      
97

  
97 98
      def params_next
98 99
        common_params.merge({:year => (date_from >> months).year, :month => (date_from >> months).month, :zoom => zoom, :months => months })
99 100
      end
......
102 103
      # Returns the number of rows that will be rendered on the Gantt chart
103 104
      def number_of_rows
104 105
        return @number_of_rows if @number_of_rows
105
        
106

  
106 107
        rows = if @project
107 108
          number_of_rows_on_project(@project)
108 109
        else
......
110 111
            total += number_of_rows_on_project(project)
111 112
          end
112 113
        end
113
        
114

  
114 115
        rows > @max_rows ? @max_rows : rows
115 116
      end
116 117

  
......
154 155
        render(options.merge(:only => :lines)) unless @lines_rendered
155 156
        @lines
156 157
      end
157
      
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
164

  
158 165
      def render(options={})
159 166
        options = {:indent => 4, :render => :subject, :format => :html}.merge(options)
160
        
161
        @subjects = '' unless options[:only] == :lines
162
        @lines = '' unless options[:only] == :subjects
167

  
168
        if options[:format] == :html
169
          @subjects = '' unless options[:only] == :lines && options[:only] == :calendars
170
          @lines = '' unless options[:only] == :subjects && options[:only] == :calendars
171
          @calendars = '' unless options[:only] == :lines && options[:only] == :subjects
172
        else
173
          @subjects = '' unless options[:only] == :lines
174
          @lines = '' unless options[:only] == :subjects
175
        end
163 176
        @number_of_rows = 0
164
        
177

  
165 178
        if @project
166 179
          render_project(@project, options)
167 180
        else
......
170 183
            break if abort?
171 184
          end
172 185
        end
186

  
187
        if options[:format] == :html
188
          @subjects_rendered = true unless options[:only] == :lines && options[:only] == :calendars
189
          @lines_rendered = true unless options[:only] == :subjects && options[:only] == :calendars
190
          @calendars_rendered = true unless options[:only] == :lines && options[:only] == :subjects
191
        else
192
          @subjects_rendered = true unless options[:only] == :lines
193
          @lines_rendered = true unless options[:only] == :subjects
194
        end
173 195
        
174
        @subjects_rendered = true unless options[:only] == :lines
175
        @lines_rendered = true unless options[:only] == :subjects
176
        
177 196
        render_end(options)
178 197
      end
179 198

  
......
182 201
        options[:indent_increment] = 20 unless options.key? :indent_increment
183 202
        options[:top_increment] = 20 unless options.key? :top_increment
184 203

  
185
        subject_for_project(project, options) unless options[:only] == :lines
186
        line_for_project(project, options) unless options[:only] == :subjects
187
        
204
        if options[:format] == :html
205
          subject_for_project(project, options) unless options[:only] == :lines && options[:only] == :calendars
206
          line_for_project(project, options) unless options[:only] == :subjects && options[:only] == :calendars
207
          calendar_for_project(project, options) unless options[:only] == :lines && options[:only] == :subjects
208
        else
209
          subject_for_project(project, options) unless options[:only] == :lines
210
          line_for_project(project, options) unless options[:only] == :subjects
211
        end
212

  
188 213
        options[:top] += options[:top_increment]
189 214
        options[:indent] += options[:indent_increment]
190 215
        @number_of_rows += 1
191 216
        return if abort?
192
        
217

  
193 218
        # Second, Issues without a version
194 219
        issues = project.issues.for_gantt.without_version.with_query(@query).all(:limit => current_limit)
195 220
        sort_issues!(issues)
......
216 241

  
217 242
      def render_issues(issues, options={})
218 243
        @issue_ancestors = []
219
        
244

  
220 245
        issues.each do |i|
221
          subject_for_issue(i, options) unless options[:only] == :lines
222
          line_for_issue(i, options) unless options[:only] == :subjects
223
          
246
          if options[:format] == :html
247
            subject_for_issue(i, options) unless options[:only] == :lines && options[:only] == :calendars
248
            line_for_issue(i, options) unless options[:only] == :subjects && options[:only] == :calendars
249
            calendar_for_issue(i, options) unless options[:only] == :lines && options[:only] == :subjects
250
          else
251
            subject_for_issue(i, options) unless options[:only] == :lines
252
            line_for_issue(i, options) unless options[:only] == :subjects
253
          end
254

  
224 255
          options[:top] += options[:top_increment]
225 256
          @number_of_rows += 1
226 257
          break if abort?
227 258
        end
228
        
259

  
229 260
        options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
230 261
      end
231 262

  
232 263
      def render_version(version, options={})
233 264
        # Version header
234
        subject_for_version(version, options) unless options[:only] == :lines
235
        line_for_version(version, options) unless options[:only] == :subjects
236
        
265
          if options[:format] == :html
266
            subject_for_version(version, options) unless options[:only] == :lines && options[:only] == :calendars
267
            line_for_version(version, options) unless options[:only] == :subjects && options[:only] == :calendars
268
            calendar_for_version(version, options) unless options[:only] == :lines && options[:only] == :subjects
269
          else
270
            subject_for_version(version, options) unless options[:only] == :lines
271
            line_for_version(version, options) unless options[:only] == :subjects
272
          end
273

  
237 274
        options[:top] += options[:top_increment]
238 275
        @number_of_rows += 1
239 276
        return if abort?
240
        
277

  
241 278
        # Remove the project requirement for Versions because it will
242 279
        # restrict issues to only be on the current project.  This
243 280
        # ends up missing issues which are assigned to shared versions.
244 281
        @query.project = nil if @query.project
245
        
282

  
246 283
        issues = version.fixed_issues.for_gantt.with_query(@query).all(:limit => current_limit)
247 284
        if issues
248 285
          sort_issues!(issues)
......
252 289
          options[:indent] -= options[:indent_increment]
253 290
        end
254 291
      end
255
      
292

  
256 293
      def render_end(options={})
257 294
        case options[:format]
258
        when :pdf        
295
        when :pdf
259 296
          options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
260 297
        end
261 298
      end
......
280 317
        if project.is_a?(Project) && project.start_date && project.due_date
281 318
          options[:zoom] ||= 1
282 319
          options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
283
            
320

  
284 321
          coords = coordinates(project.start_date, project.due_date, nil, options[:zoom])
285 322
          label = h(project)
286
          
323

  
287 324
          case options[:format]
288 325
          when :html
289
            html_task(options, coords, :css => "project task", :label => label, :markers => true)
326
            html_task(options, coords, :css => "project task", :label => label, :markers => true, :id => project.id, :kind => "p")
290 327
          when :image
291 328
            image_task(options, coords, :label => label, :markers => true, :height => 3)
292 329
          when :pdf
......
318 355
        if version.is_a?(Version) && version.start_date && version.due_date
319 356
          options[:zoom] ||= 1
320 357
          options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
321
          
358

  
322 359
          coords = coordinates(version.start_date, version.due_date, version.completed_pourcent, options[:zoom])
323 360
          label = "#{h version } #{h version.completed_pourcent.to_i.to_s}%"
324 361
          label = h("#{version.project} -") + label unless @project && @project == version.project
325 362

  
326 363
          case options[:format]
327 364
          when :html
328
            html_task(options, coords, :css => "version task", :label => label, :markers => true)
365
            html_task(options, coords, :css => "version task", :label => label, :markers => true, :id => version.id, :kind => "v")
329 366
          when :image
330 367
            image_task(options, coords, :label => label, :markers => true, :height => 3)
331 368
          when :pdf
......
342 379
          @issue_ancestors.pop
343 380
          options[:indent] -= options[:indent_increment]
344 381
        end
345
          
382

  
346 383
        output = case options[:format]
347 384
        when :html
348 385
          css_classes = ''
349 386
          css_classes << ' issue-overdue' if issue.overdue?
350 387
          css_classes << ' issue-behind-schedule' if issue.behind_schedule?
351 388
          css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
352
          
389

  
353 390
          subject = "<span class='#{css_classes}'>"
354 391
          if issue.assigned_to.present?
355 392
            assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
......
369 406
          @issue_ancestors << issue
370 407
          options[:indent] += options[:indent_increment]
371 408
        end
372
        
409

  
373 410
        output
374 411
      end
375 412

  
......
378 415
        if issue.is_a?(Issue) && issue.due_before
379 416
          coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom])
380 417
          label = "#{ issue.status.name } #{ issue.done_ratio }%"
381
          
418
          if !issue.due_date && issue.fixed_version
419
            if options[:format] == :html
420
              label += "-&nbsp;<strong>#{h(issue.fixed_version.name)}</strong>"
421
            else
422
              label += "-#{h(issue.fixed_version.name)}"
423
            end
424
          end
425

  
382 426
          case options[:format]
383 427
          when :html
384
            html_task(options, coords, :css => "task " + (issue.leaf? ? 'leaf' : 'parent'), :label => label, :issue => issue, :markers => !issue.leaf?)
428
            html_task(options, coords, :css => "task " + (issue.leaf? ? 'leaf' : 'parent'), :label => label, :issue => issue, :markers => !issue.leaf?, :id => issue.id, :kind => "i")
385 429
          when :image
386 430
            image_task(options, coords, :label => label)
387 431
          when :pdf
......
393 437
        end
394 438
      end
395 439

  
396
      # Generates a gantt image
440
    # Generates a gantt image
397 441
      # Only defined if RMagick is avalaible
398 442
      def to_image(format='PNG')
399
        date_to = (@date_from >> @months)-1    
443
        date_to = (@date_from >> @months)-1
400 444
        show_weeks = @zoom > 1
401 445
        show_days = @zoom > 2
402
        
446

  
403 447
        subject_width = 400
404 448
        header_heigth = 18
405 449
        # width of one day in pixels
......
408 452
        g_height = 20 * number_of_rows + 30
409 453
        headers_heigth = (show_weeks ? 2*header_heigth : header_heigth)
410 454
        height = g_height + headers_heigth
411
            
455

  
412 456
        imgl = Magick::ImageList.new
413 457
        imgl.new_image(subject_width+g_width+1, height)
414 458
        gc = Magick::Draw.new
415
        
459

  
416 460
        # Subjects
417 461
        gc.stroke('transparent')
418 462
        subjects(:image => gc, :top => (headers_heigth + 20), :indent => 4, :format => :image)
419
    
463

  
420 464
        # Months headers
421 465
        month_f = @date_from
422 466
        left = subject_width
423
        @months.times do 
467
        @months.times do
424 468
          width = ((month_f >> 1) - month_f) * zoom
425 469
          gc.fill('white')
426 470
          gc.stroke('grey')
......
433 477
          left = left + width
434 478
          month_f = month_f >> 1
435 479
        end
436
        
480

  
437 481
        # Weeks headers
438 482
        if show_weeks
439 483
        	left = subject_width
......
465 509
        		week_f = week_f+7
466 510
        	end
467 511
        end
468
        
512

  
469 513
        # Days details (week-end in grey)
470 514
        if show_days
471 515
        	left = subject_width
472 516
        	height = g_height + header_heigth - 1
473 517
        	wday = @date_from.cwday
474
        	(date_to - @date_from + 1).to_i.times do 
518
        	(date_to - @date_from + 1).to_i.times do
475 519
              width =  zoom
476 520
              gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
477 521
              gc.stroke('#ddd')
......
482 526
              wday = 1 if wday > 7
483 527
        	end
484 528
        end
485
    
529

  
486 530
        # border
487 531
        gc.fill('transparent')
488 532
        gc.stroke('grey')
......
490 534
        gc.rectangle(0, 0, subject_width+g_width, headers_heigth)
491 535
        gc.stroke('black')
492 536
        gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_heigth-1)
493
            
537

  
494 538
        # content
495 539
        top = headers_heigth + 20
496 540

  
497 541
        gc.stroke('transparent')
498 542
        lines(:image => gc, :top => top, :zoom => zoom, :subject_width => subject_width, :format => :image)
499
        
543

  
500 544
        # today red line
501 545
        if Date.today >= @date_from and Date.today <= date_to
502 546
          gc.stroke('red')
503 547
          x = (Date.today-@date_from+1)*zoom + subject_width
504
          gc.line(x, headers_heigth, x, headers_heigth + g_height-1)      
505
        end    
506
        
548
          gc.line(x, headers_heigth, x, headers_heigth + g_height-1)
549
        end
550

  
507 551
        gc.draw(imgl)
508 552
        imgl.format = format
509 553
        imgl.to_blob
......
520 564
        pdf.Cell(PDF::LeftPaneWidth, 20, project.to_s)
521 565
        pdf.Ln
522 566
        pdf.SetFontStyle('B',9)
523
        
567

  
524 568
        subject_width = PDF::LeftPaneWidth
525 569
        header_heigth = 5
526
        
570

  
527 571
        headers_heigth = header_heigth
528 572
        show_weeks = false
529 573
        show_days = false
530
        
574

  
531 575
        if self.months < 7
532 576
          show_weeks = true
533 577
          headers_heigth = 2*header_heigth
......
536 580
            headers_heigth = 3*header_heigth
537 581
          end
538 582
        end
539
        
583

  
540 584
        g_width = PDF.right_pane_width
541 585
        zoom = (g_width) / (self.date_to - self.date_from + 1)
542 586
        g_height = 120
543 587
        t_height = g_height + headers_heigth
544
        
588

  
545 589
        y_start = pdf.GetY
546
        
590

  
547 591
        # Months headers
548 592
        month_f = self.date_from
549 593
        left = subject_width
550 594
        height = header_heigth
551
        self.months.times do 
552
          width = ((month_f >> 1) - month_f) * zoom 
595
        self.months.times do
596
          width = ((month_f >> 1) - month_f) * zoom
553 597
          pdf.SetY(y_start)
554 598
          pdf.SetX(left)
555 599
          pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
556 600
          left = left + width
557 601
          month_f = month_f >> 1
558
        end  
559
        
602
        end
603

  
560 604
        # Weeks headers
561 605
        if show_weeks
562 606
          left = subject_width
......
582 626
            week_f = week_f+7
583 627
          end
584 628
        end
585
        
629

  
586 630
        # Days headers
587 631
        if show_days
588 632
          left = subject_width
589 633
          height = header_heigth
590 634
          wday = self.date_from.cwday
591 635
          pdf.SetFontStyle('B',7)
592
          (self.date_to - self.date_from + 1).to_i.times do 
636
          (self.date_to - self.date_from + 1).to_i.times do
593 637
            width = zoom
594 638
            pdf.SetY(y_start + 2 * header_heigth)
595 639
            pdf.SetX(left)
......
599 643
            wday = 1 if wday > 7
600 644
          end
601 645
        end
602
        
646

  
603 647
        pdf.SetY(y_start)
604 648
        pdf.SetX(15)
605 649
        pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1)
606
        
650

  
607 651
        # Tasks
608 652
        top = headers_heigth + y_start
609 653
        options = {
......
620 664
        render(options)
621 665
        pdf.Output
622 666
      end
623
      
667

  
668
      def edit(pms)
669
        id = pms[:id]
670
        kind = id.slice!(0).chr
671
        begin
672
          case kind
673
          when 'i'
674
            @issue = Issue.find(pms[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
675
          when 'p'
676
            @issue = Project.find(pms[:id])
677
          when 'v'
678
            @issue = Version.find(pms[:id], :include => [:project])
679
          end
680
        rescue ActiveRecord::RecordNotFound
681
          return "issue not found : #{pms[:id]}", 400
682
        end
683

  
684
        if !@issue.start_date || !@issue.due_before
685
          #render :text=>l(:notice_locking_conflict), :status=>400
686
          return l(:notice_locking_conflict), 400
687
        end
688
        @issue.init_journal(User.current)
689
        date_from = Date.parse(pms[:date_from])
690
        old_start_date = @issue.start_date
691
        o = get_issue_position(@issue, pms[:zoom])
692
        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]}"
693

  
694
        if pms[:day]
695
          #bar moved
696
          day = pms[:day].to_i
697
          duration = @issue.due_before - @issue.start_date
698
          @issue.start_date = date_from + day
699
          @issue.due_date = @issue.start_date + duration.to_i if @issue.due_date
700
        elsif pms[:start_date]
701
          #start date changed
702
          start_date = Date.parse(pms[:start_date])
703
          if @issue.start_date == start_date
704
            #render :text=>""
705
            return "", 200 #nothing has changed
706
          end
707
          @issue.start_date = start_date
708
          @issue.due_date = start_date if @issue.due_date && start_date > @issue.due_date
709
        elsif pms[:due_date]
710
          #due date changed
711
          due_date = Date.parse(pms[:due_date])
712
          if @issue.due_date == due_date
713
            #render :text=>""
714
            return "", 200 #nothing has changed
715
          end
716
          @issue.due_date = due_date
717
          @issue.start_date = due_date if due_date < @issue.start_date
718
        end
719
        fv = @issue.fixed_version
720
        if fv && fv.effective_date && !@issue.due_date && fv.effective_date < @issue.start_date
721
          @issue.start_date = old_start_date
722
        end
723

  
724
        begin
725
          @issue.save!
726
          o = get_issue_position(@issue, pms[:zoom])
727
          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]}"
728

  
729
          prj_map = {}
730
          text = set_project_data(@issue.project, pms[:zoom], text, prj_map)
731
          version_map = {}
732
          text = set_version_data(@issue.fixed_version, pms[:zoom], text, version_map)
733

  
734
          #check dependencies
735
          issues = @issue.all_precedes_issues
736
          issues.each do |i|
737
            o = get_issue_position(i, pms[:zoom])
738
            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]}"
739
            text = set_project_data(i.project, pms[:zoom], text, prj_map)
740
            text = set_version_data(i.fixed_version, pms[:zoom], text, version_map)
741
          end
742

  
743
          #check parent
744
          is = @issue
745
          while
746
            pid = is.parent_issue_id
747
            break if !pid
748
            i = Issue.find(pid)
749
            o = get_issue_position(i, pms[:zoom])
750
            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]}"
751
            text = set_project_data(i.project, pms[:zoom], text, prj_map)
752
            text = set_version_data(i.fixed_version, pms[:zoom], text, version_map)
753
            is = i
754
          end
755
          #render :text=>text
756
          return text, 200
757
        rescue => e
758
          #render :text=>@issue.errors.full_messages.join("\n") + "|" + text_for_revert  , :status=>400
759
          if @issue.errors.full_messages.to_s == ""
760
            return e.to_s + "\n" + [$!,$@.join("\n")].join("\n") + "\n" + @issue.errors.full_messages.join("\n") + "|" + text_for_revert, 400
761
          else
762
            return @issue.errors.full_messages.join("\n") + "|" + text_for_revert, 400
763
          end
764
        end
765
      end
766

  
624 767
      private
625
      
768

  
626 769
      def coordinates(start_date, end_date, progress, zoom=nil)
627 770
        zoom ||= @zoom
628
        
771

  
629 772
        coords = {}
630 773
        if start_date && end_date && start_date < self.date_to && end_date > self.date_from
631 774
          if start_date > self.date_from
......
640 783
          else
641 784
            coords[:bar_end] = self.date_to - self.date_from + 1
642 785
          end
643
        
786

  
644 787
          if progress
645 788
            progress_date = start_date + (end_date - start_date) * (progress / 100.0)
646 789
            if progress_date > self.date_from && progress_date > start_date
......
650 793
                coords[:bar_progress_end] = self.date_to - self.date_from + 1
651 794
              end
652 795
            end
653
            
796

  
654 797
            if progress_date < Date.today
655 798
              late_date = [Date.today, end_date].min
656 799
              if late_date > self.date_from && late_date > start_date
......
663 806
            end
664 807
          end
665 808
        end
666
        
809

  
667 810
        # Transforms dates into pixels witdh
668 811
        coords.keys.each do |key|
669 812
          coords[key] = (coords[key] * zoom).floor
......
675 818
      def sort_issues!(issues)
676 819
        issues.sort! { |a, b| gantt_issue_compare(a, b, issues) }
677 820
      end
678
  
821

  
679 822
      # TODO: top level issues should be sorted by start date
680 823
      def gantt_issue_compare(x, y, issues)
681 824
        if x.root_id == y.root_id
......
684 827
          x.root_id <=> y.root_id
685 828
        end
686 829
      end
687
      
830

  
688 831
      def current_limit
689 832
        if @max_rows
690 833
          @max_rows - @number_of_rows
......
692 835
          nil
693 836
        end
694 837
      end
695
      
838

  
696 839
      def abort?
697 840
        if @max_rows && @number_of_rows >= @max_rows
698 841
          @truncated = true
699 842
        end
700 843
      end
701
      
844

  
702 845
      def pdf_new_page?(options)
703 846
        if options[:top] > 180
704 847
          options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
......
707 850
          options[:pdf].Line(15, options[:top] - 0.1, PDF::TotalWidth, options[:top] - 0.1)
708 851
        end
709 852
      end
710
      
853

  
711 854
      def html_subject(params, subject, options={})
712 855
        output = "<div class=' #{options[:css] }' style='position: absolute;line-height:1.2em;height:16px;top:#{params[:top]}px;left:#{params[:indent]}px;overflow:hidden;'>"
713 856
        output << subject
......
715 858
        @subjects << output
716 859
        output
717 860
      end
718
      
861

  
719 862
      def pdf_subject(params, subject, options={})
720 863
        params[:pdf].SetY(params[:top])
721 864
        params[:pdf].SetX(15)
722
        
865

  
723 866
        char_limit = PDF::MaxCharactorsForSubject - params[:indent]
724 867
        params[:pdf].Cell(params[:subject_width]-15, 5, (" " * params[:indent]) +  subject.to_s.sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR")
725
      
868

  
726 869
        params[:pdf].SetY(params[:top])
727 870
        params[:pdf].SetX(params[:subject_width])
728 871
        params[:pdf].Cell(params[:g_width], 5, "", "LR")
729 872
      end
730
      
873

  
731 874
      def image_subject(params, subject, options={})
732 875
        params[:image].fill('black')
733 876
        params[:image].stroke('transparent')
734 877
        params[:image].stroke_width(1)
735 878
        params[:image].text(params[:indent], params[:top] + 2, subject)
736 879
      end
737
      
880

  
738 881
      def html_task(params, coords, options={})
739 882
        output = ''
740 883
        # Renders the task bar, with progress and late
741 884
        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>"
743
          
885
          i_width = coords[:bar_end] - coords[:bar_start] - 2
886
          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'" : ""}>"
887
          output << "<div id='task_todo_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:#{ i_width}px;' class='#{options[:css]} task_todo'>&nbsp;</div>"
888

  
744 889
          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>"
890
            l_width = coords[:bar_late_end] - coords[:bar_start] - 2
891
            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>"
892
          else
893
            output << "<div id='task_late_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:0px;' class='#{ options[:css] + " task_none"}'>&nbsp;</div>"
746 894
          end
747 895
          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>"
896
            d_width = coords[:bar_progress_end] - coords[:bar_start] - 2
897
            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>"
898
          else
899
            output << "<div id='task_done_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:0px;' class='#{ options[:css] + " task_none"}'>&nbsp;</div>"
749 900
          end
901
          output << "</div>"
902
        else
903
          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'" : ""}>"
904
          output << "<div id='task_todo_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:0px;' class='#{ options[:css]} task_todo'>&nbsp;</div>"
905
          output << "<div id='task_late_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:0px;' class='#{ options[:css] + " task_none"}'>&nbsp;</div>"
906
          output << "<div id='task_done_#{options[:kind]}#{options[:id]}' style='float:left:0px; width:0px;' class='#{ options[:css] + " task_none"}'>&nbsp;</div>"
907
          output << "</div>"
750 908
        end
751 909
        # Renders the markers
752 910
        if options[:markers]
753 911
          if coords[:start]
754
            output << "<div style='top:#{ params[:top] }px;left:#{ coords[:start] }px;width:15px;' class='#{options[:css]} marker starting'>&nbsp;</div>"
912
            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 913
          end
756 914
          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>"
915
            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 916
          end
917
        else
918
          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);}')
759 919
        end
760 920
        # Renders the label on the right
761 921
        if options[:label]
762
          output << "<div style='top:#{ params[:top] }px;left:#{ (coords[:bar_end] || 0) + 8 }px;' class='#{options[:css]} label'>"
922
          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 923
          output << options[:label]
764 924
          output << "</div>"
765 925
        end
766 926
        # Renders the tooltip
767 927
        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;'>"
928
          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 929
          output << '<span class="tip">'
770 930
          output << view.render_issue_tooltip(options[:issue])
771 931
          output << "</span></div>"
......
773 933
        @lines << output
774 934
        output
775 935
      end
776
      
936

  
777 937
      def pdf_task(params, coords, options={})
778 938
        height = options[:height] || 2
779
        
939

  
780 940
        # Renders the task bar, with progress and late
781 941
        if coords[:bar_start] && coords[:bar_end]
782 942
          params[:pdf].SetY(params[:top]+1.5)
783 943
          params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
784 944
          params[:pdf].SetFillColor(200,200,200)
785 945
          params[:pdf].Cell(coords[:bar_end] - coords[:bar_start], height, "", 0, 0, "", 1)
786
            
946

  
787 947
          if coords[:bar_late_end]
788 948
            params[:pdf].SetY(params[:top]+1.5)
789 949
            params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
......
803 963
            params[:pdf].SetY(params[:top] + 1)
804 964
            params[:pdf].SetX(params[:subject_width] + coords[:start] - 1)
805 965
            params[:pdf].SetFillColor(50,50,200)
806
            params[:pdf].Cell(2, 2, "", 0, 0, "", 1) 
966
            params[:pdf].Cell(2, 2, "", 0, 0, "", 1)
807 967
          end
808 968
          if coords[:end]
809 969
            params[:pdf].SetY(params[:top] + 1)
810 970
            params[:pdf].SetX(params[:subject_width] + coords[:end] - 1)
811 971
            params[:pdf].SetFillColor(50,50,200)
812
            params[:pdf].Cell(2, 2, "", 0, 0, "", 1) 
972
            params[:pdf].Cell(2, 2, "", 0, 0, "", 1)
813 973
          end
814 974
        end
815 975
        # Renders the label on the right
......
821 981

  
822 982
      def image_task(params, coords, options={})
823 983
        height = options[:height] || 6
824
        
984

  
825 985
        # Renders the task bar, with progress and late
826 986
        if coords[:bar_start] && coords[:bar_end]
827 987
          params[:image].fill('#aaa')
828 988
          params[:image].rectangle(params[:subject_width] + coords[:bar_start], params[:top], params[:subject_width] + coords[:bar_end], params[:top] - height)
829
 
989

  
830 990
          if coords[:bar_late_end]
831 991
            params[:image].fill('#f66')
832 992
            params[:image].rectangle(params[:subject_width] + coords[:bar_start], params[:top], params[:subject_width] + coords[:bar_late_end], params[:top] - height)
......
857 1017
          params[:image].text(params[:subject_width] + (coords[:bar_end] || 0) + 5,params[:top] + 1, options[:label])
858 1018
        end
859 1019
      end
1020

  
1021
      ##
1022
      ##  for edit gantt
1023
      ##
1024
      def set_project_data(prj, zoom, text, prj_map = {})
1025
        if !prj
1026
          return text
1027
        end
1028
        if !prj_map[prj.id]
1029
          o = get_project_position(prj, zoom)
1030
          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]}"
1031
          prj_map[prj.id] = prj
1032
        end
1033
        text = set_project_data(prj.parent, zoom, text, prj_map)
1034
      end
1035

  
1036
      def set_version_data(version, zoom, text, version_map = {})
1037
        if !version
1038
          return text
1039
        end
1040
        if !version_map[version.id]
1041
          o = get_version_position(version, zoom)
1042
          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]}"
1043
          version_map[version.id] = version
1044
        end
1045
        return text
1046
      end
1047

  
1048
      def get_pos(coords)
1049
        i_left = 0
1050
        i_width = 0
1051
        l_width = 0
1052
        d_width = 0
1053
        if coords[:bar_start]
1054
          i_left = coords[:bar_start]
1055
          if coords[:bar_end]
1056
            i_width = coords[:bar_end] - coords[:bar_start] - 2
1057
            i_width = 0 if i_width < 0
1058
          end
1059
          if coords[:bar_late_end]
1060
            l_width = coords[:bar_late_end] - coords[:bar_start] - 2
1061
          end
1062
          if coords[:bar_progress_end]
1063
            d_width = coords[:bar_progress_end] - coords[:bar_start] - 2
1064
          end
1065
        end
1066
        return i_left, i_width, l_width, d_width
1067
      end
1068

  
1069
      def get_issue_position(issue, zoom_str)
1070
        z = zoom_str.to_i
1071
        zoom = 1
1072
        z.times { zoom = zoom * 2}
1073
        id = issue.due_before
1074
        if id && @date_to < id
1075
          id = @date_to
1076
        end
1077
        coords = coordinates(issue.start_date, id, issue.done_ratio, zoom)
1078

  
1079
        i_left, i_width, l_width, d_width = get_pos(coords)
1080
        if coords[:end]
1081
          return i_left, i_width, l_width, d_width, coords[:start], coords[:end] + zoom
1082
        else
1083
          return i_left, i_width, l_width, d_width, coords[:start], nil
1084
        end
1085
      end
1086

  
1087
      def get_project_position(project, zoom_str)
1088
        z = zoom_str.to_i
1089
        zoom = 1
1090
        z.times { zoom = zoom * 2}
1091
        pd = project.due_date
1092
        if pd && @date_to < pd
1093
          pd = @date_to
1094
        end
1095
        coords = coordinates(project.start_date, pd, nil, zoom)
1096
        i_left, i_width, l_width, d_width = get_pos(coords)
1097
        if coords[:end]
1098
          return i_left, i_width, l_width, d_width, coords[:start], coords[:end] + zoom
1099
        else
1100
          return i_left, i_width, l_width, d_width, coords[:start], nil
1101
        end
1102
      end
1103

  
1104
      def get_version_position(version, zoom_str)
1105
        z = zoom_str.to_i
1106
        zoom = 1
1107
        z.times { zoom = zoom * 2}
1108
        vd = version.due_date
1109
        if vd &&  @date_to < vd
1110
          vd = @date_to
1111
        end
1112
        coords = coordinates(version.start_date, vd, version.completed_pourcent, zoom)
1113
        i_left, i_width, l_width, d_width = get_pos(coords)
1114
        if coords[:end]
1115
          return i_left, i_width, l_width, d_width, coords[:start], coords[:end] + zoom
1116
        else
1117
          return i_left, i_width, l_width, d_width, coords[:start], nil
1118
        end
1119
      end
1120

  
1121
      def calendar_for_issue(issue, options)
1122
        # Skip issues that don't have a due_before (due_date or version's due_date)
1123
        if issue.is_a?(Issue) && issue.due_before
1124

  
1125
          case options[:format]
1126
          when :html
1127
            @calendars << "<div style='position: absolute;line-height:1.2em;height:16px;top:#{options[:top]}px;left:4px;overflow:hidden;'>"
1128
            start_date = issue.start_date
1129
            if start_date
1130
              @calendars << "<span id='i#{issue.id}_start_date_str'>"
1131
              @calendars << format_date(start_date)
1132
              @calendars << "</span>"
1133
              @calendars << "<input type='hidden' size='12' id='i#{issue.id}_hidden_start_date' value='#{start_date}' />"
1134
              if issue.leaf?
1135
                @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')}"
1136
              else
1137
                @calendars << "<input type='hidden' size='12' id='i#{issue.id}_start_date' value='#{start_date}' />&nbsp;&nbsp;&nbsp;"
1138
              end
1139
              @calendars << observe_date_field("i#{issue.id}", 'start')
1140
            end
1141
            due_date = issue.due_date
1142
            if due_date
1143
              @calendars << "<span id='i#{issue.id}_due_date_str'>"
1144
              @calendars << format_date(due_date)
1145
              @calendars << "</span>"
1146
              @calendars << "<input type='hidden' size='12' id='i#{issue.id}_hidden_due_date' value='#{due_date}' />"
1147
              if issue.leaf?
1148
                @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')}"
1149
              else
1150
                @calendars << "<input type='hidden' size='12' id='i#{issue.id}_due_date' value='#{due_date}' />"
1151
              end
1152
              @calendars << observe_date_field("i#{issue.id}", 'due')
1153
            end
1154
            @calendars << "</div>"
1155
          when :image
1156
            #nop
1157
          when :pdf
1158
            #nop
1159
          end
1160
        else
1161
          ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
1162
          ''
1163
        end
1164
      end
1165

  
1166
      def calendar_for_version(version, options)
1167
        # Skip version that don't have a due_before (due_date or version's due_date)
1168
        if version.is_a?(Version) && version.start_date && version.due_date
1169

  
1170
          case options[:format]
1171
          when :html
1172
            @calendars << "<div style='position: absolute;line-height:1.2em;height:16px;top:#{options[:top]}px;left:4px;overflow:hidden;'>"
1173
            @calendars << "<span id='v#{version.id}_start_date_str'>"
1174
            @calendars << format_date(version.effective_date)
1175
            @calendars << "</span>"
1176
            @calendars << "</div>"
1177
          when :image
1178
            #nop
1179
          when :pdf
1180
            #nop
1181
          end
1182
        else
1183
          ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
1184
          ''
1185
        end
1186
      end
1187

  
1188
      def calendar_for_project(project, options)
1189
        case options[:format]
1190
        when :html
1191
          @calendars << "<div style='position: absolute;line-height:1.2em;height:16px;top:#{options[:top]}px;left:4px;overflow:hidden;'>"
1192
          @calendars << "<span id='p#{project.id}_start_date_str'>"
1193
          @calendars << format_date(project.start_date) if project.start_date
1194
          @calendars << "</span>"
1195
          @calendars << "&nbsp;&nbsp;&nbsp;"
1196
          @calendars << "<span id='p#{project.id}_due_date_str'>"
1197
          @calendars << format_date(project.due_date) if project.due_date
1198
          @calendars << "</span>"
1199
          @calendars << "</div>"
1200
        when :image
1201
          # nop
1202
        when :pdf
1203
          # nop
1204
        end
1205
      end
1206

  
1207
      def observe_date_field(id, type)
1208
        output = ''
1209
        prj_id = ''
1210
        prj_id = @project.to_param if @project
1211
        output << "<script type='text/javascript'>\n"
1212
        output << "//<![CDATA[\n"
1213
        output << "new Form.Element.Observer('#{id}_#{type}_date', 0.25,\n"
1214
        output << "  function(element, value) {\n"
1215
        output << "    if (value == document.getElementById('#{id}_hidden_#{type}_date').value) {\n"
1216
        output << "      return ;\n"
1217
        output << "    }\n"
1218
        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)});"
1219
        output << "  })\n"
1220
        output << "//]]>\n"
1221
        output << "</script>"
1222
      end
860 1223
    end
861 1224
  end
862 1225
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;}