/* General-purpose application class. Specific application
classes are descendants of this class. */!!

inherit(Object, #Application,
#(mainWindow  /* The application's main window */
commandLine /* The command line from Windows (a String) */), 2, nil)!!

now(class(Application))!!

/* Install an application with self as the application
  class. The fileName arg is the name of the image
  file to be saved. static and dynamic values are
  the default values for the application. */
Def install(self, fileName, static, dynamic | snapFile)
{ removeCompiler(self);
  removeActor(self);
  removeExtra(self);
  removeMethods(self, class(self), #(removeExtra));
  removeMethods(self, class(Application),
    #(removeCompiler removeActor removeExtra
    removeGlobalOrClass removeNames));
  remove(methods(class(Application)), #removeMethods);
  setWorkText(mainWindow(TheApp:late), nil);  /* Disable printing to Actor display */
  destroyWorkspace(TheApp:late);
  TheApp := new(self);
  cleanup();
  cleanup();  /* 2nd time is to clean up symbols. */
  snap(snapFile := create(setName(new(File), fileName)), static, dynamic);
  close(snapFile);
  exit();
}!!

/* Remove unwanted Globals and Classes that are
  part of the Actor development environment. */
Def removeActor(self)
{ do(#(ClassList MethodList VarList ClVarDialog
    ClassDialog DebugDialog DirtyDialog DirtyCLD SealDialog
    AboutWindow FileWindow WorkEdit WorkWindow
    ToolWindow Browser MethodBrowser DebugWindow
    Inspector BrowEdit DBBrowEdit DebugEdit WorkSpace),
  {using(g) removeGlobal(self, g);
  });
}!!

/* Remove all of the compiler related Classes and
  Global Variables. */
Def removeCompiler(self)
{
/*@@{
    Lex Lex1 CurrentParser Parser Loader Analyzer
    ActorAnalyzer YaccMachine ActorParser
    ParseNode InfixNode EmptyList ListNode BlockNode
    RPN ImmedFunction IfNode IfElseNode LoopNode
    RetNode MsgNode CallNode CompileState Compiler
    IvChain AssgnNode IdNode WCalls Functions
    CVars InfixOps EarlyMethods SpecialMethods
    KeyWords Constants SourceFile Source
    CStream LoadBuf BlockTemps
    Bug Context Debugger
}@@*/

 do(#(Demos ActorApp),
  {using(name) removeGlobal(self, name);
  });
}!!

/* The default removeExtra does nothing.  A removeExtra
  method defined in the specific application's class
  can be used to remove unneeded classes and methods. */
Def removeExtra(self)
{
}!!

/* Used in install and by any sub-class' removeExtra
  method to delete unwanted Globals and Classes. */
Def removeGlobal(self, name)
{ if removeUsing(Actor, name, {})
  then print(name);
    printLine(" removed from Actor.");
  endif;
  if removeUsing(Classes, name, {})
  then print(name);
    printLine(" removed from Classes.");
  endif;
}!!

/* Used in install and by any sub-class' removeExtra
  method to delete unwanted methods. */
Def removeMethods(self, aClass, coll | mdict)
{ (mdict := methods(aClass)) cand do(coll,
  {using(sym)
    if mdict[sym]
    then print(tuple(aClass, ":"));
      printLine(remove(mdict, sym));
    endif;
  });
}!!

/* Remove all the class name symbols.  This method may
  used from a sub-class' removeExtra method.  If an
  application's code needs any class name (ie. for
  printing), this should not be used. */
Def removeNames(self)
{ do(Classes,
  {using(cl)
    setName(cl, nil);
    setName(class(cl), nil);
  });
}!!

now(Application)!!

/* The default application abort method
  returns true (self) to indicate that
  the sender may continue the abort process. */
Def abort(self)
{
}!!

/* Return the string from win.ini for this key for this
  application.  If not found, return zero-length string. */
Def getProfileString(self, key)
{ ^getProfileString(System, loadString(IDSNAME), key);
}!!

/* Initialize the application. */
Def init(self, str| libHandle, memFree)
{ if TheApp == self
  then initSystem();
  endif;
  commandLine := str;
  translateMsgOn();

  if (hPrevInstance() <> 0)  /* message is displayed in PowerApp */
  then
    ^nil
  endif ;

  removeAllLibs(PreLauncher);

  memFree := Call GetFreeSpace(0);
  if (memFree < MIN_WIN_MEMORY) then
    Call MessageBox(0,
       asciiz("MP/SLD requires at least " +
         asString( asReal(MIN_WIN_MEMORY) / 1000000.0) + " Meg" +
         " of free memory to run, but only " +
         asString( asReal(memFree) / 1000000.0) + " is available." +
         "  Please close unused applications or increase the size of" +
         " your Windows swap area."),
       asciiz("FATAL: MP/SLD"),
       MB_OK bitOr MB_ICONHAND );
    ^nil;
  endif;

  loop
  while ( (libHandle := Call GetModuleHandle(asciiz("pvtask.dll"))) <> 0 )
  begin
    Call FreeLibrary( libHandle ) ;
  endLoop;
  TaskLibClass$Inst := new(TaskLib);
  loadLibrary(TaskLibClass$Inst, "pvtask.dll");

}!!

/* Return the mainWindow IVar. */
Def mainWindow(self)
{ ^mainWindow;
}!!

/* A message to the application from Windows. */
Def message(self, lpmsg)
{
}!!

/* Duplicates the Actor low-level message loop with the exception
   that it handles modeless dialogs.  It still has the problem
   with child windows without menus since it dispatchs messages
   to the hWnd passed in the message struct. It therefore does
   need the translateMsg method to handle those cases. */
Def messageLoop(self | hMsg, lpMsg, hProp, lpProp hAccel, tmp, hWnd, hMD, wind, lib, libLoaded)
{ if TheApp == self
  then hMsg := asGlobalHandle("123456789012345678", GMEM_FIXED);
    lpMsg := globalLock(hMsg);
    hProp := asGlobalHandle(asciiz("ActorOOP"), GMEM_FIXED);
    lpProp := globalLock(hProp);
    hAccel := Call LoadAccelerators(HInstance, asciiz(loadString(IDSNAME)));
    /* works only if OC returned from modelessHandles is the same for
       the "life" of the message loop, since the loop depends on hMD
       to reference to same OC as $HModelessDialog class var. */
    hMD := modelessHandles(Dialog);

    loop  /* start message loop */
    while 1
    begin
      loop while Call PeekMessage(lpMsg, 0, 0, 0, 1) <> 0 begin
         if wordAt(lpMsg+2) = 0x12 then  /* WM_QUIT */
            globalUnlock(hMsg);
            globalUnlock(hProp);
            ^nil; /* Return nil to skip the low-level message loop */
         endif;
            /* Modeless dialog processing */
         tmp := size(hMD:OrderedCollection);
         loop
         while tmp > 0
         begin tmp := tmp - 1;
           (Call IsDialogMessage(hMD:Object[tmp], lpMsg) <> 0) cand (tmp := -1);
         endLoop;
                  /* Not a message for a modeless dialog */
         if tmp = 0
         then        /* Find the ultimate ancestor of the window */
           hWnd := wordAt(lpMsg);
           loop
           while not(
             (wind := asLong(Call GetProp(hWnd, lpProp)):Object[0]):Object)
               cand ((tmp := Call GetParent(hWnd)) <> 0)
           begin hWnd := tmp;
           endLoop;
           wind cand (hWnd := translateMsg(wind, lpMsg) cor hWnd);
           if (hWnd <> 0) cand (Call TranslateAccelerator(hWnd, hAccel, lpMsg) = 0)
           then Call TranslateMessage(lpMsg);
             Call DispatchMessage(lpMsg);
           else (wordAt(lpMsg) = 0) cand message(self, lpMsg);
           endif;
         endif;
      endLoop;

      Call WaitMessage();    /* Allow Windows to idle */

    endLoop;
    globalUnlock(hMsg);
    globalUnlock(hProp);
    if (libLoaded) free(lib); endif;
    ^nil; /* Return nil to skip the low-level message loop */
  endif;
}!!

/* Register all window classes that define a wndClass
 method. Assumes that these classes should be registered
 with Windows on application startup. */
Def registerWindows(self)
{ do(descendants(WindowsObject),
  {using(cl)
    if method(class(cl), #wndClass)
    then register(cl)
    endif;
  });
}!!

/* Present an input dialog to get the name of an application to run,
  and run it. */
Def runApp(self | rD)
{ rD := new(InputDialog, loadString(359), loadString(360), "");
  if runModal(rD, INPUT_BOX, mainWindow) > 0
  then exec(getText(rD));
  endif;
}!!

/* The default is to return the shouldClose value
  of the main window. */
Def shouldClose(self)
{ ^shouldClose(mainWindow);
}!!
