;+ ; NAME: ; SHOWPROGRESS__DEFINE ; ; PURPOSE: ; ; An object for creating a progress bar. ; ; AUTHOR: ; ; FANNING SOFTWARE CONSULTING ; David Fanning, Ph.D. ; 2642 Bradbury Court ; Fort Collins, CO 80521 USA ; Phone: 970-221-0438 ; E-mail: davidf@dfanning.com ; Coyote's Guide to IDL Programming: http://www.dfanning.com ; ; CATEGORY: ; Utilities ; ; CALLING SEQUENCE: ; ; progressBar = Obj_New("SHOWPROGRESS") ; ; INPUTS: ; ; parent: A widget identifier of the widget that will be the ; group leader for this program. It is *required* for modal ; operation. If missing, you are on your own. :-( ; ; KEYWORDS: ; ; CANCELBUTTON: Set this keyword if a Cancel button is desired. ; DELAY: The total time the widget should be on the display in AutoUpDate ; mode. The keyword applies only to AutoUpDate mode. Default is 5 seconds. ; STEPS: The number of steps to take in AutoUpDate mode. The keyword applies only ; to AutoUpDate mode. ; MESSAGE: The text of the label above the progress bar. Default is "Operation ; in Progress...". ; TITLE: ; The text of the top-level base title bar. Default is "" ; COLOR: The color to draw the progress bar. ; XSIZE: The XSize of the progress bar in Device coordinates. Default is 150. ; YSIZE: The YSize of the progress bar in Device coordinates. Default is 10. ; AUTOUPDATE: Set this keyword to be in AutoUpDate mode. ; ; PROCEDURE: ; There are two modes. In AutoUpDate mode, a delay and number of steps is ; required. The modal widget stays on the display until the total time ; exceeds the DELAY or the requested number of steps is taken. A TIMER ; widget is used to generate update events. Nothing can be going on ; concurrently in AutoUpDate mode. To enter AutoUpDate mode, type this: ; ; progressBar = Obj_New("SHOWPROGRESS", /AutoUpDate, Delay=2, Steps=10) ; progressBar->Start ; Obj_Destroy, progressBar ; ; The program will update and destroy itself automatically. (The object ; itself is not destroyed. You must do this explicitly, as in the example ; above.) ; ; In normal mode, the user is responsible for starting, updating, and ; destroying the progress indicator. The sequence of commands might look ; like this: ; ; progressBar = Obj_New("SHOWPROGRESS") ; progressBar->Start ; FOR j=0,9 DO BEGIN ; Wait, 0.5 ; Would probably be doing something ELSE here! ; progressBar->Update, (j+1)*10 ; ENDFOR ; progressBar->Destroy ; Obj_Destroy, progressBar ; ; Normal mode gives you the opportunity to update the Progress Bar ; in a loop while something else is going on. See the example program ; at the end of this file. ; ; Note that the object itself is not destroyed when calling the DESTROY ; method. You must explicitly destroy the object, as in the example above. ; ; METHODS: ; ; CHECKCANCEL: This function method returns a 1 if the user has clicked ; the CANCEL button. Otherwise, it returns a 0. ; ; cancelled = progressBar->CheckCancel() ; IF cancelled THEN progressBar->Destroy ; ; DESTROY: Destroys the ShowProgress widgets. It does NOT destroy the object. ; ; progressBar->Destroy ; ; GETPROPERTY: Gets the properties that can be set in the INIT method, including ; the parent widget ID. ; ; progressBar->GetProperty, Steps=currentNSteps, Delay=currentDelay ; ; SETCOLOR: Changes the color of the progress bar. ; ; progressBar->SetColor, !P.Color ; ; SETLABEL: Changes the text on the widget label. ; ; progressBar->SetLabel, 'This text instead' ; ; SETPROPERTY: Allows the user to set the INIT parameter via keywords. ; ; progressBar->SetProperty, Color=244, XSize=200, Message='Please Wait...' ; ; START: Puts the ShowProgress bar on the display. In AutoUpDate mode, the ; widget starts to automatically update. ; ; progressBar->Start ; ; UPDATE: Updates the progress bar. Requires on argument, a number between 0 ; and 100 that indicates the percent of progress bar that should be filled ; with a color. ; ; progressBar->Update, 50 ; ; EXAMPLE: ; ; See the example program at the bottom of this file. ; ; RESTRICTIONS: ; ; In contradiction to the IDL documentation, making the parent widget ; insensitive in normal mode does NOT prevent the parent widgets from ; receiving events on my Windows NT 4.0, SP 4 system running IDL 5.2, ; IDL 5.2.1, or IDL 5.3 (beta). ; ; Note that if you specify a CANCEL button the Show Progress program CANNOT ; run as a MODAL widget program. Thus, user *may* be able to generate events ; in the calling program while this program is running. ; ; MODIFICATION HISTORY: ; Written by: David Fanning, 26 July 1999. ; Added code so that the current graphics window doesn't change. 1 September 1999. DWF. ; Added yet more code for the same purpose. 3 September 1999. DWF. ; Added a CANCEL button and made other minor modifications. 12 Oct 1999. DWF. ; ; Added a third mode - 'In Progress Mode' - for when you want to show that ; a routine is working, and provide a 'Cancel' button, but you have no ; way of estimating what percentage is done. To use, set up just like ; normal mode, but don't pass percent argument in Update method. Progress bar ; will flipflop to show progress. 18-Mar-2001, Kim Tolbert, GSFC ; Fixed two minor bugs - changed self.cancel to self.cancelbutton in the ; Reinitialize method, and changed selfwindow to self.wid in TimerEvents method ; (selfwindow not defined at that point). 18-Mar-2001, Kim Tolbert, GSFC ; Added message_text keyword to Update method - changes text shown above ; progress bar so can show #iterations completed or something. 18-Mar-2001, Kim Tolbert, GSFC ; Added position keyword to init method. Enabled multi-line (max=3) message above progress bar. ; Added 'Elapsed Time' information. Made progress bar 90% of size of widget (if message text ; causes widget to grow bigger, bar will grow too). 22-Mar-2001, Kim Tolbert, GSFC ; Changed integer type from fix to long in elapsed time calculation. 28-Mar-2001, Kim Tolbert, GSFC ; Call widget_control,/show on every update call to bring progress bar to front. 18-May-2001, Kim, GSFC ; Added cancelid to properties GetProperty can return. 21-Jun-2004, Kim Tolbert ; ;- FUNCTION ShowProgress::CheckCancel ; This method checks for a CANCEL Button event. It returns 1 ; if an event has occurred and 0 otherwise. RETURN, self.cancel END; ----------------------------------------------------------------------------- PRO ShowProgress::SetLabel, newlabel ; This method allows the widget label to be changed while ; the program is on the display. case n_elements(newlabel) of 0: text = [' ', ' ', ' '] 1: text = [' ', newlabel, ' '] 2: text = [newlabel, ' '] 3: text = newlabel else: text = newlabel[0:2] endcase for i = 0,2 do Widget_Control, self.labelID[i], Set_Value=text[i] END; ----------------------------------------------------------------------------- PRO ShowProgress::SetCancel, value ; This method sets the Cancel flag. IF N_Elements(value) EQ 0 THEN value = 1 self.cancel = value END; ----------------------------------------------------------------------------- PRO ShowProgress::SetColor, color ; This method sets the Cancel flag. IF N_Elements(color) EQ 0 THEN color = !P.Color self.color = color END; ----------------------------------------------------------------------------- PRO ShowProgress::Destroy ; This method takes the widget off the display. Widget_Control, self.tlb, Destroy=1 END; ----------------------------------------------------------------------------- PRO ShowProgress::UpDate, percent, message_text=message_text ; This method updates the display. It should be called with ; manual operation. PERCENT should be a value between 0 and 100. Catch, theError IF theError NE 0 THEN BEGIN Catch, /Cancel ; Catch a WSET error silently. IF !Error_State.Code EQ -386 THEN RETURN Message, !Error_State.Msg, /Informational RETURN ENDIF if keyword_set(message_text) then self -> SetLabel, message_text if n_elements(percent) eq 0 then flash = 1 else flash = 0 thisWindow = !D.Window WSet, self.wid top_geom = widget_info(self.tlb, /geom) bar_geom = widget_info (self.drawid, /geom) if bar_geom.xsize lt .9 * top_geom.xsize then begin self.xsize = .9 * top_geom.xsize widget_control, self.drawid, xsize = self.xsize endif ; Update the progress box. Either show percentage done, or flip black and white halves. if flash then begin colors = [self.color, !p.background] self.count = self.count + 1 x1 = 0 y1 = 0 x2 = fix(self.xsize / 2.) y2 = self.ysize Polyfill, [x1, x1, x2, x2, x1], [y1, y2, y2, y1, y1], /Device, Color=colors[self.count mod 2] x1 = x2 x2 = self.xsize Polyfill, [x1, x1, x2, x2, x1], [y1, y2, y2, y1, y1], /Device, Color=colors[(self.count+1) mod 2] endif else begin percent = 0 > percent < 100 x1 = 0 y1 = 0 x2 = Fix(self.xsize * (percent/100.0)) y2 = self.ysize Polyfill, [x1, x1, x2, x2, x1], [y1, y2, y2, y1, y1], /Device, Color=self.color endelse ; Update the elapsed time label time = long(systime(1) - self.starttime) if time gt 60 then begin m = time / 60 s = time - (m * 60) tlabel = strtrim (m,2) + ' min, ' + strtrim(s,2) + ' sec' endif else tlabel = strtrim(time,2) + ' sec' widget_control, self.timeID, set_value = 'Elapsed Time: ' + tlabel IF thisWindow GE 0 AND thisWindow NE self.wid THEN WSet, thisWindow ; Check for a CANCEL button event. IF Widget_Info(self.cancelID, /Valid_ID) THEN BEGIN event = Widget_Event(self.cancelID, /NoWait) name = Tag_Names(event, /Structure_Name) IF name EQ 'WIDGET_BUTTON' THEN self.cancel = 1 ENDIF widget_control, self.tlb, /show END; ----------------------------------------------------------------------------- PRO ShowProgress::Timer_Events, event ; This method processes the widget TIMER events. Catch, theError IF theError NE 0 THEN BEGIN Catch, /Cancel ; Catch a WSET error silently. IF !Error_State.Code EQ -386 THEN RETURN Message, !Error_State.Msg, /Informational RETURN ENDIF ; Do it the specified number of times, then quit. self.count = self.count + 1 IF self.count GE self.nsteps THEN BEGIN thisWindow = !D.Window selfWindow = self.wid WSet, self.wid Polyfill, [0, 0, self.xsize, self.xsize, 0], $ [0, self.ysize, self.ysize, 0, 0], /Normal, Color=self.color Widget_Control, self.tlb, Destroy=1 IF thisWindow GE 0 AND thisWindow NE selfWindow THEN WSet, thisWindow RETURN ENDIF ; If time gets away from you, then quit. theTime = Systime(1) - self.startTime IF theTime GT self.delay THEN BEGIN thisWindow = !D.Window WSet, self.wid Polyfill, [0, 0, self.xsize, self.xsize, 0], $ [0, self.ysize, self.ysize, 0, 0], /Normal, Color=self.color Widget_Control, self.tlb, Destroy=1 IF thisWindow GE 0 AND thisWindow NE self.wid THEN WSet, thisWindow RETURN ENDIF ; Update the progress box. thisSize = (self.xsize / self.nsteps) * self.count thisSize = Float(thisSize) / self.xsize * 100 self->Update, thisSize ; Set the next timer event if the CANCEL button is not set. IF self.cancel EQ 1 THEN BEGIN self->Destroy ENDIF ELSE BEGIN Widget_Control, self.tlb, Timer=self.step ENDELSE END; ----------------------------------------------------------------------------- PRO ShowProgress_Event, event ; This is the event handler for the program. It simply calls ; the event handling method. Widget_Control, event.top, Get_UValue=self thisEvent = Tag_Names(event, /Structure_Name) IF thisEvent EQ 'WIDGET_BUTTON' THEN self->SetCancel, 1 ELSE $ self->Timer_Events, event END; ----------------------------------------------------------------------------- PRO ShowProgress::Start ; This is the method that puts the timer on the display and gets ; things going. The initial timer event is set here. ; Find the window index number of any open display window. thisWindow = !D.Window ; Realize the widget. Widget_Control, self.tlb, /Realize ; Set an initial start time. self.startTime = Systime(1) ; Get the window index number of the draw widget. Widget_Control, self.drawID, Get_Value=wid self.wid = wid ; Back to the open display window. IF thisWindow GE 0 THEN WSet, thisWindow IF self.autoupdate THEN BEGIN ; Set the first timer event. Widget_Control, self.tlb, Timer=self.step ; Register with XMANAGER so you can receive events. XManager, 'showprogress', self.tlb, Cleanup='ShowProgress_CleanUp' ENDIF ELSE BEGIN IF Widget_Info(self.parent, /Valid_ID) THEN $ Widget_Control, self.parent, Sensitive=0 self->Update, 0 Widget_Control, self.tlb, Kill_Notify='ShowProgress_CleanUp' ENDELSE END; ----------------------------------------------------------------------------- PRO ShowProgress_Cleanup, tlb ; This is the cleanup method for the widget. The idea ; here is to reinitialize the widget after the old ; widget has been destroyed. This is necessary, because ; it is not possible to MAP and UNMAP a modal widget. Widget_Control, tlb, Get_UValue=self self->ReInitialize END; ----------------------------------------------------------------------------- PRO ShowProgress::ReInitialize ; This method just reinitializes the ShowProgress widget after the ; previous one has been destroyed. This is called from the widget's ; CLEANUP routine. IF NOT Float(self.autoupdate) THEN BEGIN IF Widget_Info(self.parent, /Valid_ID) THEN $ Widget_Control, self.parent, Sensitive=1, /Clear_Events ENDIF ; Create the widgets. IF self.parent EQ -1 THEN BEGIN self.tlb = Widget_Base(Title=self.title, Column=1, Base_Align_Center=1) ENDIF ELSE BEGIN IF self.cancelButton THEN modal = 0 ELSE modal = 1 self.tlb = Widget_Base(Group_Leader=self.parent, Modal=modal, Title=self.title, $ Column=1, Base_Align_Center=1, Floating=1) ENDELSE text = [' ', self.message, ' '] for i = 0,2 do self.labelID[i] = Widget_Label(self.tlb, Value=text[i], /Dynamic_Resize) self.drawID = Widget_Draw(self.tlb, XSize=self.xsize, YSize=self.ysize) self.timeID = widget_label (self.tlb, value = 'Elapsed Time: 0 sec', /dynamic_resize) IF self.cancelbutton THEN BEGIN self.cancelID = Widget_Button(self.tlb, Value='Cancel') ENDIF ELSE self.cancelID = -1L Widget_Control, self.tlb, Set_UValue=self ; Center the top-level base. Device, Get_Screen_Size=screenSize xCenter = screenSize(0) / 2 yCenter = screenSize(1) / 2 geom = Widget_Info(self.tlb, /Geometry) xHalfSize = geom.Scr_XSize / 2 yHalfSize = geom.Scr_YSize / 2 Widget_Control, self.tlb, XOffset = xCenter-xHalfSize, $ YOffset = yCenter-yHalfSize ; Reset the counter. self.count = 0 END; ----------------------------------------------------------------------------- PRO ShowProgress::Cleanup ; This CLEANUP method is not usually required, since other widget ; programs are destroying the widget, but is here for completeness. IF Widget_Info(self.tlb, /Valid_ID) THEN Widget_Control, self.tlb, /Destroy END; ----------------------------------------------------------------------------- PRO ShowProgress::GetProperty, Parent=parent, Delay=delay, Steps=nsteps, $ Message=message, Title=title, Color=color, XSize=xsize, $ YSize=ysize, AutoUpdate=autoupdate, cancelID=cancelID ; This method allows you to get all the properties available in the INIT method. parent = self.parent delay = self.delay nsteps = self.nsteps message = self.message title = self.title color = self.color xsize = self.xsize ysize = self.ysize autoupdate = self.autoupdate cancelID = self.cancelID END; ----------------------------------------------------------------------------- PRO ShowProgress::SetProperty, Parent=parent, Delay=delay, Steps=nsteps, $ Message=message, Title=title, Color=color, XSize=xsize, $ YSize=ysize, AutoUpdate=autoupdate ; This method allows you to set all the properties available in the INIT method. IF N_Elements(parent) NE 0 THEN BEGIN self.parent = parent Widget_Control, self.tlb, /Destroy ENDIF IF N_Elements(delay) NE 0 THEN self.delay = delay IF N_Elements(nsteps) NE 0 THEN BEGIN self.nsteps = nsteps self.step = (Float(self.delay) / self.nsteps) ENDIF IF N_Elements(message) NE 0 THEN self.message = message IF N_Elements(title) NE 0 THEN self.title = title IF N_Elements(color) NE 0 THEN self.color = color IF N_Elements(xsize) NE 0 THEN self.xsize = xsize IF N_Elements(ysize) NE 0 THEN self.ysize = ysize IF N_Elements(autoupdate) NE 0 THEN self.autoupdate = autoupdate Widget_Control, self.tlb, /Destroy END; ----------------------------------------------------------------------------- FUNCTION ShowProgress::Init, $ parent, $ ; The widget ID of the group leader. position=position, $ ; x,y position of widget in device coordinates CancelButton=cancelButton, $ ; This keyword is set if a cancel button is required. Delay=delay, $ ; The total time the widget should be on the display in AutoUpDate mode. Steps=nsteps, $ ; The number of steps to take in AutoUpDate mode. Message=message, $ ; The text of the label above the progress bar. Title=title, $ ; The text of the top-level base title bar. Color=color, $ ; The color to draw the progress bar. XSize=xsize, $ ; The XSize of the progress bar in Device coordinates. YSize=ysize, $ ; The YSize of the progress bar in Device coordinates. AutoUpdate=autoupdate ; Set this keyword to be in AutoUpDate mode. ; A group leader widget (i.e., a parent parameter) is REQUIRED for MODAL operation. ; Check keywords. IF N_Elements(delay) EQ 0 THEN delay = 5 IF N_Elements(nsteps) EQ 0 THEN nsteps = 10 theStep = (Float(delay) / nsteps) IF N_Elements(message) EQ 0 THEN message = "Operation in progress..." IF N_Elements(title) EQ 0 THEN title = "" IF N_Elements(color) EQ 0 THEN color = !P.Color IF N_Elements(xsize) EQ 0 THEN xsize = 150 IF N_Elements(ysize) EQ 0 THEN ysize = 10 self.autoupdate = Keyword_Set(autoupdate) ; Update self structure. self.delay = delay self.step = theStep self.nsteps = nsteps self.message = message self.title = title self.color = color self.xsize = xsize self.ysize = ysize self.count = 0 self.cancel = 0 self.cancelButton = Keyword_Set(cancelButton) ; Create the widgets. IF N_Elements(parent) EQ 0 THEN BEGIN self.tlb = Widget_Base(Title=self.title, Column=1, Base_Align_Center=1, space = 5) self.parent = -1L ENDIF ELSE BEGIN IF self.cancelButton THEN modal = 0 ELSE modal = 1 self.tlb = Widget_Base(Group_Leader=parent, Modal=modal, Title=self.title, $ Column=1, Base_Align_Center=1, Floating=1) self.parent = parent ENDELSE text = [' ', self.message, ' '] for i = 0,2 do self.labelID[i] = Widget_Label(self.tlb, Value=text[i], /Dynamic_Resize) self.drawID = Widget_Draw(self.tlb, XSize=self.xsize, YSize=self.ysize) Widget_Control, self.tlb, Set_UValue=self self.timeID = widget_label (self.tlb, value = 'Elapsed Time: 0 sec', /dynamic_resize) IF self.cancelButton THEN BEGIN self.cancelID = Widget_Button(self.tlb, Value='Cancel') ENDIF ELSE self.cancelID = -1L ; Position the top-level base. (center it if no position requested) if keyword_set(position) then begin widget_control, self.tlb, xoffset=position[0], yoffset=position[1] endif else begin Device, Get_Screen_Size=screenSize xCenter = screenSize(0) / 2 yCenter = screenSize(1) / 2 geom = Widget_Info(self.tlb, /Geometry) xHalfSize = geom.Scr_XSize / 2 yHalfSize = geom.Scr_YSize / 2 Widget_Control, self.tlb, XOffset = xCenter-xHalfSize, $ YOffset = yCenter-yHalfSize endelse RETURN, 1 END; ----------------------------------------------------------------------------- PRO ShowProgress__Define ; The SHOWPROGRESS class definition. struct = {SHOWPROGRESS, $ ; The SHOWPROGRESS object class. tlb:0L, $ ; The identifier of the top-level base. labelID:lonarr(3), $ ; The identifier of the label widgets. drawID:0L, $ ; The identifier of the draw widget. timeID: 0L, $ ; The identifier of the elapsed time label parent:0L, $ ; The identifier of the group leader widget. cancelID:0L, $ ; The identifier of the CANCEL button. wid:0L, $ ; The window index number of the draw widget. xsize:0L, $ ; The XSize of the progress bar. ysize:0L, $ ; The YSize of the progress bar. color:0L, $ ; The color of the progress bar. autoupdate:0L, $ ; A flag for indicating if the bar should update itself. cancel:0L, $ ; A flag to indicate the CANCEL button was clicked. cancelButton:0L, $ ; A flag to indicate a CANCEL button should be added. message:' ', $ ; The message to be written over the progress bar. title:'', $ ; The title of the top-level base widget. count:0L, $ ; The number of times the progress bar has been updated. startTime:0D, $ ; The time when the widget is started. delay:0L, $ ; The total time the widget is on the display. nsteps:0L, $ ; The number of steps you want to take. step:0.0 $ ; The time delay between steps. } END; ----------------------------------------------------------------------------- PRO Example_Event, event ; Respond to program button events. Widget_Control, event.id, Get_Value=buttonValue, Get_UValue=timer CASE buttonValue OF 'Automatic Mode':timer->start 'Manual Mode': BEGIN ; Updating of Show Progress widget occurs in loop. timer->start count = 0 FOR j=0, 1000 DO BEGIN if j mod 100 EQ 0 THEN BEGIN cancelled = timer->CheckCancel() IF cancelled THEN BEGIN ok = Dialog_Message('User cancelled operation.') timer->Destroy RETURN ENDIF timer->Update, (count * 10.0) count = count + 1 endif Wait, 0.01 ; This is where you would do something useful. ENDFOR timer->destroy ENDCASE 'In Progress Mode': begin timer->start count = 0 FOR j=0L, 100000 DO BEGIN if j mod 100 EQ 0 THEN BEGIN cancelled = timer->CheckCancel() IF cancelled THEN BEGIN ok = Dialog_Message('User cancelled operation.') timer->Destroy RETURN ENDIF timer->Update count = count + 1 endif plot,indgen(1000) ;Wait, 0.01 ; This is where you would do something useful. ENDFOR timer->destroy ENDCASE 'Quit': Widget_Control, event.top, /Destroy ENDCASE END PRO Example_Cleanup, tlb ; Cleanup routine when TLB widget dies. Be sure ; to destroy Show Progress objects. Widget_Control, tlb, Get_UValue=info Obj_Destroy, info[0] Obj_Destroy, info[1] END PRO Example Device, Decomposed=0 TVLCT, 255, 0, 0, 20 tlb = Widget_Base(Column=1, Xoffset=200, Yoffset=200) ; Create an AutoUpDate object. Store in UValue of Button. autoTimer = Obj_New("ShowProgress", tlb, Color=20, Steps=20, Delay=5, /AutoUpdate) button = Widget_Button(tlb, Value='Automatic Mode', UValue=autoTimer) ; Create a Manual Show Progress object. Store in UValue of Button. progressTimer = Obj_New("ShowProgress", tlb, Color=20, /CancelButton) button = Widget_Button(tlb, Value='Manual Mode', UValue=progressTimer) inprogressTimer = Obj_New("ShowProgress", tlb, Color=20, /CancelButton) button = Widget_Button(tlb, Value='In Progress Mode', UValue=inprogressTimer) quiter = Widget_Button(tlb, Value='Quit', UValue='QUIT') Widget_Control, tlb, /Realize, Set_UValue=[autoTimer, progressTimer, inprogressTimer] XManager, 'example', tlb, /No_Block, Cleanup='example_cleanup' END