X
88 Rate this article:
No rating

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

Zachary Norman

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