
*========================================================================
* Custom Class FORMMGR - manages all forms in either SDI or MDI Apps.
*
* Author: A.A.Katz
* V.1.0   9-2-1995
* V.1.1   10-18-1995
*
*------------------------------------------------------------------------
*
* (c) 1995 A.A.Katz, All Rights Reserved
*
* License:
*            This object is FREEWARE. You are free to include this
*            object in your own Visual dBASE programs and distribute it
*            freely as part of your programs as object code.
*
*            You may freely distribute this object to other developers
*            for inclusion in their programs.
*           
*            You may NOT sell this object or use it in any way wherby
*            it is NOT included in a program.
*
*            You may make any revisions desired to this code.
*
*            Have fun with it!                  ---AAK
*
*----------------------------------------------------------------------
*
* *********  Warning: This code is not warranteed in any way whatsoever.
*            Use it at your own risk. Your use of this code releases AAK
*            from any liability whatsoever as regards the use, suitabil-
*            ity or quality of this object.
*
*------------------------------------------------------------------------
*
* Purpose:   Managing all form openings, closings and display in SDI and
*            MDI Visual dBASE programs.
*
*            When used in default SDI mode, (single instances of forms),
*            this Manager Object conserves GDI resources by maintining
*            only two Open() modeless forms simultaneously:
*                 1. A desktop form (optional)
*                 2. A single SDI form with focus
*            FORMMGR closes existing forms before opening another when
*            you invoke the FORMMGR Open() method.
*
*            When used in MDI mode (This.MDI = .t.), this Form
*            Manager Object does not close any forms. You close them

*            Note: This form manager should be used ONLY for main 
*            forms. Dependent Modal Child forms should be instantiated
*            into a property of their parent form, so they will RETURN
*            properly when done.
*
*            Syntax: in a startup.prg file:
*
*             Set proc to C:\Visualdb\Custom\Manager.cc Addi
*
*             && Note that you do not have to SET PROCEDURE for any
*             of the forms you will open with FormMgr.
*
*            _app.aForm = New FormMgr()
*            _app.aForm.MDI = .t.   (only if you're doing multiple
*                                    instances of your forms)
*            _app.aForm.cDesktop = 'Backform'  
*                                  && Set desktop class (also optional)
*    
*            _app.aForm.Instance('My.wfm','Myform')     && Instantiate
*            _app.aForm.Instance('Your.wfm','Yourform') && Instantiate
*            _app.aForm.Instance('Back.wfm','BackForm') && Instantiate Desktop (background)
*                                && form
*
*            _app.aForm.Open('Back.wfm','Backform')    &&open desktop
*
*            If your desktop has a main menu, you're all set to go...
*            If not, you may want to open your first main form.
*
*            To close forms, just issue the normal Form.Close()
*            To close the application, sessions and forms, use
*            _app.aForm.CloseAll()
             
* Warning - do not RELEASE() any forms instantiated with this Forms 
*           Manager, or you may get errors.

*========================================================================


CLASS FORMMGR OF ARRAY()
This.cDesktop = ' '		&& Blank Background Form Class Name
                                && Needed to prevent background
                                && Desktop from being closed before
                                && application QUITs 
                                && This is only used by SDI apps.
This.nDesktop = 0 && No "desktop" background form yet defined
This.MDI = .f.                  && Property - Is app MDI? Defaults to 
                                && No
*-----------------------------------------------------------------
   * Method INSTANCE - Instantiates form, does not open!
   *                   Call directly only to pre-instantiate major
   *                   forms to improve form paint performance.
   *                   Use for MAIN SDI forms only - forms that
   *                   allow a single instance. Modal dialogs and
   *                   child forms should be instantiated from within
   *                   their parent forms to maintain communication
   *                   between them.
   
   *                   For MDI applications, a call to this class'
   *                   Open() method runs this method automatically.
   
   * Params:  Form File name, Class Name
   * Example: _app.aForm.Instance('My.wfm','MyForm')
*-----------------------------------------------------------------

Function Instance(cFileName,cClassName)
Local n
cClassName = iif(empty(cClassname),;  && Default classname if not sent
   Substr(cFilename,1, at('.',cFilename)-1)+'FORM',cClassName)


If This.MDI                       && If this is an MDI application,

   n = This.NewInstance(cFileName,cClassName) && Create new instance.
   
Else                                 && If this is an SDI application,

                                       && See if class is already
   n = This.ExistingClass(cClassName)  && instantiated.
                                         
   If n = 0                              && If not, Instantiate.
      n = This.NewInstance(cFileName,cClassName)
   Endif
   
Endif

Return n   && If Instance() returns 0, Instantiation has failed.

*------------------------------------------------------------------
    *Method: ExistingClass - check to see if there is already a
    *                        class isntantiated for this form. If
    *                        so, it returns the instance number.
    *                        If not, it returns 0
*------------------------------------------------------------------
Function ExistingClass(cClassName)

Private n

For n = 1 to aLen(This)            && Traverse array

   If Type('This[n]') = 'O' .and. .not. Empty(This[n])
                                     && If element is Object Reference
                                     && and has a valid object stored.
                                     
      If Upper(This[n].ClassName) = Upper(cClassName)
                                     && If the object's Classname 
                                     && Matches the parameter sent,
         Return n                    && Return this element's index.

      Endif

   Endif
   
EndFor
Return 0


*-----------------------------------------------------------------
    *Method: NewInstance - Instantiates object from class param.
    *                      Creates a new element or recycles an
    *                      existing element in this Formmgr array.
    *                        
    *                      Creates a dynamic Instance property in
    *                      each form for referencing.
    *                               
    *                      Stores OnClose event under a new 
    *                      Function Pointer Name and
    *                      adds the Formmgr's cleanup method
    *                      to the form being instantiated
    *
    *                      This method uses dynamic method assign-
    *                      ments to assign OnClose code to the
    *                      form instantiated and then call any 
    *                      OnClose method that you may have coded
    *                      into the form. Runs ours first, then yours.
*-----------------------------------------------------------------
Function NewInstance(cFileName,cClass)

Private cClassName,n           && Declare privates for macro
cClassName = iif(empty(cClass),;   && Default classname if not sent
   Substr(cFilename,1, at('.',cFilename)-1)+'FORM',cClass)
      
Set proc to (cFileName) addi   && Load class into memory

n = This.NextEmptyForm()       && Get "next available element" number

This[n] = New &cClassName.()   && Instantiate into array
This[n].nInstance = n          && Dynamic property to store instance

                                                                           && Set desktop nDesktop property
If   upper(cClassName) = upper(This.cDesktop)       && To desktop instance No.
    This.nDesktop = n
Endif

   
                                && number.
Return n                       && Return new element number



*-----------------------------------------------------------------
    *Method NextEmptyForm - Find the next available element
    *                       or recycle empty one if available
*-----------------------------------------------------------------
Function NextEmptyForm
Private n

For n = 1 to aLen(This)          && Traverse the array
                                 && See if there's an empty one.
   If type('This[n]') # 'O' .or.;&& or one with released object
      (Type('This[n]') = 'O' .and. Empty(This[n]))

      Return n                   && If so, return this number to
                                 && recycle this element.
   Endif

EndFor
This.Grow(1)                     && If none to recycle,
                                 && add one.

                               
This[aLen(This)] = ' '         && Initialize element (insurance)
Return aLen(This)              && Return the new element number.

    
    
*-----------------------------------------------------------------
   *Method: Open - opens forms
*-----------------------------------------------------------------

Procedure Open(cFileName,cClass)
Private n, cClassName             &&Private vars for macros
cClassName  = iif(empty(cClass),;    && Default Classname if not sent
   Substr(cFilename,1, at('.',cFilename)-1)+'FORM',cClass)

If This.MDI                       && If this application is MDI
                                  && (Multiple instances allowed)

   Create Session     
 
   n = This.Instance(cFileName,cClassName) && Instantiate Class

   If n > 0
      This[n].Open()                    && Open() the form

   Endif

Else                                  && If this form is SDI

   n = This.ExistingClass(cClassname) && See if instance exists.
   If n > 0                      && If it does,
      If This[n].hWnd # 0        && If already open, 
         This[n].SetFocus()      && just set focus
      Else
         This.CloseLastForm()    && Otherwise, close current open
                                 && Form (unless background)
         This[n].Open()          && and open the new one
      Endif
      
   else                          && If no instance exists,

      This.CloseLastForm()       && Close currently open form.
      n = This.Instance(cFileName,cClassName)  &&Instantiate and  
                                 && return number of this class

     
      This[n].Open()             && Open Form.
 
   Endif
   
Endif  
Return n                         &&return instance number

*-----------------------------------------------------------------
*     Method: ReadModal - opens single-instance Modal Child Forms
*-----------------------------------------------------------------

Procedure ReadModal(cFileName,cClass)
Private n, cClassName             &&Private vars for macros
cClassName = iif(empty(cClass),;   && Default Classname if not sent
   Substr(cFilename,1, at('.',cFilename)-1)+'FORM',cClass)
                             
   n = This.ExistingClass(cClassname) && See if instance exists.
   If n > 0                      && If it does,
   
      If This[n].hWnd # 0        && If already open, 
         This[n].SetFocus()      && just set focus
      Else
 
         This[n].MDI = .f.
         This[n].ReadModal()          && and open the new one
      Endif
      
   else                          && If no instance exists,

      n = This.Instance(cFileName,cClassName)  &&Instantiateand
                                 && return number of this class
  
      This[n].MDI = .f.
      This[n].ReadModal()                && Open Form.

   Endif
    
Return n
*---------------------------------------------------------------------
  *Method: CloseLastForm - Closes previous SDI form (SDI ONLY)
*---------------------------------------------------------------------
Procedure CloseLastForm

Private n

For n = 1 to aLen(This)          && Traverse array

   If Type('This[n]') = 'O' .and.;  && If this is a valid reference
      .not. empty(This[n])          && And not release()ed.
      If n # This.nDesktop .and. This[n].hWnd # 0       && and is already open
                                                                                        && and not the desktop Class
        This[n].Close()            && Close this last form
                                  
      Endif

   Endif
   
Next

*---------------------------------------------------------------------
*   Method RELEASE - Used for forms opened with ReadModal()
*                    Releases Form, Object Reference and 
*                    Closes Procedure file
*---------------------------------------------------------------------

Procedure Release(cFileName,cClassName)
Private n
n = 0
cClassName = iif(empty(cClassname),;
   Substr(cFilename,1, at('.',cFilename)-1)+'FORM',cClassName)
n = This.ExistingClass(cClassName)           && find array element

If n > 0                                     && If found
                                             && and reference is valid
   If Type('This[n]') = 'O' .and. .not. Empty(This[n])
      oControl = This[n].First
      Do 
        if oControl.hWnd > 0
          oControl.Release()
        endif
        oControl = oControl.Before
      Until oControl.Name = This[n].First.Name

      This[n].Release()                      && Release form
      Close Proc (cFileName)                 && Close procedure
      This[n] = ' '                          && Release Reference
   Endif
Endif


*----------------------------------------------------------------------
  *Method CloseAll() - closes and releases all open forms
  *                    If param lCloseDesk = .f., desktop
  *                    will remain, all else is closed.
  *                    If param lQuitProgram = .t., shell
  *                    will be restored, if in design mode,
  *                    and Clear All issued, which will
  *                    bring you back to dBASE, otherwise
  *                    progam will QUIT to Windows.
*----------------------------------------------------------------------

Procedure CloseAll(lCloseDesk,lQuitProgram,lReleaseToo)
    &&Default behavior is to close all open forms
    &&Leave desktop form open -  if there is one
    &&Do not release forms unless lReleaseToo or lQuitProgram
  
If empty(lCloseDesk)   &&Close desktop, or leave open
   lCloseDesk = .f.
endif
If empty(lQuitProgram) &&Quit program when all is done?
   lQuitProgram = .f.
endif
If lQuitProgram
   lReleaseToo = .t.
Endif
If empty(lReleaseToo)   &&Normally closes and releases. This one
   lReleaseToo = .f.         &&prevents the release()
Endif

Private n,nBackForm
nBackForm = 0

                                           
For n = 1 to aLen(This)               && Traverse array.

    If Type('This[n]') = 'O' .and. .not. empty(This[n])  && If this is an Object Reference
        
       If n # This.nDeskTop    && if not desktop
         
          IF This[n].hWnd # 0    && if form is open,
              This[n].Close()       && Close it
          
              If lReleaseToo .and. .not. Empty(This[n])
                                               && Checks again in case OnClose issues Release
 
                 This[n].Release()            
                 This[n] = ''      
             Endif
 
          Endif
          
       Endif

    Endif    

Next
If lQuitProgram
   Close Databases
Endif

If lCloseDesk                && If desktop is to be released, too
   If This.nDesktop > 0              && And desktop exists
       If .not. empty(This[This.nDesktop])   && And is a valid object
           If This[This.nDesktop].hWnd # 0
               This[This.nDesktop].Close()   && Close and release the desktop form
            Endif
            If .not. Empty(This[This.nDesktop]) && Check again in case of Release()
               This[This.nDesktop].Release() && Release desktop
               This[This.nDesktop] = ''
            Endif
       Endif
   Endif
Endif

If lQuitProgram
   Close All
   if set('DESIGN') = 'ON'
      shell(.t.)
      cancel
   else
      Quit
   endif                     
Endif

ENDCLAS *------------------------Eoc()