Class: Gantt

Inherits:
Gtk::Viewport
  • Object
show all
Includes:
ManqodCommon
Defined in:
lib/ListHolder/GanttHolder/Gantt.rb

Overview

this file is part of manqod manqod is distributed under the CDDL licence the author of manqod is Dobai-Pataky Balint([email protected])

Constant Summary collapse

Resolution =

seconds/pixel,scale point distance, time_format, day0_format, name

{1=>[10,3*60,"%d %b\n%H:%M","day {%j}\n%H:%M","10 seconds/pixel  (17 min/inch)"],
            2=>[60,200,"%d %b\n%H:%M","day {%j}\n%H:%M","1 minute/pixel (95 min/inch)"],
            3=>[60*10,200,"%d %b\n%H:%M","day {%j}\n%H:%M","10 minutes/pixel (16 hours/inch)"],
            4=>[60*30,200,"%d %b\n%H:%M","day {%j}\n%H:%M","30 minutes/pixel (2 days/inch)"],
            5=>[60*60,24*10,"%Y %b %d","day {%j}\n%H:%M","1 hour/pixel (4 days/inch)"],
            6=>[60*60*2,2*24*10,"%Y %b %d","day {%j}\n%H:%M","2 hour/pixel (8 days/inch)"],
            7=>[60*60*4,4*24*10,"%Y %b %d","day {%j}\n%H:%M","4 hour/pixel (16 days/inch)"],
            8=>[60*60*6,4*24*10,"%Y %b %d","day {%j}\n%H:%M","6 hour/pixel (24 days/inch)"],
            9=>[60*60*8,4*24*10,"%Y %b %d","day {%j}\n%H:%M","8 hour/pixel (32 days/inch)"],
            10=>[60*60*12,4*24*10,"%Y %b %d","day {%j}\n%H:%M","12 hour/pixel (48 days/inch)"],
            11=>[60*60*24,4*24*10,"%Y %b %d","day {%j} %H:%M","1 day/pixel (96 days/inch)"]
}
None =
0
Moving =
1
Connecting =
2
Scrolling =
3

Constants included from ManqodCommon

ManqodCommon::CRITICAL, ManqodCommon::DEBUG, ManqodCommon::ERROR, ManqodCommon::INFO, ManqodCommon::NORMAL, ManqodCommon::WARNING

Constants included from Eprint

Eprint::DOMAIN, Eprint::LEVEL

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from ManqodCommon

#add_where, #admin, #admin_cache, #admin_qrow, #admin_rows, #backtrace_to_debug, #cache, #changed_ids_of_base, #client, #client_fields, #client_image_of_id, #client_qrow, #client_query, #client_rows, #eeval, #escape_string, #getBinding, #guess_base, #guess_table, #image_of_id, #lzero, #manqod_db, #measure, #myexec, #nick, #nick_id, #number_format, #qrow, #query, #reconnect_manqod_db, #rows, #run_events, #send_message, #sendmail, #set_manqod_db_uri, #set_nick

Methods included from Eprint

#ecode, #edebug, #eerror, #einfo, #enormal, #eprint, #ewarn, #gtk_set_edebug, #set_edebug, #tell_exception

Constructor Details

#initialize(gantt_holder) ⇒ Gantt



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 25

def initialize(gantt_holder)
  @gantt_holder=gantt_holder
  #hold start and end points here for which Scaler will need to draw header
  @points=Array.new
  @rectangles=Hash.new
  #time interval/pixel
  @min_time=nil
  @res_idx=(list.gtk_attribute("gantt_res_idx") || '2').to_i
  @day0=(list.gtk_attribute("gantt_day0") || 'false') == 'true'
  @scaler_format=if @day0 then list.gtk_attribute("gantt_day0_header_format") || "%d %b\n%H:%M"
    else list.gtk_attribute("gantt_header_format") || "%d %b\n%H:%M" end
  @time_format=list.gtk_attribute("gantt_time_format") || "%H:%M"

  super(Gtk::Adjustment.new(0,0,0,0,0,0),list.holder.list_scroller.vadjustment)
  set_resize_mode(Gtk::RESIZE_PARENT)
  add(Gtk::EventBox.new.add(@widget=Gtk::Fixed.new))

  child.signal_connect("scroll-event"){|me,ev|
    zoom_in if ev.direction == Gdk::EventScroll::UP && ev.state.control_mask?
    zoom_out if ev.direction == Gdk::EventScroll::DOWN && ev.state.control_mask?
  }
  child.signal_connect("button-press-event"){|me,ev|
    case ev.button
      #right button dragging: editing successors/predecessors
      when 3 then
        unless (@successors_column && successors_editable)
          ewarn("no editable successors_column specified?")
          else
          if @start=rectangle?(ev.x,ev.y)
            #double
#             if ev.event_type == Gdk::Event::BUTTON2_PRESS
#               @start.set_new_successors(nil)
#             else
              @state = Connecting
              window.set_cursor(Gdk::Cursor.new(Gdk::Cursor::CROSS))
#             end
          end
        end
      #left click: moving
      when 1 then 
        @start=rectangle?(ev.x,ev.y)
        if start_editable && !@start.nil? && !@start.has_child?
          @state = Moving
          @move_shift=ev.x-@start.x1
          window.set_cursor(Gdk::Cursor.new(Gdk::Cursor::HAND2))
        end
      #middle click: panning
      when 2 then 
        @state = Scrolling
        @scroll_x=ev.x
        @scroll_y=ev.y
        window.set_cursor(Gdk::Cursor.new(Gdk::Cursor::HAND1))
    end
    clear
#     gantt_holder.scaler.clear
    false
  }
  child.signal_connect("button-release-event"){|me,ev|
    case @state
      when Connecting
        @motion=nil
        @end=rectangle?(ev.x,ev.y)
        @start.toggle_successor(@end)
      when Moving
        #moving finished
        @start.move_to(hadjustment.value+self.pointer[0]-@move_shift,true)
        @motion=nil
      when Scrolling
    end
    window.set_cursor(nil)
    @state = None
    clear
    false
  }
  child.signal_connect("motion-notify-event"){|me,ev|
    case @state
      when Connecting then 
        @motion=[ev.x,ev.y]
        clear
      when Moving then
        @start.move_to(hadjustment.value+self.pointer[0]-@move_shift)
        @motion=[ev.x,ev.y]
        clear
      when Scrolling then
        px=@scroll_x - self.pointer[0]
        py=@scroll_y - self.pointer[1]
        px=hadjustment.lower if px < hadjustment.lower
        py=vadjustment.lower if py < hadjustment.lower
        px=hadjustment.upper - hadjustment.page_size if px > hadjustment.upper - hadjustment.page_size
        py=vadjustment.upper - vadjustment.page_size if py > vadjustment.upper - vadjustment.page_size
        hadjustment.value=px
        vadjustment.value=py
        clear
    end
  }
  #set footer height
  list.holder.signal_connect("size-allocate"){|me,alloc|
    h=list.holder.buttonholder.holder.allocation.height
    h+=list.holder.list_panel.allocation.height if list.holder.list_panel.visibility
    gantt_holder.footer.set_height_request(h) unless gantt_holder.footer.destroyed?
    false
  }
  #set gantt height
  list.holder.list_scroller.vadjustment.signal_connect("changed"){|vad|
    @widget.set_height_request(vad.upper) unless @widget.destroyed?
    false
  }
  @widget.signal_connect("expose-event"){|me,ev|
    cr=me.window.create_cairo_context.set_line_cap(Cairo::LineCap::ROUND)
    @rectangles.each{|rid,r|
      #draw rectangles, hilight the hovered on connecting
      r.draw(cr,(@start == r) || (@end == r) || (@motion && r.in_area?(@motion[0],@motion[1])))
    }
    #draw arrows,verticals
    @rectangles.each{|rid,r| 
      r.draw_arrow(cr).draw_verticals(cr)
      r.draw_hilight(cr) if @cursor_id == rid
    }
    #printing percentages
    @rectangles.each{|rid,r| 
      r.print_percentage(cr)
    } if percentage_column
    #draw connecting line
    if @start && @state == Connecting && @motion
      cr.set_line_width(3).set_source_rgba(148.0/255,88.0/255,116.0/255,0.5).set_dash(100000)
      cr.move_to(@start.x,@start.y)
      cr.line_to(@motion[0],@motion[1])
      cr.stroke
    end
  }
  signal_connect("destroy"){|me|
    list.delete_observer(self)
  }
end

Instance Attribute Details

#cursor_idObject (readonly)

Returns the value of attribute cursor_id.



160
161
162
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 160

def cursor_id
  @cursor_id
end

#day0Object (readonly)

Returns the value of attribute day0.



161
162
163
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 161

def day0
  @day0
end

#dur_columnObject (readonly)

Returns the value of attribute dur_column.



161
162
163
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 161

def dur_column
  @dur_column
end

#gantt_holderObject (readonly)

Returns the value of attribute gantt_holder.



160
161
162
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 160

def gantt_holder
  @gantt_holder
end

#group_color_columnObject (readonly)

Returns the value of attribute group_color_column.



161
162
163
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 161

def group_color_column
  @group_color_column
end

#group_columnObject (readonly)

Returns the value of attribute group_column.



161
162
163
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 161

def group_column
  @group_column
end

#max_cooObject (readonly)

Returns the value of attribute max_coo.



163
164
165
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 163

def max_coo
  @max_coo
end

#max_timeObject (readonly)

Returns the value of attribute max_time.



160
161
162
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 160

def max_time
  @max_time
end

#min_cooObject (readonly)

Returns the value of attribute min_coo.



163
164
165
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 163

def min_coo
  @min_coo
end

#min_timeObject (readonly)

Returns the value of attribute min_time.



160
161
162
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 160

def min_time
  @min_time
end

#partial_columnObject (readonly)

Returns the value of attribute partial_column.



161
162
163
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 161

def partial_column
  @partial_column
end

#percentage_columnObject (readonly)

Returns the value of attribute percentage_column.



161
162
163
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 161

def percentage_column
  @percentage_column
end

#pointsObject (readonly)

Returns the value of attribute points.



160
161
162
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 160

def points
  @points
end

#rectanglesObject (readonly)

Returns the value of attribute rectangles.



162
163
164
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 162

def rectangles
  @rectangles
end

#res_idxObject (readonly)

Returns the value of attribute res_idx.



160
161
162
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 160

def res_idx
  @res_idx
end

#scaler_formatObject (readonly)

Returns the value of attribute scaler_format.



163
164
165
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 163

def scaler_format
  @scaler_format
end

#start_columnObject (readonly)

Returns the value of attribute start_column.



161
162
163
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 161

def start_column
  @start_column
end

#start_editableObject (readonly)

Returns the value of attribute start_editable.



162
163
164
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 162

def start_editable
  @start_editable
end

#successors_columnObject (readonly)

Returns the value of attribute successors_column.



161
162
163
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 161

def successors_column
  @successors_column
end

#successors_editableObject (readonly)

Returns the value of attribute successors_editable.



162
163
164
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 162

def successors_editable
  @successors_editable
end

#time_formatObject (readonly)

Returns the value of attribute time_format.



163
164
165
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 163

def time_format
  @time_format
end

#widgetObject (readonly)

Returns the value of attribute widget.



159
160
161
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 159

def widget
  @widget
end

Instance Method Details

#clearObject



280
281
282
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 280

def clear
  @widget.queue_draw_area(0, 0, @widget.allocation.width, @widget.allocation.height)
end

#collisions(pr) ⇒ Object



265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 265

def collisions(pr)
  cs=Array.new
  @rectangles.each_value{|r| 
    #by goup items' maximum
    if group_column && r != pr && r.group == pr.group
      min=[pr.start,r.start].max
      max=[pr.start+pr.dur,r.start+r.dur].min
      cs.push([min,max]) if min<max
    end
  }
  cs
end

#create_rectangle(iter, level = 0) ⇒ Object



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 214

def create_rectangle(iter,level=0)
  #crawl the tree recursively and create rectangles
  vi=!iter.nil?
  start=nil
  finish=nil
  height=nil
  percent=0
  dursum=0
  while vi
    r=Rectangle.new(self,iter)
    r.level=level
    start=r.start if start.nil? || start>r.start
    finish=r.finish if finish.nil? || finish<r.finish
    height=r.y1+r.height if height.nil? || height<r.height+r.y1
    dursum+=r.dur
    percent+=r.dur.to_f * r.percentage/100.0 #calculate the current complete percentage of r
    @rectangles[r.iter_id]=r
    if iter.has_child?
      starts,finishs,percents,heights=create_rectangle(iter.first_child,level+1)
      r.set_height(heights-r.y1) unless heights-r.y1 == r.height
      height=r.y1+r.height if height.nil? || height<r.height+r.y1 #recalc height, since it changes by depth
      r.set_start(starts,false) unless starts == r.start
      r.set_duration(finishs-starts,false) unless finishs-starts == r.dur
      r.set_percentage(percents,true) unless percents == r.percentage
    end
    vi=iter.next!
  end
  [start,finish,if dursum==0 then 0 else (100.0*percent/dursum).to_i end,height]
end

#display_time(t) ⇒ Object



306
307
308
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 306

def display_time(t)
  s=Time.at(t).strftime(@time_format)
end

#listObject



283
284
285
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 283

def list
  gantt_holder.list
end

#min_for_rectangle(pr) ⇒ Object



254
255
256
257
258
259
260
261
262
263
264
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 254

def min_for_rectangle(pr)
  min=0
  chmin=0
  @rectangles.each_value{|r|
    #by goup items' maximum
    min=r.finish if group_column && r.group == pr.group && r.start<pr.start && pr.start<r.finish
    #children's maxmimum
    r.successors.each{|sid,s| chmin=r.partial_finish if s == pr && r.partial_finish>chmin }
  }
  [min,chmin].max
end

#modelObject



286
287
288
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 286

def model
  list.list_model.data
end

#rectangle?(x, y) ⇒ Boolean

check if this rectangle contains the point @ x,y



245
246
247
248
249
250
251
252
253
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 245

def rectangle?(x,y)
  found=nil
  @rectangles.each_value{|r| 
    if r.in_area?(x,y) && (found.nil? || found.level<r.level)
      found=r
    end
  }
  found
end

#rectangle_by_id?(rid) ⇒ Boolean



277
278
279
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 277

def rectangle_by_id?(rid)
  @rectangles[rid]
end

#resObject



289
290
291
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 289

def res
  Resolution[res_idx][0]
end

#res_nameObject



309
310
311
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 309

def res_name
  Resolution[res_idx][4]
end

#scaler_stepObject



292
293
294
295
296
297
298
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 292

def scaler_step
  if @scaler_format.include?("%H") then 60*24
    elsif @scaler_format.include?("%d") then 60*60*24
       elsif @scaler_format.include?("%W") then 60*60*24*7
         else eerror("set gantt_header_format to a value which includes %W,%d or %H"); 60*24
       end
end

#time_round_res(t) ⇒ Object



299
300
301
302
303
304
305
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 299

def time_round_res(t)
  Time.at(t.to_i).round(if @scaler_format.include?("%H") then "H"
    elsif @scaler_format.include?("%d") then "d"
       elsif @scaler_format.include?("%W") then "W"
         else "d"
       end)
end

#to_sObject



326
327
328
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 326

def to_s
  "Gantt of #{list}"
end

#update(notifier) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 165

def update(notifier)
  if list.model && model && list.list_model.column_of_gantt_start && list.list_model.column_of_gantt_duration
    @start_column=list.list_model.column_of_gantt_start["model_col"].to_i if list.list_model.column_of_gantt_start
    @start_editable=list.list_model.column_of_gantt_start["editable"] if list.list_model.column_of_gantt_start
    @dur_column=list.list_model.column_of_gantt_duration["model_col"].to_i if list.list_model.column_of_gantt_duration
    @successors_column=list.list_model.column_of_gantt_successors["model_col"].to_i if list.list_model.column_of_gantt_successors
    @successors_editable=list.list_model.column_of_gantt_successors["editable"] if list.list_model.column_of_gantt_successors
    @group_column=list.list_model.column_of_gantt_group["model_col"].to_i if list.list_model.column_of_gantt_group
    @partial_column=list.list_model.column_of_gantt_partial["model_col"].to_i if list.list_model.column_of_gantt_partial
    @group_color_column=list.list_model.column_of_gantt_group_color["model_col"].to_i if list.list_model.column_of_gantt_group_color
    @percentage_column=list.list_model.column_of_gantt_percentage["model_col"].to_i if list.list_model.column_of_gantt_percentage
    #set up rectangles
    @rectangles.clear
    create_rectangle(model.iter_first)
    #visible iters
    @min_time=@max_time=nil
    @rectangles.each_value{|r|
      @min_time=r.start if @min_time.nil? || @min_time>r.start
      @max_time=r.finish if @max_time.nil? || @max_time<r.finish
    }

    @min_time=0 if @min_time.nil?
    @max_time=0 if @max_time.nil?
    #extend min_time and max_time to fit resolution
    @min_time=time_round_res(@min_time).to_i
    @max_time=time_round_res(@max_time + scaler_step).to_i
    @points.clear
    @min_coo=if day0 then 0 else @min_time/res||0 end
    @max_coo=@max_time/res||0
    #set our maximum width
    @widget.set_width_request(@max_coo-@min_coo)
    @cursor_id=list.get_cursor_id
    @rectangles.each_pair{|rid,r|
      r.set_gantt_min_x(@min_coo)
      #center on selected
      hadjustment.clamp_page(r.x1-hadjustment.page_size/3,r.x2+hadjustment.page_size/3) if @cursor_id == rid
      #set up successors
      r.init_successors if successors_column
      #set up scaler points
      time_round_res(r.start).to_i.step(time_round_res(r.finish+scaler_step).to_i,scaler_step){|i| @points.push(time_round_res(i).to_i)}}
    @points=@points.uniq.sort!

    #redraw
    gantt_holder.scaler.clear
    show_all
    clear
  end
end

#zoom_inObject



312
313
314
315
316
317
318
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 312

def zoom_in
  unless @res_idx==1
    @res_idx-=1 
    gantt_holder.footer.set_res_label
    update(self)
  end
end

#zoom_outObject



319
320
321
322
323
324
325
# File 'lib/ListHolder/GanttHolder/Gantt.rb', line 319

def zoom_out
  unless @res_idx==Resolution.size
    @res_idx+=1 
    gantt_holder.footer.set_res_label
    update(self)
  end
end