wxruby3-shapes 0.9.0.pre.beta.3 → 0.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/INSTALL.md +5 -7
  3. data/README.md +38 -6
  4. data/assets/logo.svg +339 -0
  5. data/assets/logo.xpm +60 -0
  6. data/assets/screenshot.png +0 -0
  7. data/assets/social.png +0 -0
  8. data/bin/wx-shapes +1 -1
  9. data/lib/wx/shapes/arrow_base.rb +4 -11
  10. data/lib/wx/shapes/arrows/circle_arrow.rb +22 -11
  11. data/lib/wx/shapes/arrows/circle_prong_arrow.rb +48 -0
  12. data/lib/wx/shapes/arrows/cross_bar_arrow.rb +57 -0
  13. data/lib/wx/shapes/arrows/cross_bar_circle_arrow.rb +56 -0
  14. data/lib/wx/shapes/arrows/cross_bar_prong_arrow.rb +49 -0
  15. data/lib/wx/shapes/arrows/crossed_circle.rb +46 -0
  16. data/lib/wx/shapes/arrows/cup_arrow.rb +65 -0
  17. data/lib/wx/shapes/arrows/diamond_arrow.rb +8 -13
  18. data/lib/wx/shapes/arrows/double_cross_bar_arrow.rb +27 -0
  19. data/lib/wx/shapes/arrows/filled_arrow.rb +60 -0
  20. data/lib/wx/shapes/arrows/line_arrow.rb +67 -0
  21. data/lib/wx/shapes/arrows/open_arrow.rb +22 -23
  22. data/lib/wx/shapes/arrows/prong_arrow.rb +42 -0
  23. data/lib/wx/shapes/arrows/solid_arrow.rb +21 -35
  24. data/lib/wx/shapes/arrows/square_arrow.rb +37 -0
  25. data/lib/wx/shapes/auto_layout.rb +2 -2
  26. data/lib/wx/shapes/base.rb +1 -1
  27. data/lib/wx/shapes/canvas_history.rb +20 -0
  28. data/lib/wx/shapes/connection_point.rb +10 -6
  29. data/lib/wx/shapes/diagram.rb +98 -78
  30. data/lib/wx/shapes/events.rb +8 -8
  31. data/lib/wx/shapes/printout.rb +3 -16
  32. data/lib/wx/shapes/serializable.rb +2 -436
  33. data/lib/wx/shapes/serialize/wx.rb +30 -18
  34. data/lib/wx/shapes/shape.rb +211 -168
  35. data/lib/wx/shapes/shape_canvas.rb +728 -267
  36. data/lib/wx/shapes/shape_data_object.rb +99 -18
  37. data/lib/wx/shapes/shape_handle.rb +18 -11
  38. data/lib/wx/shapes/shape_list.rb +34 -67
  39. data/lib/wx/shapes/shapes/bitmap_shape.rb +23 -24
  40. data/lib/wx/shapes/shapes/box_shape.rb +389 -0
  41. data/lib/wx/shapes/shapes/circle_shape.rb +19 -22
  42. data/lib/wx/shapes/shapes/control_shape.rb +77 -41
  43. data/lib/wx/shapes/shapes/curve_shape.rb +38 -31
  44. data/lib/wx/shapes/shapes/diamond_shape.rb +7 -17
  45. data/lib/wx/shapes/shapes/edit_text_shape.rb +6 -9
  46. data/lib/wx/shapes/shapes/ellipse_shape.rb +12 -15
  47. data/lib/wx/shapes/shapes/flex_grid_shape.rb +58 -33
  48. data/lib/wx/shapes/shapes/grid_shape.rb +259 -161
  49. data/lib/wx/shapes/shapes/line_shape.rb +155 -161
  50. data/lib/wx/shapes/shapes/manager_shape.rb +77 -0
  51. data/lib/wx/shapes/shapes/multi_sel_rect.rb +8 -8
  52. data/lib/wx/shapes/shapes/ortho_shape.rb +31 -36
  53. data/lib/wx/shapes/shapes/polygon_shape.rb +23 -29
  54. data/lib/wx/shapes/shapes/rect_shape.rb +95 -53
  55. data/lib/wx/shapes/shapes/round_ortho_shape.rb +6 -8
  56. data/lib/wx/shapes/shapes/round_rect_shape.rb +20 -24
  57. data/lib/wx/shapes/shapes/square_shape.rb +14 -17
  58. data/lib/wx/shapes/shapes/text_shape.rb +95 -53
  59. data/lib/wx/shapes/version.rb +1 -1
  60. data/lib/wx/shapes/wx.rb +16 -7
  61. data/lib/wx/wx-shapes/cmd/test.rb +1 -1
  62. data/samples/demo/arrows.json +1 -0
  63. data/samples/demo/arrows.yaml +793 -0
  64. data/samples/demo/art/HBox.xpm +22 -0
  65. data/samples/demo/art/VBox.xpm +22 -0
  66. data/samples/demo/art/logo.xpm +60 -0
  67. data/samples/demo/class.json +1 -0
  68. data/samples/demo/class.yaml +5631 -0
  69. data/samples/demo/demo.rb +301 -91
  70. data/samples/demo/dialogs.rb +1405 -0
  71. data/samples/demo/erd.json +1 -0
  72. data/samples/demo/erd.yaml +4072 -0
  73. data/samples/demo/frame_canvas.rb +409 -33
  74. data/samples/sample1/art/logo.xpm +60 -0
  75. data/samples/sample1/sample.rb +11 -11
  76. data/samples/sample2/art/logo.xpm +60 -0
  77. data/samples/sample2/sample.rb +2 -2
  78. data/samples/sample2/sample_shape.rb +15 -15
  79. data/samples/sample3/art/logo.xpm +60 -0
  80. data/samples/sample3/sample.rb +3 -3
  81. data/samples/sample4/art/logo.xpm +60 -0
  82. data/samples/sample4/sample.rb +2 -2
  83. data/tests/lib/wxapp_runner.rb +4 -0
  84. data/tests/serializer_tests.rb +8 -441
  85. data/tests/test_grid_shapes.rb +2 -2
  86. data/tests/test_serialize_xml.rb +17 -0
  87. data/tests/test_serialize_yaml.rb +2 -2
  88. metadata +78 -28
  89. data/lib/wx/shapes/serialize/core.rb +0 -40
  90. data/lib/wx/shapes/serialize/id.rb +0 -82
  91. data/lib/wx/shapes/serializer/json.rb +0 -258
  92. data/lib/wx/shapes/serializer/yaml.rb +0 -125
  93. data/samples/demo/art/sample.xpm +0 -251
  94. data/samples/sample1/art/sample.xpm +0 -251
  95. data/samples/sample2/art/sample.xpm +0 -251
  96. data/samples/sample3/art/sample.xpm +0 -251
  97. data/samples/sample4/art/sample.xpm +0 -251
@@ -2,6 +2,7 @@
2
2
  # Copyright (c) M.J.N. Corino, The Netherlands
3
3
 
4
4
  require 'wx/shapes/shapes/rect_shape'
5
+ require 'wx/shapes/shapes/manager_shape'
5
6
 
6
7
  module Wx::SF
7
8
 
@@ -10,45 +11,75 @@ module Wx::SF
10
11
  # shapes are aligned into defined grid with a behaviour similar to classic Wx::GridSizer class.
11
12
  class GridShape < RectShape
12
13
 
14
+ include ManagerShape
15
+
13
16
  # default values
14
17
  module DEFAULT
15
- # Default value of GridShape @rows data member.
16
- ROWS = 3
17
18
  # Default value of GridShape @cols data member.
18
- COLS = 3
19
+ COLUMNS = 3
19
20
  # Default value of GridShape @cell_space data member.
20
21
  CELLSPACE = 5
21
22
  end
22
23
 
23
- property :rows, :cols, :cell_space, :cells
24
-
25
- # @overload initialize()
26
- # Default constructor.
27
- # @overload initialize(pos, size, rows, cols, cell_space, diagram)
28
- # User constructor.
29
- # @param [Wx::RealPoint] pos Initial position
30
- # @param [Wx::RealPoint] size Initial size
31
- # @param [Integer] cols Number of grid rows
32
- # @param [Integer] rows Number of grid columns
33
- # @param [Integer] cell_space Additional space between managed shapes
34
- # @param [Wx::SF::Diagram] diagram parent diagram
35
- def initialize(*args)
36
- if args.empty?
37
- super()
38
- @rows = DEFAULT::ROWS
39
- @cols = DEFAULT::COLS
40
- @cell_space = DEFAULT::CELLSPACE
41
- else
42
- pos, size, rows, cols, cell_space, diagram = args
43
- super(pos, size, diagram)
44
- @rows = rows || 0
45
- @cols = cols || 0
46
- @cell_space = cell_space || 0
24
+ class << self
25
+
26
+ # Returns the minimum size for *empty* grids
27
+ # @return [Wx::Size]
28
+ def get_min_size
29
+ @min_size ||= Wx::Size.new(20, 20)
47
30
  end
31
+ alias :min_size :get_min_size
32
+
33
+ # Sets the minimum size for *empty* grids
34
+ # @overload set_min_size(sz)
35
+ # @param [Wx::Size] sz
36
+ # @overload set_min_size(w, h)
37
+ # @param [Integer] w
38
+ # @param [Integer] h
39
+ def set_min_size(arg1, arg2 = nil)
40
+ @min_size = if arg2.nil?
41
+ raise ArgumentError, 'Expected Wx::Size' unless Wx::Size === arg1
42
+ arg1
43
+ else
44
+ Wx::Size.new(arg1, arg2)
45
+ end
46
+ end
47
+ alias :min_size= :set_min_size
48
+
49
+ end
50
+
51
+ property :cols, :max_rows, :cell_space, :cells
52
+
53
+ # Constructor.
54
+ # @param [Wx::RealPoint,Wx::Point] pos Initial position
55
+ # @param [Wx::RealPoint,Wx::Size,Wx::Point] size Initial size
56
+ # @param [Integer] cols Number of grid columns
57
+ # @param [Integer] max_rows Maximum number of grid rows
58
+ # @param [Integer] cell_space Additional space between managed shapes
59
+ # @param [Wx::SF::Diagram] diagram parent diagram
60
+ def initialize(pos = Shape::DEFAULT::POSITION, size = RectShape::DEFAULT::SIZE,
61
+ cols: DEFAULT::COLUMNS, max_rows: 0, cell_space: DEFAULT::CELLSPACE, diagram: nil)
62
+ super(pos, size, diagram: diagram)
63
+ @cols = [1, cols.to_i].max # at least one column
64
+ @max_rows = [0, max_rows.to_i].max # no or >=1 max rows
65
+ @cell_space = [0, cell_space.to_i].max
66
+ @rows = 1
48
67
  @cells = []
49
68
  remove_style(Shape::STYLE::SIZE_CHANGE)
50
69
  end
51
70
 
71
+ attr_reader :max_rows
72
+
73
+ # Sets the maximum number of rows for the grid (by default there this value is 0 == no maximum).
74
+ # In case the number of already managed cells exceeds the new maximum no change is made.
75
+ # @return [Integer] the active maximum
76
+ def set_max_rows(num)
77
+ # only change as long as this does not invalidate already managed cells
78
+ @max_rows = num unless (num * @cols) < @cells.size
79
+ @max_rows
80
+ end
81
+ alias :max_rows= :set_max_rows
82
+
52
83
  # Set grid dimensions.
53
84
  # @param [Integer] rows Number of rows
54
85
  # @param [Integer] cols Number of columns
@@ -67,6 +98,13 @@ module Wx::SF
67
98
  [@rows, @cols]
68
99
  end
69
100
 
101
+ # Get number of available grid cells
102
+ # @return [Integer]
103
+ def get_cell_count
104
+ @rows * @cols
105
+ end
106
+ alias :cell_count :get_cell_count
107
+
70
108
  # Set space between grid cells (managed shapes).
71
109
  # @param [Integer] cellspace Cellspace size
72
110
  def set_cell_space(cellspace)
@@ -81,14 +119,14 @@ module Wx::SF
81
119
  end
82
120
  alias :cell_space :get_cell_space
83
121
 
84
- # Iterate all cells. If a block is given passes row, col and id for each cell to block.
122
+ # Iterate all cells. If a block is given passes row, col and shape (if any) for each cell to block.
85
123
  # Returns Enumerator if no block given.
86
124
  # @overload each_cell()
87
125
  # @return [Enumerator]
88
126
  # @overload each_cell(&block)
89
127
  # @yieldparam [Integer] row
90
128
  # @yieldparam [Integer] col
91
- # @yieldparam [Wx::SF::Serializable::ID,nil] id
129
+ # @yieldparam [shape,nil] shape
92
130
  # @return [Object]
93
131
  def each_cell(&block)
94
132
  if block
@@ -108,40 +146,40 @@ module Wx::SF
108
146
  end
109
147
  end
110
148
 
149
+ # Clear the cell at given row and column index
150
+ # @param [Integer] row
151
+ # @param [Integer] col
152
+ # @return [Boolean] true if cell existed, false otherwise
153
+ # Note that this function doesn't remove managed (child) shapes from the parent grid shape
154
+ # (they are still its child shapes but aren't managed anymore).
111
155
  def clear_cell(row, col)
112
156
  if row>=0 && row<@rows && col>=0 && col<@cols
113
157
  @cells[row*@cols + col] = nil
114
- end
115
- end
116
-
117
- def get_cell(row, col)
118
- if row>=0 && row<@rows && col>=0 && col<@cols
119
- @cells[row*@cols + col]
158
+ true
159
+ else
160
+ false
120
161
  end
121
162
  end
122
163
 
123
164
  # Get managed shape specified by lexicographic cell index.
124
165
  # @overload get_managed_shape(index)
125
166
  # @param [Integer] index Lexicographic index of requested shape
126
- # @return [Shape] shape object of given cell index if exists, otherwise nil
167
+ # @return [Shape, nil] shape object of given cell index if exists, otherwise nil
127
168
  # @overload get_managed_shape(row, col)
128
169
  # @param [Integer] row Zero-base row index
129
170
  # @param [Integer] col Zero-based column index
130
- # @return [Shape] shape object stored in specified grid cell if exists, otherwise nil
171
+ # @return [Shape, nil] shape object stored in specified grid cell if exists, otherwise nil
131
172
  def get_managed_shape(*args)
132
173
  index = args.size == 1 ? args.first : (args[0]*@cols)+args[1]
133
- if index>=0 && index<@cells.size && @cells[index]
134
- return @child_shapes.find { |child| @cells[index] == child.id }
135
- end
136
- nil
174
+ @cells[index]
137
175
  end
138
176
 
139
- # Clear information about managed shapes and set number of rows and columns to zero.
177
+ # Clear information about managed shapes and set number of rows to 1 (number of columns does not change).
140
178
  #
141
179
  # Note that this function doesn't remove managed (child) shapes from the parent grid shape
142
180
  # (they are still its child shapes but aren't managed anymore).
143
181
  def clear_grid
144
- @rows = @cols = 0
182
+ @rows = 1
145
183
  @cells = []
146
184
  end
147
185
 
@@ -155,12 +193,14 @@ module Wx::SF
155
193
  end
156
194
 
157
195
  # Insert given shape to the grid at the given position.
196
+ # In case a shape is inserted in a cell already occupied the cells at that position and following will
197
+ # be shifted to the next lexicographic position.
198
+ # A maximum row setting may prevent a new shape of being inserted.
158
199
  # @overload insert_to_grid(row, col, shape)
159
200
  # Note that the grid can grow in a vertical direction only, so if the user specifies a desired
160
201
  # horizontal position bigger than the current number of columns is then this function exits with
161
202
  # an error (false) return value. If specified vertical position exceeds the number or grid rows than
162
- # the grid is resized. Any occupied grid cells at given position or beyond will be shifted to the next
163
- # lexicographic position.
203
+ # the grid is resized.
164
204
  # @param [Integer] row Vertical position
165
205
  # @param [Integer] col Horizontal position
166
206
  # @param [Shape] shape shape to insert
@@ -168,9 +208,8 @@ module Wx::SF
168
208
  # @overload insert_to_grid(index, shape)
169
209
  # Note that the given index is a lexicographic position of inserted shape. The given shape is inserted before
170
210
  # the existing item 'index', thus insert_to_grid(0, something) will insert an item in such way that it will become
171
- # the first grid element. Any occupied grid cells at given position or beyond will be shifted to the next
172
- # lexicographic position.
173
- # @param [Integer] index Lexicographic position of inserted shape
211
+ # the first grid element.
212
+ # @param [Integer] index Lexicographic position of inserted shape (>= 0)
174
213
  # @param [Shape] shape shape to insert
175
214
  # @return [Boolean] true on success, otherwise false
176
215
  def insert_to_grid(*args)
@@ -178,10 +217,15 @@ module Wx::SF
178
217
  row, col, shape = args
179
218
  if shape && shape.is_a?(Shape) && is_child_accepted(shape.class)
180
219
  # protect duplicated occurrences
181
- return false if @cells.index(shape.id)
220
+ return false if @cells.index(shape)
182
221
 
183
222
  # protect unbounded horizontal index (grid can grow in a vertical direction only)
184
223
  return false if col >= @cols
224
+ # protect maximum rows
225
+ index = row * @cols + col
226
+ return false if @max_rows > 0 &&
227
+ (row >= @max_rows || # cannot insert beyond max_rows
228
+ (@cells[index] && @cells.size >= (@max_rows * @cols))) # cannot grow beyond max_rows
185
229
 
186
230
  # add the shape to the children list if necessary
187
231
  unless @child_shapes.include?(shape)
@@ -192,23 +236,29 @@ module Wx::SF
192
236
  end
193
237
  end
194
238
 
195
- @cells.insert(row * @cols + col, shape.id)
196
-
197
- # adjust row count if necessary
198
- if @cells.size > (@rows * @cols)
199
- @rows = @cells.size / @cols
239
+ if @cells[index]
240
+ @cells.insert(row * @cols + col, shape)
241
+ else
242
+ @cells[index] = shape
200
243
  end
201
244
 
245
+ # adjust row count
246
+ update_rows
247
+
202
248
  return true
203
249
  end
204
250
  else
205
251
  index, shape = args
206
252
  if shape && shape.is_a?(Shape) && is_child_accepted(shape.class)
207
253
  # protect duplicated occurrences
208
- return false if @cells.index(shape.id)
254
+ return false if @cells.index(shape)
209
255
 
210
256
  # protect unbounded index
211
- return false if index >= (@rows * @cols)
257
+ max_size = @cols * @max_rows
258
+ return false if index < 0 ||
259
+ (@max_rows > 0 &&
260
+ (index >= max_size || # cannot insert beyond max_rows
261
+ (@cells[index] && @cells.size >= (@cols * @max_rows)))) # cannot grow beyond max_rows
212
262
 
213
263
  # add the shape to the children list if necessary
214
264
  unless @child_shapes.include?(shape)
@@ -219,170 +269,218 @@ module Wx::SF
219
269
  end
220
270
  end
221
271
 
222
- @cells.insert(index, shape.id)
223
-
224
- # adjust row count if necessary
225
- if @cells.size > (@rows * @cols)
226
- @rows = @cells.size / @cols
272
+ if @cells[index]
273
+ @cells.insert(index, shape)
274
+ else
275
+ @cells[index] = shape
227
276
  end
228
277
 
278
+ # adjust row count
279
+ update_rows
280
+
229
281
  return true
230
282
  end
231
283
  end
232
284
  false
233
285
  end
234
286
 
235
- # Remove shape with given ID from the grid.
236
- # Shifts any occupied cells beyond the cell containing the given id to the previous lexicographic position.
237
- # @param [Serializable::ID] id ID of shape which should be removed
287
+ # Remove given shape from the grid.
288
+ # Shifts any occupied cells beyond the cell containing the given shape to the previous lexicographic position.
289
+ # @param [Shape] shape shape which should be removed
290
+ # @return [Shape,nil] removed shape or nil if not found
238
291
  # @note Note this does *not* remove the shape as a child shape.
239
- def remove_from_grid(id)
240
- @cells.delete(id)
292
+ def remove_from_grid(shape)
293
+ if @cells.delete(shape)
294
+ # remove trailing empty cells
295
+ @cells.pop until @cells.last
296
+ # update row count
297
+ @rows = @cells.size / @cols
298
+ @rows += 1 if (@cells.size % @cols) > 0
299
+ return shape
300
+ end
301
+ nil
241
302
  end
242
303
 
243
- # Update shape (align all child shapes an resize it to fit them)
244
- def update
304
+ # Update shape (align all child shapes and resize it to fit them)
305
+ def update(recurse= true)
245
306
  # check for existence of de-assigned shapes
246
- @cells.delete_if do |id|
247
- @child_shapes.find { |child| child.id == id }.nil?
307
+ @cells.delete_if do |shape|
308
+ shape && !@child_shapes.include?(shape)
248
309
  end
249
310
 
250
- # check whether all child shapes' IDs are present in the cells array...
311
+ # check whether all child shapes are present in the cells array...
251
312
  @child_shapes.each do |child|
252
- @cells << child.id unless @cells.include?(child.id)
313
+ unless @cells.include?(child)
314
+ # see if we can match the position of the new child with the position of another
315
+ # (previously assigned) managed shape
316
+ position_child_cell(child)
317
+ end
253
318
  end
254
319
 
255
320
  # do self-alignment
256
321
  do_alignment
257
-
258
- # do alignment of shape's children
259
- do_children_layout
260
-
322
+
261
323
  # fit the shape to its children
262
324
  fit_to_children unless has_style?(STYLE::NO_FIT_TO_CHILDREN)
263
325
 
264
326
  # do it recursively on all parent shapes
265
- get_parent_shape.update if get_parent_shape
327
+ get_parent_shape.update(recurse) if recurse && get_parent_shape
266
328
  end
267
329
 
268
330
  # Resize the shape to bound all child shapes. The function can be overridden if necessary.
269
331
  def fit_to_children
270
- # HINT: overload it for custom actions...
271
-
272
- # get bounding box of the shape and children set be inside it
332
+ # get bounding box of the shape and children set to be inside it
273
333
  abs_pos = get_absolute_position
274
334
  ch_bb = Wx::Rect.new(abs_pos.to_point, [0, 0])
275
335
 
276
336
  @child_shapes.each do |child|
277
- child.get_complete_bounding_box(ch_bb, BBMODE::SELF | BBMODE::CHILDREN) if child.has_style?(STYLE::ALWAYS_INSIDE)
337
+ ch_bb = child.get_complete_bounding_box(ch_bb, BBMODE::SELF | BBMODE::CHILDREN) if child.has_style?(STYLE::ALWAYS_INSIDE)
278
338
  end
279
-
280
- # do not let the grid shape 'disappear' due to zero sizes...
281
- if (ch_bb.width == 0 || ch_bb.height == 0) && @cell_space == 0
282
- ch_bb.set_width(10)
283
- ch_bb.set_height(10)
339
+
340
+ if @child_shapes.empty?
341
+ # do not let the empty grid shape 'disappear' due to zero sizes...
342
+ ch_bb.size = GridShape.min_size - @cell_space
343
+ end
344
+
345
+ @rect_size = Wx::RealPoint.new(ch_bb.width + @cell_space, ch_bb.height + @cell_space)
346
+ end
347
+
348
+ # Event handler called when any shape is dropped above this shape (and the dropped
349
+ # shape is accepted as a child of this shape). The function can be overridden if necessary.
350
+ #
351
+ # The function is called by the framework (by the shape canvas).
352
+ # @param [Wx::RealPoint] _pos Relative position of dropped shape
353
+ # @param [Shape] child dropped shape
354
+ def on_child_dropped(_pos, child)
355
+ # see if we can match the position of the new child with the position of another
356
+ # (previously assigned) managed shape
357
+ if child && !child.is_a?(LineShape)
358
+ # insert child based on it's current (possibly dropped) position
359
+ position_child_cell(child)
284
360
  end
285
-
286
- @rect_size = Wx::RealPoint.new(ch_bb.width + 2*@cell_space, ch_bb.height + 2*@cell_space)
287
361
  end
288
362
 
363
+ protected
364
+
289
365
  # Do layout of assigned child shapes
290
366
  def do_children_layout
291
367
  return if @cols == 0 || @rows == 0
292
-
293
- max_rect = Wx::Rect.new(0,0,0,0)
294
-
295
- # get maximum size of all managed (child) shapes
296
- @child_shapes.each do |shape|
297
- curr_rect = shape.get_bounding_box
298
368
 
299
- max_rect.set_width(curr_rect.width) if shape.get_h_align != HALIGN::EXPAND && curr_rect.width > max_rect.width
300
- max_rect.set_height(curr_rect.height) if shape.get_v_align != VALIGN::EXPAND && curr_rect.height > max_rect.height
301
- end
369
+ max_size = get_max_child_size
302
370
 
303
- @cells.each_with_index do |id, i|
304
- if id
305
- shape = @child_shapes[id]
371
+ @cells.each_with_index do |shape, i|
372
+ if shape
306
373
  col = (i % @cols)
307
374
  row = (i / @cols)
308
375
 
309
- fit_shape_to_rect(shape, Wx::Rect.new(col*max_rect.width + (col+1)*@cell_space,
310
- row*max_rect.height + (row+1)*@cell_space,
311
- max_rect.width, max_rect.height))
376
+ fit_shape_to_rect(shape, Wx::Rect.new(col*max_size.width + (col+1)*@cell_space,
377
+ row*max_size.height + (row+1)*@cell_space,
378
+ max_size.width, max_size.height))
312
379
  end
313
380
  end
314
381
  end
315
382
 
316
- # Event handler called when any shape is dropped above this shape (and the dropped
317
- # shape is accepted as a child of this shape). The function can be overridden if necessary.
318
- #
319
- # The function is called by the framework (by the shape canvas).
320
- # @param [Wx::RealPoint] _pos Relative position of dropped shape
321
- # @param [Shape] child dropped shape
322
- def on_child_dropped(_pos, child)
323
- append_to_grid(child) if child && !child.is_a?(LineShape)
383
+ # called after the shape has been newly imported/pasted/dropped
384
+ # checks the cells for stale links
385
+ def on_import
386
+ # check for existence of non-included shapes
387
+ @cells.delete_if do |shape|
388
+ shape && !@child_shapes.include?(shape)
389
+ end
324
390
  end
325
391
 
326
- protected
392
+ # update row count
393
+ def update_rows
394
+ # remove trailing empty cells (if any)
395
+ @cells.pop until @cells.last
396
+ @rows = @cells.size / @cols
397
+ @rows += 1 if (@cells.size % @cols) > 0
398
+ end
327
399
 
328
- # Move and resize given shape so it will fit the given bounding rectangle.
329
- #
330
- # The shape is aligned inside the given bounding rectangle in accordance to the shape's
331
- # valign and halign flags.
332
- # @param [Shape] shape modified shape
333
- # @param [Wx::Rect] rct Bounding rectangle
334
- # @see Shape#set_v_align
335
- # @see Shape#set_h_align
336
- def fit_shape_to_rect(shape, rct)
337
- shape_bb = shape.get_bounding_box
338
- prev_pos = shape.get_relative_position
339
-
340
- # do vertical alignment
341
- case shape.get_v_align
342
- when VALIGN::TOP
343
- shape.set_relative_position(prev_pos.x, rct.top + shape.get_v_border)
344
- when VALIGN::MIDDLE
345
- shape.set_relative_position(prev_pos.x, rct.top + (rct.height/2 - shape_bb.height/2))
346
- when VALIGN::BOTTOM
347
- shape.set_relative_position(prev_pos.x, rct.bottom - shape_bb.height - shape.get_v_border)
348
- when VALIGN::EXPAND
349
- if shape.has_style?(STYLE::SIZE_CHANGE)
350
- shape.set_relative_position(prev_pos.x, rct.top + shape.get_v_border)
351
- shape.scale(1.0, (rct.height - 2*shape.get_v_border).to_f/shape_bb.height)
400
+ # returns maximum size of all managed (child) shapes
401
+ # @return [Wx::Size]
402
+ def get_max_child_size
403
+ @child_shapes.inject(Wx::Size.new(0, 0)) do |max_size, shape|
404
+ child_rect = shape.get_bounding_box
405
+
406
+ max_size.set_width(child_rect.width) if shape.get_h_align != HALIGN::EXPAND && child_rect.width > max_size.width
407
+ max_size.set_height(child_rect.height) if shape.get_v_align != VALIGN::EXPAND && child_rect.height > max_size.height
408
+ max_size
409
+ end
410
+ end
411
+
412
+ def find_cell(child_rect)
413
+ max_size = get_max_child_size
414
+ child_centre = child_rect.get_position
415
+ child_centre.x += child_rect.width/2
416
+ child_centre.y += child_rect.height/2
417
+ # find the cell index where the new or dragged child is positioned above and in front of
418
+ offset = get_bounding_box.top_left
419
+ cell_count.times.find do |cell|
420
+ col = (cell % @cols)
421
+ row = (cell / @cols)
422
+ cell_rct = Wx::Rect.new(col*max_size.width + (col+1)*@cell_space,
423
+ row*max_size.height + (row+1)*@cell_space,
424
+ max_size.width, max_size.height).offset!(offset)
425
+ child_centre.x <= cell_rct.right && child_centre.y <= cell_rct.bottom
426
+ end
427
+ end
428
+
429
+ def position_child_cell(child)
430
+ crct = child.get_bounding_box
431
+ # see if the child already had a cell in this grid (moving a child)
432
+ child_index = @cells.index(child)
433
+ # if the child intersects this box shape we look
434
+ # for the cell it should go into (if any)
435
+ if @cells.size>0 && intersects?(crct)
436
+ # find the cell index where the new child is positioned above and in front of
437
+ index = find_cell(crct)
438
+ # now see where to put the new/moved child
439
+ if index # found a matching cell?
440
+ # if the child being inserted already had a slot
441
+ if child_index
442
+ # if the newly found index equals the existing index there is nothing to do
443
+ return if child_index == index
444
+ # else clear the child's current cell; this provides support for reordering child shapes by dragging
445
+ @cells[child_index] = nil
446
+ end
447
+ # insert/move the child
448
+ unless insert_to_grid(index, child)
449
+ # if failed to insert (max rows exceeded?) restore the child to it's previous cell
450
+ if child_index
451
+ @cells[child_index] = child
452
+ else # or make the child a toplevel shape
453
+ # move relative to current parent (if any)
454
+ child.move_by(child.get_parent_shape.get_absolute_position) if child.get_parent_shape
455
+ diagram.reparent_shape(child, nil)
456
+ end
457
+ end
458
+ return # done
352
459
  end
353
- else
354
- shape.set_relative_position(prev_pos.x, rct.top)
355
460
  end
356
-
357
- prev_pos = shape.get_relative_position
358
-
359
- # do horizontal alignment
360
- case shape.get_h_align
361
- when HALIGN::LEFT
362
- shape.set_relative_position(rct.left + shape.get_h_border, prev_pos.y)
363
- when HALIGN::CENTER
364
- shape.set_relative_position(rct.left + (rct.width/2 - shape_bb.width/2), prev_pos.y)
365
- when HALIGN::RIGHT
366
- shape.set_relative_position(rct.right - shape_bb.width - shape.get_h_border, prev_pos.y)
367
- when HALIGN::EXPAND
368
- if shape.has_style?(STYLE::SIZE_CHANGE)
369
- shape.set_relative_position(rct.left + shape.get_h_border, prev_pos.y)
370
- shape.scale((rct.width - 2*shape.get_h_border).to_f/shape_bb.width, 1.9)
461
+ # otherwise append
462
+ # clear the child's current cell if already part of grid
463
+ @cells[child_index] = nil if child_index
464
+ # append
465
+ unless append_to_grid(child)
466
+ # if failed to append (max rows exceeded?) restore the child to it's previous cell
467
+ if child_index
468
+ @cells[child_index] = child
469
+ else # or make the child a toplevel shape
470
+ # move relative to current parent (if any)
471
+ child.move_by(child.get_parent_shape.get_absolute_position) if child.get_parent_shape
472
+ diagram.reparent_shape(child, nil)
371
473
  end
372
- else
373
- shape.set_relative_position(rct.left, prev_pos.y)
374
474
  end
375
475
  end
376
476
 
377
477
  private
378
478
 
379
- # Deserialization only.
479
+ # (de-)serialization only.
380
480
 
381
- def get_rows
382
- @rows
383
- end
384
- def set_rows(num)
385
- @rows = num
481
+ # default deserialization finalizer
482
+ def create
483
+ update_rows
386
484
  end
387
485
 
388
486
  def get_cols