;+ ; NAME: ; cw_unit_field ; ; PURPOSE: ; This is similiar to the cw_field widget in IDL. However, along with the ; data field the user also enters units of the data. The widget can be set ; up so that either the units are always fixed in the display, or they can be ; changed by the user. This is a very useful widget for people working in an ; environment where everybody has their own favorite units. The behaviour is ; designed to be very intuitive. The user can do five things in the text ; field. ; 1. Type in new value and unit. ; 2. Type in new value only and not change unit. ; 3. Type in new unit only and not change value. ; 4. Type in new value without a unit. ; 5. Type in new unit without a value. ; As mentioned above, two modes exist for this widget. "Keep" keyword set or not set. ; When the keep keyword is set, all further unit changes input by the user are ; changed back to initial units. For example, if the widget was created with kilometers ; as the default then typing in 1 mile will cause the display to change to 1.609 km. ; KEEP KEYWORD SET MODE: ; Responses to each above items. ; 1. Number and units will be changed to default units. i.e. if default is kilometers, ; typing in 1 nmi (nautical mile) will cause the display to change to 1.852 km. ; 2. No further action will take place ; 3. Widget will take the new entry and change it back to the default units. i.e. if ; the entry was originally 1 km and the user changed the km to mile then the widget ; will assume that the user wishes to change 1 mile to kilomters and display 1.609 km. ; 4. Only entering a value will cause the default unit to be displayed after pressing return. ; 5. Only entering a unit with no number will cause NaN to be displayed. ; KEEP KEYWORD NOT SET. ; 1. New units will be taken as the current default and no further action will take place. ; 2. No further action will take place. ; 3. Widget will take the new unit and change the value by the appropriate conversion factor. ; i.e. if the original entry was 1.0 km and the user changed the unit only to mile then the ; widget will redisplay the entry as .62137119 mi. ; 4. Only entering a value will cause the last unit used to be displayed after pressing return. ; 5. Only entering a unit with no number will cause NaN to be displayed. ; BOTH MODES: ; Entering invalid units (like furlongs) will cause the last unit to be displayed. ; ; THESE INSTRUCTIONS SHOULD NOT SCARE OFF DEVELOPERS! THIS IS A VERY INTUITIVE WIDGET THAT WORKS ; EXACTLY AS USERS "EXPECT" IT TO! ; This functionality can easily be extended to additional units and types. At some point in the future ; I am going to add a rate type so that users can change between rates easily (ft/sec to km/hour for example). ; ; CATEGORY: ; Compound Widget ; ; CALLING SEQUENCE: ; Result = cw_unit_field(Parent,lots_of_keywords) ; ; INPUTS: ; Parent: The widget ID of the widget to be the field's parent. ; ; KEYWORD PARAMETERS: ; TITLE: A string containing the text to be used as the label for the ; field. The default is "Input Field:". If title is equal to one space ; then no base will be created for this field. This is useful when trying to ; get widget layouts like you want them. ; ; VALUE: The initial value in the text widget. ; ; UVALUE: A user value to assign to the field cluster. This value ; can be of any type. ; ; FRAME: The width, in pixels, of a frame to be drawn around the ; entire field cluster. The default is no frame. ; ; ; COLUMN: Set this keyword to center the label above the text field. ; The default is to position the label to the left of the text ; field. ; ; ROW: Set this keyword to position the label to the left of the text ; field. This is the default. ; ; XSIZE: An explicit horizontal size (in characters) for the text input ; area. The default is to let the window manager size the ; widget. Using the XSIZE keyword is not recommended. ; ; YSIZE: An explicit vertical size (in lines) for the text input ; area. The default is 1. ; ; ; FONT: A string containing the name of the platform windows font to use ; for the TITLE of the field. ; ; FIELDFONT: A string containing the name of the platform windows font to use ; for the TEXT part of the field. ; ; FORMAT : Format for the text part of the field. If none is specified than ; a format of f5.1 is used. ; ; OUTPUTS: ; This function returns the widget ID of the newly-created widget. ; ; COMMON BLOCKS: ; None. ; ; PROCEDURE: ; Create the widgets, set up the appropriate event handlers, and return ; the widget ID of the newly-created cluster. ; ; SIDE EFFECTS: ; Reasonable error checking is done, but developers should be aware that more ; may be needed for their environment. A good example is double negative signs ; cause the program to get confused. ; EXAMPLE: ; The code below creates a main base with a field cluster attached ; to it. The cluster accepts string input, has the title "Name:", and ; has a frame around it: ; ; base = widget_base() ; dis_unit = cw_unit_field(base1,/row,value='1.0 kilom',xsize=20,ysize=1,$ ; title='distance',/distance,/keep) ; WIDGET_CONTROL, base, /REALIZE ; ; MODIFICATION HISTORY: ; Written by: ; RLK ; Kling Research and Software ; ronn@rlkling.com ; www.rlkling.com ; ; LICENSE ; ; This software is OSI Certified Open Source Software. ; OSI Certified is a certification mark of the Open Source Initiative. ; ; Copyright © 1997-2001 Kling Research and Software. ; ; This software is provided "as-is", without any express or ; implied warranty. In no event will the authors be held liable ; for any damages arising from the use of this software. ; ; Permission is granted to anyone to use this software for any ; purpose, including commercial applications, and to alter it and ; redistribute it freely, subject to the following restrictions: ; ; 1. The origin of this software must not be misrepresented; you must ; not claim you wrote the original software. If you use this software ; in a product, an acknowledgment in the product documentation ; would be appreciated, but is not required. ; ; 2. Altered source versions must be plainly marked as such, and must ; not be misrepresented as being the original software. ; ; 3. This notice may not be removed or altered from any source distribution. ; ; For more information on Open Source Software, visit the Open Source ; web site: http://www.opensource.org. ;- ;-------------------------------------------------------------------------------------- pro convert,entry,unit,old_unit,state ;Performs the conversion between different unit systems. This ;happens after the input units have been converted to standard ;form so that we only have to check on those. To add to these values the developer ;only needs to change the unit_search function and add new elements and arrays in ;cw_unit_field! y_el = where(old_unit eq state.elements) x_el = where(unit eq state.elements) factor = state.array[x_el,y_el] if state.keep eq 1 then factor=1.d/factor entry = strcompress(string(float(entry) * factor),/remove_all) entry = entry[0] if state.keep eq 0 then old_unit = unit else unit = old_unit return end ;--------------------------------------------------- function base_find,value,units,base_unit ;matches up the input unit field with the ;standard abbreviations num=n_elements(units) for i=0,num-1 do begin check=strpos(value,units[i]) ;match up units if check ne-1 then begin value=strcompress(strmid(value,0,check),/remove_all);pull out the #'s return,base_unit[i] endif endfor return,'INVALID' end ;--------------------------------------------------- function unit_search,value,type ;Template for units checking. To add new types just ;duplicate the if-then statements. To add new units just extend the arrays. if type eq 'distance' then begin distance = ['km','kil','ft','fe','nau', 'nm','mi','m','me'] base_unit= ['km', 'km','ft','ft','nmi','nmi','mi','m', 'm'] return,base_find(value,distance,base_unit) endif else if type eq 'angle' then begin angle= [ 'ra', 'rd', 'de', 'dg'] base_unit=['rad','rad','deg','deg'] return,base_find(value,angle,base_unit) endif else if type eq 'time' then begin time = ['min', 'mn','sec', 'sc', 'ho', 'da'] base_unit = ['min','min','sec','sec','hour','day'] return,base_find(value,time,base_unit) endif else if type eq 'none' then return,'' return,'INVALID TYPE' end ;--------------------------------------------- function cw_unit_text_event,event ;Processing of the events in the widget. ;This is where the unit checking begins and some error checking is ;done. Some things will slip through here, bad exponential notation is ;one example. Developers can tailor to their own desires. on_ioerror,label ;This should never happen, but just in case. widget_control,event.id,get_uvalue=state widget_control,event.id,get_value=entry entry=entry[0] ;change into a scalar valid = byte(entry) ;change into ascii values min_valid = min(valid,max=max_valid) if max_valid lt 58 then begin ;To reach this path the user only entered a numerical ;string, Therefore, I will assume that he/she wants to ;retain the old units, unless only spaces and blanks exist. unit = state.old_unit if ((min_valid eq max_valid) and (min_valid eq 32b)) then begin ;Only spaces were entered. Replace string with IEEE NaN. entry = 'NaN' unit = '' endif if ((min_valid eq max_valid) and (min_valid eq 0b)) then begin ;"Nothing" was entered. Replace string with IEEE NaN. entry = 'NaN' unit = '' endif endif else if min_valid gt 96 then begin ;This path is reached when only a unit field is typed ;in. Replace string with IEEE NaN. entry = 'NaN' unit = '' endif else $ unit=unit_search(entry,state.type) ;This is the most common case (I hope!) if unit eq 'INVALID' then begin ;if this happens then invalid units were entered temp = byte(entry) index = where( (temp le 57) and (temp ge 43) ,count) ;pull out the numbers if count gt 0 then entry = string(temp[index]) else entry = '' unit = state.old_unit ;replace with the old units endif if( ((entry eq state.old_entry) and (unit ne state.old_unit)) or (state.keep eq 1))then begin ;conversions here old_unit = state.old_unit convert,entry,unit,old_unit,state endif if state.type eq 'none' then begin ;for unitless numbers temp = byte(entry) index = where( (temp le 57) and (temp ge 43) ,count) ;pull out the numbers if count gt 0 then entry = string(temp[index]) else entry = '' endif ;convert event to the right format for display if entry ne 'NaN' then entry = string(entry,format=state.format) state.old_unit = unit state.old_entry = entry widget_control,event.id,set_value=entry+' '+unit ;always put a blank between # and unit widget_control,event.id,set_uvalue=state return,0 label: ; a string was probably entered in the number field. return,0 end ;---------------------------------------------- function cw_unit_get,event ;called when a value is needed widget_control,event,get_uvalue=state,/no_copy widget_control,state.text,get_value=entry widget_control,event,set_uvalue=state,/no_copy return,entry[0] end ;---------------------------------------------- pro cw_unit_set,id,value ;called when the widget is updated by a widget_control widget_control,id,get_uvalue=state,/no_copy value = value[0] ;create a fake event structure to operate on fake_event = {id : state.text} widget_control,state.text,set_value=value widget_control,id,set_uvalue=state,/no_copy void = cw_unit_text_event(fake_event) ;do the processing return end ;--------------------------------------- function cw_unit_field,Parent,column=column, row=row,$ font=labelFont, frame=frame, title=title,$ uvalue=uvalue, value=value, fieldfont=fieldFont,$ xsize=xsize, ysize=ysize, time=time,$ distance=distance, angle=angle, format=format, $ none=none, keep=keep, grid_layout=grid_layout ;see the keyword list in the header if keyword_set(distance)then type='distance' if keyword_set(angle)then type='angle' if keyword_set(rate)then type='rate' if keyword_set(time) then type='time' if keyword_set(none) then type='none' if keyword_set(column) then column=1 else row=1 if keyword_set(keep) then keep=1 else keep=0 IF KEYWORD_SET(FieldFont) EQ 0 THEN FieldFont='' IF KEYWORD_SET(Frame) EQ 0 THEN Frame=0 IF KEYWORD_SET(labelfont) EQ 0 THEN LabelFont='' IF KEYWORD_SET(Title) EQ 0 THEN Title='Input Field:' IF N_Elements(value) EQ 0 THEN value='' IF KEYWORD_SET(UValue) EQ 0 THEN UValue=0 IF KEYWORD_SET(XSize) EQ 0 THEN XSize=0 IF KEYWORD_SET(YSize) EQ 0 THEN YSize=1 if not keyword_set(grid_layout) then grid_layout = 0 if not keyword_set(format) then format = '(f5.1)' version = fix(!version.release) if version lt 5 then begin print,'cw_unit_field only works with IDL version 5.0 or greater' return,0 endif ;Now build the conversion arrays, the format is a matrix where the off ;diagonal terms are the conversions. The columns represent the "from" ;and the rows represent the "to". For example, in the distance array to convert ;from feet to miles the factor is the 3rd column,4th row value. if type eq 'distance' then begin elements = ['m','km','ft','mi','nmi'] ;names for the columns and rows ; meters kilometers feet miles nautical miles array =[[ 1.d,(1000.d^(-1)), (.3048d^(-1)), (1609.344d^(-1)), (1852.d^(-1))],$ ;meters [ 1000.d, 1.d,(.3048d^(-1))*1d03,(1609.344d^(-1))*1d03,(1852.d^(-1))*1d03],$ ;kilometers [ .3048d, .3048d-03, 1.d, (5280.d^(-1)), (6076.12d^(-1))],$ ;feet [1609.344d, 1.609344d, 5280.d, 1.d, (1.15078d^(-1))],$ ;miles [ 1852.d, 1.852d, 6076.12d, 1.15078d, 1.d]] ;nautical miles endif else if type eq 'angle' then begin elements = ['rad','deg'] ; radians degrees array = [[ 1.d, !radeg], $ ;radians [ !dtor, 1.d]] ;degrees endif else if type eq 'time' then begin elements = ['sec','min','hour','day'] ; seconds minutes hours days array = [[ 1.d, 1.d/60.d, 1.d/3600.d, 1.d/86400.d], $ ;seconds [ 60.d, 1.d, 1.d/60.d, 1.d/1440.d], $ ;minutes [ 3600.d, 60.d, 1.d, 1.d/24.d], $ ;hours [86400.d, 1440.d, 24.d, 1.d]] ;days endif else if type eq 'none' then begin elements = [' '] array = [ 1.d ] endif base=widget_base(Parent,row=row,column=column,$ uvalue=uvalue,$ pro_set_value='cw_unit_set',$ func_get_value='cw_unit_get',$ frame=frame,$ grid_layout=grid_layout,$ kbrd_focus_events=1) if keyword_set(grid_layout) then begin left_base = widget_base(base,/column) right_base =widget_base(base,/column) endif else begin left_base = base right_base = base endelse if title ne ' ' then label=widget_label(left_base,value=title,font=labelFont) unit=unit_search(value,type) text =widget_text(right_base,value=strtrim(value,2)+' '+unit,$ xsize=xsize,ysize=ysize,font=fieldFont,$ /edit,event_func='cw_unit_text_event',$ /kbrd_focus_events) state = { text: text ,$ ;text widget id type : type, $ ;unit type ie distance,angle,time unit :unit ,$ ;actual unit format : format, $ ;format for the field old_unit :unit, $ ;old unit for conversion old_entry : value, $ ;old value elements : elements, $ ;name of conversions array : array , $ ;array of conversions keep : keep} ;if set then don't change units widget_control,text,set_uvalue=state widget_control,base,set_uvalue=state,/no_copy return,base end