wxruby3-shapes 0.9.0.pre.beta.3

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 (112) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +12 -0
  3. data/CREDITS.md +18 -0
  4. data/INSTALL.md +39 -0
  5. data/LICENSE +21 -0
  6. data/README.md +118 -0
  7. data/assets/screenshot.png +0 -0
  8. data/bin/wx-shapes +9 -0
  9. data/lib/wx/shapes/arrow_base.rb +86 -0
  10. data/lib/wx/shapes/arrows/circle_arrow.rb +39 -0
  11. data/lib/wx/shapes/arrows/diamond_arrow.rb +33 -0
  12. data/lib/wx/shapes/arrows/open_arrow.rb +56 -0
  13. data/lib/wx/shapes/arrows/solid_arrow.rb +69 -0
  14. data/lib/wx/shapes/art/shape_canvas/page.xpm +73 -0
  15. data/lib/wx/shapes/auto_layout.rb +358 -0
  16. data/lib/wx/shapes/base.rb +33 -0
  17. data/lib/wx/shapes/canvas_history.rb +84 -0
  18. data/lib/wx/shapes/connection_point.rb +238 -0
  19. data/lib/wx/shapes/core.rb +19 -0
  20. data/lib/wx/shapes/diagram.rb +659 -0
  21. data/lib/wx/shapes/events.rb +389 -0
  22. data/lib/wx/shapes/printout.rb +136 -0
  23. data/lib/wx/shapes/serializable.rb +440 -0
  24. data/lib/wx/shapes/serialize/core.rb +40 -0
  25. data/lib/wx/shapes/serialize/id.rb +82 -0
  26. data/lib/wx/shapes/serialize/wx.rb +104 -0
  27. data/lib/wx/shapes/serializer/json.rb +258 -0
  28. data/lib/wx/shapes/serializer/yaml.rb +125 -0
  29. data/lib/wx/shapes/shape.rb +2129 -0
  30. data/lib/wx/shapes/shape_canvas.rb +3285 -0
  31. data/lib/wx/shapes/shape_data_object.rb +43 -0
  32. data/lib/wx/shapes/shape_handle.rb +287 -0
  33. data/lib/wx/shapes/shape_list.rb +161 -0
  34. data/lib/wx/shapes/shapes/bitmap_shape.rb +257 -0
  35. data/lib/wx/shapes/shapes/circle_shape.rb +136 -0
  36. data/lib/wx/shapes/shapes/control_shape.rb +483 -0
  37. data/lib/wx/shapes/shapes/curve_shape.rb +231 -0
  38. data/lib/wx/shapes/shapes/diamond_shape.rb +62 -0
  39. data/lib/wx/shapes/shapes/edit_text_shape.rb +317 -0
  40. data/lib/wx/shapes/shapes/ellipse_shape.rb +106 -0
  41. data/lib/wx/shapes/shapes/flex_grid_shape.rb +78 -0
  42. data/lib/wx/shapes/shapes/grid_shape.rb +404 -0
  43. data/lib/wx/shapes/shapes/line_shape.rb +907 -0
  44. data/lib/wx/shapes/shapes/multi_sel_rect.rb +214 -0
  45. data/lib/wx/shapes/shapes/ortho_shape.rb +357 -0
  46. data/lib/wx/shapes/shapes/polygon_shape.rb +294 -0
  47. data/lib/wx/shapes/shapes/rect_shape.rb +378 -0
  48. data/lib/wx/shapes/shapes/round_ortho_shape.rb +131 -0
  49. data/lib/wx/shapes/shapes/round_rect_shape.rb +142 -0
  50. data/lib/wx/shapes/shapes/square_shape.rb +119 -0
  51. data/lib/wx/shapes/shapes/text_shape.rb +324 -0
  52. data/lib/wx/shapes/thumbnail.rb +234 -0
  53. data/lib/wx/shapes/version.rb +12 -0
  54. data/lib/wx/shapes/wx.rb +29 -0
  55. data/lib/wx/shapes.rb +18 -0
  56. data/lib/wx/wx-shapes/base.rb +87 -0
  57. data/lib/wx/wx-shapes/cmd/sampler.rb +58 -0
  58. data/lib/wx/wx-shapes/cmd/test.rb +27 -0
  59. data/rakelib/yard/templates/default/fulldoc/html/css/wxruby3.css +7 -0
  60. data/rakelib/yard/templates/default/layout/html/setup.rb +5 -0
  61. data/rakelib/yard/yard/relative_markdown_links/version.rb +8 -0
  62. data/rakelib/yard/yard/relative_markdown_links.rb +39 -0
  63. data/rakelib/yard/yard-custom-templates.rb +2 -0
  64. data/rakelib/yard/yard-relative_markdown_links.rb +4 -0
  65. data/samples/demo/art/AlignBottom.xpm +35 -0
  66. data/samples/demo/art/AlignCenter.xpm +35 -0
  67. data/samples/demo/art/AlignLeft.xpm +35 -0
  68. data/samples/demo/art/AlignMiddle.xpm +35 -0
  69. data/samples/demo/art/AlignRight.xpm +35 -0
  70. data/samples/demo/art/AlignTop.xpm +35 -0
  71. data/samples/demo/art/Bitmap.xpm +25 -0
  72. data/samples/demo/art/Circle.xpm +22 -0
  73. data/samples/demo/art/Curve.xpm +21 -0
  74. data/samples/demo/art/Diamond.xpm +22 -0
  75. data/samples/demo/art/EditText.xpm +21 -0
  76. data/samples/demo/art/Ellipse.xpm +22 -0
  77. data/samples/demo/art/FixedRect.xpm +22 -0
  78. data/samples/demo/art/FlexGrid.xpm +22 -0
  79. data/samples/demo/art/GC.xpm +23 -0
  80. data/samples/demo/art/Grid.xpm +22 -0
  81. data/samples/demo/art/Line.xpm +21 -0
  82. data/samples/demo/art/NoSource.xpm +69 -0
  83. data/samples/demo/art/OrthoLine.xpm +21 -0
  84. data/samples/demo/art/Rect.xpm +22 -0
  85. data/samples/demo/art/RoundOrthoLine.xpm +21 -0
  86. data/samples/demo/art/RoundRect.xpm +22 -0
  87. data/samples/demo/art/Shadow.xpm +23 -0
  88. data/samples/demo/art/StandAloneLine.xpm +22 -0
  89. data/samples/demo/art/Text.xpm +21 -0
  90. data/samples/demo/art/Tool.xpm +23 -0
  91. data/samples/demo/art/sample.xpm +251 -0
  92. data/samples/demo/demo.rb +658 -0
  93. data/samples/demo/frame_canvas.rb +422 -0
  94. data/samples/demo/images/motyl.bmp +0 -0
  95. data/samples/demo/images/motyl2.bmp +0 -0
  96. data/samples/sample1/art/sample.xpm +251 -0
  97. data/samples/sample1/sample.rb +263 -0
  98. data/samples/sample2/art/sample.xpm +251 -0
  99. data/samples/sample2/sample.rb +133 -0
  100. data/samples/sample2/sample_canvas.rb +35 -0
  101. data/samples/sample2/sample_shape.rb +108 -0
  102. data/samples/sample3/art/sample.xpm +251 -0
  103. data/samples/sample3/sample.rb +281 -0
  104. data/samples/sample4/art/sample.xpm +251 -0
  105. data/samples/sample4/sample.rb +180 -0
  106. data/tests/art/motyl.bmp +0 -0
  107. data/tests/lib/wxapp_runner.rb +64 -0
  108. data/tests/serializer_tests.rb +521 -0
  109. data/tests/test_grid_shapes.rb +42 -0
  110. data/tests/test_serialize.rb +7 -0
  111. data/tests/test_serialize_yaml.rb +17 -0
  112. metadata +242 -0
@@ -0,0 +1,3285 @@
1
+ # Wx::SF::ShapeCanvas - shape canvas class
2
+ # Copyright (c) M.J.N. Corino, The Netherlands
3
+
4
+ require 'wx/shapes/shape_data_object'
5
+ require 'wx/shapes/canvas_history'
6
+ require 'wx/shapes/printout'
7
+
8
+ require 'tempfile'
9
+ require 'fileutils'
10
+
11
+ module Wx::SF
12
+
13
+ if Wx.has_feature?(:USE_DRAG_AND_DROP)
14
+
15
+ # Auxiliary class encapsulating shape drop target.
16
+ class CanvasDropTarget < Wx::DropTarget
17
+
18
+ # @param [Wx::DataObject] data
19
+ # @param [Wx::SF::ShapeCanvas] parent
20
+ def initialize(data, parent)
21
+ super(data)
22
+ @parent_canvas = parent
23
+ end
24
+
25
+ # @param [Integer] x
26
+ # @param [Integer] y
27
+ # @param [Wx::DragResult] deflt
28
+ # @return [Wx::DragResult]
29
+ def on_data(x, y, deflt)
30
+ return Wx::DragResult::DragNone unless get_data
31
+
32
+ @parent_canvas.__send__(:_on_drop, x, y, deflt, get_data_object)
33
+ deflt
34
+ end
35
+
36
+ end
37
+ end
38
+
39
+ # Class encapsulating a Shape canvas. The shape canvas is window control
40
+ # which extends the Wx::ScrolledWindow and is responsible for displaying of shapes diagrams.
41
+ # It also supports clipboard and drag&drop operations, undo/redo operations,
42
+ # and graphics exporting functions.
43
+ #
44
+ # This class is a core framework class and provides many member functions suitable for adding,
45
+ # removing, moving, resizing and drawing of shape objects. It can be used as it is or as a base class
46
+ # if necessary. In that case, the default class functionality can be enhanced by overriding
47
+ # its methods or by manual events handling. In both cases the user is responsible
48
+ # for invoking of default event handlers/virtual functions otherwise the
49
+ # built in functionality wont be available.
50
+ # @see Wx::SF::Diagram
51
+ class ShapeCanvas < Wx::ScrolledWindow
52
+
53
+ # Working modes
54
+ class MODE < Wx::Enum
55
+ # The shape canvas is in ready state (no operation is pending)
56
+ READY = self.new(0)
57
+ # Some shape handle is dragged
58
+ HANDLEMOVE = self.new(1)
59
+ # Handle of multiselection tool is dragged
60
+ MULTIHANDLEMOVE = self.new(2)
61
+ # Some shape/s is/are dragged
62
+ SHAPEMOVE = self.new(3)
63
+ # Multiple shape selection is in progress
64
+ MULTISELECTION = self.new(4)
65
+ # Interactive connection creation is in progress
66
+ CREATECONNECTION = self.new(5)
67
+ # Canvas is in the Drag&Drop mode
68
+ DND = self.new(6)
69
+ end
70
+
71
+ # Selection modes
72
+ class SELECTIONMODE < Wx::Enum
73
+ NORMAL = self.new(0)
74
+ ADD = self.new(1)
75
+ REMOVE = self.new(2)
76
+ end
77
+
78
+ # Search mode flags for GetShapeAtPosition function
79
+ class SEARCHMODE < Wx::Enum
80
+ # Search for selected shapes only
81
+ SELECTED = self.new(0)
82
+ # Search for unselected shapes only
83
+ UNSELECTED = self.new(1)
84
+ # Search for both selected and unselected shapes
85
+ BOTH = self.new(2)
86
+ end
87
+
88
+ # Flags for AlignSelected function
89
+ class VALIGN < Wx::Enum
90
+ NONE = self.new(0)
91
+ TOP = self.new(1)
92
+ MIDDLE = self.new(2)
93
+ BOTTOM = self.new(3)
94
+ end
95
+
96
+ # Flags for AlignSelected function
97
+ class HALIGN < Wx::Enum
98
+ NONE = self.new(0)
99
+ LEFT = self.new(1)
100
+ CENTER = self.new(2)
101
+ RIGHT = self.new(3)
102
+ end
103
+
104
+ # Style flags
105
+ class STYLE < Wx::Enum
106
+ # Allow multiselection box.
107
+ MULTI_SELECTION = self.new(1)
108
+ # Allow shapes' size change done via the multiselection box.
109
+ MULTI_SIZE_CHANGE = self.new(2)
110
+ # Show grid.
111
+ GRID_SHOW = self.new(4)
112
+ # Use grid.
113
+ GRID_USE = self.new(8)
114
+ # Enable Drag & Drop operations.
115
+ DND = self.new(16)
116
+ # Enable Undo/Redo operations.
117
+ UNDOREDO = self.new(32)
118
+ # Enable the clipboard.
119
+ CLIPBOARD = self.new(64)
120
+ # Enable mouse hovering
121
+ HOVERING = self.new(128)
122
+ # Enable highlighting of shapes able to accept dragged shape(s).
123
+ HIGHLIGHTING = self.new(256)
124
+ # Use gradient color for the canvas background.
125
+ GRADIENT_BACKGROUND = self.new(512)
126
+ # Print also canvas background.
127
+ PRINT_BACKGROUND = self.new(1024)
128
+ # Process mouse wheel by the canvas (canvas scale will be changed).
129
+ PROCESS_MOUSEWHEEL = self.new(2048)
130
+ # Default canvas style.
131
+ DEFAULT_CANVAS_STYLE = MULTI_SELECTION | MULTI_SIZE_CHANGE | DND | UNDOREDO | CLIPBOARD | HOVERING | HIGHLIGHTING
132
+ end
133
+
134
+ # Flags for ShowShadow function.
135
+ class SHADOWMODE < Wx::Enum
136
+ # Show/hide shadow under topmost shapes only.
137
+ TOPMOST = self.new(0)
138
+ # Show/hide shadow under all shapes in the diagram.
139
+ ALL = self.new(1)
140
+ end
141
+
142
+ # Change mode flags
143
+ class CHANGE < Wx::Enum
144
+ SET_SCALE = self.new(0)
145
+ RESCALED = self.new(1)
146
+ VIRTUAL_SIZE = self.new(2)
147
+ FOCUS = self.new(3)
148
+ end
149
+
150
+ # Printing modes used by SetPrintMode() function.
151
+ class PRINTMODE < Wx::Enum
152
+ # This sets the user scale and origin of the DC so that the image fits
153
+ # within the paper rectangle (but the edges could be cut off by printers
154
+ # that can't print to the edges of the paper -- which is most of them. Use
155
+ # this if your image already has its own margins.
156
+ FIT_TO_PAPER = self.new(0)
157
+ # This sets the user scale and origin of the DC so that the image fits
158
+ # within the page rectangle, which is the printable area on Mac and MSW
159
+ # and is the entire page on other platforms.
160
+ FIT_TO_PAGE = self.new(1)
161
+ # This sets the user scale and origin of the DC so that the image fits
162
+ # within the page margins as specified by g_PageSetupData, which you can
163
+ # change (on some platforms, at least) in the Page Setup dialog. Note that
164
+ # on Mac, the native Page Setup dialog doesn't let you change the margins
165
+ # of a Wx::PageSetupDialogData object, so you'll have to write your own dialog or
166
+ # use the Mac-only Wx::MacPageMarginsDialog, as we do in this program.
167
+ FIT_TO_MARGINS = self.new(2)
168
+ # This sets the user scale and origin of the DC so that you could map the
169
+ # screen image to the entire paper at the same size as it appears on screen.
170
+ MAP_TO_PAPER = self.new(3)
171
+ # This sets the user scale and origin of the DC so that the image appears
172
+ # on the paper at the same size that it appears on screen (i.e., 10-point
173
+ # type on screen is 10-point on the printed page).
174
+ MAP_TO_PAGE = self.new(4)
175
+ # This sets the user scale and origin of the DC so that you could map the
176
+ # screen image to the page margins specified by the native Page Setup dialog at the same
177
+ # size as it appears on screen.
178
+ MAP_TO_MARGINS = self.new(5)
179
+ # This sets the user scale and origin of the DC so that you can to do you own
180
+ # scaling in order to draw objects at full native device resolution.
181
+ MAP_TO_DEVICE = self.new(6)
182
+ end
183
+
184
+ class PRECON_FINISH_STATE < Wx::Enum
185
+ # Finish line connection.
186
+ OK = self.new(0)
187
+ # Cancel line connection and abort the interactive connection.
188
+ FAILED_AND_CANCEL_LINE = self.new(1)
189
+ # Cancel line connection and continue with the interactive connection.
190
+ FAILED_AND_CONTINUE_EDIT = self.new(2)
191
+ end
192
+
193
+ # Default values
194
+ module DEFAULT
195
+ # Default value of Wx::SF::CanvasSettings @background_color data member
196
+ BACKGROUNDCOLOR = Wx::Colour.new(240, 240, 240) if Wx::App.is_main_loop_running
197
+ Wx.add_delayed_constant(self, :BACKGROUNDCOLOR) { Wx::Colour.new(240, 240, 240) }
198
+ # Default value of Wx::SF::CanvasSettings @grid_size data member
199
+ GRIDSIZE = Wx::Size.new(10, 10)
200
+ # Default value of Wx::SF::CanvasSettings @grid_line_mult data member
201
+ GRIDLINEMULT = 1
202
+ # Default value of Wx::SF::CanvasSettings @grid_color data member
203
+ GRIDCOLOR = Wx::Colour.new(200, 200, 200) if Wx::App.is_main_loop_running
204
+ Wx.add_delayed_constant(self, :GRIDCOLOR) { Wx::Colour.new(200, 200, 200) }
205
+ # Default value of Wx::SF::CanvasSettings @grid_style data member
206
+ GRIDSTYLE = Wx::PenStyle::PENSTYLE_SOLID
207
+ # Default value of Wx::SF::CanvasSettings @common_hover_color data member
208
+ HOVERCOLOR = Wx::Colour.new(120, 120, 255) if Wx::App.is_main_loop_running
209
+ Wx.add_delayed_constant(self, :HOVERCOLOR) { Wx::Colour.new(120, 120, 255) }
210
+ # Default value of Wx::SF::CanvasSettings @gradient_from data member
211
+ GRADIENT_FROM = Wx::Colour.new(240, 240, 240) if Wx::App.is_main_loop_running
212
+ Wx.add_delayed_constant(self, :GRADIENT_FROM) { Wx::Colour.new(240, 240, 240) }
213
+ # Default value of Wx::SF::CanvasSettings @gradient_to data member
214
+ GRADIENT_TO = Wx::Colour.new(200, 200, 255) if Wx::App.is_main_loop_running
215
+ Wx.add_delayed_constant(self, :GRADIENT_TO) { Wx::Colour.new(200, 200, 255) }
216
+ # Default value of Wx::SF::CanvasSettings @style data member
217
+ CANVAS_STYLE = STYLE::DEFAULT_CANVAS_STYLE
218
+ # Default value of Wx::SF::CanvasSettings @shadow_offset data member
219
+ SHADOWOFFSET = Wx::RealPoint.new(4, 4)
220
+ # Default shadow colour
221
+ SHADOWCOLOR = Wx::Colour.new(150, 150, 150, 128) if Wx::App.is_main_loop_running
222
+ Wx.add_delayed_constant(self, :SHADOWCOLOR) { Wx::Colour.new(150, 150, 150, 128) }
223
+ # Default value of Wx::SF::CanvasSettings @shadow_fill data member
224
+ SHADOWBRUSH = Wx::Brush.new(SHADOWCOLOR.call, Wx::BrushStyle::BRUSHSTYLE_SOLID) if Wx::App.is_main_loop_running
225
+ Wx.add_delayed_constant(self, :SHADOWBRUSH) { Wx::Brush.new(Wx::Colour.new(150, 150, 150, 128), Wx::BrushStyle::BRUSHSTYLE_SOLID) }
226
+ # Default value of Wx::SF::CanvasSettings @print_h_align data member
227
+ PRINT_HALIGN = HALIGN::CENTER
228
+ # Default value of Wx::SF::CanvasSettings @print_v_align data member
229
+ PRINT_VALIGN = VALIGN::MIDDLE
230
+ # Default value of Wx::SF::CanvasSettings @print_mode data member
231
+ PRINT_MODE = PRINTMODE::FIT_TO_MARGINS
232
+ # Default value of Wx::SF::CanvasSettings @min_scale data member
233
+ SCALE_MIN = 0.1
234
+ # Default value of Wx::SF::CanvasSettings @max_scale data member
235
+ SCALE_MAX = 5.0
236
+ end
237
+
238
+ class << self
239
+
240
+ def gc_enabled?
241
+ if @gc_enabled.nil?
242
+ @gc_enabled = Wx.has_feature?(:USE_GRAPHICS_CONTEXT)
243
+ end
244
+ @gc_enabled
245
+ end
246
+
247
+ def enable_gc(f = true)
248
+ if Wx.has_feature?(:USE_GRAPHICS_CONTEXT)
249
+ @gc_enabled = f
250
+ else
251
+ @gc_enabled = false
252
+ Wx.log_warning(%Q{Couldn't enable Graphics context due to missing USE_GRAPHICS_CONTEXT})
253
+ end
254
+ end
255
+
256
+ TLS_LOADING_VERSION_KEY = :loading_version.freeze
257
+ private_constant :TLS_LOADING_VERSION_KEY
258
+
259
+ def compat_loading?
260
+ !!::Thread::current[TLS_LOADING_VERSION_KEY]
261
+ end
262
+
263
+ def compat_loading_version
264
+ ::Thread::current[TLS_LOADING_VERSION_KEY]
265
+ end
266
+
267
+ def set_compat_loading(ver_info)
268
+ ::Thread::current[TLS_LOADING_VERSION_KEY] = ver_info
269
+ end
270
+
271
+ def reset_compat_loading
272
+ ::Thread::current[TLS_LOADING_VERSION_KEY] = nil
273
+ end
274
+
275
+ def print_data
276
+ _init_printing unless @print_data
277
+ @print_data
278
+ end
279
+
280
+ def print_data=(pd)
281
+ @print_data = pd
282
+ end
283
+
284
+ def page_setup_data
285
+ _init_printing unless @page_setup_data
286
+ @page_setup_data
287
+ end
288
+
289
+ def page_setup_data=(psd)
290
+ @page_setup_data = psd
291
+ end
292
+
293
+ def _init_printing
294
+ @print_data = Wx::PRT::PrintData.new
295
+ # You could set an initial paper size here
296
+ # @print_data.set_paper_id(Wx::PaperSize::PAPER_LETTER) # for Americans
297
+ @print_data.set_paper_id(Wx::PaperSize::PAPER_A4) # for everyone else
298
+
299
+ # copy over initial paper size from print record
300
+ @page_setup_data = Wx::PRT::PageSetupDialogData.new(@print_data)
301
+ # Set some initial page margins in mm.
302
+ @page_setup_data.set_margin_top_left([15, 15])
303
+ @page_setup_data.set_margin_bottom_right([15, 15])
304
+ end
305
+ private :_init_printing
306
+
307
+ end
308
+
309
+ # Auxiliary serializable class encapsulating canvas version info
310
+ # and providing version check on loading.
311
+ class Version
312
+
313
+ class Exception < SFException; end
314
+
315
+ VersionInfo = ::Struct.new(:major, :minor, :release) do
316
+ def to_s
317
+ "#{major}.#{minor}.#{release}"
318
+ end
319
+ end
320
+
321
+ include Serializable
322
+
323
+ property :version_info
324
+
325
+ def initialize
326
+ # get version numbers as [major, minor, release]
327
+ @version_info = VersionInfo.new(*Wx::SF::VERSION.split(/\D/).shift(3).collect { |s| s.to_i })
328
+ end
329
+
330
+ attr_reader :version_info
331
+
332
+ # Deserialization only.
333
+ def set_version_info(ver_info)
334
+ if @version_info.major < ver_info.major ||
335
+ (@version_info.major == ver_info.major && @version_info.minor < ver_info.minor)
336
+ ::Kernel.raise Version::Exception, "Incompatible Wx::SF diagram version #{ver_info} cannot be loaded (current Wx::SF version #{@version_info})."
337
+ elsif @version_info.major > ver_info.major ||
338
+ (@version_info.major == ver_info.major && @version_info.minor > ver_info.minor)
339
+ # this should normally work but may give trouble
340
+ # set compat loading info
341
+ ShapeCanvas.set_compat_loading(ver_info)
342
+ else
343
+ # this should never give any trouble
344
+ end
345
+ end
346
+ private :set_version_info
347
+
348
+ end
349
+
350
+ # Auxiliary serializable class encapsulating the canvas properties.
351
+ class Settings
352
+
353
+ include Serializable
354
+
355
+ include DEFAULT
356
+
357
+ property :scale, :min_scale, :max_scale, :background_color, :common_hover_color,
358
+ :grid_size, :grid_line_mult, :grid_color, :grid_style,
359
+ :gradient_from, :gradient_to, :style, :shadow_offset, :shadow_fill,
360
+ :print_h_align, :print_v_align, :print_mode
361
+
362
+ def initialize
363
+ @scale = 1.0
364
+ @min_scale = SCALE_MIN
365
+ @max_scale = SCALE_MAX
366
+ @background_color = BACKGROUNDCOLOR
367
+ @common_hover_color = HOVERCOLOR
368
+ @grid_size = GRIDSIZE.dup
369
+ @grid_line_mult = GRIDLINEMULT
370
+ @grid_color = GRIDCOLOR
371
+ @grid_style = GRIDSTYLE
372
+ @gradient_from = GRADIENT_FROM
373
+ @gradient_to = GRADIENT_TO
374
+ @style = CANVAS_STYLE
375
+ @shadow_offset = SHADOWOFFSET.dup
376
+ @shadow_fill = SHADOWBRUSH
377
+ @print_h_align = PRINT_HALIGN
378
+ @print_v_align = PRINT_VALIGN
379
+ @print_mode = PRINT_MODE
380
+ end
381
+
382
+ attr_accessor :scale, :min_scale, :max_scale, :background_color, :common_hover_color,
383
+ :grid_size, :grid_line_mult, :grid_color, :grid_style,
384
+ :gradient_from, :gradient_to, :style, :shadow_offset, :shadow_fill,
385
+ :print_h_align, :print_v_align, :print_mode
386
+
387
+ end
388
+
389
+ # @overload initialize()
390
+ # Default constructor
391
+ # @overload initialize(diagram, parent, id = Wx::ID_ANY, pos = Wx::DEFAULT_POSITION, size = Wx::DEFAULT_SIZE, style = Wx::HSCROLL | Wx::VSCROLL)
392
+ # Constructor
393
+ # @param [Wx::SF::Diagram] diagram shape diagram
394
+ # @param [Wx::Window] parent Parent window
395
+ # @param [Integer] id Window ID
396
+ # @param [Wx::Point] pos Initial position
397
+ # @param [Wx::Size] size Initial size
398
+ # @param [Integer] style Window style
399
+ def initialize(diagram = nil, *mixed_args)
400
+ super()
401
+
402
+ @dnd_started_here = false
403
+ @dnd_started_at = nil
404
+ @can_save_state_on_mouse_up = false
405
+ @working_mode = MODE::READY
406
+ @selection_mode = SELECTIONMODE::NORMAL
407
+ @selected_handle = nil
408
+ @selection_start = Wx::RealPoint.new
409
+ @new_line_shape = nil
410
+ @unselected_shape_under_cursor = nil
411
+ @selected_shape_under_cursor = nil
412
+ @topmost_shape_under_cursor = nil
413
+ @current_shapes = []
414
+ @invalidate_rect = nil
415
+
416
+ @prev_mouse_pos = Wx::Point.new
417
+ @prev_positions = {}
418
+
419
+ @settings = Settings.new
420
+ @canvas_history = CanvasHistory.new
421
+
422
+ if diagram
423
+ parent = mixed_args.first.is_a?(Wx::Window) ? mixed_args.shift : nil
424
+ real_args = []
425
+ begin
426
+ real_args = [parent] + Wx::ScrolledWindow.args_as_list(*mixed_args)
427
+ create(*real_args)
428
+ rescue => err
429
+ msg = "Error initializing #{self.inspect}\n" +
430
+ " : #{err.message} \n" +
431
+ "Provided are #{real_args} \n" +
432
+ "Correct parameters for #{self.class.name}.new are:\n" +
433
+ self.class.describe_constructor
434
+
435
+ new_err = err.class.new(msg)
436
+ new_err.set_backtrace(caller)
437
+ Kernel.raise new_err
438
+ end
439
+
440
+ self.diagram = diagram
441
+
442
+ save_canvas_state
443
+ end
444
+
445
+ # set up event handlers
446
+ evt_paint :_on_paint
447
+ evt_erase_background :_on_erase_background
448
+ evt_left_down :_on_left_down
449
+ evt_left_up :_on_left_up
450
+ evt_right_down :_on_right_down
451
+ evt_right_up :_on_right_up
452
+ evt_left_dclick :_on_left_double_click
453
+ evt_right_dclick :_on_right_double_click
454
+ evt_motion :_on_mouse_move
455
+ evt_mousewheel :_on_mouse_wheel
456
+ evt_key_down :_on_key_down
457
+ evt_enter_window :_on_enter_window
458
+ evt_leave_window :_on_leave_window
459
+ evt_size :_on_resize
460
+ end
461
+
462
+ # Creates the window in two-step construction mode. set_diagram() function must also be called to complete the canvas initialization.
463
+ # @param [Wx::Window] parent Parent window
464
+ # @param [Integer] id Window ID
465
+ # @param [Wx::Point] pos Initial position
466
+ # @param [Wx::Size] size Initial size
467
+ # @param [Integer] style Window style
468
+ # @param [String] name Window name
469
+ def create(parent, id = -1, pos = Wx::DEFAULT_POSITION, size = Wx::DEFAULT_SIZE, style = (Wx::HSCROLL | Wx::VSCROLL), name = "Wx::ScrolledWindow")
470
+ # NOTE: user must call Wx::SF::ShapeCanvas#set_diagram() to complete
471
+ # canvas initialization!
472
+
473
+ # perform basic window initialization
474
+ super
475
+
476
+ # set drop target
477
+ if Wx.has_feature?(:USE_DRAG_AND_DROP)
478
+ set_drop_target(Wx::SF::CanvasDropTarget.new(Wx::SF::ShapeDataObject.new, self))
479
+ end
480
+
481
+ # initialize selection rectangle
482
+ @shp_selection = MultiSelRect.new
483
+ @shp_selection.send(:set_id, nil)
484
+ @shp_selection.create_handles
485
+ @shp_selection.select(true)
486
+ @shp_selection.show(false)
487
+ @shp_selection.show_handles(true)
488
+
489
+ # initialize multi-edit rectangle
490
+ @shp_multi_edit = MultiSelRect.new
491
+ @shp_multi_edit.send(:set_id, nil)
492
+ @shp_multi_edit.create_handles
493
+ @shp_multi_edit.select(true)
494
+ @shp_multi_edit.show(false)
495
+ @shp_multi_edit.show_handles(true)
496
+
497
+ set_scrollbars(5, 5, 100, 100)
498
+ set_background_style(Wx::BG_STYLE_PAINT)
499
+
500
+ true
501
+ end
502
+
503
+ attr_reader :settings
504
+
505
+ # Returns the shape diagram which shapes are displayed on this canvas.
506
+ # @return [Wx::SF::Diagram]
507
+ def get_diagram
508
+ @diagram
509
+ end
510
+ alias :diagram :get_diagram
511
+
512
+ # Set the shape diagram to display on this canvas
513
+ # @param [Wx::SF::Diagram] diagram
514
+ def set_diagram(diagram)
515
+ @diagram = diagram
516
+ @shp_selection.set_diagram(@diagram)
517
+ @shp_multi_edit.set_diagram(@diagram)
518
+ @diagram.shape_canvas = self if @diagram
519
+ clear_temporaries
520
+ @diagram.update_all
521
+ end
522
+
523
+ # Load serialized canvas content (diagrams).
524
+ # @overload load_canvas(file)
525
+ # @param [String] file Full file name
526
+ # @return [self]
527
+ # @overload load_canvas(io)
528
+ # @param [IO] io IO object
529
+ # @return [self]
530
+ def load_canvas(io)
531
+ # get IO stream to read from
532
+ ios = io.is_a?(::String) ? File.open(io, 'r') : io
533
+ begin
534
+ _, @settings, diagram = Serializable.deserialize(ios)
535
+ rescue SFException
536
+ ::Kernel.raise
537
+ rescue ::Exception
538
+ ::Kernel.raise SFException, "Failed to load canvas: #{$!.message}"
539
+ ensure
540
+ ShapeCanvas.reset_compat_loading
541
+ ios.close if io.is_a?(::String) && ios
542
+ end
543
+ set_diagram(diagram)
544
+ clear_canvas_history
545
+ save_canvas_state
546
+ set_scale(@settings.scale)
547
+ update_virtual_size
548
+ refresh(false)
549
+
550
+ @diagram.set_modified(false)
551
+
552
+ self
553
+ end
554
+
555
+ # Save canvas content (diagrams).
556
+ # @overload save_canvas(file, compact: true)
557
+ # @param [String] file Full file name
558
+ # @param [Boolean] compact specifies whether to write content in compact mode (true) or not (false)
559
+ # @return [self]
560
+ # @overload save_canvas(io, compact: true)
561
+ # @param [IO] io IO object
562
+ # @param [Boolean] compact specifies whether to write content in compact mode (true) or not (false)
563
+ # @return [self]
564
+ def save_canvas(io, compact: true)
565
+ return self unless @diagram
566
+ # get IO stream to write to
567
+ ios = io.is_a?(::String) ? Tempfile.new(File.basename(io, '.*')) : io
568
+ # write canvas data to temp file
569
+ begin
570
+ [Version.new, @settings, @diagram].serialize(ios, pretty: !compact)
571
+ rescue SFException
572
+ ::Kernel.raise
573
+ rescue Exception
574
+ ::Kernel.raise SFException, "Error writing canvas: #{$!.message}"
575
+ end
576
+ if io.is_a?(::String)
577
+ ios.close(false) # close but keep temp file
578
+ full_path = File.absolute_path(io)
579
+ if File.exist?(full_path)
580
+ # create temporary backup
581
+ ftmp = Tempfile.new(File.basename(io))
582
+ ftmp_name = ftmp.path.dup
583
+ ftmp.close(true) # close AND unlink
584
+ FileUtils::mv(full_path, ftmp_name) # backup existing file
585
+ # replace original
586
+ begin
587
+ # rename newly generated file
588
+ FileUtils.mv(ios.path, full_path)
589
+ # preserve file mode
590
+ FileUtils.chmod(File.lstat(ftmp_name).mode, full_path)
591
+ rescue Exception
592
+ # restore backup
593
+ FileUtils.mv(ftmp_name, full_path)
594
+ ::Kernel.raise SFException, "Unable to save canvas file #{io}: #{$!.message}"
595
+ end
596
+ # remove backup
597
+ FileUtils.rm_f(ftmp_name)
598
+ else
599
+ begin
600
+ # rename newly generated file
601
+ FileUtils.mv(ios.path, full_path)
602
+ rescue Exception
603
+ ::Kernel.raise SFException, "Unable to save canvas file #{io}: #{$!.message}"
604
+ end
605
+ end
606
+ end
607
+
608
+ @diagram.set_modified(false)
609
+
610
+ self
611
+ end
612
+
613
+ # @overload save_canvas_to_image(type: Wx::BITMAP_TYPE_BMP, background: true, scale: -1.0)
614
+ # Export canvas content to image.
615
+ # @param [Wx::BitmapType] type Image type. See Wx::BitmapType for more details. Default type is Wx::BITMAP_TYPE_BMP.
616
+ # @param [Boolean] background Export also diagram background
617
+ # @param [Float] scale Image scale. If -1 then current canvas scale id used.
618
+ # @return [Wx::Bitmap,nil] exported canvas image or nil if failed to create bitmap
619
+ # @overload save_canvas_to_image(file, type: Wx::BITMAP_TYPE_BMP, background: true, scale: -1.0)
620
+ # Export canvas content to image file.
621
+ # @param [String] file Full file name
622
+ # @param [Wx::BitmapType] type Image type. See Wx::BitmapType for more details. Default type is Wx::BITMAP_TYPE_BMP.
623
+ # @param [Boolean] background Export also diagram background
624
+ # @param [Float] scale Image scale. If -1 then current canvas scale id used.
625
+ # @return [Boolean] true if saving the image to file succeeded, false otherwise
626
+ def save_canvas_to_image(file = nil, type: Wx::BITMAP_TYPE_BMP, background: true, scale: -1.0)
627
+ # create memory DC a draw the canvas content into
628
+
629
+ prev_scale = get_scale
630
+ scale = prev_scale if scale == -1
631
+
632
+ bmp_bb = get_total_bounding_box
633
+
634
+ bmp_bb.left = (bmp_bb.left * scale).to_i
635
+ bmp_bb.top = (bmp_bb.top * scale).to_i
636
+ bmp_bb.width = (bmp_bb.width * scale).to_i
637
+ bmp_bb.height = (bmp_bb.height * scale).to_i
638
+
639
+ bmp_bb.inflate!(@settings.grid_size * scale)
640
+
641
+ outbmp = Wx::Bitmap.new(bmp_bb.width, bmp_bb.height)
642
+ Wx::MemoryDC.draw_on(outbmp) do |mdc|
643
+
644
+ Wx::ScaledDC.draw_on(mdc, scale) do |outdc|
645
+
646
+ if outdc.ok?
647
+ set_scale(scale) if scale != prev_scale
648
+
649
+ outdc.set_device_origin(-bmp_bb.left, -bmp_bb.top)
650
+
651
+ prev_style = get_style
652
+ prev_colour = get_canvas_colour
653
+
654
+ unless background
655
+ remove_style(STYLE::GRADIENT_BACKGROUND)
656
+ remove_style(STYLE::GRID_SHOW)
657
+ set_canvas_colour(Wx::WHITE)
658
+ end
659
+
660
+ draw_background(outdc, NOT_FROM_PAINT)
661
+ draw_content(outdc, NOT_FROM_PAINT)
662
+ draw_foreground( outdc, NOT_FROM_PAINT)
663
+
664
+ unless background
665
+ set_style(prev_style)
666
+ set_canvas_colour(prev_colour)
667
+ end
668
+
669
+ set_scale(prev_scale) if scale != prev_scale
670
+
671
+ if file
672
+ return outbmp.save_file(file, type)
673
+ else
674
+ return outbmp
675
+ end
676
+ elsif file
677
+ Wx.message_box('Could not create output bitmap.', 'wxRuby ShapeFramework', Wx::OK | Wx::ICON_ERROR)
678
+ end
679
+ end
680
+ end
681
+ nil
682
+ end
683
+
684
+ def _start_interactive_connection(lpos, src_shape_id, cpt)
685
+ if @new_line_shape
686
+ @working_mode = MODE::CREATECONNECTION
687
+ @new_line_shape.send(:set_line_mode, LineShape::LINEMODE::UNDERCONSTRUCTION)
688
+
689
+ @new_line_shape.set_src_shape_id(src_shape_id)
690
+
691
+ # switch on the "under-construction" mode
692
+ @new_line_shape.send(:set_unfinished_point, lpos)
693
+ # assign starting point of new line shapes to the nearest connection point of
694
+ # connected shape if exists
695
+ @new_line_shape.set_starting_connection_point(cpt)
696
+ ERRCODE::OK
697
+ else
698
+ ERRCODE::NOT_CREATED
699
+ end
700
+ end
701
+ private :_start_interactive_connection
702
+
703
+ # Start interactive connection creation.
704
+ #
705
+ # This function switches the canvas to a mode in which a new shape connection
706
+ # can be created interactively (by mouse operations). Every connection must
707
+ # start and finish in some shape object or another connection. At the end of the
708
+ # process the on_connection_finished event handler is invoked so the user can
709
+ # set needed connection properties immediately.
710
+ #
711
+ # Function must be called from mouse event handler and the event must be passed
712
+ # to the function.
713
+ # @overload start_interactive_connection(shape_info, pos)
714
+ # @param [Class] shape_info Connection type
715
+ # @param [Wx::Point] pos Position where to start
716
+ # @return [Wx::SF::ERRCODE] operation result
717
+ # @overload start_interactive_connection(shape, pos)
718
+ # @param [Wx::SF::LineShape] shape existing line shape object which will be used as a connection.
719
+ # @param [Wx::Point] pos Position where to start
720
+ # @return [Wx::SF::ERRCODE] err operation result
721
+ # @overload start_interactive_connection(shape, connection_point, pos)
722
+ # @param [Wx::SF::LineShape] shape existing line shape object which will be used as a connection.
723
+ # @param [Wx::SF::ConnectionPoint] connection_point Initial connection point
724
+ # @param [Wx::Point] pos Position where to start
725
+ # @return [Wx::SF::ERRCODE] err operation result
726
+ # @see create_connection
727
+ def start_interactive_connection(*args)
728
+ return ERRCODE::INVALID_INPUT unless @diagram
729
+
730
+ shape_info = shape = pos = connection_point = nil
731
+ shape_klass = nil
732
+ case args.first
733
+ when Wx::SF::LineShape
734
+ shape = args.shift
735
+ shape_klass = shape.class.name
736
+ if args.first.is_a?(Wx::SF::ConnectionPoint)
737
+ connection_point = args.shift
738
+ end
739
+ pos = args.shift.to_point
740
+ when ::Class
741
+ shape_info = args.shift
742
+ pos = args.shift.to_point
743
+ shape_klass = shape_info.name
744
+ end
745
+ ::Kernel.raise ArgumentError, "Invalid arguments #{args}" unless args.empty?
746
+ return ERRCODE::INVALID_INPUT unless pos
747
+
748
+ lpos = dp2lp(pos)
749
+
750
+ if @working_mode == MODE::READY && ((shape_info && shape_info <= Wx::SF::LineShape) || (shape.is_a?(Wx::SF::LineShape)))
751
+
752
+ if connection_point
753
+
754
+ if @diagram.contains?(shape)
755
+ @new_line_shape = shape
756
+ else
757
+ @new_line_shape = @diagram.add_shape(shape, nil, Wx::DEFAULT_POSITION, INITIALIZE, DONT_SAVE_STATE)
758
+ end
759
+ return _start_interactive_connection(lpos, connection_point.get_parent_shape.id, connection_point)
760
+
761
+ else
762
+
763
+ shape_under = get_shape_at_position(lpos)
764
+ if shape_info
765
+ # propagate request for interactive connection if requested
766
+ shape_under = shape_under.get_parent_shape while shape_under &&
767
+ shape_under.has_style?(Shape::STYLE::PROPAGATE_INTERACTIVE_CONNECTION)
768
+ end
769
+
770
+ # start the connection's creation process if possible
771
+ if shape_under&.id && shape_under.is_connection_accepted(shape_klass)
772
+ if shape && @diagram.contains?(shape)
773
+ @new_line_shape = shape
774
+ else
775
+ if shape
776
+ err = @diagram.add_shape(shape, nil, Wx::DEFAULT_POSITION, INITIALIZE, DONT_SAVE_STATE)
777
+ else
778
+ err, shape = @diagram.create_shape(shape_info, DONT_SAVE_STATE)
779
+ end
780
+ @new_line_shape = (err == ERRCODE::OK ? shape : nil)
781
+ end
782
+ return _start_interactive_connection(lpos, shape_under.id, shape_under.get_nearest_connection_point(lpos.to_real))
783
+ else
784
+ return ERRCODE::NOT_ACCEPTED
785
+ end
786
+
787
+ end
788
+ end
789
+ ERRCODE::INVALID_INPUT
790
+ end
791
+
792
+ # Abort interactive connection creation process
793
+ def abort_interactive_connection
794
+ return unless @diagram
795
+
796
+ if @new_line_shape
797
+ @diagram.remove_shape(@new_line_shape)
798
+ @new_line_shape = nil
799
+ on_connection_finished(nil)
800
+ end
801
+ @working_mode = MODE::READY
802
+ refresh(false)
803
+ end
804
+
805
+ # Select all shapes in the canvas
806
+ def select_all
807
+ return unless @diagram
808
+
809
+ shapes = @diagram.get_shapes
810
+
811
+ unless shapes.empty?
812
+ shapes.each { |shape| shape.select(true) }
813
+
814
+ validate_selection(get_selected_shapes)
815
+
816
+ hide_all_handles
817
+ update_multiedit_size
818
+ @shp_multi_edit.show(true)
819
+ @shp_multi_edit.show_handles(true)
820
+
821
+ refresh(false)
822
+ end
823
+ end
824
+
825
+ # Deselect all shapes
826
+ def deselect_all
827
+ return unless @diagram
828
+
829
+ @diagram.get_shapes.each { |shape| shape.select(false) }
830
+
831
+ @shp_multi_edit.show(false)
832
+ end
833
+
834
+ # Hide handles of all shapes
835
+ def hide_all_handles
836
+ return unless @diagram
837
+
838
+ @diagram.get_shapes.each { |shape| shape.show_handles(false) }
839
+ end
840
+
841
+ # Repaint the shape canvas.
842
+ # @param [Boolean] erase true if the canvas should be erased before repainting
843
+ # @param [Wx::Rect] rct Refreshed region (rectangle)
844
+ def refresh_canvas(erase, rct)
845
+ lpos = dp2lp(Wx::Point.new(0, 0))
846
+
847
+ upd_rct = rct.inflate((20/@settings.scale).to_i, (20/@settings.scale).to_i)
848
+ upd_rct.offset!(-lpos.x, -lpos.y)
849
+
850
+ refresh_rect(Wx::Rect.new((upd_rct.x*@settings.scale).to_i,
851
+ (upd_rct.y*@settings.scale).to_i,
852
+ (upd_rct.width*@settings.scale).to_i,
853
+ (upd_rct.height*@settings.scale).to_i),
854
+ erase)
855
+ end
856
+
857
+ # Mark given rectangle as an invalidated one, i.e. as a rectangle which should
858
+ # be refreshed (by using Wx::SF::ShapeCanvas::refresh_invalidated_rect).
859
+ # @param [Wx::Rect] rct Rectangle to be invalidated
860
+ def invalidate_rect(rct)
861
+ if @invalidate_rect.nil?
862
+ @invalidate_rect = rct.dup
863
+ else
864
+ @invalidate_rect.union!(rct)
865
+ end
866
+ end
867
+
868
+ # Mark whole visible canvas portion as an invalidated rectangle.
869
+ def invalidate_visible_rect
870
+ invalidate_rect(dp2lp(get_client_rect))
871
+ end
872
+
873
+ # Refresh all canvas rectangles marked as invalidated.
874
+ # @see Wx::SF::ShapeCanvas::invalidate_rect
875
+ def refresh_invalidated_rect
876
+ unless @invalidate_rect.nil? || @invalidate_rect.empty?
877
+ refresh_canvas(false, @invalidate_rect)
878
+ @invalidate_rect = nil
879
+ end
880
+ end
881
+
882
+ # Show shapes shadows (only current diagram shapes are affected).
883
+ #
884
+ # The functions sets/unsets SHOW_SHADOW flag for all shapes currently included in the diagram.
885
+ # @param [Boolean] show true if the shadow should be shown, otherwise false
886
+ # @param [SHADOWMODE] style Shadow style
887
+ # @see SHADOWMODE
888
+ def show_shadows(show, style)
889
+ return unless @diagram
890
+
891
+ shapes = @diagram.get_shapes
892
+
893
+ shapes.each do |shape|
894
+ shape.remove_style(Shape::STYLE::SHOW_SHADOW) if show
895
+
896
+ case style
897
+ when SHADOWMODE::TOPMOST
898
+ unless shape.get_parent_shape
899
+ if show
900
+ shape.add_style(Shape::STYLE::SHOW_SHADOW)
901
+ else
902
+ shape.remove_style(Shape::STYLE::SHOW_SHADOW)
903
+ end
904
+ end
905
+
906
+ when SHADOWMODE::ALL
907
+ if show
908
+ shape.add_style(Shape::STYLE::SHOW_SHADOW)
909
+ else
910
+ shape.remove_style(Shape::STYLE::SHOW_SHADOW)
911
+ end
912
+ end
913
+ end
914
+ end
915
+
916
+ if Wx.has_feature?(:USE_DRAG_AND_DROP)
917
+
918
+ # Start Drag&Drop operation with shapes included in the given list.
919
+ # @param [Array<Wx::SF::Shape>] shapes List of shapes which should be dragged
920
+ # @param [Wx::Point] start A point where the dragging operation has started
921
+ # @return [Wx::DragResult] Drag result
922
+ def do_drag_drop(shapes, start = Wx::Point.new(-1, -1))
923
+ return Wx::DragNone unless has_style?(STYLE::DND)
924
+
925
+ @working_mode = MODE::DND
926
+
927
+ result = Wx::DragNone
928
+
929
+ validate_selection_for_clipboard(shapes, true)
930
+
931
+ unless shapes.empty?
932
+ deselect_all
933
+
934
+ @dnd_started_here = true
935
+ @dnd_started_at = start.to_point
936
+
937
+ data_obj = Wx::SF::ShapeDataObject.new(shapes)
938
+
939
+ dnd_src = if Wx::PLATFORM == 'WXGTK'
940
+ Wx::DropSource.new(data_obj, self, Wx::Icon(:page), Wx::Icon(:page), Wx::Icon(:page))
941
+ else
942
+ Wx::DropSource.new(data_obj)
943
+ end
944
+
945
+ result = dnd_src.do_drag_drop(Wx::Drag_AllowMove)
946
+ case result
947
+ when Wx::DragResult::DragMove
948
+ @diagram.remove_shapes(shapes)
949
+ end
950
+
951
+ @dnd_started_here = false
952
+
953
+ restore_prev_positions
954
+
955
+ move_shapes_from_negatives
956
+ update_virtual_size
957
+
958
+ save_canvas_state
959
+ refresh(false)
960
+ end
961
+
962
+ @working_mode = MODE::READY
963
+
964
+ result
965
+ end
966
+
967
+ end # if Wx.has_feature?(:USE_DRAG_AND_DROP)
968
+
969
+ # Copy selected shapes to the clipboard
970
+ def copy
971
+ return unless has_style?(STYLE::CLIPBOARD)
972
+ return unless @diagram
973
+
974
+ # copy selected shapes to the clipboard
975
+ Wx::Clipboard.open do |clipboard|
976
+ lst_selection = get_selected_shapes
977
+
978
+ validate_selection_for_clipboard(lst_selection,true)
979
+
980
+ unless lst_selection.empty?
981
+ data_obj = Wx::SF::ShapeDataObject.new(lst_selection)
982
+ clipboard.place(data_obj)
983
+
984
+ restore_prev_positions
985
+ end
986
+ end
987
+ end
988
+
989
+ # Copy selected shapes to the clipboard and remove them from the canvas
990
+ def cut
991
+ return unless has_style?(STYLE::CLIPBOARD)
992
+ return unless @diagram
993
+
994
+ copy
995
+
996
+ clear_temporaries
997
+
998
+ # remove selected shapes
999
+ lst_selection = get_selected_shapes
1000
+
1001
+ validate_selection_for_clipboard(lst_selection,false)
1002
+
1003
+ unless lst_selection.empty?
1004
+ @diagram.remove_shapes(lst_selection)
1005
+ @shp_multi_edit.show(false)
1006
+ save_canvas_state
1007
+ refresh(false)
1008
+ end
1009
+ end
1010
+
1011
+ # Paste shapes stored in the clipboard to the canvas
1012
+ def paste
1013
+ return unless has_style?(STYLE::CLIPBOARD)
1014
+ return unless @diagram
1015
+
1016
+ Wx::Clipboard.open do |clipboard|
1017
+ # read data object from the clipboard
1018
+ data_obj = Wx::SF::ShapeDataObject.new
1019
+ if clipboard.fetch(data_obj)
1020
+
1021
+ # deserialize shapes
1022
+ new_shapes = Wx::SF::Serializable.deserialize(data_obj.get_data_here)
1023
+ # add new shapes to diagram and remove those that are not accepted
1024
+ new_shapes.select! do |shape|
1025
+ ERRCODE::OK == @diagram.add_shape(shape, nil, shape.get_relative_position, INITIALIZE, DONT_SAVE_STATE)
1026
+ end
1027
+
1028
+ # verify newly added shapes (may remove shapes from list)
1029
+ @diagram.send(:check_new_shapes, new_shapes)
1030
+
1031
+ update_virtual_size # update for new shapes
1032
+
1033
+ # call user-defined handler
1034
+ on_paste(new_shapes)
1035
+
1036
+ save_canvas_state
1037
+ refresh(false)
1038
+ end
1039
+ end
1040
+ end
1041
+
1042
+ # Perform Undo operation (if available)
1043
+ def undo
1044
+ return unless has_style?(STYLE::UNDOREDO)
1045
+
1046
+ clear_temporaries
1047
+
1048
+ restore_canvas_state(@canvas_history.restore_older_state)
1049
+ @shp_multi_edit.show(false)
1050
+ end
1051
+
1052
+ # Perform Redo operation (if available)
1053
+ def redo
1054
+ return unless has_style?(STYLE::UNDOREDO)
1055
+
1056
+ clear_temporaries
1057
+
1058
+ restore_canvas_state(@canvas_history.restore_newer_state)
1059
+ @shp_multi_edit.show(false)
1060
+ end
1061
+
1062
+ # Function returns true if some shapes can be copied to the clipboard (it means they are selected)
1063
+ # @return [Boolean]
1064
+ def can_copy
1065
+ return false unless has_style?(STYLE::CLIPBOARD)
1066
+
1067
+ !get_selected_shapes.empty?
1068
+ end
1069
+ alias :can_copy? :can_copy
1070
+
1071
+ # Function returns true if some shapes can be cut to the clipboard (it means they are selected)
1072
+ # @return [Boolean]
1073
+ def can_cut
1074
+ can_copy
1075
+ end
1076
+ alias :can_cut? :can_cut
1077
+
1078
+ # Function returns true if some shapes can be copied from the clipboard to the canvas
1079
+ # (it means the clipboard contains stored shapes)
1080
+ # @return [Boolean]
1081
+ def can_paste
1082
+ return false unless has_style?(STYLE::CLIPBOARD)
1083
+
1084
+ Wx::Clipboard.open do |clipboard|
1085
+ return clipboard.supported?(Wx::DataFormat.new(Wx::SF::ShapeDataObject::DataFormatID))
1086
+ end
1087
+ end
1088
+ alias :can_paste? :can_paste
1089
+
1090
+ # Function returns true if undo operation can be done
1091
+ # @return [Boolean]
1092
+ def can_undo
1093
+ return false unless has_style?(STYLE::UNDOREDO)
1094
+
1095
+ @canvas_history.can_undo
1096
+ end
1097
+ alias :can_undo? :can_undo
1098
+
1099
+ # Function returns TRUE if Redo operation can be done
1100
+ # @return [Boolean]
1101
+ def can_redo
1102
+ return false unless has_style?(STYLE::UNDOREDO)
1103
+
1104
+ @canvas_history.can_redo
1105
+ end
1106
+ alias :can_redo? :can_redo
1107
+
1108
+ # Function returns true if align_selected function can be invoked (if more than
1109
+ # @return [Boolean]
1110
+ def can_align_selected
1111
+ @shp_multi_edit.visible? && @working_mode == MODE::READY
1112
+ end
1113
+ alias :can_align_selected? :can_align_selected
1114
+
1115
+ # Save current canvas state (for Undo/Redo operations)
1116
+ def save_canvas_state
1117
+ return unless has_style?(STYLE::UNDOREDO)
1118
+
1119
+ @canvas_history.save_canvas_state(@diagram.serialize)
1120
+ end
1121
+
1122
+ # Clear all stored canvas states (no Undo/Redo operations will be available)
1123
+ def clear_canvas_history
1124
+ @canvas_history.clear
1125
+ end
1126
+
1127
+ # Restores given canvas state (unless nil given)
1128
+ # @param [String,nil] state to restore
1129
+ def restore_canvas_state(state)
1130
+ return unless state
1131
+ set_diagram(Wx::SF::Serializable.deserialize(state))
1132
+ update_virtual_size
1133
+ @diagram.set_modified
1134
+ refresh(false)
1135
+ end
1136
+ protected :restore_canvas_state
1137
+
1138
+ # @!group Print methods
1139
+
1140
+ # Print current canvas content.
1141
+ # @overload print(prompt = PROMPT)
1142
+ # @param [Boolean] prompt If true (PROMPT) then the the native print dialog will be displayed before printing
1143
+ # @overload print(printout, prompt = PROMPT)
1144
+ # @param [Wx::SF::Printout] printout user-defined printout object (inherited from Wx::SF::Printout class) for printing.
1145
+ # @param [Boolean] prompt If true (PROMPT) then the the native print dialog will be displayed before printing
1146
+ # @see Wx::SF::Printout
1147
+ def print(*args)
1148
+ if args.first.is_a?(Wx::PRT::Printout)
1149
+ printout, prompt = args
1150
+ else
1151
+ printout = Printout.new('wxRuby SF Printout', self)
1152
+ prompt = args.shift
1153
+ end
1154
+ prompt = PROMPT if prompt.nil?
1155
+
1156
+ print_dialog_data = Wx::PRT::PrintDialogData.new(ShapeCanvas.print_data)
1157
+ printer = Wx::PRT::Printer.new(print_dialog_data)
1158
+
1159
+ deselect_all
1160
+
1161
+ if !printer.print(self, printout, prompt)
1162
+ if Wx::PRT::Printer.get_last_error == Wx::PRT::PrinterError::PRINTER_ERROR
1163
+ Wx.message_box("There was a problem printing.\nPerhaps your current printer is not set correctly?",
1164
+ 'wxRuby SF Printing',
1165
+ Wx::OK | Wx::ICON_ERROR)
1166
+ end
1167
+ else
1168
+ ShapeCanvas.print_data = printer.get_print_dialog_data.get_print_data
1169
+ end
1170
+ end
1171
+
1172
+ # Show print preview.
1173
+ # @overload print_preview()
1174
+ # @overload print_preview(preview, printout = nil)
1175
+ # @param [Wx::SF::Printout] preview user-defined printout object (inherited from Wx::SF::Printout class) used for print preview.
1176
+ # @param [Wx::SF::Printout] printout user-defined printout class (inherited from Wx::SF::Printout class) used for printing.
1177
+ # This parameter can be nil (in this case a print button will not be available in the print preview window).
1178
+ # @see Wx::SF::Printout
1179
+ def print_preview(*args)
1180
+ if args.empty?
1181
+ preview = Printout.new('wxRuby SF Preview', self)
1182
+ printout = Printout.new('wxRuby SF Printout', self)
1183
+ else
1184
+ preview, printout = args
1185
+ end
1186
+
1187
+ deselect_all
1188
+
1189
+ # Pass two printout objects: for preview, and possible printing.
1190
+ print_dialog_data = Wx::PRT::PrintDialogData.new(ShapeCanvas.print_data)
1191
+ prn_preview = Wx::PRT::PrintPreview.new(preview, printout, print_dialog_data)
1192
+ unless prn_preview.ok?
1193
+ Wx.message_box("There was a problem previewing.\nPerhaps your current printer is not set correctly?",
1194
+ 'wxRuby SF Previewing',
1195
+ Wx::OK | Wx::ICON_ERROR)
1196
+ return
1197
+ end
1198
+
1199
+ frame = Wx::PRT::PreviewFrame.new(prn_preview, self, 'wxRuby SF Print Preview', [100, 100], [800, 700])
1200
+ frame.centre(Wx::BOTH)
1201
+ frame.init
1202
+ frame.show
1203
+ end
1204
+
1205
+ # Show page setup dialog for printing.
1206
+ def page_setup
1207
+ ShapeCanvas.page_setup_data.set_print_data(ShapeCanvas.print_data)
1208
+
1209
+ Wx::PRT::PageSetupDialog(self, ShapeCanvas.page_setup_data) do |dlg|
1210
+ dlg.show_modal
1211
+ ShapeCanvas.print_data = dlg.get_page_setup_data.get_print_data
1212
+ ShapeCanvas.page_setup_data = dlg.get_page_setup_data
1213
+ end
1214
+ end
1215
+
1216
+ # @!endgroup
1217
+
1218
+ # Convert device position to logical position.
1219
+ #
1220
+ # The function returns unscrolled unscaled canvas position.
1221
+ # @overload dp2lp(pos)
1222
+ # @param [Wx::Point] pos Device position (for example mouse position)
1223
+ # @return [Wx::Point] Logical position
1224
+ # @overload dp2lp(rct)
1225
+ # @param [Wx::Rect] rct Device position (for example mouse position)
1226
+ # @return [Wx::Rect] Logical position
1227
+ def dp2lp(arg)
1228
+ if arg.is_a?(Wx::Rect)
1229
+ x, y = calc_unscrolled_position(arg.x, arg.y)
1230
+ Wx::Rect.new((x/@settings.scale).to_i, (y/@settings.scale).to_i,
1231
+ (arg.width/@settings.scale).to_i, (arg.height/@settings.scale).to_i)
1232
+ else
1233
+ arg = arg.to_point
1234
+ x, y = calc_unscrolled_position(arg.x, arg.y)
1235
+ Wx::Point.new((x/@settings.scale).to_i, (y/@settings.scale).to_i)
1236
+ end
1237
+ end
1238
+
1239
+ # Convert logical position to device position.
1240
+ #
1241
+ # The function returns scrolled scaled canvas position.
1242
+ # @overload lp2dp(pos)
1243
+ # @param [Wx::Point] pos Logical position (for example shape position)
1244
+ # @return [Wx::Point] Device position
1245
+ # @overload lp2dp(rct)
1246
+ # @param [Wx::Rect] rct Logical position (for example shape position)
1247
+ # @return [Wx::Rect] Device position
1248
+ def lp2dp(arg)
1249
+ if arg.is_a?(Wx::Rect)
1250
+ x, y = calc_unscrolled_position(arg.x, arg.y)
1251
+ Wx::Rect.new((x*@settings.scale).to_i, (y*@settings.scale).to_i,
1252
+ (arg.width*@settings.scale).to_i, (arg.height*@settings.scale).to_i)
1253
+ else
1254
+ arg = arg.to_point
1255
+ x, y = calc_unscrolled_position(arg.x, arg.y)
1256
+ Wx::Point.new((x*@settings.scale).to_i, (y*@settings.scale).to_i)
1257
+ end
1258
+ end
1259
+
1260
+ # Search for any shape located at the (mouse cursor) position (result used by #get_shape_under_cursor)
1261
+ # @param [Wx::Point] lpos
1262
+ def update_shape_under_cursor_cache(lpos)
1263
+ sel_shape = unsel_shape = top_shape = nil
1264
+ sel_line = unsel_line = top_line = nil
1265
+ lpos = lpos.to_point
1266
+
1267
+ @topmost_shape_under_cursor = nil
1268
+
1269
+ @current_shapes.replace(@diagram.get_all_shapes) if @diagram
1270
+
1271
+ @current_shapes.reverse_each do |shape|
1272
+ if shape.visible? && shape.active? && shape.contains?(lpos)
1273
+ if shape.is_a?(Wx::SF::LineShape)
1274
+ top_line ||= shape
1275
+ if shape.selected?
1276
+ sel_line ||= shape
1277
+ else
1278
+ unsel_line ||= shape
1279
+ end
1280
+ else
1281
+ top_shape ||= shape
1282
+ if shape.selected?
1283
+ sel_shape ||= shape
1284
+ else
1285
+ unsel_shape ||= shape
1286
+ end
1287
+ end
1288
+ end
1289
+ end
1290
+
1291
+ # set reference to logically topmost selected and unselected shape under the mouse cursor
1292
+ @topmost_shape_under_cursor = top_line ? top_line : top_shape
1293
+
1294
+ @selected_shape_under_cursor = sel_line ? sel_line : sel_shape
1295
+
1296
+ @unselected_shape_under_cursor = unsel_line ? unsel_line : unsel_shape
1297
+ end
1298
+
1299
+ # Get shape under current mouse cursor position (fast implementation - use everywhere
1300
+ # it is possible instead of much slower GetShapeAtPosition()).
1301
+ # @param [SEARCHMODE] mode Search mode
1302
+ # @return [Wx::SF::Shape,nil] shape if found, otherwise nil
1303
+ # @see SEARCHMODE, Wx::SF::ShapeCanvas#dp2lp, Wx::SF::ShapeCanvas#get_shape_at_position
1304
+ def get_shape_under_cursor(mode = SEARCHMODE::BOTH)
1305
+ case mode
1306
+ when SEARCHMODE::BOTH
1307
+ @topmost_shape_under_cursor
1308
+ when SEARCHMODE::SELECTED
1309
+ @selected_shape_under_cursor
1310
+ when SEARCHMODE::UNSELECTED
1311
+ @unselected_shape_under_cursor
1312
+ else
1313
+ nil
1314
+ end
1315
+ end
1316
+
1317
+ # Get shape at given logical position
1318
+ # @param [Wx::Point] pos Logical position
1319
+ # @param [Integer] zorder Z-order of searched shape (useful if several shapes are located
1320
+ # at the given position)
1321
+ # @param [SEARCHMODE] mode Search mode
1322
+ # @return [Wx::SF::Shape,nil] shape if found, otherwise nil
1323
+ # @see SEARCHMODE, Wx::SF::ShapeCanvas#dp2lp, Wx::SF::ShapeCanvas#get_shape_under_cursor
1324
+ def get_shape_at_position(pos, zorder = 1, mode = SEARCHMODE::BOTH)
1325
+ return nil unless @diagram
1326
+
1327
+ @diagram.get_shape_at_position(pos, zorder, mode)
1328
+ end
1329
+
1330
+ # Get topmost handle at given position
1331
+ # @param [Wx::Point] pos Logical position
1332
+ # @return [Wx::SF::Shape::Handle,nil] shape handle if found, otherwise nil
1333
+ # @see Wx::SF::ShapeCanvas#dp2lp
1334
+ def get_topmost_handle_at_position(pos)
1335
+ return nil unless @diagram
1336
+
1337
+ pos = pos.to_point
1338
+ # first test multiedit handles...
1339
+ if @shp_multi_edit.visible?
1340
+ @shp_multi_edit.handles.each do |handle|
1341
+ return handle if handle.visible? && handle.contains?(pos)
1342
+ end
1343
+ end
1344
+
1345
+ # ... then test normal handles
1346
+ @diagram.get_shapes.each do |shape|
1347
+ # iterate through all shape's handles
1348
+ if shape.has_style?(Shape::STYLE::SIZE_CHANGE)
1349
+ shape.handles.each do |handle|
1350
+ return handle if handle.visible? && handle.contains?(pos)
1351
+ end
1352
+ end
1353
+ end
1354
+
1355
+ nil
1356
+ end
1357
+
1358
+ # Get list of all shapes located at given position
1359
+ # @param [Wx::Point] pos Logical position
1360
+ # @param [Array<Wx::SF::Shape>] shapes shape list where pointers to all found shapes will be stored
1361
+ # @return [Array<Wx::SF::Shape>] shapes shape list
1362
+ # @see Wx::SF::ShapeCanvas#dp2lp
1363
+ def get_shapes_at_position(pos, shapes = [])
1364
+ @diagram.get_shapes_at_position(pos, shapes) if @diagram
1365
+ shapes
1366
+ end
1367
+
1368
+ # Get list of shapes located inside given rectangle
1369
+ # @param [Wx::Rect] rct Examined rectangle
1370
+ # @param [Array<Wx::SF::Shape>] shapes shape list where pointers to all found shapes will be stored
1371
+ # @return [Array<Wx::SF::Shape>] shapes shape list
1372
+ def get_shapes_inside(rct, shapes = [])
1373
+ @diagram.get_shapes_inside(rct, shapes) if @diagram
1374
+ shapes
1375
+ end
1376
+
1377
+ # Get list of selected shapes.
1378
+ # @param [Array<Wx::SF::Shape>] selection shape list where pointers to all selected shapes will be stored
1379
+ # @return [Array<Wx::SF::Shape>] shapes shape list
1380
+ def get_selected_shapes(selection = [])
1381
+ return selection unless @diagram
1382
+
1383
+ selection.clear
1384
+ @diagram.get_shapes.each do |shape|
1385
+ selection << shape if shape.selected?
1386
+ end
1387
+ selection
1388
+ end
1389
+
1390
+ # Get box bounding all shapes in the canvas.
1391
+ # @return [Wx::Rect] Total bounding box
1392
+ def get_total_bounding_box
1393
+ virt_rct = nil
1394
+ if @diagram
1395
+ # calculate total bounding box (includes all shapes)
1396
+ @diagram.get_shapes.each_with_index do |shape, ix|
1397
+ if ix == 0
1398
+ virt_rct = shape.get_bounding_box
1399
+ else
1400
+ virt_rct.union!(shape.get_bounding_box)
1401
+ end
1402
+ end
1403
+ end
1404
+ virt_rct || Wx::Rect.new
1405
+ end
1406
+
1407
+ # Get bounding box of all selected shapes.
1408
+ # @return [Wx::Rect] Selection bounding box
1409
+ def get_selection_bb
1410
+ bb_rct = Wx::Rect.new
1411
+ # get selected shapes
1412
+ get_selected_shapes.each do |shape|
1413
+ shape.get_complete_bounding_box(
1414
+ bb_rct,
1415
+ Shape::BBMODE::SELF | Shape::BBMODE::CHILDREN | Shape::BBMODE::CONNECTIONS | Shape::BBMODE::SHADOW)
1416
+ end
1417
+ bb_rct
1418
+ end
1419
+
1420
+ # Align selected shapes in given directions.
1421
+ #
1422
+ # Shapes will be aligned according to most far shape in appropriate direction.
1423
+ # @param [HALIGN] halign Horizontal alignment
1424
+ # @param [VALIGN] valign Vertical alignment
1425
+ def align_selected(halign, valign)
1426
+ cnt = 0
1427
+ min_pos = max_pos = nil
1428
+
1429
+ lst_selection = get_selected_shapes
1430
+
1431
+ upd_rct = get_selection_bb
1432
+ upd_rct.inflate!(DEFAULT_ME_OFFSET, DEFAULT_ME_OFFSET)
1433
+
1434
+ # find most distant position
1435
+ lst_selection.each do |shape|
1436
+ unless shape.is_a?(LineShape)
1437
+ pos = shape.get_absolute_position
1438
+ shape_bb = shape.get_bounding_box
1439
+
1440
+ if cnt == 0
1441
+ min_pos = pos
1442
+ max_pos = Wx::RealPoint.new(pos.x + shape_bb.width, pos.y + shape_bb.height)
1443
+ else
1444
+ min_pos.x = pos.x if pos.x < min_pos.x
1445
+ min_pos.y = pos.y if pos.y < min_pos.y
1446
+ max_pos.x = pos.x + shape_bb.width if (pos.x + shape_bb.width) > max_pos.x
1447
+ max_pos.y = pos.y + shape_bb.height if (pos.y + shape_bb.height) > max_pos.y
1448
+ end
1449
+
1450
+ cnt += 1
1451
+ end
1452
+ end
1453
+
1454
+ # if only one non-line shape is in the selection then alignment has no sense so exit...
1455
+ return if cnt < 2
1456
+
1457
+ # set new positions
1458
+ lst_selection.each do |shape|
1459
+ unless shape.is_a?(LineShape)
1460
+ pos = shape.get_absolute_position
1461
+ shape_bb = shape.get_bounding_box
1462
+
1463
+ case halign
1464
+ when HALIGN::LEFT
1465
+ shape.move_to(min_pos.x, pos.y)
1466
+
1467
+ when HALIGN::RIGHT
1468
+ shape.move_to(max_pos.x - shape_bb.width, pos.y)
1469
+
1470
+ when HALIGN::CENTER
1471
+ shape.move_to((max_pos.x + min_pos.x)/2 - shape_bb.width/2, pos.y)
1472
+ end
1473
+
1474
+ case valign
1475
+ when VALIGN::TOP
1476
+ shape.move_to(pos.x, min_pos.y)
1477
+
1478
+ when VALIGN::BOTTOM
1479
+ shape.move_to(pos.x, max_pos.y - shape_bb.height)
1480
+
1481
+ when VALIGN::MIDDLE
1482
+ shape.move_to(pos.x, (max_pos.y + min_pos.y)/2 - shape_bb.height/2)
1483
+ end
1484
+
1485
+ # update the shape and its parent
1486
+ shape.update
1487
+ parent = shape.get_parent_shape
1488
+ parent.update if parent
1489
+ end
1490
+ end
1491
+
1492
+ unless upd_rct.empty?
1493
+ update_multiedit_size
1494
+ save_canvas_state
1495
+ refresh_canvas(false, upd_rct)
1496
+ end
1497
+ end
1498
+
1499
+ # @!group Style accessors
1500
+
1501
+ # Set canvas style.
1502
+ #
1503
+ # Default value is STYLE::MULTI_SELECTION | STYLE::MULTI_SIZE_CHANGE | STYLE::DND | STYLE::UNDOREDO | STYLE::CLIPBOARD | STYLE::HOVERING | STYLE::HIGHLIGHTING
1504
+ # @param [STYLE] style Combination of the canvas styles
1505
+ # @see STYLE
1506
+ def set_style(style)
1507
+ @settings.style = style
1508
+ end
1509
+ alias :style= :set_style
1510
+
1511
+ # Get current canvas style.
1512
+ def get_style
1513
+ @settings.style
1514
+ end
1515
+ alias :style :get_style
1516
+
1517
+ # Add new style flag.
1518
+ # @param [STYLE] style canvas style to add
1519
+ def add_style(style)
1520
+ @settings.style |= style
1521
+ end
1522
+
1523
+ # Remove given style flag.
1524
+ # @param [STYLE] style canvas style to remove
1525
+ def remove_style(style)
1526
+ @settings.style &= ~style
1527
+ end
1528
+
1529
+ # Check whether given style flag is used.
1530
+ # @param [STYLE] style canvas style to check
1531
+ def contains_style(style)
1532
+ (@settings.style & style) != 0
1533
+ end
1534
+ alias :contains_style? :contains_style
1535
+ alias :has_style? :contains_style
1536
+
1537
+ # @!endgroup
1538
+
1539
+ # @!group Public attribute accessors
1540
+
1541
+ # Set canvas background color.
1542
+ # @param [Wx::Colour] col Background color
1543
+ def set_canvas_colour(col)
1544
+ @settings.background_color = col
1545
+ end
1546
+ alias :canvas_colour= :set_canvas_colour
1547
+
1548
+ # Get canvas background color.
1549
+ # @return [Wx::Colour] Background color
1550
+ def get_canvas_colour
1551
+ @settings.background_color
1552
+ end
1553
+ alias :canvas_colour :get_canvas_colour
1554
+
1555
+ # Set starting gradient color.
1556
+ # @param [Wx::Colour] col Color
1557
+ def set_gradient_from(col)
1558
+ @settings.gradient_from = col
1559
+ end
1560
+ alias :gradient_from= :set_gradient_from
1561
+
1562
+ # Get starting gradient color.
1563
+ # @return [Wx::Colour] Color
1564
+ def get_gradient_from
1565
+ @settings.gradient_from
1566
+ end
1567
+ alias :gradient_from :get_gradient_from
1568
+
1569
+ # Set ending gradient color.
1570
+ # @param [Wx::Colour] col Color
1571
+ def set_gradient_to(col)
1572
+ @settings.gradient_to = col
1573
+ end
1574
+ alias :gradient_to= :set_gradient_to
1575
+
1576
+ # Get ending gradient color.
1577
+ # @return [Wx::Colour] Color
1578
+ def get_gradient_to
1579
+ @settings.gradient_to
1580
+ end
1581
+ alias :gradient_to :get_gradient_to
1582
+
1583
+ # Get grid size.
1584
+ # @return [Wx::Size] Grid size
1585
+ def get_grid_size
1586
+ @settings.grid_size
1587
+ end
1588
+ alias :grid_size :get_grid_size
1589
+
1590
+ # Set grid size.
1591
+ # @param [Wx::Size] grid Grid size
1592
+ def set_grid_size(grid)
1593
+ @settings.grid_size = grid.to_size
1594
+ end
1595
+ alias :grid_size= :set_grid_size
1596
+
1597
+ # Set grid line multiple.
1598
+ #
1599
+ # Grid lines will be drawn in a distance calculated as grid size multiplicated by this value.
1600
+ # Default value is 1.
1601
+ # @param [Integer] multiple Multiple value
1602
+ def set_grid_line_mult(multiple)
1603
+ @settings.grid_line_mult = multiple
1604
+ end
1605
+ alias :grid_line_mult= :set_grid_line_mult
1606
+
1607
+ # Get grid line multiple.
1608
+ # @return [Integer] Value by which a grid size will be multiplicated to determine grid lines distance
1609
+ def get_grid_line_mult
1610
+ @settings.grid_line_mult
1611
+ end
1612
+ alias :grid_line_mult :get_grid_line_mult
1613
+
1614
+ # Set grid color.
1615
+ # @param [Wx::Colour] col Grid color
1616
+ def set_grid_colour(col)
1617
+ @settings.grid_color = col
1618
+ end
1619
+ alias :grid_colour= :set_grid_colour
1620
+
1621
+ # Get grid color.
1622
+ # @return [Wx::Colour] Grid color
1623
+ def get_grid_colour
1624
+ @settings.grid_color
1625
+ end
1626
+ alias :grid_colour :get_grid_colour
1627
+
1628
+ # Set grid line style.
1629
+ # @param [Wx::PenStyle] style Line style
1630
+ def set_grid_style(style)
1631
+ @settings.grid_style = style
1632
+ end
1633
+ alias :grid_style= :set_grid_style
1634
+
1635
+ # Get grid line style.
1636
+ # @return [Wx::PenStyle] Line style
1637
+ def get_grid_style
1638
+ @settings.grid_style
1639
+ end
1640
+ alias :grid_style :get_grid_style
1641
+
1642
+ # Set shadow offset.
1643
+ # @param [Wx::RealPoint] offset Shadow offset
1644
+ def set_shadow_offset(offset)
1645
+ @settings.shadow_offset = offset.to_real_point
1646
+ end
1647
+ alias :shadow_offset= :set_shadow_offset
1648
+
1649
+ # Get shadow offset.
1650
+ # @return [Wx::RealPoint] Shadow offset
1651
+ def get_shadow_offset
1652
+ @settings.shadow_offset
1653
+ end
1654
+ alias :shadow_offset :get_shadow_offset
1655
+
1656
+ # Set shadow fill (used for shadows of non-text shapes only).
1657
+ # @param [Wx::Brush] brush Reference to brush object
1658
+ def set_shadow_fill(brush)
1659
+ @settings.shadow_fill = brush
1660
+ end
1661
+ alias :shadow_fill= :set_shadow_fill
1662
+
1663
+ # Get shadow fill.
1664
+ # @return [Wx::Brush] Current shadow brush
1665
+ def get_shadow_fill
1666
+ @settings.shadow_fill
1667
+ end
1668
+ alias :shadow_fill :get_shadow_fill
1669
+
1670
+ # Set horizontal align of printed drawing.
1671
+ # @param [HALIGN] val Horizontal align
1672
+ # @see HALIGN
1673
+ def set_print_h_align(val)
1674
+ @settings.print_h_align = val
1675
+ end
1676
+ alias :print_h_align= :set_print_h_align
1677
+
1678
+ # Get horizontal align of printed drawing.
1679
+ # @return [HALIGN] Current horizontal align
1680
+ # @see HALIGN
1681
+ def get_print_h_align
1682
+ @settings.print_h_align
1683
+ end
1684
+ alias :print_h_align :get_print_h_align
1685
+
1686
+ # Set vertical align of printed drawing.
1687
+ # @param [VALIGN] val Vertical align
1688
+ # @see VALIGN
1689
+ def set_print_v_align(val)
1690
+ @settings.print_v_align = val
1691
+ end
1692
+ alias :print_v_align= :set_print_v_align
1693
+
1694
+ # Get vertical align of printed drawing.
1695
+ # @return [VALIGN] Current vertical align
1696
+ # @see VALIGN
1697
+ def get_print_v_align
1698
+ @settings.print_v_align
1699
+ end
1700
+ alias :print_v_align :get_print_v_align
1701
+
1702
+ # Set printing mode for this canvas.
1703
+ # @param [PRINTMODE] mode Printing mode
1704
+ # @see PRINTMODE
1705
+ def set_print_mode(mode)
1706
+ @settings.print_mode = mode
1707
+ end
1708
+ alias :print_mode= :set_print_mode
1709
+
1710
+ # Get printing mode for this canvas.
1711
+ # @return [PRINTMODE] Current printing mode
1712
+ # #see PRINTMODE
1713
+ def get_print_mode
1714
+ @settings.print_mode
1715
+ end
1716
+ alias :print_mode :get_print_mode
1717
+
1718
+ # Set canvas scale.
1719
+ # @param [Float] scale Scale value
1720
+ def set_scale(scale)
1721
+ return unless @diagram
1722
+
1723
+ if scale != 1.0
1724
+ # query shapes
1725
+ msg = ''
1726
+ unless _query_canvas_change(CHANGE::SET_SCALE, msg)
1727
+ Wx.message_box("Cannot change scale of shape canvas: #{msg}.", 'wxRuby ShapeFramework', Wx::ICON_WARNING | Wx::OK)
1728
+ scale = 1.0
1729
+ end
1730
+ end
1731
+
1732
+ @settings.scale = scale != 0.0 ? scale : 1.0
1733
+
1734
+ # inform shapes
1735
+ _notify_canvas_change(CHANGE::RESCALED)
1736
+
1737
+ update_virtual_size
1738
+ end
1739
+ alias :scale= :set_scale
1740
+
1741
+ # Set minimal allowed scale (for mouse wheel scale change).
1742
+ # @param [Float] scale Minimal scale
1743
+ def set_min_scale(scale)
1744
+ @settings.min_scale = scale
1745
+ end
1746
+ alias :min_scale= :set_min_scale
1747
+
1748
+ # Get minimal allowed scale (for mouse wheel scale change).
1749
+ # @return [Float] Minimal scale
1750
+ def get_min_scale
1751
+ @settings.min_scale
1752
+ end
1753
+ alias :min_scale :get_min_scale
1754
+
1755
+ # Set maximal allowed scale (for mouse wheel scale change).
1756
+ # @param [Float] scale Maximal scale
1757
+ def set_max_scale(scale)
1758
+ @settings.max_scale = scale
1759
+ end
1760
+ alias :max_scale= :set_max_scale
1761
+
1762
+ # Set maximal allowed scale (for mouse wheel scale change).
1763
+ # @return [FLOAT] Maximal scale
1764
+ def get_max_scale
1765
+ @settings.max_scale
1766
+ end
1767
+ alias :max_scale :get_max_scale
1768
+
1769
+ # Get the canvas scale.
1770
+ # @return [Float] Canvas scale
1771
+ def get_scale
1772
+ @settings.scale
1773
+ end
1774
+ alias :scale :get_scale
1775
+
1776
+ # @!endgroup
1777
+
1778
+ # Set the canvas scale so a whole diagram is visible.
1779
+ def set_scale_to_view_all
1780
+ phys_rct = get_client_size
1781
+ virt_rct = get_total_bounding_box
1782
+
1783
+ hz = phys_rct.width.to_f / virt_rct.right
1784
+ vz = phys_rct.height.to_f / virt_rct.bottom
1785
+
1786
+ if hz < vz
1787
+ set_scale(hz < 1 ? hz : 1.0)
1788
+ else
1789
+ set_scale(vz < 1 ? vz : 1.0)
1790
+ end
1791
+ end
1792
+
1793
+ # Scroll the shape canvas so the given shape will be located in its center.
1794
+ # @param [Wx::SF::Shape] shape Pointer to focused shape
1795
+ def scroll_to_shape(shape)
1796
+ if shape
1797
+ ux, uy = get_scroll_pixels_per_unit
1798
+ sz_canvas = get_client_size
1799
+ pt_pos = shape.center
1800
+
1801
+ scroll(((pt_pos.x * @settings.scale) - sz_canvas.x/2)/ux, ((pt_pos.y * @settings.scale) - sz_canvas.y/2)/uy)
1802
+ end
1803
+ end
1804
+
1805
+ # Get canvas working mode.
1806
+ # @return [MODE] Working mode
1807
+ # @see MODE
1808
+ def get_mode
1809
+ @working_mode
1810
+ end
1811
+ alias :mode :get_mode
1812
+
1813
+ # Set default hover color.
1814
+ # @param [Wx::Colour] col Hover color.
1815
+ def set_hover_colour(col)
1816
+ return unless @diagram
1817
+
1818
+ @settings.common_hover_color = col
1819
+
1820
+ # update Hover color in all existing shapes
1821
+ @diagram.get_shapes.each { |shape| shape.set_hover_colour(col) }
1822
+ end
1823
+ alias :hover_colour= :set_hover_colour
1824
+
1825
+ # Get default hover colour.
1826
+ # @return [Wx::Colour] Hover colour
1827
+ def get_hover_colour
1828
+ @settings.common_hover_color
1829
+ end
1830
+ alias :hover_colour :get_hover_colour
1831
+
1832
+ # Get canvas history manager.
1833
+ # @return [Wx::SF::CanvasHistory] the canvas history manager
1834
+ # @see Wx::SF::CanvasHistory
1835
+ def get_history_manager
1836
+ @canvas_history
1837
+ end
1838
+ alias :history_manager :get_history_manager
1839
+
1840
+ # Update given position so it will fit canvas grid (if enabled).
1841
+ # @param [Wx::Point] pos Position which should be updated
1842
+ # @return [Wx::Point] Updated position
1843
+ def fit_position_to_grid(pos)
1844
+ pos = pos.to_point
1845
+ if has_style?(STYLE::GRID_USE)
1846
+ Wx::Point.new(pos.x / @settings.grid_size.x * @settings.grid_size.x,
1847
+ pos.y / @settings.grid_size.y * @settings.grid_size.y)
1848
+ else
1849
+ pos
1850
+ end
1851
+ end
1852
+
1853
+ # Update size of multi selection rectangle
1854
+ def update_multiedit_size
1855
+ # calculate bounding box
1856
+ union_rct = nil
1857
+ get_selected_shapes.each_with_index do |shape, ix|
1858
+ if ix == 0
1859
+ union_rct = shape.get_bounding_box
1860
+ else
1861
+ union_rct.union!(shape.get_bounding_box)
1862
+ end
1863
+ end
1864
+ union_rct ||= Wx::Rect.new
1865
+ union_rct.inflate!([DEFAULT_ME_OFFSET, DEFAULT_ME_OFFSET])
1866
+
1867
+ # draw rectangle
1868
+ @shp_multi_edit.set_relative_position(Wx::RealPoint.new(union_rct.x.to_f, union_rct.y.to_f))
1869
+ @shp_multi_edit.set_rect_size(Wx::RealPoint.new(union_rct.width.to_f, union_rct.height.to_f))
1870
+ end
1871
+
1872
+ # Update scroll window virtual size so it can display all shape canvas
1873
+ def update_virtual_size
1874
+ virt_rct = get_total_bounding_box
1875
+
1876
+ # allow user to modify calculated virtual canvas size
1877
+ on_update_virtual_size(virt_rct)
1878
+
1879
+ # update virtual area of the scrolled window if necessary
1880
+ if virt_rct.empty?
1881
+ set_virtual_size(500, 500)
1882
+ else
1883
+ set_virtual_size((virt_rct.right*@settings.scale).to_i, (virt_rct.bottom*@settings.scale).to_i)
1884
+ end
1885
+ _notify_canvas_change(CHANGE::VIRTUAL_SIZE)
1886
+ end
1887
+
1888
+ # Move all shapes so none of it will be located in negative position
1889
+ def move_shapes_from_negatives
1890
+ @diagram.move_shapes_from_negatives if @diagram
1891
+ end
1892
+
1893
+ # Center diagram in accordance to the shape canvas extent.
1894
+ def center_shapes
1895
+ rct_prev_bb = get_total_bounding_box
1896
+
1897
+ rct_bb = rct_prev_bb.center_in(Wx::Rect.new(Wx::Point.new(0, 0), get_size))
1898
+
1899
+ dx = (rct_bb.left - rct_prev_bb.left).to_f
1900
+ dy = (rct_bb.top - rct_prev_bb.top).to_f
1901
+
1902
+ @current_shapes.each do |shape|
1903
+ shape.move_by(dx, dy) unless shape.get_parent_shape
1904
+ end
1905
+
1906
+ move_shapes_from_negatives
1907
+ end
1908
+
1909
+ # Validate selection (remove redundantly selected shapes etc...).
1910
+ # @param [Array<Wx::SF::Shape>] selection List of selected shapes that should be validated
1911
+ def validate_selection(selection)
1912
+ return unless @diagram
1913
+
1914
+ # find child shapes that have parents in the list and deselect and remove those
1915
+ # so we only have regular toplevel shapes and orphaned child shapes
1916
+ selection.select! do |shape|
1917
+ if selection.include?(shape.get_parent_shape)
1918
+ shape.select(false)
1919
+ false
1920
+ else
1921
+ true
1922
+ end
1923
+ end
1924
+
1925
+ # move selected (toplevel) shapes to the back of the shapes list in the diagram
1926
+ # this gives a higher Z-order so they will float on top of other shapes when dragging
1927
+ selection.each do |shape|
1928
+ # in case of child shapes find the toplevel parent it belongs to and move that one
1929
+ shape = shape.get_parent_shape while shape.get_parent_shape
1930
+ @diagram.move_to_end(shape)
1931
+ end
1932
+ end
1933
+
1934
+ # Function responsible for drawing of the canvas's content to given DC. The default
1935
+ # implementation draws actual objects managed by assigned diagram manager.
1936
+ # @param [Wx::DC] dc device context where the shapes will be drawn to
1937
+ # @param [Boolean] from_paint Set the argument to true if the dc argument refers to the Wx::PaintDC instance
1938
+ # or derived classes (i.e. the function is called as a response to Wx::EVT_PAINT event)
1939
+ def draw_content(dc, from_paint)
1940
+ return unless @diagram
1941
+
1942
+ if from_paint
1943
+ # wxRect updRct
1944
+ bb_rct = Wx::Rect.new
1945
+ #
1946
+ # ShapeList m_lstToDraw
1947
+ lst_lines_to_draw = []
1948
+
1949
+ # get all existing shapes
1950
+ lst_to_draw = @diagram.get_shapes(Shape, Shape::SEARCHMODE::DFS)
1951
+
1952
+ upd_rct = nil
1953
+ # get the update rect list
1954
+ Wx::RegionIterator.for_region(get_update_region) do |region_it|
1955
+ # combine updated rectangles
1956
+ region_it.each do |rct|
1957
+ if upd_rct.nil?
1958
+ upd_rct = dp2lp(rct.inflate(5, 5))
1959
+ else
1960
+ upd_rct.union!(dp2lp(rct.inflate(5, 5)))
1961
+ end
1962
+ end
1963
+ end
1964
+ upd_rct ||= Wx::Rect.new
1965
+
1966
+ if @working_mode == MODE::SHAPEMOVE
1967
+ # draw unselected non line-based shapes first...
1968
+ lst_to_draw.each do |shape|
1969
+ parent_shape = shape.get_parent_shape
1970
+
1971
+ if !shape.is_a?(LineShape) || shape.stand_alone?
1972
+ if shape.intersects?(upd_rct)
1973
+ if parent_shape
1974
+ shape.draw(dc, WITHOUTCHILDREN) if !parent_shape.is_a?(LineShape) || parent_shape.stand_alone?
1975
+ else
1976
+ shape.draw(dc, WITHOUTCHILDREN)
1977
+ end
1978
+ end
1979
+ else
1980
+ lst_lines_to_draw << shape
1981
+ end
1982
+ end
1983
+
1984
+ # ... and draw connections
1985
+ lst_lines_to_draw.each do |line|
1986
+ line.get_complete_bounding_box(bb_rct, Shape::BBMODE::SELF | Shape::BBMODE::CHILDREN | Shape::BBMODE::SHADOW)
1987
+ line.draw(dc, line.get_line_mode == LineShape::LINEMODE::READY) if bb_rct.intersects(upd_rct)
1988
+ end
1989
+ else
1990
+ # draw parent shapes (children are processed by parent objects)
1991
+ lst_to_draw.each do |shape|
1992
+ parent_shape = shape.get_parent_shape
1993
+
1994
+ if !shape.is_a?(LineShape) || shape.stand_alone?
1995
+ if shape.intersects?(upd_rct)
1996
+ if parent_shape
1997
+ shape.draw(dc, WITHOUTCHILDREN) if !parent_shape.is_a?(LineShape) || shape.stand_alone?
1998
+ else
1999
+ shape.draw(dc, WITHOUTCHILDREN)
2000
+ end
2001
+ end
2002
+ else
2003
+ lst_lines_to_draw << shape
2004
+ end
2005
+ end
2006
+
2007
+ # draw connections
2008
+ lst_lines_to_draw.each do |line|
2009
+ line.get_complete_bounding_box(bb_rct, Shape::BBMODE::SELF | Shape::BBMODE::CHILDREN)
2010
+ line.draw(dc, line.get_line_mode == LineShape::LINEMODE::READY) if bb_rct.intersects(upd_rct)
2011
+ end
2012
+ end
2013
+
2014
+ # draw multiselection if necessary
2015
+ @shp_selection.draw(dc) if @shp_selection.visible?
2016
+ @shp_multi_edit.draw(dc) if @shp_multi_edit.visible?
2017
+ else
2018
+ # draw parent shapes (children are processed by parent objects)
2019
+ @diagram.get_top_shapes.each do |shape|
2020
+ shape.draw(dc) if !shape.is_a?(LineShape) || shape.stand_alone?
2021
+ end
2022
+
2023
+ # draw connections
2024
+ @diagram.get_top_shapes.each do |shape|
2025
+ shape.draw(dc) if shape.is_a?(LineShape) && !shape.stand_alone?
2026
+ end
2027
+ end
2028
+ end
2029
+
2030
+ # Function responsible for drawing of the canvas's background to given DC. The default
2031
+ # implementation draws canvas background and grid.
2032
+ # @param [Wx::DC] dc device context where the shapes will be drawn to
2033
+ # @param [Boolean] _from_paint Set the argument to true if the dc argument refers to the Wx::PaintDC instance
2034
+ # or derived classes (i.e. the function is called as a response to Wx::EVT_PAINT event)
2035
+ def draw_background(dc, _from_paint)
2036
+ # erase background
2037
+ if has_style?(STYLE::GRADIENT_BACKGROUND)
2038
+ bcg_size = @settings.grid_size + get_virtual_size
2039
+ if @settings.scale != 1.0
2040
+ dc.gradient_fill_linear(Wx::Rect.new([0, 0], [(bcg_size.x/@settings.scale).to_i, (bcg_size.y/@settings.scale).to_i]),
2041
+ @settings.gradient_from, @settings.gradient_to, Wx::SOUTH)
2042
+ else
2043
+ dc.gradient_fill_linear(Wx::Rect.new(Wx::Point.new(0, 0), bcg_size),
2044
+ @settings.gradient_from, @settings.gradient_to, Wx::SOUTH)
2045
+ end
2046
+ else
2047
+ dc.set_background(Wx::Brush.new(@settings.background_color))
2048
+ dc.clear
2049
+ end
2050
+
2051
+ # show grid
2052
+ if has_style?(STYLE::GRID_SHOW)
2053
+ linedist = @settings.grid_size.x * @settings.grid_line_mult
2054
+
2055
+ if (linedist * @settings.scale) > 3.0
2056
+ grid_rct = Wx::Rect.new([0, 0], @settings.grid_size + get_virtual_size)
2057
+ max_x = (grid_rct.right/@settings.scale).to_i
2058
+ max_y = (grid_rct.bottom/@settings.scale).to_i
2059
+
2060
+ dc.set_pen(Wx::Pen.new(@settings.grid_color, 1, @settings.grid_style))
2061
+ (grid_rct.left..max_x).step(linedist) do |x|
2062
+ dc.draw_line(x, 0, x, max_y)
2063
+ end
2064
+ (grid_rct.top..max_y).step(linedist) do |y|
2065
+ dc.draw_line(0, y, max_x, y)
2066
+ end
2067
+ end
2068
+ end
2069
+ end
2070
+
2071
+ # Function responsible for drawing of the canvas's foreground to given DC. The default
2072
+ # do nothing.
2073
+ # @param [Wx::DC] _dc device context where the shapes will be drawn to
2074
+ # @param [Boolean] _from_paint Set the argument to true if the dc argument refers to the Wx::PaintDC instance
2075
+ # or derived classes (i.e. the function is called as a response to Wx::EVT_PAINT event)
2076
+ def draw_foreground(_dc, _from_paint)
2077
+ # do nothing here...
2078
+ end
2079
+
2080
+ # Get reference to multiselection box
2081
+ # @return [Wx::SF::MultiSelRect] multiselection box object
2082
+ def get_multiselection_box
2083
+ @shp_multi_edit
2084
+ end
2085
+
2086
+ # @!group Public event handlers
2087
+
2088
+ # Event handler called when the canvas is clicked by
2089
+ # the left mouse button. The function can be overridden if necessary.
2090
+ #
2091
+ # The function is called by the framework and provides basic functionality
2092
+ # needed for proper management of displayed shape. It is necessary to call
2093
+ # this function from overridden methods if the default canvas behaviour
2094
+ # should be preserved.
2095
+ # @param [Wx::MouseEvent] event Mouse event
2096
+ # @see _on_left_down
2097
+ def on_left_down(event)
2098
+ # HINT: override it for custom actions...
2099
+ return unless @diagram
2100
+
2101
+ _notify_canvas_change(CHANGE::FOCUS)
2102
+ set_focus
2103
+
2104
+ lpos = dp2lp(event.get_position)
2105
+
2106
+ @can_save_state_on_mouse_up = false
2107
+
2108
+ case @working_mode
2109
+ when MODE::READY
2110
+ @selected_handle = get_topmost_handle_at_position(lpos)
2111
+
2112
+ if event.control_down && event.shift_down
2113
+ @selection_mode = SELECTIONMODE::REMOVE
2114
+ elsif event.shift_down
2115
+ @selection_mode = SELECTIONMODE::ADD
2116
+ else
2117
+ @selection_mode = SELECTIONMODE::NORMAL
2118
+ end
2119
+
2120
+ if @selected_handle.nil?
2121
+ selected_shape = get_shape_at_position(lpos)
2122
+
2123
+ selected_top_shape = selected_shape
2124
+ while selected_top_shape && selected_top_shape.has_style?(Shape::STYLE::PROPAGATE_SELECTION)
2125
+ selected_top_shape = selected_top_shape.get_parent_shape
2126
+ end
2127
+
2128
+ if selected_shape
2129
+ # perform selection
2130
+ lst_selection = get_selected_shapes
2131
+
2132
+ # cancel previous selections if necessary...
2133
+ if @selection_mode == SELECTIONMODE::NORMAL && (selected_top_shape.nil? || !lst_selection.include?(selected_top_shape))
2134
+ deselect_all
2135
+ end
2136
+ selected_top_shape.select(@selection_mode != SELECTIONMODE::REMOVE) if selected_top_shape
2137
+
2138
+ get_selected_shapes(lst_selection)
2139
+
2140
+ # remove child shapes from the selection
2141
+ validate_selection(lst_selection)
2142
+
2143
+ if lst_selection.size > 1
2144
+ hide_all_handles
2145
+ elsif @selection_mode == SELECTIONMODE::REMOVE && lst_selection.size == 1
2146
+ lst_selection.first.select(true)
2147
+ end
2148
+
2149
+ fit_pos = fit_position_to_grid(lpos)
2150
+
2151
+ # call user defined actions
2152
+ selected_shape.on_left_click(fit_pos)
2153
+
2154
+ # inform selected shapes about begin of dragging...
2155
+ lst_connections = []
2156
+
2157
+ lst_selection.each do |shape|
2158
+ shape.send(:_on_begin_drag, fit_pos)
2159
+
2160
+ # inform also connections assigned to the shape and its children
2161
+ lst_connections.clear
2162
+ append_assigned_connections(shape, lst_connections, true)
2163
+
2164
+ lst_connections.each do |line|
2165
+ line.send(:_on_begin_drag, fit_pos)
2166
+ end
2167
+ end
2168
+
2169
+ if @selection_mode == SELECTIONMODE::NORMAL
2170
+ @shp_multi_edit.show(false)
2171
+ @working_mode = MODE::SHAPEMOVE
2172
+ else
2173
+ if lst_selection.size > 1
2174
+ @shp_multi_edit.show(true)
2175
+ @shp_multi_edit.show_handles(true)
2176
+ else
2177
+ @shp_multi_edit.show(false)
2178
+ end
2179
+ @working_mode = MODE::READY
2180
+ end
2181
+ else
2182
+ if has_style?(STYLE::MULTI_SELECTION)
2183
+ deselect_all if @selection_mode == SELECTIONMODE::NORMAL
2184
+ @selection_start = Wx::RealPoint.new(lpos.x, lpos.y)
2185
+ @shp_selection.show(true)
2186
+ @shp_selection.show_handles(false)
2187
+ @shp_selection.set_relative_position(@selection_start)
2188
+ @shp_selection.set_rect_size(Wx::RealPoint.new(0, 0))
2189
+ @working_mode = MODE::MULTISELECTION
2190
+ else
2191
+ deselect_all
2192
+ @working_mode = MODE::READY
2193
+ end
2194
+ end
2195
+
2196
+ # update canvas
2197
+ invalidate_visible_rect
2198
+ else
2199
+ if @selected_handle.get_parent_shape == @shp_multi_edit
2200
+ if has_style?(STYLE::MULTI_SIZE_CHANGE)
2201
+ @working_mode = MODE::MULTIHANDLEMOVE
2202
+ else
2203
+ @working_mode = MODE::READY
2204
+ end
2205
+ else
2206
+ @working_mode = MODE::HANDLEMOVE
2207
+ case @selected_handle.get_type
2208
+ when Shape::Handle::TYPE::LINESTART
2209
+ line = @selected_handle.get_parent_shape
2210
+ line.send(:set_line_mode, LineShape::LINEMODE::SRCCHANGE)
2211
+ line.send(:set_unfinished_point, lpos)
2212
+
2213
+ when Shape::Handle::TYPE::LINEEND
2214
+ line = @selected_handle.get_parent_shape
2215
+ line.send(:set_line_mode, LineShape::LINEMODE::TRGCHANGE)
2216
+ line.send(:set_unfinished_point, lpos)
2217
+ end
2218
+ end
2219
+ @selected_handle.send(:_on_begin_drag, fit_position_to_grid(lpos))
2220
+ end
2221
+
2222
+ when MODE::CREATECONNECTION
2223
+ # update the line shape being created
2224
+ if @new_line_shape
2225
+ shape_under = get_shape_under_cursor
2226
+ # propagate request for interactive connection if requested
2227
+ while shape_under && shape_under.has_style?(Shape::STYLE::PROPAGATE_INTERACTIVE_CONNECTION)
2228
+ shape_under = shape_under.get_parent_shape
2229
+ end
2230
+ # finish connection's creation process if possible
2231
+ if shape_under && !event.control_down
2232
+ if @new_line_shape.get_trg_shape_id.nil? && (shape_under != @new_line_shape) &&
2233
+ shape_under.get_id && (shape_under.is_connection_accepted(@new_line_shape.class))
2234
+ # find out whether the target shape can be connected to the source shape
2235
+ source_shape = @diagram.find_shape(@new_line_shape.get_src_shape_id)
2236
+
2237
+ if source_shape &&
2238
+ shape_under.is_src_neighbour_accepted(source_shape.class) &&
2239
+ source_shape.is_trg_neighbour_accepted(shape_under.class)
2240
+ @new_line_shape.set_trg_shape_id(shape_under.get_id)
2241
+ @new_line_shape.set_ending_connection_point(shape_under.get_nearest_connection_point(lpos.to_real))
2242
+
2243
+ # inform user that the line is completed
2244
+ case on_pre_connection_finished(@new_line_shape)
2245
+ when PRECON_FINISH_STATE::OK
2246
+ when PRECON_FINISH_STATE::FAILED_AND_CANCEL_LINE
2247
+ @new_line_shape.set_trg_shape_id(nil)
2248
+ @diagram.remove_shape(@new_line_shape)
2249
+ @working_mode = MODE::READY
2250
+ @new_line_shape = nil
2251
+ return
2252
+ when PRECON_FINISH_STATE::FAILED_AND_CONTINUE_EDIT
2253
+ @new_line_shape.set_trg_shape_id(nil)
2254
+ return
2255
+ end
2256
+ @new_line_shape.create_handles
2257
+
2258
+ # switch off the "under-construction" mode
2259
+ @new_line_shape.send(:set_line_mode, LineShape::LINEMODE::READY)
2260
+
2261
+ on_connection_finished(@new_line_shape)
2262
+
2263
+ @new_line_shape.update
2264
+ @new_line_shape.refresh(DELAYED)
2265
+
2266
+ @working_mode = MODE::READY
2267
+ @new_line_shape = nil
2268
+
2269
+ save_canvas_state
2270
+ end
2271
+ end
2272
+ else
2273
+ if @new_line_shape.get_src_shape_id
2274
+ fit_pos = fit_position_to_grid(lpos)
2275
+ @new_line_shape.get_control_points << Wx::RealPoint.new(fit_pos.x, fit_pos.y)
2276
+ end
2277
+ end
2278
+ end
2279
+
2280
+ else
2281
+ @working_mode = MODE::READY
2282
+ end
2283
+
2284
+ refresh_invalidated_rect
2285
+ end
2286
+
2287
+ # Event handler called when the canvas is double-clicked by
2288
+ # the left mouse button. The function can be overridden if necessary.
2289
+ #
2290
+ # The function is called by the framework and provides basic functionality
2291
+ # needed for proper management of displayed shape. It is necessary to call
2292
+ # this function from overridden methods if the default canvas behaviour
2293
+ # should be preserved.
2294
+ # @param [Wx::MouseEvent] event Mouse event
2295
+ # @see _on_left_double_click
2296
+ def on_left_double_click(event)
2297
+ # HINT: override it for custom actions...
2298
+
2299
+ _notify_canvas_change(CHANGE::FOCUS)
2300
+ set_focus
2301
+
2302
+ lpos = dp2lp(event.get_position)
2303
+
2304
+ if @working_mode == MODE::READY
2305
+ shape = get_shape_under_cursor
2306
+ if shape
2307
+ shape.on_left_double_click(lpos)
2308
+
2309
+ # double click onto a line shape always change its set of
2310
+ # control points so the canvas state should be saved now...
2311
+ save_canvas_state if shape.is_a?(LineShape)
2312
+ end
2313
+ end
2314
+
2315
+ refresh_invalidated_rect
2316
+ end
2317
+
2318
+ # Event handler called when the left mouse button is released.
2319
+ # The function can be overridden if necessary.
2320
+ #
2321
+ # The function is called by the framework and provides basic functionality
2322
+ # needed for proper management of displayed shape. It is necessary to call
2323
+ # this function from overridden methods if the default canvas behaviour
2324
+ # should be preserved.
2325
+ # @param [Wx::MouseEvent] event Mouse event
2326
+ # @see _on_left_up
2327
+ def on_left_up(event)
2328
+ # HINT: override it for custom actions...
2329
+ lpos = dp2lp(event.get_position)
2330
+
2331
+ case @working_mode
2332
+ when MODE::MULTIHANDLEMOVE, MODE::HANDLEMOVE
2333
+ # resize parent shape to fit all its children if necessary
2334
+ if @selected_handle.get_parent_shape.get_parent_shape
2335
+ @selected_handle.get_parent_shape.get_parent_shape.update
2336
+ end
2337
+
2338
+ # if the handle is line handle then return the line to normal state
2339
+ # and re-assign line's source/target shape
2340
+ case @selected_handle.get_type
2341
+ when Shape::Handle::TYPE::LINESTART, Shape::Handle::TYPE::LINEEND
2342
+ line = @selected_handle.get_parent_shape
2343
+ line.send(:set_line_mode, LineShape::LINEMODE::READY)
2344
+
2345
+ parent_shape = get_shape_under_cursor
2346
+
2347
+ if parent_shape && (parent_shape != line) && (parent_shape.is_connection_accepted(line.class))
2348
+ if @selected_handle.get_type == Shape::Handle::TYPE::LINESTART
2349
+ trg_shape = @diagram.find_shape(line.get_trg_shape_id)
2350
+ if trg_shape && parent_shape.is_trg_neighbour_accepted(trg_shape.class)
2351
+ line.set_src_shape_id(parent_shape.get_id)
2352
+ end
2353
+ else
2354
+ src_shape = @diagram.find_shape(line.get_src_shape_id)
2355
+ if src_shape && parent_shape.is_src_neighbour_accepted(src_shape.class)
2356
+ line.set_trg_shape_id(parent_shape.get_id)
2357
+ end
2358
+ end
2359
+ end
2360
+ end
2361
+
2362
+ @selected_handle.send(:_on_end_drag, lpos)
2363
+
2364
+ @selected_handle = nil
2365
+ save_canvas_state if @can_save_state_on_mouse_up
2366
+
2367
+ when MODE::SHAPEMOVE
2368
+ lst_selection = get_selected_shapes
2369
+
2370
+ lst_selection.each do |shape|
2371
+ # notify shape
2372
+ shape.send(:_on_end_drag, lpos)
2373
+ # reparent based on new position
2374
+ reparent_shape(shape, lpos)
2375
+ end
2376
+
2377
+ if lst_selection.size>1
2378
+ @shp_multi_edit.show(true)
2379
+ @shp_multi_edit.show_handles(true)
2380
+ else
2381
+ @shp_multi_edit.show(false)
2382
+ end
2383
+
2384
+ move_shapes_from_negatives
2385
+
2386
+ save_canvas_state if @can_save_state_on_mouse_up
2387
+
2388
+ when MODE::MULTISELECTION
2389
+ lst_selection = get_selected_shapes
2390
+
2391
+ sel_rect = @shp_selection.get_bounding_box
2392
+ @current_shapes.each do |shape|
2393
+ if shape.active? && sel_rect.contains?(shape.get_bounding_box)
2394
+ shape = shape.get_parent_shape while shape && shape.has_style?(Shape::STYLE::PROPAGATE_SELECTION)
2395
+ if shape
2396
+ shape.select(@selection_mode != SELECTIONMODE::REMOVE)
2397
+ shape_pos = lst_selection.index(shape)
2398
+ if @selection_mode != SELECTIONMODE::REMOVE && shape_pos.nil?
2399
+ lst_selection << shape
2400
+ elsif @selection_mode == SELECTIONMODE::REMOVE && shape_pos
2401
+ lst_selection.delete_at(shape_pos)
2402
+ end
2403
+ end
2404
+ end
2405
+ end
2406
+
2407
+ validate_selection(lst_selection)
2408
+
2409
+ if lst_selection.empty?
2410
+ @shp_multi_edit.show(false)
2411
+ else
2412
+ hide_all_handles
2413
+ @shp_multi_edit.show(true)
2414
+ @shp_multi_edit.show_handles(true)
2415
+ end
2416
+
2417
+ @shp_selection.show(false)
2418
+ end
2419
+
2420
+ if @working_mode != MODE::CREATECONNECTION
2421
+ # update canvas
2422
+ @working_mode = MODE::READY
2423
+ update_multiedit_size
2424
+ update_virtual_size
2425
+ refresh(false)
2426
+ else
2427
+ refresh_invalidated_rect
2428
+ end
2429
+ end
2430
+
2431
+ # Event handler called when the canvas is clicked by
2432
+ # the right mouse button. The function can be overridden if necessary.
2433
+ #
2434
+ # The function is called by the framework and provides basic functionality
2435
+ # needed for proper management of displayed shape. It is necessary to call
2436
+ # this function from overridden methods if the default canvas behaviour
2437
+ # should be preserved.
2438
+ # @param [Wx::MouseEvent] event Mouse event
2439
+ # @see _on_right_down
2440
+ def on_right_down(event)
2441
+ # HINT: override it for custom actions...
2442
+
2443
+ _notify_canvas_change(CHANGE::FOCUS)
2444
+ set_focus
2445
+
2446
+ lpos = dp2lp(event.get_position)
2447
+
2448
+ if @working_mode == MODE::READY
2449
+ deselect_all
2450
+
2451
+ shape = get_shape_under_cursor
2452
+ if shape
2453
+ shape.select(true)
2454
+ shape.on_right_click(lpos)
2455
+ end
2456
+ end
2457
+
2458
+ refresh(false)
2459
+ end
2460
+
2461
+ # Event handler called when the canvas is double-clicked by
2462
+ # the right mouse button. The function can be overridden if necessary.
2463
+ #
2464
+ # The function is called by the framework and provides basic functionality
2465
+ # needed for proper management of displayed shape. It is necessary to call
2466
+ # this function from overridden methods if the default canvas behaviour
2467
+ # should be preserved.
2468
+ # @param [Wx::MouseEvent] event Mouse event
2469
+ # @see _on_right_double_click
2470
+ def on_right_double_click(event)
2471
+ # HINT: override it for custom actions...
2472
+
2473
+ _notify_canvas_change(CHANGE::FOCUS)
2474
+ set_focus
2475
+
2476
+ lpos = dp2lp(event.get_position)
2477
+
2478
+ if @working_mode == MODE::READY
2479
+ shape = get_shape_under_cursor
2480
+ shape.on_right_double_click(lpos) if shape
2481
+ end
2482
+
2483
+ refresh_invalidated_rect
2484
+ end
2485
+
2486
+ # Event handler called when the right mouse button is released.
2487
+ # The function can be overridden if necessary.
2488
+ #
2489
+ # The function is called by the framework and provides basic functionality
2490
+ # needed for proper management of displayed shape. It is necessary to call
2491
+ # this function from overridden methods if the default canvas behaviour
2492
+ # should be preserved.
2493
+ # @param [Wx::MouseEvent] _event Mouse event
2494
+ # @see _on_right_up
2495
+ def on_right_up(_event)
2496
+ # HINT: override it for custom actions...
2497
+ end
2498
+
2499
+ # Event handler called when the mouse pointer is moved.
2500
+ # The function can be overridden if necessary.
2501
+ #
2502
+ # The function is called by the framework and provides basic functionality
2503
+ # needed for proper management of displayed shape. It is necessary to call
2504
+ # this function from overridden methods if the default canvas behaviour
2505
+ # should be preserved.
2506
+ # @param [Wx::MouseEvent] event Mouse event
2507
+ # @see _on_mouse_move
2508
+ def on_mouse_move(event)
2509
+ # HINT: override it for custom actions...
2510
+ return unless @diagram
2511
+
2512
+ lpos = dp2lp(event.get_position)
2513
+
2514
+ case @working_mode
2515
+ when MODE::READY, MODE::CREATECONNECTION
2516
+ unless event.dragging
2517
+ # send event to multiedit shape
2518
+ @shp_multi_edit.send(:_on_mouse_move, lpos) if @shp_multi_edit.visible?
2519
+
2520
+ # send event to all user shapes
2521
+ @current_shapes.each { |shape| shape.send(:_on_mouse_move, lpos) }
2522
+
2523
+ # update unfinished line if any
2524
+ if @new_line_shape
2525
+ line_rct = Wx::Rect.new
2526
+ upd_line_rct = Wx::Rect.new
2527
+ @new_line_shape.get_complete_bounding_box(line_rct, Shape::BBMODE::SELF | Shape::BBMODE::CHILDREN)
2528
+
2529
+ @new_line_shape.send(:set_unfinished_point, fit_position_to_grid(lpos))
2530
+ @new_line_shape.update
2531
+
2532
+ @new_line_shape.get_complete_bounding_box(upd_line_rct, Shape::BBMODE::SELF | Shape::BBMODE::CHILDREN)
2533
+
2534
+ line_rct.union!(upd_line_rct)
2535
+
2536
+ invalidate_rect(line_rct)
2537
+ end
2538
+ end
2539
+
2540
+ when MODE::HANDLEMOVE, MODE::MULTIHANDLEMOVE, MODE::SHAPEMOVE
2541
+ if @working_mode != MODE::SHAPEMOVE
2542
+ if event.dragging
2543
+ @selected_handle.send(:_on_dragging, fit_position_to_grid(lpos)) if @selected_handle
2544
+ update_multiedit_size if @working_mode == MODE::MULTIHANDLEMOVE
2545
+ @can_save_state_on_mouse_up = true
2546
+ else
2547
+ @selected_handle.send(:_on_end_drag, lpos) if @selected_handle
2548
+ @selected_handle = nil
2549
+ @working_mode = MODE::READY
2550
+ end
2551
+ end
2552
+ unless @working_mode == MODE::MULTIHANDLEMOVE
2553
+ if event.dragging
2554
+ if has_style?(STYLE::GRID_USE)
2555
+ return if (event.get_position.x - @prev_mouse_pos.x).abs < @settings.grid_size.x &&
2556
+ (event.get_position.y - @prev_mouse_pos.y).abs < @settings.grid_size.y
2557
+ end
2558
+ @prev_mouse_pos = event.get_position
2559
+
2560
+ if event.control_down || event.shift_down
2561
+ lst_selection = get_selected_shapes
2562
+ deselect_all
2563
+ if Wx.has_feature?(:USE_DRAG_AND_DROP)
2564
+ do_drag_drop(lst_selection, lpos)
2565
+ end
2566
+ else
2567
+ lst_connections = []
2568
+ @current_shapes.each do |shape|
2569
+ if shape.selected? && @working_mode == MODE::SHAPEMOVE
2570
+ shape.send(:_on_dragging, fit_position_to_grid(lpos))
2571
+
2572
+ # move also connections assigned to this shape and its children
2573
+ lst_connections.clear
2574
+
2575
+ append_assigned_connections(shape, lst_connections,true)
2576
+
2577
+ lst_connections.each { |line| line.send(:_on_dragging, fit_position_to_grid(lpos)) }
2578
+
2579
+ # update connections assigned to this shape
2580
+ lst_connections = @diagram.get_assigned_connections(shape, LineShape, Shape::CONNECTMODE::BOTH)
2581
+ lst_connections.each { |line| line.update }
2582
+ else
2583
+ shape.send(:_on_mouse_move, lpos)
2584
+ end
2585
+ end
2586
+
2587
+ @can_save_state_on_mouse_up = true
2588
+ end
2589
+ else
2590
+ @working_mode = MODE::READY
2591
+ end
2592
+ end
2593
+
2594
+ when MODE::MULTISELECTION
2595
+ selection_pos = Wx::RealPoint.new(*@selection_start.to_ary)
2596
+ selection_size = Wx::RealPoint.new(lpos.x - @selection_start.x, lpos.y - @selection_start.y)
2597
+ if selection_size.x < 0
2598
+ selection_pos.x += selection_size.x
2599
+ selection_size.x = -selection_size.x
2600
+ end
2601
+ if selection_size.y < 0
2602
+ selection_pos.y += selection_size.y
2603
+ selection_size.y = -selection_size.y
2604
+ end
2605
+ @shp_selection.set_relative_position(selection_pos)
2606
+ @shp_selection.set_rect_size(selection_size)
2607
+
2608
+ invalidate_visible_rect
2609
+ end
2610
+
2611
+ refresh_invalidated_rect
2612
+ end
2613
+
2614
+ # Event handler called when the mouse wheel position is changed.
2615
+ # The function can be overridden if necessary.
2616
+ #
2617
+ # The function is called by the framework and provides basic functionality
2618
+ # needed for proper management of displayed shape. It is necessary to call
2619
+ # this function from overridden methods if the default canvas behaviour
2620
+ # should be preserved.
2621
+ # @param [Wx::MouseEvent] event Mouse event
2622
+ def on_mouse_wheel(event)
2623
+ # HINT: override it for custom actions...
2624
+
2625
+ if event.control_down
2626
+ scale = get_scale
2627
+ scale += (event.get_wheel_rotation/(event.get_wheel_delta*10)).to_f
2628
+
2629
+ scale = @settings.min_scale if scale < @settings.min_scale
2630
+ scale = @settings.max_scale if scale > @settings.max_scale
2631
+
2632
+ set_scale(scale)
2633
+ refresh(false)
2634
+ end
2635
+
2636
+ event.skip
2637
+ end
2638
+
2639
+ # Event handler called when any key is pressed.
2640
+ # The function can be overridden if necessary.
2641
+ #
2642
+ # The function is called by the framework and provides basic functionality
2643
+ # needed for proper management of displayed shape. It is necessary to call
2644
+ # this function from overridden methods if the default canvas behaviour
2645
+ # should be preserved.
2646
+ # @param [Wx::KeyEvent] event Keyboard event
2647
+ # @see _on_key_down
2648
+ def on_key_down(event)
2649
+ # HINT: override it for custom actions...
2650
+ return unless @diagram
2651
+
2652
+ lst_selection = get_selected_shapes
2653
+
2654
+ case event.get_key_code
2655
+ when Wx::K_DELETE
2656
+ # send event to selected shapes
2657
+ lst_selection.delete_if do |shape|
2658
+ if shape.has_style?(Shape::STYLE::PROCESS_DEL)
2659
+ shape.send(:_on_key, event.get_key_code)
2660
+ true
2661
+ else
2662
+ false
2663
+ end
2664
+ end
2665
+
2666
+ clear_temporaries
2667
+
2668
+ # delete selected shapes
2669
+ @diagram.remove_shapes(lst_selection)
2670
+ @shp_multi_edit.show(false)
2671
+ save_canvas_state
2672
+ refresh(false)
2673
+
2674
+ when Wx::K_ESCAPE
2675
+ case @working_mode
2676
+ when MODE::CREATECONNECTION
2677
+ abort_interactive_connection
2678
+
2679
+ when MODE::HANDLEMOVE
2680
+ if @selected_handle && @selected_handle.get_parent_shape.is_a?(LineShape)
2681
+ @selected_handle.send(:_on_end_drag, Wx::Point.new(0, 0))
2682
+
2683
+ line = @selected_handle.get_parent_shape
2684
+ line.send(:set_line_mode, LineShape::LINEMODE::READY)
2685
+ @selected_handle = nil
2686
+ end
2687
+
2688
+ else
2689
+ # send event to selected shapes
2690
+ lst_selection.each { |shape| shape.send(:_on_key, event.get_key_code) }
2691
+ end
2692
+ @working_mode = MODE::READY
2693
+ refresh(false)
2694
+
2695
+ when Wx::K_LEFT, Wx::K_RIGHT, Wx::K_UP, Wx::K_DOWN
2696
+ lst_connections = []
2697
+ lst_selection.each do |shape|
2698
+ shape.send(:_on_key, event.get_key_code)
2699
+
2700
+ # inform also connections assigned to this shape
2701
+ lst_connections.clear
2702
+ append_assigned_connections(shape, lst_connections, true)
2703
+
2704
+ lst_connections.each do |line|
2705
+ line.send(:_on_key, event.get_key_code) unless line.selected?
2706
+ end
2707
+ end
2708
+
2709
+ # send the event to multiedit ctrl if displayed
2710
+ @shp_multi_edit.send(:_on_key, event.get_key_code) if @shp_multi_edit.visible?
2711
+
2712
+ refresh_invalidated_rect
2713
+ save_canvas_state
2714
+
2715
+ else
2716
+ lst_selection.each { |shape| shape.send(:_on_key, event.get_key_code) }
2717
+ update_multiedit_size if @shp_multi_edit.visible?
2718
+ end
2719
+ end
2720
+
2721
+ # Event handler called when any editable text shape is changed.
2722
+ # The function can be overridden if necessary.
2723
+ # The function is called by the framework and its default implementation
2724
+ # generates Wx::SF::EVT_SF_TEXT_CHANGE event.
2725
+ # @param [Wx::SF::EditTextShape] shape Changed Wx::SF::EditTextShape object
2726
+ # @see Wx::SF::EditTextShape#edit_label
2727
+ # @see Wx::SF::ShapeTextEvent
2728
+ def on_text_change(shape)
2729
+ # HINT: override it for custom actions...
2730
+
2731
+ # ... standard implementation generates the Wx::SF::EVT_SF_TEXT_CHANGE event.
2732
+ id = shape ? shape.get_id : nil
2733
+
2734
+ event = ShapeTextEvent.new(Wx::SF::EVT_SF_TEXT_CHANGE, id)
2735
+ event.set_shape(shape)
2736
+ event.set_text(shape.get_text)
2737
+ process_event(event)
2738
+ end
2739
+
2740
+ # Event handler called after (successful or cancelled) connection creation. The function
2741
+ # can be overridden if necessary. The default implementation
2742
+ # generates Wx::SF::EVT_SF_LINE_DONE event.
2743
+ # @param [Wx::SF::LineShape,nil] connection new connection object (nil if cancelled)
2744
+ # @see start_interactive_connection
2745
+ # @see Wx::SF::ShapeEvent
2746
+ def on_connection_finished(connection)
2747
+ # HINT: override to perform user-defined actions...
2748
+
2749
+ # ... standard implementation generates the Wx::SF::EVT_SF_LINE_DONE event.
2750
+ id = connection ? connection.get_id : -1
2751
+
2752
+ event = ShapeEvent.new(Wx::SF::EVT_SF_LINE_DONE, id)
2753
+ event.set_shape(connection)
2754
+ process_event(event)
2755
+ end
2756
+
2757
+ # Event handler called after successful connection creation in
2758
+ # order to allow developer to perform some kind of checks
2759
+ # before the connection is really added to the diagram. The function
2760
+ # can be overridden if necessary. The default implementation
2761
+ # generates Wx::SF::EVT_SF_LINE_DONE event.
2762
+ # @param [Wx::SF::LineShape] connection new connection object
2763
+ # @return [PRECON_FINISH_STATE] PRECONNECTIONFINISHEDSTATE::OK if the connection is accepted, otherwise
2764
+ # if the generated event has been vetoed the connection creation is cancelled
2765
+ # @see start_interactive_connection
2766
+ # @see Wx::SF::ShapeEvent
2767
+ def on_pre_connection_finished(connection)
2768
+ # HINT: override to perform user-defined actions...
2769
+
2770
+ # ... standard implementation generates the Wx::SF::EVT_SF_LINE_DONE event.
2771
+ id = connection ? connection.get_id : -1
2772
+
2773
+ event = ShapeEvent.new(Wx::SF::EVT_SF_LINE_BEFORE_DONE, id)
2774
+ event.set_shape(connection)
2775
+ process_event(event)
2776
+
2777
+ return PRECON_FINISH_STATE::FAILED_AND_CANCEL_LINE if event.vetoed?
2778
+
2779
+ PRECON_FINISH_STATE::OK
2780
+ end
2781
+
2782
+ if Wx.has_feature?(:USE_DRAG_AND_DROP)
2783
+
2784
+ # Event handler called by the framework after any dragged shapes
2785
+ # are dropped to the canvas. The default implementation
2786
+ # generates Wx::SF::EVT_SF_ON_DROP event.
2787
+ # @param [Integer] x X-coordinate of a position the data was dropped to
2788
+ # @param [Integer] y Y-coordinate of a position the data was dropped to
2789
+ # @param [Wx::DragResult] deflt Drag result
2790
+ # @param [Array<Wx::SF::Shape>] dropped a list containing the dropped data
2791
+ # @see Wx::SF::CanvasDropTarget
2792
+ # @see Wx::SF::ShapeDropEvent
2793
+ def on_drop(x, y, deflt, dropped)
2794
+ # HINT: override it for custom actions...
2795
+
2796
+ # ... standard implementation generates the Wx::SF::EVT_SF_ON_DROP event.
2797
+ return unless has_style?(STYLE::DND)
2798
+
2799
+ # create the drop event and process it
2800
+ event = ShapeDropEvent.new(Wx::SF::EVT_SF_ON_DROP, x, y, self, deflt, Wx::ID_ANY)
2801
+ event.set_dropped_shapes(dropped)
2802
+ process_event(event)
2803
+ end
2804
+
2805
+ end
2806
+
2807
+ # Event handler called by the framework after pasting of shapes
2808
+ # from the clipboard to the canvas. The default implementation
2809
+ # generates Wx::SF::EVT_SF_ON_PASTE event.
2810
+ # @param [Array<Wx::SF::Shape>] pasted a list containing the pasted data
2811
+ # @see Wx::SF::ShapeCanvas#paste
2812
+ # @see Wx::SF::ShapePasteEvent
2813
+ def on_paste(pasted)
2814
+ # HINT: override it for custom actions...
2815
+
2816
+ # ... standard implementation generates the Wx::SF::EVT_SF_ON_PASTE event.
2817
+ return unless has_style?(STYLE::CLIPBOARD)
2818
+
2819
+ # create the drop event and process it
2820
+ event = ShapePasteEvent.new(Wx::SF::EVT_SF_ON_PASTE, self, Wx::ID_ANY)
2821
+ event.set_pasted_shapes(pasted)
2822
+ process_event(event)
2823
+ end
2824
+
2825
+ # Event handler called if canvas virtual size is going to be updated.
2826
+ # The default implementation does nothing but the function can be overridden by
2827
+ # a user to modify calculated virtual canvas size.
2828
+ # @param [Wx::Rect] _virtrct Calculated canvas virtual size
2829
+ def on_update_virtual_size(_virtrct)
2830
+ # HINT: override it for custom actions...
2831
+ end
2832
+
2833
+ # @!endgroup
2834
+
2835
+ def inspect
2836
+ "#<Wx::SF::ShapeCanvas:#{object_id}>"
2837
+ end
2838
+
2839
+ private
2840
+
2841
+ # Validate selection so the shapes in the given list can be processed by the clipboard functions
2842
+ # @param [Array<Wx::SF::Shape>] selection
2843
+ # @param [Boolean] storeprevpos
2844
+ def validate_selection_for_clipboard(selection, storeprevpos)
2845
+ selection.dup.each do |shape|
2846
+ if shape.get_parent_shape
2847
+ # remove child shapes without parent in the selection and without STYLE::PARENT_CHANGE style
2848
+ # defined from the selection
2849
+ if !shape.has_style?(Shape::STYLE::PARENT_CHANGE) && !selection.include?(shape.get_parent_shape)
2850
+ selection.delete(shape)
2851
+ else
2852
+ # convert relative position to absolute position if the shape is copied
2853
+ # without its parent
2854
+ unless selection.include?(shape.get_parent_shape)
2855
+ store_prev_position(shape) if storeprevpos
2856
+ shape.set_relative_position(shape.get_absolute_position)
2857
+ end
2858
+ end
2859
+ end
2860
+
2861
+ append_assigned_connections(shape, selection, false)
2862
+ end
2863
+ end
2864
+
2865
+ # Append connections assigned to shapes in given list to this list as well
2866
+ # @param [Wx::SF::Shape] shape
2867
+ # @param [Array<Wx::SF::Shape>] selection
2868
+ # @param [Boolean] childrenonly
2869
+ def append_assigned_connections(shape, selection, childrenonly)
2870
+ # add connections assigned to copied topmost shapes and their children to the copy list
2871
+ lst_children = shape.get_child_shapes(ANY, RECURSIVE)
2872
+
2873
+ # get connections assigned to the parent shape
2874
+ lst_connections = @diagram.get_assigned_connections(shape, LineShape, Shape::CONNECTMODE::BOTH) unless childrenonly
2875
+ lst_connections ||= []
2876
+ # get connections assigned to its child shape
2877
+ lst_children.each do |child|
2878
+ # get connections assigned to the child shape
2879
+ @diagram.get_assigned_connections(child, LineShape, Shape::CONNECTMODE::BOTH, lst_connections)
2880
+ end
2881
+
2882
+ # insert connections to the copy list
2883
+ lst_connections.each do |line|
2884
+ selection << line unless selection.include?(line)
2885
+ end
2886
+ end
2887
+
2888
+ # Remove given shape for temporary containers
2889
+ # @param [Wx::SF::Shape] shape
2890
+ def remove_from_temporaries(shape)
2891
+ if shape
2892
+ @current_shapes.delete(shape)
2893
+ @new_line_shape = nil if @new_line_shape == shape
2894
+ @unselected_shape_under_cursor = nil if @unselected_shape_under_cursor == shape
2895
+ @selected_shape_under_cursor = nil if @selected_shape_under_cursor == shape
2896
+ @topmost_shape_under_cursor = nil if @topmost_shape_under_cursor == shape
2897
+ end
2898
+ end
2899
+
2900
+ # Clear all temporary containers
2901
+ def clear_temporaries
2902
+ @current_shapes.clear
2903
+ @new_line_shape = nil
2904
+ @unselected_shape_under_cursor = nil
2905
+ @selected_shape_under_cursor = nil
2906
+ @topmost_shape_under_cursor = nil
2907
+ end
2908
+
2909
+ # Assign give shape to parent at given location (if exists)
2910
+ # @param [Wx::SF::Shape] shape
2911
+ # @param [Wx::Point] parentpos
2912
+ def reparent_shape(shape, parentpos)
2913
+ return unless @diagram
2914
+ # is shape dropped into accepting shape?
2915
+ parent_shape = get_shape_at_position(parentpos, 1, SEARCHMODE::UNSELECTED)
2916
+
2917
+ parent_shape = nil if parent_shape && !parent_shape.is_child_accepted(shape.class)
2918
+
2919
+ # set new parent
2920
+ if shape.has_style?(Shape::STYLE::PARENT_CHANGE) && !shape.is_a?(LineShape)
2921
+ prev_parent = shape.get_parent_shape
2922
+
2923
+ if parent_shape
2924
+ if parent_shape.get_parent_shape != shape
2925
+ # update relative position to new parent
2926
+ apos = shape.get_absolute_position - parent_shape.get_absolute_position
2927
+ shape.set_relative_position(apos)
2928
+ # reparent
2929
+ @diagram.reparent_shape(shape, parent_shape)
2930
+ # notify the parent shape about dropped child
2931
+ parent_shape.on_child_dropped(apos, shape)
2932
+ end
2933
+ else
2934
+ if @diagram.is_top_shape_accepted(shape.class)
2935
+ # move relative to previous parent
2936
+ shape.move_by(prev_parent.get_absolute_position) if prev_parent
2937
+ # reparent
2938
+ @diagram.reparent_shape(shape, parent_shape)
2939
+ end
2940
+ end
2941
+
2942
+ prev_parent.update if prev_parent
2943
+ parent_shape.update if parent_shape
2944
+ end
2945
+ end
2946
+
2947
+ # Store previous shape's position modified in validate_selection_for_clipboard() function
2948
+ # @param [Wx::SF::Shape] shape
2949
+ def store_prev_position(shape)
2950
+ @prev_positions[shape] = shape.get_relative_position.dup
2951
+ end
2952
+
2953
+ # Restore previously stored shape positions and clear the storage
2954
+ def restore_prev_positions
2955
+ @prev_positions.each_pair { |shape, pos| shape.set_relative_position(pos) }
2956
+ @prev_positions.clear
2957
+ end
2958
+
2959
+ # private event handlers
2960
+
2961
+ # Event handler called when the canvas should be repainted.
2962
+ # @param [Wx::PaintEvent] _event Paint event
2963
+ def _on_paint(_event)
2964
+ paint_buffered do |paint_dc|
2965
+ if ShapeCanvas.gc_enabled?
2966
+ Wx::GCDC.draw_on(paint_dc) do |gdc|
2967
+ prepare_dc(gdc)
2968
+
2969
+ # scale GC
2970
+ gc = gdc.get_graphics_context
2971
+ gc.scale(@settings.scale, @settings.scale)
2972
+
2973
+ draw_background(gdc, FROM_PAINT)
2974
+ draw_content(gdc, FROM_PAINT)
2975
+ draw_foreground(gdc, FROM_PAINT)
2976
+ end
2977
+ else
2978
+ Wx::ScaledDC.draw_on(paint_dc, @settings.scale) do |dc|
2979
+ prepare_dc(dc)
2980
+ draw_background(dc, FROM_PAINT)
2981
+ draw_content(dc, FROM_PAINT)
2982
+ draw_foreground(dc, FROM_PAINT)
2983
+ end
2984
+ end
2985
+ end
2986
+ end
2987
+
2988
+ # Event handler called when the canvas should be erased.
2989
+ # @param [Wx::EraseEvent] _event Erase event
2990
+ def _on_erase_background(_event)
2991
+ # do nothing to suppress window flickering
2992
+ end
2993
+
2994
+ # Event handler called when the mouse pointer leaves the canvas window.
2995
+ # @param [Wx::MouseEvent] event Mouse event
2996
+ def _on_leave_window(event)
2997
+ case @working_mode
2998
+ when MODE::MULTISELECTION
2999
+ when MODE::SHAPEMOVE
3000
+ when MODE::CREATECONNECTION
3001
+ when MODE::HANDLEMOVE
3002
+ when MODE::MULTIHANDLEMOVE
3003
+ else
3004
+ @working_mode = MODE::READY
3005
+ end
3006
+
3007
+ event.skip
3008
+ end
3009
+
3010
+ # Event handler called when the mouse pointer enters the canvas window.
3011
+ # @param [Wx::MouseEvent] event Mouse event
3012
+ def _on_enter_window(event)
3013
+ @prev_mouse_pos = event.get_position
3014
+
3015
+ lpos = dp2lp(event.get_position)
3016
+
3017
+ case @working_mode
3018
+ when MODE::MULTISELECTION
3019
+ unless event.left_is_down
3020
+ update_multiedit_size
3021
+ @shp_multi_edit.show(false)
3022
+ @working_mode = MODE::READY
3023
+
3024
+ invalidate_visible_rect
3025
+ end
3026
+
3027
+ when MODE::HANDLEMOVE
3028
+ unless event.left_is_down
3029
+ if @selected_handle
3030
+ if @selected_handle.get_parent_shape.is_a?(LineShape)
3031
+ @selected_handle.get_parent_shape.send(:set_line_mode, LineShape::LINEMODE::READY)
3032
+ end
3033
+
3034
+ @selected_handle.send(:_on_end_drag, lpos)
3035
+
3036
+ save_canvas_state
3037
+ @working_mode = MODE::READY
3038
+ @selected_handle = nil
3039
+
3040
+ invalidate_visible_rect
3041
+ end
3042
+ end
3043
+
3044
+ when MODE::MULTIHANDLEMOVE
3045
+ unless event.left_is_down
3046
+ if @selected_handle
3047
+ @selected_handle.send(:_on_end_drag, lpos)
3048
+
3049
+ save_canvas_state
3050
+ @working_mode = MODE::READY
3051
+
3052
+ invalidate_visible_rect
3053
+ end
3054
+ end
3055
+
3056
+ when MODE::SHAPEMOVE
3057
+ unless event.left_is_down
3058
+ lst_selection = get_selected_shapes
3059
+
3060
+ move_shapes_from_negatives
3061
+ update_virtual_size
3062
+
3063
+ if lst_selection.size > 1
3064
+ update_multiedit_size
3065
+ @shp_multi_edit.show(true)
3066
+ @shp_multi_edit.show_handles(true)
3067
+ end
3068
+
3069
+ lst_selection.each { |shape| shape.send(:_on_end_drag, lpos) }
3070
+
3071
+ @working_mode = MODE::READY
3072
+
3073
+ invalidate_visible_rect
3074
+ end
3075
+ end
3076
+
3077
+ refresh_invalidated_rect
3078
+
3079
+ event.skip
3080
+ end
3081
+
3082
+ # Event handler called when the canvas size has changed.
3083
+ # @param [Wx::SizeEvent] event Size event
3084
+ def _on_resize(event)
3085
+ refresh(false) if has_style?(STYLE::GRADIENT_BACKGROUND)
3086
+
3087
+ event.skip
3088
+ end
3089
+
3090
+ # original private event handlers
3091
+
3092
+ # Original private event handler called when the canvas is clicked by
3093
+ # left mouse button. The handler calls user-overridable event handler function
3094
+ # and skips the event for next possible processing.
3095
+ # @param [Wx::MouseEvent] event Mouse event
3096
+ # @see Wx::SF::ShapeCanvas#on_left_down
3097
+ def _on_left_down(event)
3098
+ on_left_down(event)
3099
+
3100
+ event.skip
3101
+ end
3102
+
3103
+ # Original private event handler called when the canvas is double-clicked by
3104
+ # left mouse button. The handler calls user-overridable event handler function
3105
+ # and skips the event for next possible processing.
3106
+ # @param [Wx::MouseEvent] event Mouse event
3107
+ # @see Wx::SF::ShapeCanvas#on_left_double_click
3108
+ def _on_left_double_click(event)
3109
+ on_left_double_click(event)
3110
+
3111
+ event.skip
3112
+ end
3113
+
3114
+ # Original private event handler called when the left mouse button
3115
+ # is release above the canvas. The handler calls user-overridable event handler function
3116
+ # and skips the event for next possible processing.
3117
+ # @param [Wx::MouseEvent] event Mouse event
3118
+ # @see Wx::SF::ShapeCanvas#on_left_up
3119
+ def _on_left_up(event)
3120
+ on_left_up(event)
3121
+
3122
+ event.skip
3123
+ end
3124
+
3125
+ # Original private event handler called when the canvas is clicked by
3126
+ # right mouse button. The handler calls user-overridable event handler function
3127
+ # and skips the event for next possible processing.
3128
+ # @param [Wx::MouseEvent] event Mouse event
3129
+ # @see Wx::SF::ShapeCanvas#on_right_down
3130
+ def _on_right_down(event)
3131
+ on_right_down(event)
3132
+
3133
+ event.skip
3134
+ end
3135
+
3136
+ # Original private event handler called when the canvas is double-clicked by
3137
+ # right mouse button. The handler calls user-overridable event handler function
3138
+ # and skips the event for next possible processing.
3139
+ # @param [Wx::MouseEvent] event Mouse event
3140
+ # @see Wx::SF::ShapeCanvas#on_right_double_click
3141
+ def _on_right_double_click(event)
3142
+ on_right_double_click(event)
3143
+
3144
+ event.skip
3145
+ end
3146
+
3147
+ # Original private event handler called when the right mouse button
3148
+ # is release above the canvas. The handler calls user-overridable event handler function
3149
+ # and skips the event for next possible processing.
3150
+ # @param [Wx::MouseEvent] event Mouse event
3151
+ # @see Wx::SF::ShapeCanvas#on_right_up
3152
+ def _on_right_up(event)
3153
+ on_right_up(event)
3154
+
3155
+ event.skip
3156
+ end
3157
+
3158
+ # Original private event handler called when the mouse pointer is moving above
3159
+ # the canvas. The handler calls user-overridable event handler function
3160
+ # and skips the event for next possible processing.
3161
+ # @param [Wx::MouseEvent] event Mouse event
3162
+ # @see Wx::SF::ShapeCanvas#on_mouse_move
3163
+ def _on_mouse_move(event)
3164
+ lpos = dp2lp(event.get_position)
3165
+
3166
+ update_shape_under_cursor_cache(lpos)
3167
+
3168
+ # call user event handler
3169
+ on_mouse_move(event)
3170
+
3171
+ event.skip
3172
+ end
3173
+
3174
+ # Original private event handler called when the mouse wheel position is changed.
3175
+ # The handler calls user-overridable event handler function and skips the event
3176
+ # for next possible processing.
3177
+ # @param [Wx::MouseEvent] event Mouse event
3178
+ # @see Wx::SF::ShapeCanvas#on_mouse_wheel
3179
+ def _on_mouse_wheel(event)
3180
+ on_mouse_wheel(event) if has_style?(STYLE::PROCESS_MOUSEWHEEL)
3181
+
3182
+ event.skip
3183
+ end
3184
+
3185
+ # Original private event handler called when any key is pressed.
3186
+ # The handler calls user-overridable event handler function
3187
+ # and skips the event for next possible processing.
3188
+ # @param [Wx::KeyEvent] event Keyboard event
3189
+ # @see Wx::SF::ShapeCanvas#on_key_down
3190
+ def _on_key_down(event)
3191
+ on_key_down(event)
3192
+
3193
+ event.skip
3194
+ end
3195
+
3196
+ if Wx.has_feature?(:USE_DRAG_AND_DROP)
3197
+
3198
+ # Function is called by associated wxSFCanvasDropTarget after any dragged shapes
3199
+ # are dropped to the canvas.
3200
+ # @param [Integer] x X-coordinate of a position the data was dropped to
3201
+ # @param [Integer] y Y-coordinate of a position the data was dropped to
3202
+ # @param [Wx::DragResult] deflt Drag result
3203
+ # @param [Wx::ShapeDataObject] data a data object encapsulating dropped data
3204
+ # @see Wx::SF::CanvasDropTarget
3205
+ def _on_drop(x, y, deflt, data)
3206
+ if data && Wx::SF::ShapeDataObject === data
3207
+ lst_new_content = Wx::SF::Serializable.deserialize(data.get_data_here)
3208
+ if lst_new_content && !lst_new_content.empty?
3209
+ lst_parents_to_update = []
3210
+ lpos = dp2lp(Wx::Point.new(x, y))
3211
+
3212
+ dx = 0
3213
+ dy = 0
3214
+ if @dnd_started_here
3215
+ dx = lpos.x - @dnd_started_at.x
3216
+ dy = lpos.y - @dnd_started_at.y
3217
+ end
3218
+
3219
+ parent = @diagram.get_shape_at_position(lpos, 1, SEARCHMODE::UNSELECTED)
3220
+
3221
+ # add each shape to diagram keeping only those that are accepted
3222
+ lst_new_content.select! do |shape|
3223
+ shape.move_by(dx, dy)
3224
+ # do not reparent connection lines
3225
+ rc = if shape.is_a?(LineShape) && !shape.stand_alone?
3226
+ @diagram.add_shape(shape,
3227
+ nil,
3228
+ lp2dp(shape.get_absolute_position.to_point),
3229
+ INITIALIZE,
3230
+ DONT_SAVE_STATE)
3231
+ else
3232
+ @diagram.add_shape(shape,
3233
+ parent,
3234
+ lp2dp((shape.get_absolute_position - parent.get_absolute_position).to_point),
3235
+ INITIALIZE,
3236
+ DONT_SAVE_STATE)
3237
+ end
3238
+ rc == ERRCODE::OK # keep or remove?
3239
+ end
3240
+
3241
+ # verify newly added shapes (may remove shapes from list)
3242
+ @diagram.send(:check_new_shapes, lst_new_content)
3243
+
3244
+ update_virtual_size # update for new shapes
3245
+
3246
+ # notify parents and collect for update
3247
+ lst_new_content.each do |shape|
3248
+ if (parent_shape = shape.get_parent_shape)
3249
+ parent_shape.on_child_dropped(shape.get_absolute_position - parent_shape.get_absolute_position,
3250
+ shape)
3251
+ lst_parents_to_update << parent_shape unless lst_parents_to_update.include?(parent_shape)
3252
+ end
3253
+ end
3254
+
3255
+ deselect_all
3256
+
3257
+ lst_parents_to_update.each { |shape| shape.update }
3258
+
3259
+ unless @dnd_started_here
3260
+ save_canvas_state
3261
+ refresh(false)
3262
+ end
3263
+
3264
+ # call user-defined drop handler
3265
+ on_drop(x, y, deflt, lst_new_content)
3266
+ end
3267
+ end
3268
+ end
3269
+
3270
+ end
3271
+
3272
+ def _notify_canvas_change(change, *args)
3273
+ @diagram.get_all_shapes.each { |shape| shape.send(:_on_canvas, change, *args) } if @diagram
3274
+ end
3275
+
3276
+ def _query_canvas_change(change, *args)
3277
+ return true unless @diagram
3278
+ @diagram.get_all_shapes.all? { |shape| shape.send(:_on_canvas, change, *args) }
3279
+ end
3280
+
3281
+ end # class ShapeCanvas
3282
+
3283
+ end
3284
+
3285
+ # module Wx::SF