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

Returns a new instance of 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

Returns:

  • (Boolean)


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

Returns:

  • (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