rUtilAnts 0.1.0.20091014

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.
@@ -0,0 +1,577 @@
1
+ #--
2
+ # Copyright (c) 2009 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
+ # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
+ #++
5
+
6
+ # WxRuby has to be loaded correctly in the environment before requiring this file
7
+
8
+ module RUtilAnts
9
+
10
+ module GUI
11
+
12
+ # The class that assigns dynamically images to a given TreeCtrl items
13
+ class ImageListManager
14
+
15
+ # Constructor
16
+ #
17
+ # Parameters:
18
+ # * *ioImageList* (<em>Wx::ImageList</em>): The image list this manager will handle
19
+ # * *iWidth* (_Integer_): The images width
20
+ # * *iHeight* (_Integer_): The images height
21
+ def initialize(ioImageList, iWidth, iHeight)
22
+ @ImageList = ioImageList
23
+ # TODO (WxRuby): Get the size directly from ioImageList (get_size does not work)
24
+ @Width = iWidth
25
+ @Height = iHeight
26
+ # The internal map of image IDs => indexes
27
+ # map< Object, Integer >
28
+ @Id2Idx = {}
29
+ end
30
+
31
+ # Get the image index for a given image ID
32
+ #
33
+ # Parameters:
34
+ # * *iID* (_Object_): Id of the image
35
+ # * *CodeBlock*: The code that will be called if the image ID is unknown. This code has to return a Wx::Bitmap object, representing the bitmap for the given image ID.
36
+ def getImageIndex(iID)
37
+ if (@Id2Idx[iID] == nil)
38
+ # Bitmap unknown.
39
+ # First create it.
40
+ lBitmap = yield
41
+ # Then check if we need to resize it
42
+ lBitmap = getResizedBitmap(lBitmap, @Width, @Height)
43
+ # Then add it to the image list, and register it
44
+ @Id2Idx[iID] = @ImageList.add(lBitmap)
45
+ end
46
+
47
+ return @Id2Idx[iID]
48
+ end
49
+
50
+ end
51
+
52
+ # Generic progress dialog, meant to be overriden to customize behaviour
53
+ class ProgressDialog < Wx::Dialog
54
+
55
+ # Value for the undetermined range
56
+ DEFAULT_UNDETERMINED_RANGE = 10
57
+
58
+ # Is the current dialog in determined mode ?
59
+ # Boolean
60
+ attr_reader :Determined
61
+
62
+ # Has the current dialog been cancelled ?
63
+ # Boolean
64
+ attr_reader :Cancelled
65
+
66
+ # Constructor
67
+ #
68
+ # Parameters:
69
+ # * *iParentWindow* (<em>Wx::Window</em>): Parent window
70
+ # * *iCodeToExecute* (_Proc_): The code to execute that will update the progression
71
+ # * *iParameters* (<em>map<Symbol,Object></em>): Additional parameters:
72
+ # ** *:Cancellable* (_Boolean_): Can we cancel this dialog ? [optional = false]
73
+ # ** *:Title* (_String_): Caption of the progress dialog [optional = '']
74
+ # ** *:Icon* (<em>Wx::Bitmap</em>): Icon of the progress dialog [optional = nil]
75
+ def initialize(iParentWindow, iCodeToExecute, iParameters = {})
76
+ lCancellable = iParameters[:Cancellable]
77
+ if (lCancellable == nil)
78
+ lCancellable = false
79
+ end
80
+ lTitle = iParameters[:Title]
81
+ if (lTitle == nil)
82
+ lTitle = ''
83
+ end
84
+ lIcon = iParameters[:Icon]
85
+ super(iParentWindow,
86
+ :title => lTitle,
87
+ :style => Wx::THICK_FRAME|Wx::CAPTION
88
+ )
89
+ if (lIcon != nil)
90
+ lRealIcon = Wx::Icon.new
91
+ lRealIcon.copy_from_bitmap(lIcon)
92
+ set_icon(lRealIcon)
93
+ end
94
+
95
+ @CodeToExecute = iCodeToExecute
96
+
97
+ # Has the transaction been cancelled ?
98
+ @Cancelled = false
99
+
100
+ # Create components
101
+ @GProgress = Wx::Gauge.new(self, Wx::ID_ANY, 0)
102
+ @GProgress.set_size_hints(-1,12,-1,12)
103
+ lBCancel = nil
104
+ if (lCancellable)
105
+ lBCancel = Wx::Button.new(self, Wx::CANCEL, 'Cancel')
106
+ end
107
+ lPTitle = getTitlePanel
108
+
109
+ # Put them into sizers
110
+ lMainSizer = Wx::BoxSizer.new(Wx::VERTICAL)
111
+ lMainSizer.add_item(lPTitle, :flag => Wx::GROW|Wx::ALL, :proportion => 1, :border => 8)
112
+ if (lCancellable)
113
+ lBottomSizer = Wx::BoxSizer.new(Wx::HORIZONTAL)
114
+ lBottomSizer.add_item(@GProgress, :flag => Wx::ALIGN_CENTER|Wx::ALL, :proportion => 1, :border => 4)
115
+ lBottomSizer.add_item(lBCancel, :flag => Wx::ALIGN_CENTER, :proportion => 0)
116
+ lMainSizer.add_item(lBottomSizer, :flag => Wx::GROW|Wx::ALL, :proportion => 0, :border => 8)
117
+ else
118
+ lMainSizer.add_item(@GProgress, :flag => Wx::GROW|Wx::ALL, :proportion => 0, :border => 4)
119
+ end
120
+ self.sizer = lMainSizer
121
+
122
+ # Set events
123
+ if (lCancellable)
124
+ evt_button(lBCancel) do |iEvent|
125
+ @Cancelled = true
126
+ lBCancel.enable(false)
127
+ lBCancel.label = 'Cancelling ...'
128
+ self.fit
129
+ end
130
+ end
131
+ lExecCompleted = false
132
+ evt_idle do |iEvent|
133
+ # Execute the code once
134
+ if (!lExecCompleted)
135
+ lExecCompleted = true
136
+ @CodeToExecute.call(self)
137
+ end
138
+ self.end_modal(Wx::ID_OK)
139
+ end
140
+
141
+ # By default, consider that we don't know the range of progression
142
+ # That's why we set a default range (undetermined progression needs a range > 0 to have visual effects)
143
+ @GProgress.range = DEFAULT_UNDETERMINED_RANGE
144
+ @Determined = false
145
+
146
+ self.fit
147
+
148
+ refreshState
149
+ end
150
+
151
+ # Called to refresh our dialog
152
+ def refreshState
153
+ self.refresh
154
+ self.update
155
+ # Process eventual user request to stop transaction
156
+ Wx.get_app.yield
157
+ end
158
+
159
+ # Set the progress range
160
+ #
161
+ # Parameters:
162
+ # * *iRange* (_Integer_): The progress range
163
+ def setRange(iRange)
164
+ @GProgress.range = iRange
165
+ if (!@Determined)
166
+ @Determined = true
167
+ @GProgress.value = 0
168
+ end
169
+ refreshState
170
+ end
171
+
172
+ # Set the progress value
173
+ #
174
+ # Parameters:
175
+ # * *iValue* (_Integer_): The progress value
176
+ def setValue(iValue)
177
+ @GProgress.value = iValue
178
+ refreshState
179
+ end
180
+
181
+ # Increment the progress value
182
+ #
183
+ # Parameters:
184
+ # * *iIncrement* (_Integer_): Value to increment [optional = 1]
185
+ def incValue(iIncrement = 1)
186
+ @GProgress.value += iIncrement
187
+ refreshState
188
+ end
189
+
190
+ # Increment the progress range
191
+ #
192
+ # Parameters:
193
+ # * *iIncrement* (_Integer_): Value to increment [optional = 1]
194
+ def incRange(iIncrement = 1)
195
+ if (@Determined)
196
+ @GProgress.range += iIncrement
197
+ else
198
+ @Determined = true
199
+ @GProgress.range = iIncrement
200
+ @GProgress.value = 0
201
+ end
202
+ refreshState
203
+ end
204
+
205
+ # Pulse the progression (to be used when we don't know the range)
206
+ def pulse
207
+ if (@Determined)
208
+ @Determined = false
209
+ @GProgress.range = DEFAULT_UNDETERMINED_RANGE
210
+ end
211
+ @GProgress.pulse
212
+ refreshState
213
+ end
214
+
215
+ end
216
+
217
+ # Text progress dialog
218
+ class TextProgressDialog < ProgressDialog
219
+
220
+ # Constructor
221
+ #
222
+ # Parameters:
223
+ # * *iParentWindow* (<em>Wx::Window</em>): Parent window
224
+ # * *iCodeToExecute* (_Proc_): The code to execute that will update the progression
225
+ # * *iText* (_String_): The text to display
226
+ # * *iParameters* (<em>map<Symbol,Object></em>): Additional parameters (check RUtilAnts::GUI::ProgressDialog#initialize documentation):
227
+ def initialize(iParentWindow, iCodeToExecute, iText, iParameters = {})
228
+ @Text = iText
229
+ super(iParentWindow, iCodeToExecute, iParameters)
230
+ end
231
+
232
+ # Get the panel to display as title
233
+ #
234
+ # Return:
235
+ # * <em>Wx::Panel</em>: The panel to use as a title
236
+ def getTitlePanel
237
+ rPanel = Wx::Panel.new(self)
238
+
239
+ # Create components
240
+ @STText = Wx::StaticText.new(rPanel, Wx::ID_ANY, @Text, :style => Wx::ALIGN_CENTRE)
241
+
242
+ # Put them into sizers
243
+ lMainSizer = Wx::BoxSizer.new(Wx::VERTICAL)
244
+ lMainSizer.add_item(@STText, :flag => Wx::GROW, :proportion => 1)
245
+ rPanel.sizer = lMainSizer
246
+
247
+ return rPanel
248
+ end
249
+
250
+ # Set the text
251
+ #
252
+ # Parameters:
253
+ # * *iText* (_String_): The text
254
+ def setText(iText)
255
+ @STText.label = iText
256
+ self.fit
257
+ refreshState
258
+ end
259
+
260
+ end
261
+
262
+ # Bitmap progress dialog
263
+ class BitmapProgressDialog < ProgressDialog
264
+
265
+ # Constructor
266
+ #
267
+ # Parameters:
268
+ # * *iParentWindow* (<em>Wx::Window</em>): Parent window
269
+ # * *iCodeToExecute* (_Proc_): The code to execute that will update the progression
270
+ # * *iBitmap* (<em>Wx::Bitmap</em>): The bitmap to display (can be nil)
271
+ # * *iParameters* (<em>map<Symbol,Object></em>): Additional parameters (check RUtilAnts::GUI::ProgressDialog#initialize documentation):
272
+ def initialize(iParentWindow, iCodeToExecute, iBitmap, iParameters = {})
273
+ @Bitmap = iBitmap
274
+ super(iParentWindow, iCodeToExecute, iParameters)
275
+ end
276
+
277
+ # Get the panel to display as title
278
+ #
279
+ # Return:
280
+ # * <em>Wx::Panel</em>: The panel to use as a title
281
+ def getTitlePanel
282
+ rPanel = Wx::Panel.new(self)
283
+
284
+ # Create components
285
+ if (@Bitmap == nil)
286
+ @SBBitmap = Wx::StaticBitmap.new(rPanel, Wx::ID_ANY, Wx::Bitmap.new)
287
+ else
288
+ @SBBitmap = Wx::StaticBitmap.new(rPanel, Wx::ID_ANY, @Bitmap)
289
+ end
290
+
291
+ # Put them into sizers
292
+ lMainSizer = Wx::BoxSizer.new(Wx::VERTICAL)
293
+ lMainSizer.add_item(@SBBitmap, :flag => Wx::GROW, :proportion => 1)
294
+ rPanel.sizer = lMainSizer
295
+
296
+ return rPanel
297
+ end
298
+
299
+ # Set the bitmap
300
+ #
301
+ # Parameters:
302
+ # * *iBitmap* (<em>Wx::Bitmap</em>): The bitmap
303
+ def setBitmap(iBitmap)
304
+ @SBBitmap.bitmap = iBitmap
305
+ self.fit
306
+ refreshState
307
+ end
308
+
309
+ end
310
+
311
+ # Manager that handles normal Wx::Timer, integrating a mechanism that can kill it and wait until it has been safely killed.
312
+ # Very handy for timers processing data that might be destroyed.
313
+ # To be used with safeTimerAfter and safeTimerEvery.
314
+ class SafeTimersManager
315
+
316
+ # Constructor
317
+ def initialize
318
+ # List of registered timers
319
+ # list< Wx::Timer >
320
+ @Timers = []
321
+ end
322
+
323
+ # Register a given timer
324
+ #
325
+ # Parameters:
326
+ # * *iTimer* (<em>Wx::Timer</em>): The timer to register
327
+ def registerTimer(iTimer)
328
+ @Timers << iTimer
329
+ end
330
+
331
+ # Unregister a given timer
332
+ #
333
+ # Parameters:
334
+ # * *iTimer* (<em>Wx::Timer</em>): The timer to unregister
335
+ # Return:
336
+ # * _Boolean_: Was the Timer registered ?
337
+ def unregisterTimer(iTimer)
338
+ rFound = false
339
+
340
+ @Timers.delete_if do |iRegisteredTimer|
341
+ if (iRegisteredTimer == iTimer)
342
+ rFound = true
343
+ next true
344
+ else
345
+ next false
346
+ end
347
+ end
348
+
349
+ return rFound
350
+ end
351
+
352
+ # Kill all registered Timers and wait for their completion.
353
+ # Does not return unless they are stopped.
354
+ def killTimers
355
+ # Notify each Timer that it has to stop
356
+ @Timers.each do |ioTimer|
357
+ ioTimer.stop
358
+ end
359
+ # Wait for each one to be stopped
360
+ lTimersToStop = []
361
+ # Try first time, to not enter the loop if they were already stopped
362
+ @Timers.each do |iTimer|
363
+ if (iTimer.is_running)
364
+ lTimersToStop << iTimer
365
+ end
366
+ end
367
+ while (!lTimersToStop.empty?)
368
+ lTimersToStop.delete_if do |iTimer|
369
+ next (!iTimer.is_running)
370
+ end
371
+ # Give time to the application to effectively stop its timers
372
+ Wx.get_app.yield
373
+ # Little sleep
374
+ sleep(0.1)
375
+ end
376
+ end
377
+
378
+ end
379
+
380
+ # Initialize the GUI methods in the Kernel namespace
381
+ def self.initializeGUI
382
+ Object.module_eval('include RUtilAnts::GUI')
383
+ end
384
+
385
+ # Get a bitmap resized to a given size if it differs from it
386
+ #
387
+ # Parameters:
388
+ # * *iBitmap* (<em>Wx::Bitmap</em>): The original bitmap
389
+ # * *iWidth* (_Integer_): The width of the resized bitmap
390
+ # * *iHeight* (_Integer_): The height of the resized bitmap
391
+ # Return:
392
+ # * <em>Wx::Bitmap</em>: The resized bitmap (can be the same object as iBitmap)
393
+ def getResizedBitmap(iBitmap, iWidth, iHeight)
394
+ rResizedBitmap = iBitmap
395
+
396
+ if ((iBitmap.width != iWidth) or
397
+ (iBitmap.height != iHeight))
398
+ rResizedBitmap = Wx::Bitmap.new(iBitmap.convert_to_image.scale(iWidth, iHeight))
399
+ end
400
+
401
+ return rResizedBitmap
402
+ end
403
+
404
+ # Display a dialog in modal mode, ensuring it is destroyed afterwards.
405
+ #
406
+ # Parameters:
407
+ # * *iDialogClass* (_class_): Class of the dialog to display
408
+ # * *iParentWindow* (<em>Wx::Window</em>): Parent window (can be nil)
409
+ # * *iParameters* (...): List of parameters to give the constructor
410
+ # * *CodeBlock*: The code called once the dialog has been displayed and modally closed
411
+ # ** *iModalResult* (_Integer_): Modal result
412
+ # ** *iDialog* (<em>Wx::Dialog</em>): The dialog
413
+ def showModal(iDialogClass, iParentWindow, *iParameters)
414
+ # If the parent is nil, we fall into a buggy behaviour in the case of GC enabled:
415
+ # * If we destroy the window after show_modal, random core dumps occur in the application
416
+ # * If not, the application can't exit normally
417
+ # Therefore, in case of nil, we assign the top window as the parent.
418
+ # Sometimes, there is no top_window. So we'll stick with nil.
419
+ lParentWindow = iParentWindow
420
+ if (lParentWindow == nil)
421
+ lParentWindow = Wx.get_app.get_top_window
422
+ end
423
+ lDialog = iDialogClass.new(lParentWindow, *iParameters)
424
+ lDialog.centre(Wx::CENTRE_ON_SCREEN|Wx::BOTH)
425
+ lModalResult = lDialog.show_modal
426
+ yield(lModalResult, lDialog)
427
+ # If we destroy windows having parents, we get SegFaults during execution when mouse hovers some toolbar icons and moves (except if we disable GC: in this case it works perfectly fine, but consumes tons of memory).
428
+ # If we don't destroy, we got ObjectPreviouslyDeleted exceptions on exit with wxRuby 2.0.0 (seems to have disappeared in 2.0.1).
429
+ # TODO (wxRuby): Correct bug on Tray before enabling GC and find the good solution for modal destruction.
430
+ if (lParentWindow == nil)
431
+ lDialog.destroy
432
+ end
433
+ end
434
+
435
+ # Get a bitmap/icon from a URL.
436
+ # If no type has been provided, it detects the type of icon based on the file extension.
437
+ # Use URL caching.
438
+ #
439
+ # Parameters:
440
+ # * *iFileName* (_String_): The file name
441
+ # * *iIconIndex* (_Integer_): Specify the icon index (used by Windows for EXE/DLL/ICO...) [optional = nil]
442
+ # * *iBitmapTypes* (_Integer_ or <em>list<Integer></em>): Bitmap/Icon type. Can be nil for autodetection. Can be the list of types to try. [optional = nil]
443
+ # Return:
444
+ # * <em>Wx::Bitmap</em>: The bitmap, or nil in case of failure
445
+ # * _Exception_: The exception containing details about the error, or nil in case of success
446
+ def getBitmapFromURL(iFileName, iIconIndex = nil, iBitmapTypes = nil)
447
+ rReadBitmap = nil
448
+ rReadError = nil
449
+
450
+ rReadBitmap, rReadError = getURLContent(iFileName, :LocalFileAccess => true) do |iRealFileName|
451
+ rBitmap = nil
452
+ rError = nil
453
+
454
+ lBitmapTypesToTry = iBitmapTypes
455
+ if (iBitmapTypes == nil)
456
+ # Autodetect
457
+ lBitmapTypesToTry = [ Wx::Bitmap::BITMAP_TYPE_GUESS[File.extname(iRealFileName).downcase[1..-1]] ]
458
+ if (lBitmapTypesToTry == [ nil ])
459
+ # Here we handle extensions that wxruby is not aware of
460
+ case File.extname(iRealFileName).upcase
461
+ when '.CUR', '.ANI', '.EXE', '.DLL'
462
+ lBitmapTypesToTry = [ Wx::BITMAP_TYPE_ICO ]
463
+ else
464
+ logErr "Unable to determine the bitmap type corresponding to extension #{File.extname(iRealFileName).upcase}. Assuming ICO."
465
+ lBitmapTypesToTry = [ Wx::BITMAP_TYPE_ICO ]
466
+ end
467
+ end
468
+ elsif (!iBitmapTypes.is_a?(Array))
469
+ lBitmapTypesToTry = [ iBitmapTypes ]
470
+ end
471
+ # Try each type
472
+ lBitmapTypesToTry.each do |iBitmapType|
473
+ # Special case for the ICO type
474
+ if (iBitmapType == Wx::BITMAP_TYPE_ICO)
475
+ lIconID = iRealFileName
476
+ if ((iIconIndex != nil) and
477
+ (iIconIndex != 0))
478
+ # TODO: Currently this implementation does not work. Uncomment when ok.
479
+ #lIconID += ";#{iIconIndex}"
480
+ end
481
+ rBitmap = Wx::Bitmap.new
482
+ begin
483
+ rBitmap.copy_from_icon(Wx::Icon.new(lIconID, Wx::BITMAP_TYPE_ICO))
484
+ rescue Exception
485
+ rError = $!
486
+ rBitmap = nil
487
+ end
488
+ else
489
+ rBitmap = Wx::Bitmap.new(iRealFileName, iBitmapType)
490
+ end
491
+ if (rBitmap != nil)
492
+ if (rBitmap.is_ok)
493
+ break
494
+ else
495
+ # File seems to be corrupted
496
+ rError = RuntimeError.new("Bitmap #{iFileName} is corrupted.")
497
+ rBitmap = nil
498
+ end
499
+ else
500
+ rBitmap = nil
501
+ end
502
+ end
503
+
504
+ return rBitmap, rError
505
+ end
506
+
507
+ # Check if it is ok and the error set correctly
508
+ if ((rReadBitmap == nil) and
509
+ (rReadError == nil))
510
+ rError = RuntimeError.new("Unable to get bitmap from #{iFileName}")
511
+ end
512
+
513
+ return rReadBitmap, rReadError
514
+ end
515
+
516
+ # Setup a progress bar with some text in it and call code around it
517
+ #
518
+ # Parameters:
519
+ # * *iParentWindow* (<em>Wx::Window</em>): The parent window
520
+ # * *iText* (_String_): The text to display
521
+ # * *iParameters* (<em>map<Symbol,Object></em>): Additional parameters (check RUtilAnts::GUI::ProgressDialog#initialize documentation):
522
+ # * _CodeBlock_: The code called with the progress bar created:
523
+ # ** *ioProgressDlg* (<em>RUtilAnts::GUI::ProgressDialog</em>): The progress dialog
524
+ def setupTextProgress(iParentWindow, iText, iParameters = {}, &iCodeToExecute)
525
+ showModal(TextProgressDialog, iParentWindow, iCodeToExecute, iText, iParameters) do |iModalResult, iDialog|
526
+ # Nothing to do
527
+ end
528
+ end
529
+
530
+ # Setup a progress bar with some bitmap in it and call code around it
531
+ #
532
+ # Parameters:
533
+ # * *iParentWindow* (<em>Wx::Window</em>): The parent window
534
+ # * *iBitmap* (<em>Wx::Bitmap</em>): The bitmap to display
535
+ # * *iParameters* (<em>map<Symbol,Object></em>): Additional parameters (check RUtilAnts::GUI::ProgressDialog#initialize documentation):
536
+ # * _CodeBlock_: The code called with the progress bar created:
537
+ # ** *ioProgressDlg* (<em>RUtilAnts::GUI::ProgressDialog</em>): The progress dialog
538
+ def setupBitmapProgress(iParentWindow, iBitmap, iParameters = {}, &iCodeToExecute)
539
+ showModal(BitmapProgressDialog, iParentWindow, iCodeToExecute, iBitmap, iParameters) do |iModalResult, iDialog|
540
+ # Nothing to do
541
+ end
542
+ end
543
+
544
+ # Execute some code after some elapsed time.
545
+ #
546
+ # Parameters:
547
+ # * *ioSafeTimersManager* (_SafeTimersManager_): The manager that handles this SafeTimer
548
+ # * *iElapsedTime* (_Integer_): The elapsed time to wait before running the code
549
+ # * _CodeBlock_: The code to execute
550
+ def safeTimerAfter(ioSafeTimersManager, iElapsedTime)
551
+ # Create the Timer and register it
552
+ lTimer = nil
553
+ lTimer = Wx::Timer.after(iElapsedTime) do
554
+ yield
555
+ # Now the Timer can be safely destroyed.
556
+ ioSafeTimersManager.unregisterTimer(lTimer)
557
+ end
558
+ ioSafeTimersManager.registerTimer(lTimer)
559
+ end
560
+
561
+ # Execute some code every some elapsed time.
562
+ #
563
+ # Parameters:
564
+ # * *ioSafeTimersManager* (_SafeTimersManager_): The manager that handles this SafeTimer
565
+ # * *iElapsedTime* (_Integer_): The elapsed time to wait before running the code
566
+ # * _CodeBlock_: The code to execute
567
+ def safeTimerEvery(ioSafeTimersManager, iElapsedTime)
568
+ # Create the Timer and register it
569
+ lTimer = Wx::Timer.every(iElapsedTime) do
570
+ yield
571
+ end
572
+ ioSafeTimersManager.registerTimer(lTimer)
573
+ end
574
+
575
+ end
576
+
577
+ end