rUtilAnts 0.1.0.20091014

Sign up to get free protection for your applications and to get access to all the features.
@@ -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