[Review for external] A console-style widget for text output with an IDL Virtual Machine Distribution

One of the challenges when creating an IDL Virtual Machine (VM) distribution is that the IDL Console is not available. This means that you cannot use very helpful commands like print in your IDL VM program or .sav file. One method to get around this is to create a text widget to send some helpful information or output to.
For this example, there is a simple widget with some buttons and a large text field. In order to print to the widget, the command widget_print can be used in a similar way to print or printf. Here is a description of what each button does:
- Reset Display - This resets the display by clearing the text widget and setting the default number of vertical lines to 20.
- Save Display - This saves a text file of all of the information in the widget. If there is not a file, then IDL will create a new file or, if a file does exist, then saving the output simply adds additional lines to the current text file. When you press this button, information is displayed in the widget that tells you where the file is saved to and what the name of the output file is.
- Add Line - This button adds an additional line at the bottom of the text widget
- Remove Line - This button removes an additional line at the bottom of the text widget.
- Exit - Pressing this button closes the widget application.
One important thing to note is that the widget is created automatically when you use the command widget_print. This way you don't have to worry about errors coming from the widget not existing. You are also only allowed to have one print widget open at a time. This program can also be extended to suit different printing conditions. As is, this will automatically print arrays, but with no specific formatting other than wrapping to new lines. This means that printing a 2D array will simply create a new line for each index instead of printing one row per line as the IDL Console automatically does. This logic can be changed within the widget_print procedure below and added if you want.
One nice addition to the code is that if you print from a variable, i.e. instead of widget_print, 'test' you use widget_print, test where test is a variable or array, widget print can recognize that the argument to be printed is a variable and will display that variable before printing the contents of the variable.
It's also important to point out that this may not work for printing out every type of IDL variable, structure, object, or hash. The code takes advantage of the keyword /IMPLIED in the function STRING which should be able to convert most inputs to arrays of strings, but has not been tested for every array type. This example was meant to be more simple so that you can easily make additions/changes to your widget_print formatting.
Here is the code for running widget_print and, below that, is an example of widget_print being used in another widget application. This first portion of code will need to be saved as widget_print.pro and, if placed in you IDL search path, can be called from the IDL Console with widget_print just like print. The second section of code should be saved as wdraw_widget_print.pro and can be run by typing wdraw_widget_print into the IDL Console after compiled.
;========================================================================
;widget_print code
;========================================================================
pro vm_output_window_event, event
compile_opt idl2
;get the widget id of the information widget
widget_control, event.top, get_uvalue = info
;if there is an event, then check which widget button is pressed
CASE TAG_NAMES(event, /STRUCTURE_NAME) OF
'WIDGET_BUTTON': BEGIN
;Get the visible label of the button pressed
widget_control, event.id, get_value=name
;clear windows if Reset Display is pressed
if (name eq 'Reset Display') then begin
widget_control, info.console, set_value = 'IDL> '
widget_control, info.console, ysize = 35
info.nlines = 35
widget_control, event.top, set_uvalue = info
endif
if (name eq 'Add Line') then begin
widget_control, info.console, ysize = info.nlines + 1
info.nlines = info.nlines + 1
widget_control, event.top, set_uvalue = info
endif
if (name eq 'Remove Line') then begin
info.nlines = info.nlines - 1
widget_control, info.console, ysize = info.nlines
widget_control, event.top, set_uvalue = info
endif
if (name eq 'Save Display') then begin
widget_control, info.console, get_value = console_text
cd, current = c
if (file_which(c,'save_output.txt') eq '') then begin
;create a new file if the log doesn't exist
OPENW, outfile, c + path_sep() +'save_output.txt', /GET_LUN
printf, outfile, 'Log save date/timet: ' + systime()
endif else begin
;append to an existing log if the file does exist
OPENU, outfile, c + path_sep() +'save_output.txt', /GET_LUN, /APPEND
printf, outfile, 'Log save date/timet: ' + systime()
endelse
;save all of the lines of text with out IDL>
for i=0,n_elements(console_text)-1 do begin
;save the output only if there is something on the line
printf, outfile, strmid(console_text[i],5)
endfor
;print extra space and close file
printf, outfile, ''
FREE_LUN, outfile
;print the location of the
widget_print, 'Output Saved to: ' + c + path_sep() +'save_output.txt'
endif
;close the widget if the 'Exit' button is pressed
if (name eq 'Exit') then WIDGET_CONTROL, event.top, /destroy
END
;resize event for the widget base
'WIDGET_BASE': BEGIN
;re-size all informational labelslabels
console_widget = info.console
widget_control, console_widget, xsize = event.x*(78/500.)
;re-size base widget only in x-direction
widget_control, event.id, xSize=event.x
END
ELSE:
ENDCASE
end
pro vm_output_window
compile_opt idl2
;this line only allows one instance of the vm_output_window widget
IF ( XREGISTERED('vm_output_window') NE 0 ) THEN RETURN
;xpixel size of the widget base
x_size = 500
;create a simple widget
wBase = WIDGET_BASE(/COLUMN, UNAME = 'vm_output_window',$
xsize = x_size, /TLB_SIZE_EVENTS)
;realize the widget base, i.e. display it
WIDGET_CONTROL, wBase, /REALIZE
;add a widget base for some buttons and add some buttons
wBase2 = WIDGET_BASE(wBase, /ROW, UNAME = 'button_display_widget', /align_center)
refresh_button = WIDGET_BUTTON(wBase2, value = 'Reset Display', xsize = 85, /align_center)
save_button = WIDGET_BUTTON(wBase2, value = 'Save Display', xsize = 75, /align_center)
add_button = WIDGET_BUTTON(wBase2, value = 'Add Line', xsize = 75, /align_center)
remove_button = WIDGET_BUTTON(wBase2, value = 'Remove Line', xsize = 75, /align_center)
exit_button = WIDGET_BUTTON(wBase2, value = 'Exit', xsize = 32, /align_center)
;make the text widget for printing to, ysize is 35 lines
nlines = 35
text_display = widget_text(wBase, xsize = 77, ysize = nlines, $
/SCROLL, /wrap, /align_center, value='IDL> ')
;create a structure of the console text widget ID and the number of lines
info = {console:text_display, $
nlines:nlines}
;label widgets
WIDGET_CONTROL, wBase, SET_UVALUE = info
;save our main widget base ID as a system variable
DEFSYSV, '!print_widget', wBase
;declare our widget event handler
XMANAGER, 'vm_output_window', wBase, /NO_BLOCK
end
pro widget_print, arg1
compile_opt idl2
if (arg1 eq !null) then begin
!null = dialog_message('Input variable is !NULL, returning')
return
endif
;make sure that the vm_output window exists, if not then start it
if ~XREGISTERED('vm_output_window') then vm_output_window
;proceed with printing!
widget_control, !print_widget, get_uvalue = info
;check how many lines are there
widget_control, info.console, get_value = current_text
;convert the argument to a string
arg = string(arg1,/implied)
;find the current scope level
scope = scope_level()
;find the variable sin the above scope
variables = scope_varname(level = scope-1)
;get detailed inormation on the variable sin the above scope
vars_undefined = make_array(variables.length, /int)
for i= 0, variables.length-1 do begin
help, name = variables[i], level = scope-1, output = var_scope
vars_undefined[i] = strmatch(strlowcase(var_scope),'*undefined*')
endfor
;try to extract the name of the variable that represents arg for
;more information when printing
if (variables[0] ne '') then begin
for i = 0, variables.length-1 do begin
;only check variable sthat are defined in the above scope
if (vars_undefined[i] ne 1) then begin
;get specific variable value from above scope
var_scope = string(scope_varfetch(variables[i], level = scope-1, /ENTER),/implied)
;compare if not a structure as an input argument
if array_equal(arg, var_scope) then begin
name = variables[i]
break
endif
endif
endfor
endif
;change the array to be printed if we find the variable that it is coming from
if (name ne !null) then begin
temp_array = make_array(n_elements(arg)+1, /string)
temp_array[0] = 'widget_print, ' + name
temp_array[1] = arg
arg = temp_array
endif
;check if it is the first line being printed in the widget or not
if( (n_elements(current_text) eq 1) and (current_text[0] eq 'IDL> ')) then begin
;write over current line so there is not a blank line at the top
widget_control, info.console, set_value = 'IDL> ' + arg[0]
if (n_elements(arg) gt 1) then $
widget_control, info.console, set_value = ' ' + arg[1:*], /append
endif else begin
;append to new line if there is already a line
widget_control, info.console, set_value = 'IDL> ' + arg[0], /append
if (n_elements(arg) gt 1) then $
widget_control, info.console, set_value = ' ' + arg[1:*], /append
endelse
;check to see if there are more lines in the window than the window size
widget_control, info.console, get_value = current_text
if (n_elements(current_text) gt 5) then $
widget_control, info.console, set_text_top_line = n_elements(current_text)-1
end
;========================================================================
;end of widget_print
;========================================================================
;========================================================================
;wdraw_widget_print code
;modified from the file wdraw.pro and original file can be found by
;typing '.edit wdraw' wihtout quotes into the IDL Console
;========================================================================
PRO wdraw_widget_print_event, event
WIDGET_CONTROL, event.id, GET_UVALUE = eventval
; Perform actions based on the User Value of the event:
CASE eventval OF
'DRAW_WIN_EVENT': BEGIN
IF event.press EQ 1 THEN BEGIN
; Print the device coordinates of the cursor
widget_print, 'X = ' + strtrim(event.x,1)
widget_print, 'Y = ' + strtrim(event.y,1)
ENDIF
END
'DONE': WIDGET_CONTROL, event.top, /DESTROY
ENDCASE
END
PRO wdraw_widget_print, XSIZE=x_size, YSIZE=y_size, GROUP=GROUP
; Remember the current window so it can be restored
swin = !D.WINDOW
base = WIDGET_BASE(TITLE = 'Simple Draw Widget Example', $
/COLUMN)
WIDGET_CONTROL, /MANAGED, base
; The DONE button:
button1 = WIDGET_BUTTON(base, $
UVALUE = 'DONE', $
VALUE = 'DONE')
; A label containing some instructions:
wdrlabel = WIDGET_LABEL(base, $
VALUE = 'Press the left mouse button to see cursor coordinates.')
; A widget called 'draw' is created.
draw = WIDGET_DRAW(base, $
/BUTTON_EVENTS, $ ;generate events when buttons pressed
/FRAME, $
UVALUE = 'DRAW_WIN_EVENT', $
RETAIN = 2, $ ;make sure draw is redrawn when covered
XSIZE = 400, $
YSIZE = 500)
; Realize the widgets:
WIDGET_CONTROL, base, /REALIZE
; Get the window number from the draw widget. This can only be done
; after the widget has been realized.
WIDGET_CONTROL, draw, GET_VALUE=win_num
; Use TVSCL to display an image in the draw widget. Set the window for
; the TVSCL command since there may be other draw windows.
WSET, win_num
orig_image = DIST(50)
TVSCL, REBIN(orig_image, 400, 500)
WSET, swin ; Restore the original window
; Hand off control of the widget to the XMANAGER:
XMANAGER, "wdraw_widget_print", base, GROUP_LEADER=GROUP, /NO_BLOCK
END
;========================================================================
; end of wdraw_widget_print
;========================================================================
reviewed by ZN 9/22/2015