;+
; NAME:
;    ERROR_MESSAGE
;
; PURPOSE:
;
;    The purpose of this function  is to have a device-independent
;    error messaging function. The error message is reported
;    to the user by using DIALOG_MESSAGE if widgets are
;    supported and MESSAGE otherwise.
;
;    In general, the ERROR_MESSAGE function is not called directly.
;    Rather, it is used in a CATCH error handler. Errors are thrown
;    to ERROR_MESSAGE with the MESSAGE command. A typical CATCH error
;    handler is shown below.
;
;       Catch, theError
;       IF theError NE 0 THEN BEGIN
;          Catch, /Cancel
;          ok = Error_Message()
;          RETURN
;       ENDIF
;
;    Error messages would get into the ERROR_MESSAGE function by
;    throwing an error with the MESSAGE command, like this:
;
;       IF test NE 1 THEN Message, 'The test failed.'
;
; AUTHOR:
;
;   FANNING SOFTWARE CONSULTING
;   David Fanning, Ph.D.
;   1645 Sheely Drive
;   Fort Collins, CO 80526 USA
;   Phone: 970-221-0438
;   E-mail: davidf@dfanning.com
;   Coyote's Guide to IDL Programming: http://www.dfanning.com/
;
; CATEGORY:
;
;    Utility.
;
; CALLING SEQUENCE:
;
;    ok = Error_Message(the_Error_Message)
;
; INPUTS:
;
;    the_Error_Message: This is a string argument containing the error
;       message you want reported. If undefined, this variable is set
;       to the string in the !Error_State.Msg system variable.
;
; KEYWORDS:
;
;    ERROR: Set this keyword to cause Dialog_Message to use the ERROR
;       reporting dialog. Note that a bug in IDL causes the ERROR dialog
;       to be used whether this keyword is set to 0 or 1!
;
;    INFORMATIONAL: Set this keyword to cause Dialog_Message to use the
;       INFORMATION dialog instead of the WARNING dialog. Note that a bug
;       in IDL causes the ERROR dialog to be used if this keyword is set to 0!
;
;    TITLE: Set this keyword to the title of the DIALOG_MESSAGE window. By
;       default the keyword is set to 'System Error' unless !ERROR_STATE.NAME
;       equals "IDL_M_USER_ERR", in which case it is set to "Trapped Error'.
;
;    TRACEBACK: Setting this keyword results in an error traceback
;       being printed to standard output with the PRINT command. Set to
;       1 (ON) by default. Use TRACEBACK=0 to turn this functionality off.
;
; OUTPUTS:
;
;    Currently the only output from the function is the string "OK".
;
; RESTRICTIONS:
;
;    The WARNING Dialog_Message dialog is used by default.
;
; EXAMPLE:
;
;    To handle an undefined variable error:
;
;    IF N_Elements(variable) EQ 0 THEN $
;       ok = Error_Message('Variable is undefined')
;
; MODIFICATION HISTORY:
;
;    Written by: David W. Fanning, 27 April 1999.
;    Added the calling routine's name in the message and NoName keyword. 31 Jan 2000. DWF.
;    Added _Extra keyword. 10 February 2000. DWF.
;    Forgot to add _Extra everywhere. Fixed for MAIN errors. 8 AUG 2000. DWF.
;    Adding call routine's name to Traceback Report. 8 AUG 2000. DWF.
;    Added ERROR, INFORMATIONAL, and TITLE keywords. 19 SEP 2002. DWF.
;    Removed the requirement that you use the NONAME keyword with the MESSAGE
;      command when generating user-trapped errors. 19 SEP 2002. DWF.
;    Added distinctions between trapped errors (errors generated with the
;      MESSAGE command) and IDL system errors. Note that if you call ERROR_MESSAGE
;      directly, then the state of the !ERROR_STATE.NAME variable is set
;      to the *last* error generated. It is better to access ERROR_MESSAGE
;      indirectly in a Catch error handler from the MESSAGE command. 19 SEP 2002. DWF.
;    Change on 19 SEP 2002 to eliminate NONAME requirement did not apply to object methods.
;      Fixed program to also handle messages from object methods. 30 JULY 2003. DWF.
;    Removed obsolete STR_SEP and replaced with STRSPLIT. 27 Oct 2004. DWF.
;    Made a traceback the default case without setting TRACEBACK keyword. 19 Nov 2004. DWF.
;    Added check for window connection specifically for CRON jobs. 6 May 2008. DWF.
;-
;###########################################################################
;
; LICENSE
;
; This software is OSI Certified Open Source Software.
; OSI Certified is a certification mark of the Open Source Initiative.
;
; Copyright 1999-2008 Fanning Software Consulting
;
; 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.
;
;###########################################################################


FUNCTION ERROR_MESSAGE, theMessage, Error=error, Informational=information, $
   Traceback=traceback, NoName=noname, Title=title, _Extra=extra

   On_Error, 2
   
   ; Check for presence and type of message.
   
   IF N_Elements(theMessage) EQ 0 THEN theMessage = !Error_State.Msg
   s = Size(theMessage)
   messageType = s[s[0]+1]
   IF messageType NE 7 THEN BEGIN
      Message, "The message parameter must be a string.", _Extra=extra
   ENDIF
   IF N_Elements(traceback) EQ 0 THEN traceback = 1
   
   ; Get the call stack and the calling routine's name.
   Help, Calls=callStack
   callingRoutine = (StrSplit(StrCompress(callStack[1])," ", /Extract))[0]
   
   
   
   
   ; Are widgets supported?
   widgetsSupported = ((!D.Flags AND 65536L) NE 0)
   
   ; It is not enough to know if widgets are supported. In CRON jobs, widgets are
   ; supported, but there is no X connection and pop-up dialogs are not allowed.
   ; Here is a quick test to see if we can connect to a windowing system. If not,
   ; then we are going to assume widgets are not supported.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /CANCEL
      widgetsSupported = 0
      GOTO, testWidgetSupport
   ENDIF
   theWindow = !D.Window
   Window, /FREE, XSIZE=5, YSIZE=5, /PIXMAP
   Catch, /CANCEL
   
   testWidgetSupport: ; Come here if you choke on creating a window.
   IF !D.Window NE theWindow THEN BEGIN
      WDelete, !D.Window
      IF theWindow GE 0 THEN WSet, theWindow
   ENDIF
   
   IF widgetsSupported THEN BEGIN
   
      ; If this is an error produced with the MESSAGE command, it is a trapped
      ; error and will have the name "IDL_M_USER_ERR".
      IF !ERROR_STATE.NAME EQ "IDL_M_USER_ERR" THEN BEGIN
   
         IF N_Elements(title) EQ 0 THEN title = 'Trapped Error'
   
            ; If the message has the name of the calling routine in it,
            ; it should be stripped out. Can you find a colon in the string?
   
         ; Is the calling routine an object method? If so, special processing
         ; is required. Object methods will have two colons together.
         doublecolon = StrPos(theMessage, "::")
         IF doublecolon NE -1 THEN BEGIN
   
            prefix = StrMid(theMessage, 0, doublecolon+2)
            submessage = StrMid(theMessage, doublecolon+2)
            colon = StrPos(submessage, ":")
            IF colon NE -1 THEN BEGIN
   
               ; Extract the text up to the colon. Is this the same as
               ; the callingRoutine? If so, strip it.
               IF StrMid(theMessage, 0, colon+StrLen(prefix)) EQ callingRoutine THEN $
                  theMessage = StrMid(theMessage, colon+1+StrLen(prefix))
            ENDIF
         ENDIF ELSE BEGIN
   
            colon = StrPos(theMessage, ":")
            IF colon NE -1 THEN BEGIN
   
               ; Extract the text up to the colon. Is this the same as
               ; the callingRoutine? If so, strip it.
               IF StrMid(theMessage, 0, colon) EQ callingRoutine THEN $
                  theMessage = StrMid(theMessage, colon+1)
            ENDIF
   
         ENDELSE
   
   
         ; Add the calling routine's name, unless NONAME is set.
         IF Keyword_Set(noname) THEN BEGIN
            answer = Dialog_Message(theMessage, Title=title, _Extra=extra, $
               Error=error, Information=information)
         ENDIF ELSE BEGIN
            answer = Dialog_Message(StrUpCase(callingRoutine) + ": " + $
               theMessage, Title=title, _Extra=extra, $
               Error=error, Information=information)
         ENDELSE
   
      ENDIF ELSE BEGIN
   
         ; Otherwise, this is an IDL system error.
         IF N_Elements(title) EQ 0 THEN title = 'System Error'
   
         IF StrUpCase(callingRoutine) EQ "$MAIN$" THEN $
            answer = Dialog_Message(theMessage, _Extra=extra, Title=title, $
               Error=error, Information=information) ELSE $
         IF Keyword_Set(noname) THEN BEGIN
            answer = Dialog_Message(theMessage, _Extra=extra, Title=title, $
               Error=error, Information=information)
         ENDIF ELSE BEGIN
            answer = Dialog_Message(StrUpCase(callingRoutine) + "--> " + $
               theMessage, _Extra=extra, Title=title, $
               Error=error, Information=information)
         ENDELSE
      ENDELSE
   ENDIF ELSE BEGIN
         Message, theMessage, /Continue, /NoPrint, /NoName, /NoPrefix, _Extra=extra
         Print, '%' + callingRoutine + ': ' + theMessage
         answer = 'OK'
   ENDELSE
   
   ; Provide traceback information if requested and this is NOT an informational message.
   IF Keyword_Set(traceback) AND ~Keyword_Set(informational)THEN BEGIN
      Help, /Last_Message, Output=traceback
      Print,''
      Print, 'Traceback Report from ' + StrUpCase(callingRoutine) + ':'
      Print, ''
      FOR j=0,N_Elements(traceback)-1 DO Print, "     " + traceback[j]
   ENDIF
   
   RETURN, answer
END ; ----------------------------------------------------------------------------

