/***************************************************************************
 *                                                                         *
 *   TLCTM.CPP                                                             *
 *                                                                         *
 *   Copyright (c) 1998 Galacticomm, Inc.         All Rights Reserved.     *
 *                                                                         *
 *   Teleconference terminal mode interface.                               *
 *                                                                         *
 *                                            - J. Alvrus   02/23/1998     *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "tlcapi.hpp"
#include "tlccmdm.h"
#include "tlccs.h"
#include "tlctm.h"
#include "tlctmtra.h"

#define FILREV "$Revision: 25 $"

// module interface functions
GBOOL tlclon(VOID);
GBOOL tlcinp(VOID);
VOID tlcsthn(VOID);
GBOOL tlclof(VOID);
VOID tlchup(VOID);

struct module tlcmod={             /* module interface block               */
     "",                           /*    name used to refer to this module */
     tlclon,                       /*    user logon supplemental routine   */
     tlcinp,                       /*    input routine if selected         */
     tlcsthn,                      /*    status-input routine if selected  */
     NULL,                         /*    "injoth" routine for this module  */
     NULL,                         /*    user logoff supplemental routine  */
     tlchup,                       /*    hangup (lost carrier) routine     */
     NULL,                         /*    midnight cleanup routine          */
     NULL,                         /*    delete-account routine            */
     NULL                          /*    finish-up (sys shutdown) routine  */
};

// miscellaneous global variables

CTerminalTransport* transTerm;     // terminal-mode transport object
INT* OutFlagArr;                   // user-has-output flag array
INT* PmtFlagArr;                   // user-is-at-prompt flag array
CHAR* inbuf;                       // our own input buffer
CHAR* delim;                       // string to separate prompt from output
CHAR* TextNone;                    // no-text replacement
CHAR* ChanDescMain;                // main channel description
CHAR* ChanDescPrivate;             // private channel description
bool tmEcho;                       // echo typed text back to t-mode user?
bool tmInSysopMenu;                // are we already in sysop editor?
INT tlcModuleState;                // terminal-mode module state number

// local functions

static CTlcUser*
tmCreateUser();                    // create terminal-mode user object

static bool
tmJoinInitialChan(                 // terminal-mode join initial channel
const CHAR* ChannelName);          //   channel to join

static bool                        //   returns true if X entered
checkx(                            // check for user-selected exit
CTlcUser* pUser,                   //   user's user object
CTlcChannel* pChannel,             //   user's channel object
bool* exitTele);                   //   exit from teleconference?

static VOID
tmLeaveTele(                       // terminal-mode user leaving tele
CTlcUser* pUser,                   //   user's user object
CTlcChannel* pChannel,             //   user's channel object
SHORT fExitType);                  //   reason user is leaving tele

static VOID
EditMenu(                          // handle EDIT command menu choices
CTlcUser* pUser);                  //   user editing their profile

static VOID
RetEditMenu(                       // return to user profile editor menu
CTlcUser* pUser);                  //   user to display

static const CHAR*
tmTextOrNone(                      // display a string or "(none)"
const CHAR* text);                 //   text to display (if not empty)

bool                               //   returns FALSE if insufficient credits
tmCheckMsgCred(                    // check enter/exit message access/credits
CTlcUser* pUser,                   //   user changing message
SHORT* pMsgType);                  //   which message type (current/req)

static VOID
SysopMenu();                       // handle user editor selections

static VOID
RetSysopMenu();                    // return to sysop user editor menu

static VOID
DisplaySysopMenu(                  // display sysop user profile editor menu
LPSYSUSREDT pUserInfo);            //   info on user being edited

static VOID
SysApproveMsg(                     // sysop approve enter/exit message
bool isEntrance);                  //   approve entrance message? (or exit)

static VOID
SysUpdateUser();                   // sysop update user profile record

static VOID
ChanEditor();                      // handle channel editor selections

static VOID
VChannels();                       // display list of channels

static VOID
ListEditor();                      // handle action list editor selections

static VOID
VLists();                          // display list of action lists

static VOID
ActionEditor();                    // handle action editor selections

static VOID
ALists(                            // display list of words
CTlcAList* pList);                 //   in this action list

CHAR*                              //   pointer to allocated buffer
optString(                         // read a string option
INT OptNum);                       //   option number to read

// function definitions start here

MARKSOURCE(tlctm)

VOID
tlcInitTM(VOID)                    // initialize terminal-mode interface
{
     stlcpy(tlcmod.descrp,gmdnam("galtele.mdf"),MNMSIZ);
     tlcModuleState=register_module(&tlcmod);
     tlcsysedt.m_usrnum=-1;

     OutFlagArr=new INT[FLAG_NUM(nterms)];
     memset(OutFlagArr,0,FLAG_SIZE(nterms));
     PmtFlagArr=new INT[FLAG_NUM(nterms)];
     memset(PmtFlagArr,0,FLAG_SIZE(nterms));
     delim=optString(DELIM);
     inbuf=new CHAR[tinpsz];
     memset(inbuf,0,tinpsz);
     tmEcho=(bool)ynopt(TERMECH);
     TextNone=optString(TXTNONE);
     ChanDescMain=optString(CHNMAIN);
     ChanDescPrivate=optString(CHNPRV);

     transTerm=new CTerminalTransport;
     tlcAPI->transRegister("Terminal Mode",transTerm);
     initfsd();
}

VOID
tlcTMdwn(VOID)                     // shut down terminal-mode
{
     if (transTerm != NULL) {
          delete transTerm;
          transTerm=NULL;
     }
     if (OutFlagArr != NULL) {
          delete [] OutFlagArr;
          OutFlagArr=NULL;
     }
     if (PmtFlagArr != NULL) {
          delete [] PmtFlagArr;
          PmtFlagArr=NULL;
     }
     if (delim != NULL) {
          delete [] delim;
          delim=NULL;
     }
     if (inbuf != NULL) {
          delete [] inbuf;
          inbuf=NULL;
     }
     if (TextNone != NULL) {
          delete [] TextNone;
          TextNone=NULL;
     }
     if (ChanDescMain != NULL) {
          delete [] ChanDescMain;
          ChanDescMain=NULL;
     }
     if (ChanDescPrivate != NULL) {
          delete [] ChanDescPrivate;
          ChanDescPrivate=NULL;
     }
}

GBOOL
tlclon(VOID)                       // terminal-mode user logon handler
{
     if (usepalt) {
          setmbk(msgTlc);
          palLoad(usrnum);
          if (!(usrptr->flags&WSGCSU)) {
               palNotify(usaptr->userid,PAL_LOGON);
          }
          if (ppref[usrnum]&PAL_FLAG_LMSG) {
               palStatus();
               outprf(usrnum);
               clrprf();
          }
          rstmbk();
     }
     if (!(usrptr->flags&WSGCSU)) {
          tlcConnect();
     }
     return(FALSE);
}

GBOOL
tlcinp(VOID)                       // teleconference input handler
{
     CTlcUser* pUser=NULL;
     CTlcChannel* pChannel=NULL;

     BEG_PHASE("Teleconference Input",usrptr->substt);
     setmbk(msgTlc);

     // if this function is being called, the current user has some sort
     // of output on his channel, so we don't want to send out the delim
     FLAG_CLEAR(PmtFlagArr,usrnum);

     // get current user and channel
     if (usrptr->substt != 0) {
          BEG_PHASE("Getting TlcUser Pointer",usrptr->substt);
          curUser=pUser=tlcAPI->usrGetByName(usaptr->userid);
          END_PHASE("Getting TlcUser Pointer",usrptr->substt);
          if (pUser != NULL) {
               BEG_PHASE("Getting Channel Pointer",usrptr->substt);
               curChannel=pChannel=tlcAPI->chanGetByName(pUser->GetChannelName());
               END_PHASE("Getting Channel Pointer",usrptr->substt);
          }
     }

     // check for user requested exit
     bool exitTele;
     if (pUser != NULL && checkx(pUser,pChannel,&exitTele)) {
          if (exitTele) {
               if (pUser->IsSwitchingTooFast()) {
                    exitTele=false;
                    prfmsg(NANNOY);
                    outtele(usrnum);
               }
               else {
                    usrptr->substt=0;
                    aachat[usrnum].switchcount=pUser->GetSwitchCount();
                    stlcpy(aachat[usrnum].lastchannel
                          ,pUser->GetChannelName(),CHAN_MAX_SIZE);
                    tmExitChannel(pUser);
                    tmLeaveTele(pUser,pChannel,LEAVE_TLC);
                    prfmsg(EXITLC2);
                    FLAG_CLEAR(OutFlagArr,usrnum);
                    FLAG_CLEAR(PmtFlagArr,usrnum);
                    outprf(usrnum);

                    // check for entry via tjoinrou
                    if (aachat[usrnum].flags&XTOOTH) {
                         if (aachat[usrnum].flags&RSTX2M) {
                             usrptr->flags&=~X2MAIN;
                         }
                         aachat[usrnum].flags&=~(XTOOTH|RSTX2M);
                         condex();
                         usrptr->state=aachat[usrnum].retstt;
                         usrptr->substt=aachat[usrnum].retsub;
                         usrptr->crdrat=aachat[usrnum].retrat;
                         injacr();
                         exitTele=FALSE;
                    }
               }
          }
          else {
               outtele(usrnum);
          }
          tlcAPI->OutputFinished();
          rstmbk();
          END_PHASE("Teleconference Input",usrptr->substt);
          return(!exitTele);
     }

     // handle user input
     switch (usrptr->substt) {
     case 0:
          if (tlcAPI->usrGetByName(usaptr->userid) != NULL) {
               prfmsg(ALRTLC2);
               outprf(usrnum);
               rstmbk();
               END_PHASE("Teleconference Input",usrptr->substt);
               return(FALSE);
          }
          tmCreateUser();
          bgncnc();                // initialize concatenation
          cncchr();                // eat module selection character
          morcnc();                // skip any whitespace
          if (!tmJoinInitialChan(cncall())) {
               FLAG_CLEAR(OutFlagArr,usrnum);
               FLAG_CLEAR(PmtFlagArr,usrnum);
               tlcAPI->OutputFinished();
               rstmbk();
               END_PHASE("Teleconference Input",usrptr->substt);
               return(FALSE);
          }
          usrptr->crdrat=tlcAPI->m_iTlcSur;
          break;
     case TLC_STT_INTLC:           // regular multi-user tele
          if (pUser->m_fFlags&USR_CHATTING) {
               if (aachat[usrnum].chatch < 0
                || (usroff(aachat[usrnum].chatch)->flags&ISGCSU)) {
                    rstrin();
                    CTlcUser* pOthUser=tlcAPI->usrGetByName(pUser->GetChatUser());
                    if (pOthUser == NULL) {
                         ExitChat(TRUE,usaptr->userid,FALSE);
                    }
                    else {
                         pOthUser->OnRecvText(pUser->GetName(),NULL,margv[0]
                                             ,RECV_TYPE_CHATMSG);
                    }
               }
               tlcAPI->OutputFinished();
               rstmbk();
               END_PHASE("Teleconference Input",usrptr->substt);
               return(TRUE);
          }
          ASSERT(pChannel != NULL);
          rstrin();
          stlcpy(inbuf,margv[0],tinpsz);
          if (margc > 0) {
               // give channel commmand handler first shot,
               // then global command handler, then actions
               if (!pChannel->CommandParse(inbuf,pUser->GetName())
                && !tlcAPI->Parse(inbuf,pUser->GetName(),pChannel)
                && !pChannel->ActionParse(inbuf,pUser->GetName())) {
                    SHORT spd;
                    if (usrptr->flags&INVISB) {
                         pUser->CmdResult(CMD_SPEAK,FALSE,CMD_SPEAK_INVIS);
                    }
                    else if (pUser->m_fFlags&USR_SQUELCH) {
                         pUser->CmdResult(CMD_SPEAK,FALSE,CMD_SPEAK_SQUEL);
                    }
                    else if (!pUser->IsTypeOf(USR_NORMAL)
                     && pUser->m_iTimesSpoken >= tlcAPI->m_npaymx) {
                         pUser->CmdResult(CMD_SPEAK,FALSE,CMD_SPEAK_LIMITED);
                    }
                    else if ((spd=pUser->IsTalkingTooFast()) == USR_SPEED_TOOFAST) {
                         pUser->CmdResult(CMD_SPEAK,FALSE,CMD_SPEAK_TOOFAST);
                    }
                    else if (spd == USR_SPEED_DROP) {
                         byenow(TOOANOY);
                    }
                    else {
                         (*setpfn)(inbuf);
                         switch (chk4pfn()) {
                         case PFN_HANGUP:
                              byenow(PFNHUP);
                              break;
                         case PFN_WARNED:
                              outtele(usrnum);
                              break;
                         case PFN_NONE:
                              pChannel->PublicSend(usaptr->userid,NULL,inbuf
                                                  ,RECV_TYPE_PUBLIC);
                              pUser->CmdResult(CMD_SPEAK,TRUE,0);
                              break;
                         }
                    }
               }
          }
          else if (usrptr->flags&INJOIP) {
               clrprf();
               outtele(usrnum);
          }
          else {
               pChannel->SendWelcome(pUser);
          }
          break;
     case EDTMNUP:                 // user profile editor
     case DEFCHN4:
     case CHAINT3:
     case CHATHE3:
     case REQENT3:
     case REQEXT3:
          ASSERT(pUser != NULL);
          if (margc == 0 && (usrptr->flags&INJOIP)) {
               prfmsg(usrptr->substt);
          }
          else {
               do {
                    bgncnc();
                    EditMenu(pUser);
               } while (!endcnc());
          }
          outprf(usrnum);
          break;
     case SYSEDT3:                 // sysop user editor menu
     case SYSPMT:
     case SYSENT:
     case SYSEXT:
          ASSERT(pUser != NULL);
          if (margc == 0 && (usrptr->flags&INJOIP)) {
               if (usrptr->substt == SYSENT) {
                    prfmsg(SYSENT,tlcsysedt.m_pszEntCur);
               }
               else if (usrptr->substt == SYSEXT) {
                    prfmsg(SYSEXT,tlcsysedt.m_pszExitCur);
               }
               else {
                    prfmsg(usrptr->substt);
               }
          }
          else {
               do {
                    bgncnc();
                    SysopMenu();
               } while (!endcnc());
          }
          outprf(usrnum);
          break;
     case CEDHDRP3:                // channel editor menu
     case CEDEDP2:
     case CEDCRP2:
     case CEDDLP2:
     case CEDVWP2:
     case CEDDLSR2:
          if (margc == 0) {
               if (usrptr->substt == CEDHDRP3 && !(usrptr->flags&INJOIP)) {
                    prfmsg(CEDHDR2);
               }
               prfmsg(usrptr->substt);
          }
          else {
               do {
                    bgncnc();
                    ChanEditor();
               } while (!endcnc());
          }
          outprf(usrnum);
          break;
     case LEDHDRP2:                // action list editor menu
     case LEDVWP2:
     case LEDCRP2:
     case LEDEDP2:
     case LEDDLP2:
     case LEDDLSR2:
     case LEDWDP2:
          if (margc == 0) {
               if (usrptr->substt == LEDHDRP2 && !(usrptr->flags&INJOIP)) {
                    prfmsg(LEDHDR2);
               }
               prfmsg(usrptr->substt,aedtemp.list); // LEDDLSR2 uses list
          }
          else {
               do {
                    bgncnc();
                    ListEditor();
               } while (!endcnc());
          }
          outprf(usrnum);
          break;
     case AEDHDRP2:                // action word editor menu
     case AEDVWP2:
     case AEDCRP2:
     case AEDEDP2:
     case AEDDLP2:
     case AEDDLSR2:
          if (margc == 0) {
               if (usrptr->substt == AEDHDRP2 && !(usrptr->flags&INJOIP)) {
                    prfmsg(AEDHDR2);
               }
               prfmsg(usrptr->substt,aedtemp.name); // AEDDLSR2 uses name
          }
          else {
               do {
                    bgncnc();
                    ActionEditor();
               } while (!endcnc());
          }
          outprf(usrnum);
          break;
     }
     tlcAPI->OutputFinished();
     rstmbk();
     END_PHASE("Teleconference Input",usrptr->substt);
     return(TRUE);
}

VOID
tlcsthn(VOID)                      // terminal-mode status handler
{
     if (status == 251 && usrptr->usrcls == ACTUSR) {
          if (aachat[usrnum].chatch > 0
           && !(usroff(aachat[usrnum].chatch)->flags&ISGCSU)) {
               btucli(usrnum);
               btucli(aachat[usrnum].chatch);
          }
     }
     else {
          dfsthn();
     }
}

VOID
tlchup(VOID)                       // terminal-mode user logoff handler
{
     CTlcChannel* pChannel;
     CTlcUser* pUser;
     ULONG tlcunum;

     setmbk(msgTlc);
     if (usepalt) {
          palSave(usrnum);
          if (!(usrptr->flags&WSGCSU)) {
               palNotify(usaptr->userid,PAL_LOGOFF);
          }
     }
     if (!(usrptr->flags&WSGCSU)) {
          tlcunum=tlcAPI->FindIDByUser(usaptr->userid);
          RemoveInviteNUM(tlcunum,0);
          RemoveForgetNUM(tlcunum,0);
     }
     if (usrptr->state == tlcModuleState
      || cedctrl.user == usrnum
      || aedctrl.user == usrnum) {
          curUser=pUser=tlcAPI->usrGetByName(usaptr->userid);
          if (pUser != NULL) {
               CHAR assertbuf[256];
               if (pUser->m_fFlags&USR_CHATTING) {
                    ExitChat(TRUE,usaptr->userid,TRUE);
               }
               pChannel=tlcAPI->chanGetByName(pUser->GetChannelName());
               sprintf(assertbuf,"Trying to leave %s",pUser->GetChannelName());
               ASSERTM(pChannel != NULL,assertbuf);
               tmLeaveTele(pUser,pChannel,LEAVE_LOGOFF);
          }
          if (cedctrl.user == usrnum) {
               cedctrl.user=-1;
          }
          if (aedctrl.user == usrnum) {
               aedctrl.user=-1;
          }
          if (tlcsysedt.m_usrnum == usrnum) {
               tlcsysedt.m_usrnum=-1;
          }
     }
     if (!(usrptr->flags&WSGCSU)) {
          tlcDisconnect();
     }
     FLAG_CLEAR(OutFlagArr,usrnum);
     FLAG_CLEAR(PmtFlagArr,usrnum);
     tlcAPI->OutputFinished();
     rstmbk();
}

static CTlcUser*
tmCreateUser()                     // create terminal-mode user object
{
     CTlcUser User;
     CTlcUser* pUser;
     ULONG unum;

     setmbk(msgTlc);
     User.SetName(usaptr->userid);
     User.SetTransport(transTerm);
     User.SetUsrnum(usrnum);
     if ((unum=tlcAPI->FindIDByUser(usaptr->userid)) == 0L) {
          unum=tlcAPI->CreateUniqueID(usaptr->userid);
     }
     User.SetTlcUnum(unum);
     User.SetTlcBoardNum(0);
     User.SetSex(usaptr->sex == 'F' ? 'F' : 'M');
     User.SetAge(usaptr->age);
     User.LoadRecord();
     if (!hasmkey(UNLKEY)) {
          User.m_iTimesSpoken+=teltimes[usrnum];
     }
     if (usrptr->flags&INVISB) {
          User.m_fFlags|=USR_INVISB;
     }
     else {
          User.m_fFlags&=~USR_INVISB;
     }
     pUser=tlcAPI->usrAdd(&User);
     transTerm->AddUser(pUser);
     pUser->SetSwitchCount(aachat[usrnum].switchcount);
     rstmbk();
     return(pUser);
}

static bool
tmJoinInitialChan(                 // terminal-mode join initial channel
const CHAR* ChannelName)           //   channel to join
{
     CTlcChannel* pChannel;

     CTlcUser* pUser=tlcAPI->usrGetByName(usaptr->userid);
     ASSERT(pUser != NULL);

     BEG_PHASE("Joining channel",usrptr->substt);
     if (NULSTR(ChannelName)) {
          ChannelName=aachat[usrnum].lastchannel;
     }
     if (NULSTR(ChannelName)
      || (pChannel=tlcAPI->chanGetByName(ChannelName)) == NULL) {
          if (pUser->GetDefaultChan() == CHANDFT_MAIN) {
               if ((pChannel=tlcAPI->chanGetByName(dftchan)) == NULL) {
                    pChannel=tlcAPI->chanGetByName(mainchan);
               }
               if (!pChannel->CanAccess(pUser,CHAN_AXS_JOIN)) {
                    pChannel=tlcAPI->chanGetByName(tlcPrivateFromUser(pUser->GetName()));
               }
          }
          else {
               pChannel=tlcAPI->chanGetByName(tlcPrivateFromUser(pUser->GetName()));
          }
     }
     ASSERT(pChannel != NULL);

     tmEnterChannel(pUser);
     if (pChannel->AddUser(pUser,TRUE) == CHAN_JOIN_OK) {
          tlcAPI->usrDelete(pUser);
          END_PHASE("Joinining channel",usrptr->substt);
          return(true);
     }
//HACK: some other cleanup may be needed here, need to determine what
     tmExitChannel(pUser);
     transTerm->RemoveUser(pUser);
     tlcAPI->usrDelete(pUser);
     END_PHASE("Joining channel",usrptr->substt);
     return(false);
}

static bool                        //   returns true if X entered
checkx(                            // check for user-selected exit
CTlcUser* pUser,                   //   user's user object
CTlcChannel* pChannel,             //   user's channel object
bool* exitTele)                    //   exit from teleconference?
{
     *exitTele=false;
     if (margc == 1 && sameas(margv[0],"x")) {
          switch (usrptr->substt) {
          case SYSENT:             // sysop user editor options
          case SYSEXT:
               RetSysopMenu();
               break;
          case SYSEDT3:
               if (tmInSysopMenu) {
                    RetSysopMenu();
                    break;
               }
          case SYSPMT:             // sysop user editor menu
               pChannel->PublicSend(pUser->GetName(),NULL,NULL,RECV_TYPE_RETEDIT);
               tlcsysedt.m_usrnum=-1;
               pUser->m_fFlags&=~USR_INMENU;
               prfmsg(SYSTLR);
               outprf(usrnum);
               clrprf();
               tmReturnChannel(pUser,pChannel);
               break;
          case EDTMNUP:            // user profile editor menu
               pChannel->PublicSend(pUser->GetName(),NULL,NULL,RECV_TYPE_RETEDIT);
               pUser->m_fFlags&=~USR_INEDIT;
               prfmsg(EDTTLR);
               outprf(usrnum);
               clrprf();
               tmReturnChannel(pUser,pChannel);
               break;
          case DEFCHN4:            // user profile editor options
          case CHAINT3:
          case CHATHE3:
          case REQENT3:
          case REQEXT3:
               RetEditMenu(pUser);
               prfmsg(usrptr->substt);
               break;
          case CEDHDRP3:           // channel editor menu
               pChannel->PublicSend(pUser->GetName(),NULL,NULL,RECV_TYPE_RETEDIT);
               cedctrl.user=-1;
               prfmsg(CEDTLR);
               outprf(usrnum);
               clrprf();
               tmReturnChannel(pUser,pChannel);
               break;
          case CEDEDP2:            // channel editor options
          case CEDCRP2:
          case CEDDLP2:
          case CEDVWP2:
          case CEDDLSR2:
               RetChanEditor();
               break;
          case LEDHDRP2:           // action list editor menu
               pChannel->PublicSend(pUser->GetName(),NULL,NULL,RECV_TYPE_RETEDIT);
               aedctrl.user=-1;
               prfmsg(LEDTLR);
               outprf(usrnum);
               clrprf();
               tmReturnChannel(pUser,pChannel);
               break;
          case LEDEDP2:            // action list editor options
          case LEDCRP2:
          case LEDDLP2:
          case LEDVWP2:
          case LEDDLSR2:
          case LEDWDP2:
          case AEDHDRP2:
               RetListEditor();
               break;
          case AEDEDP2:            // action word editor options
          case AEDCRP2:
          case AEDDLP2:
          case AEDVWP2:
          case AEDDLSR2:
               RetActionEditor();
               break;
          case TLC_STT_INTLC:
               if (pUser->m_fFlags&USR_CHATTING) {
                    ExitChat(FALSE,pUser->GetName(),TRUE);
                    tmReturnChannel(pUser,pChannel);
               }
               else {
                    *exitTele=true;
               }
               break;
#ifdef DEBUG
          default:
               ASSERTM(FALSE,"Unhandled exit state");
#endif /* DEBUG */
          }
          return(true);
     }
     return(false);
}

static VOID
tmLeaveTele(                       // terminal-mode user leaving tele
CTlcUser* pUser,                   //   user's user object
CTlcChannel* pChannel,             //   user's channel object
SHORT fExitType)                   //   reason user is leaving tele
{
     ASSERT(pUser != NULL);
     ASSERT(pChannel != NULL);

     pUser->SaveRecord();
     transTerm->RemoveUser(pUser);
     pChannel->RemoveUser(pUser,fExitType);
}

/* User Profile Editor Functions */

static VOID
EditMenu(                          // handle EDIT command menu choices
CTlcUser* pUser)                   //   user editing their profile
{
     CHAR c;

     ASSERT(pUser != NULL);
     switch (usrptr->substt) {
     case EDTMNUP:                 // main user profile menu
          c=cncchr();
          switch (c) {
          case '\0':                    // user pressed ENTER
               cncall();
               DisplayEditMenu(pUser);
               break;
          case '1':                     // select default channel
               usrptr->substt=DEFCHN4;
               break;
          case '2':                     // set chat interval
               usrptr->substt=CHAINT3;
               break;
          case '3':                     // set private channel topic
               if (pUser->CheckAccess(rawmsg(TOPKEY))) {
                    usrptr->substt=CHATHE3;
               }
               else {
                    cncall();
                    pUser->SetPrivateTopic("");
                    prfmsg(NOTHEM2);
               }
               break;
          case '4':                     // request entrance message
          case '5':                     // request exit message
               if (pUser->CheckAccess(rawmsg(REQKEY))) {
                    usrptr->substt=c == '4' ? REQENT3 : REQEXT3;
               }
               else {
                    cncall();
                    prfmsg(REQACC);
               }
               break;
          default:
               cncall();
               prfmsg(SAYWHA2);
               break;
          }
          break;
     case DEFCHN4:                 // enter new default channel
          c=cncchr();
          switch (c) {
          case 'M':
          case 'P':
               pUser->SetDefaultChan(c == 'M' ? CHANDFT_MAIN
                                              : CHANDFT_PRIVATE);
               prfmsg(OKSET2);
               RetEditMenu(pUser);
               break;
          default:
               prfmsg(SAYWHA2);
               break;
          }
          cncall();
          break;
     case CHAINT3:                 // enter new chat interval
          {
               INT NewChatInterval=cncint();
               if (0 < NewChatInterval && NewChatInterval <= 9) {
                    pUser->SetChatInterval(NewChatInterval);
                    prfmsg(OKSET2);
                    RetEditMenu(pUser);
               }
               else {
                    prfmsg(SAYWHA2);
               }
               cncall();
          }
          break;
     case CHATHE3:                 // enter new private channel topic
          {
               CHAR NewTopic[CHAN_MAX_TOPIC_SIZE];
               stlcpy(NewTopic,cncall(),CHAN_MAX_TOPIC_SIZE);
               pUser->SetPrivateTopic(NewTopic);
               if (*NewTopic == '\0') {
                    prfmsg(CLRTHE2);
               }
               else {
                    prfmsg(URTHEM2,pUser->GetPrivateTopic());
               }
               RetEditMenu(pUser);
          }
          break;
     case REQENT3:                 // enter new entrance/exit message
     case REQEXT3:
          {
               SHORT fMsg;
               CHAR* pszMsg;

               // check access/credits
               if (!tmCheckMsgCred(pUser,&fMsg)) {
                    prfmsg(REQAFD);
                    howbuy();
                    RetEditMenu(pUser);
                    break;
               }

               pszMsg=cncall();
               // set appropriate message
               if (usrptr->substt == REQENT3) {
                    pUser->SetEntrance(pszMsg,fMsg);
               }
               else {
                    pUser->SetExit(pszMsg,fMsg);
               }

               // handle auto-approved messages
               if (fMsg == MSG_CURRENT) {

                    // clear requested message
                    if (usrptr->substt == REQENT3) {
                         pUser->SetEntrance("",MSG_REQUESTED);
                    }
                    else {
                         pUser->SetExit("",MSG_REQUESTED);
                    }

                    // confirm change to user
                    if (tlcAPI->m_bEMsgChg && *pszMsg != '\0') {
                         prfmsg(YOUUPD3,tlcAPI->m_iEMsgChg);
                    }
                    else {
                         prfmsg(OKSETAT2);
                    }
               }
               else {

                    // notify Sysop of message change (if necessary)
                    if (!msgchanged[usrnum] && *pszMsg != '\0') {
                         chgnot(pUser->GetName(),EEMNOT);
                         msgchanged[usrnum]=TRUE;
                    }

                    // confirm change to user
                    if (*pszMsg == '\0') {
                         prfmsg(OKSETAT2);
                    }
                    else {
                         prfmsg(OKSET2);
                    }
               }
               RetEditMenu(pUser);
          }
          break;
     default:
          cncall();
          prfmsg(SAYWHA2);
          break;
     }
     prfmsg(usrptr->substt);
}

static VOID
RetEditMenu(                       // return to user profile editor menu
CTlcUser* pUser)                   //   user to display
{
     if (shortm) {                 // shortm actually means long menu
          DisplayEditMenu(pUser);
     }
     usrptr->substt=EDTMNUP;
     // expects prfmsg(usrptr->substt) to follow
}

VOID
DisplayEditMenu(                   // display user's profile editor menu
CTlcUser* pUser)                   //   user to display
{
     prfmsg(EDTMNUH
           ,tmTextOrNone(pUser->GetEntrance(MSG_CURRENT))
           ,tmTextOrNone(pUser->GetExit(MSG_CURRENT))
           ,pUser->GetDefaultChan() == CHANDFT_MAIN ? ChanDescMain
                                                    : ChanDescPrivate
           ,pUser->GetChatInterval()
           ,tmTextOrNone(pUser->GetPrivateTopic())
           ,tmTextOrNone(pUser->GetEntrance(MSG_REQUESTED))
           ,tmTextOrNone(pUser->GetExit(MSG_REQUESTED)));
}

bool                               //   returns FALSE if insufficient credits
tmCheckMsgCred(                    // check enter/exit message access/credits
CTlcUser* pUser,                   //   user changing message
SHORT* pMsgType)                   //   which message type (current/req)
{
     *pMsgType=MSG_REQUESTED;
     if (pUser->CheckAccess(rawmsg(AUTKEY))) {
          if (tlcAPI->m_bEMsgChg) {
               if (!gdedcrd(pUser->GetName(),(LONG)tlcAPI->m_iEMsgChg,0,0)) {
                    return(false);
               }
               if (tlcAPI->m_bLogEMsg) {
                    logcrd(pUser->GetName(),tlcAPI->m_iEMsgChg);
               }
          }
          *pMsgType=MSG_CURRENT;
     }
     return(true);
}

/* Sysop User Editor Functions */

static VOID
SysopMenu()                        // handle sysop user editor selections
{
     CTlcUser* pOthUser;
     CHAR* pUserID;
     USRDSK UserInfoDisk;

     switch (usrptr->substt) {
     case SYSEDT3:                 // enter User-ID to work with
          pUserID=cncall();
          dfaSetBlk(dfaTlcUser);
          if (*pUserID == '\0') {
                prfmsg(usrptr->substt);
                return;
               // user pressed ENTER, just re-prompt
          }
          else if (!dfaAcqEQ(&UserInfoDisk,pUserID,0)) {
               struct usracc* usra;
               dfaSetBlk(accbb);
               if (dfaAcqEQ(NULL,pUserID,0)) {
                    usra=(struct usracc*)(accbb->data);
                    memset(&UserInfoDisk,0,sizeof(USRDSK));
                    stlcpy(UserInfoDisk.m_pszUserid,usra->userid,UIDSIZ);
                    UserInfoDisk.m_iChatInterval=2;
                    dfaSetBlk(dfaTlcUser);
                    dfaInsert(&UserInfoDisk);
                    dfaRstBlk();
               }
               else {
                    prfmsg(NOSUCH3);
                    prfmsg(usrptr->substt);
                    dfaRstBlk();
                    dfaRstBlk();
                    return;
               }
               dfaRstBlk();
          }
          if ((pOthUser=tlcAPI->usrGetByName(pUserID)) != NULL) {
               stlcpy(tlcsysedt.m_pszUserid,pOthUser->GetName(),UIDSIZ);
               stlcpy(tlcsysedt.m_pszEntCur
                         ,pOthUser->GetEntrance(MSG_CURRENT),EMSGSIZ);
               stlcpy(tlcsysedt.m_pszEntReq
                         ,pOthUser->GetEntrance(MSG_REQUESTED),EMSGSIZ);
               stlcpy(tlcsysedt.m_pszExitCur
                         ,pOthUser->GetExit(MSG_CURRENT),EMSGSIZ);
               stlcpy(tlcsysedt.m_pszExitReq
                         ,pOthUser->GetExit(MSG_REQUESTED),EMSGSIZ);

               tlcsysedt.m_iChanDft=pOthUser->GetDefaultChan();
               tlcsysedt.m_iChatInterval=pOthUser->GetChatInterval();

               tlcsysedt.m_bSquelched
                    =((pOthUser->m_fFlags&USR_SQUELCH) != 0);
               tlcsysedt.m_iCredits=pOthUser->GetCredits();
          }
          else {
               memcpy(&tlcsysedt,&UserInfoDisk,sizeof(USRDSK));
               if (onbbs(pUserID,1)) {
                    tlcsysedt.m_iCredits=othuap->creds;
               }
               else {
                    struct usracc* usra;
                    dfaSetBlk(accbb);
                    if (dfaAcqEQ(NULL,pUserID,0)) {
                         usra=(struct usracc*)(accbb->data);
                         tlcsysedt.m_iCredits=usra->creds;
                    }
                    dfaRstBlk();
               }
          }
          usrptr->substt=SYSPMT;
          DisplaySysopMenu(&tlcsysedt);
          tmInSysopMenu=true;
          prfmsg(usrptr->substt);
          dfaRstBlk();
          break;
     case SYSPMT:                  // main user editor menu
          switch (cncchr()) {
          case '\0':                    // user pressed ENTER
               DisplaySysopMenu(&tlcsysedt);
               prfmsg(SYSPMT);
               break;
          case '1':                     // set entrance message
               prfmsg(usrptr->substt=SYSENT,tlcsysedt.m_pszEntCur);
               break;
          case '2':                     // set exit message
               prfmsg(usrptr->substt=SYSEXT,tlcsysedt.m_pszExitCur);
               break;
          case '3':                     // approve entrance message
               cncall();
               SysApproveMsg(true);
               break;
          case '4':                     // approve exit message
               cncall();
               SysApproveMsg(false);
               break;
          case '5':                     // squelch/unsquelch user
               cncall();
               if ((pOthUser=tlcAPI->usrGetByName(tlcsysedt.m_pszUserid)) != NULL) {
                    if (pOthUser->m_fFlags&USR_SQUELCH) {
                         pOthUser->m_fFlags&=~USR_SQUELCH;
                    }
                    else {
                         pOthUser->m_fFlags|=USR_SQUELCH;
                    }
                    tlcsysedt.m_bSquelched
                         =((pOthUser->m_fFlags&USR_SQUELCH) != 0);
               }
               else {
                    tlcsysedt.m_bSquelched=!tlcsysedt.m_bSquelched;
                    SysUpdateUser();
               }
               RetSysopMenu();
               break;
          case 'U':                     // get new user to work with
               prfmsg(usrptr->substt=SYSEDT3);
               break;
          default:
               cncall();
               prfmsg(SAYWHA2);
               prfmsg(SYSPMT);
               break;
          }
          break;
     case SYSENT:                  // update entrance message
          stlcpy(tlcsysedt.m_pszEntCur,cncall(),EMSGSIZ);
          if ((pOthUser=tlcAPI->usrGetByName(tlcsysedt.m_pszUserid)) != NULL) {
               pOthUser->SetEntrance(tlcsysedt.m_pszEntCur,MSG_CURRENT);
          }
          else {
               SysUpdateUser();
          }
          RetSysopMenu();
          break;
     case SYSEXT:                  // update entrance message
          stlcpy(tlcsysedt.m_pszExitCur,cncall(),EMSGSIZ);
          if ((pOthUser=tlcAPI->usrGetByName(tlcsysedt.m_pszUserid)) != NULL) {
               pOthUser->SetExit(tlcsysedt.m_pszExitCur,MSG_CURRENT);
          }
          else {
               SysUpdateUser();
          }
          RetSysopMenu();
          break;
     }
}

static VOID
RetSysopMenu()                     // return to sysop user editor menu
{
     if (shortm) {                 // shortm actually means long menu
          DisplaySysopMenu(&tlcsysedt);
     }
     prfmsg(usrptr->substt=SYSPMT);
}

static VOID
DisplaySysopMenu(                  // display sysop user profile editor menu
LPSYSUSREDT pUserInfo)             //   info on user being edited
{
     prfmsg(SYSMNU
           ,pUserInfo->m_pszUserid
           ,spr("%ld",pUserInfo->m_iCredits)
           ,tmTextOrNone(pUserInfo->m_pszEntCur)
           ,tmTextOrNone(pUserInfo->m_pszExitCur)
           ,tmTextOrNone(pUserInfo->m_pszEntReq)
           ,tmTextOrNone(pUserInfo->m_pszExitReq)
           ,pUserInfo->m_bSquelched ? "YES" : "NO");
}

static VOID
SysApproveMsg(                     // sysop approve enter/exit message
bool isEntrance)                   //   approve entrance message? (or exit)
{
     CHAR* CurMsg=(isEntrance ? tlcsysedt.m_pszEntCur : tlcsysedt.m_pszExitCur);
     CHAR* ReqMsg=(isEntrance ? tlcsysedt.m_pszEntReq : tlcsysedt.m_pszExitReq);
     if (*ReqMsg == '\0') {
          RetSysopMenu();
          return;
     }
     if (tlcAPI->m_bEMsgChg) {
          if (!gdedcrd(tlcsysedt.m_pszUserid
                      ,(LONG)tlcAPI->m_iEMsgChg,0,0)) {
               prfmsg(CNTAFT2,tlcsysedt.m_pszUserid);
               RetSysopMenu();
               return;
          }
          if (tlcAPI->m_bLogEMsg) {
               logcrd(tlcsysedt.m_pszUserid,tlcAPI->m_iEMsgChg);
          }
          prfmsg(USRDED2,tlcsysedt.m_pszUserid,tlcAPI->m_iEMsgChg);
     }
     stlcpy(CurMsg,ReqMsg,EMSGSIZ);
     *ReqMsg='\0';
     CTlcUser* pOthUser=tlcAPI->usrGetByName(tlcsysedt.m_pszUserid);
     if (pOthUser != NULL) {
          if (isEntrance) {
               pOthUser->SetEntrance(CurMsg,MSG_CURRENT);
               pOthUser->SetEntrance(ReqMsg,MSG_REQUESTED);
          }
          else {
               pOthUser->SetExit(CurMsg,MSG_CURRENT);
               pOthUser->SetExit(ReqMsg,MSG_REQUESTED);
          }
          msgchanged[pOthUser->GetUsrnum()]=FALSE;
          pOthUser->SetChangedMsg(FALSE);
     }
     else {
          if (onsysn(tlcsysedt.m_pszUserid,1)) {
               msgchanged[othusn]=FALSE;
          }
          SysUpdateUser();
     }
     RetSysopMenu();
}

static VOID
SysUpdateUser()                    // sysop update user profile record
{
     dfaSetBlk(dfaTlcUser);
     if (dfaAcqEQ(NULL,tlcsysedt.m_pszUserid,0)) {
          dfaUpdate(&tlcsysedt);
     }
     else {
          dfaInsert(&tlcsysedt);
     }
     dfaRstBlk();
}

/* Channel Editor Functions */

static VOID
ChanEditor()                       // handle channel editor selections
{
     CTlcChannel* pChannel;
     CHAR* chanName;

     switch (usrptr->substt) {
     case CEDHDRP3:                // main channel editor menu
          switch (cncchr()) {
          case 'L':
               cncall();
               VChannels();
               RetChanEditor();
               break;
          case 'V':
               prfmsg(usrptr->substt=CEDVWP2);
               break;
          case 'E':
               prfmsg(usrptr->substt=CEDEDP2);
               break;
          case 'D':
               prfmsg(usrptr->substt=CEDDLP2);
               break;
          case 'C':
               prfmsg(usrptr->substt=CEDCRP2);
               break;
          default:
               cncall();
               prfmsg(SAYWHA2);
               prfmsg(CEDHDRP3);
               break;
          }
          break;
     case CEDVWP2:                 // enter channel to view
          chanName=cncall();
          if (sameas(chanName,"?")) {   // list channels
               VChannels();
               prfmsg(CEDVWP2);
          }                             // no configuration to view
          else if ((pChannel=tlcAPI->chanGetByName(chanName)) == NULL
                || (pChannel->GetType()&CHAN_TYPE_NOEDIT)) {
               prfmsg(CEDBDC2);
               prfmsg(CEDVWP2);
          }
          else {                        // display info
               memcpy(&cedtemp,pChannel->GetConfig(),sizeof(CHANINFO));
               cedprp(CEDFSIN2,-1);
               fsddsp(tfsdpft());
               RetChanEditor();
          }
          break;
     case CEDCRP2:                 // enter name of new channel
          chanName=cncall();
          if (!chkalph(chanName)) {     // name not valid
               prfmsg(CEDNOA2,chanName);
               prfmsg(CEDCRP2);
          }                             // channel already exists
          else if ((pChannel=tlcAPI->chanGetByName(chanName)) != NULL) {
               prfmsg(CEDALRX2,pChannel->GetName());
               prfmsg(CEDCRP2);
          }
          else {                        // enter FSD
               memset(&cedtemp,0,sizeof(CHANINFO));
               stlcpy(cedtemp.m_strName,chanName,CHAN_MAX_SIZE);
               cedtemp.m_flags=CHAN_FLAG_PROFOK;
               stlcpy(cedtemp.m_keyJoin,getmsg(UNLKEY),KEYSIZ);
               stlcpy(cedtemp.m_keySpeak,getmsg(UNLKEY),KEYSIZ);
               stlcpy(cedtemp.m_keyMod,getmsg(TLCOPKY),KEYSIZ);
               stlcpy(cedtemp.m_keyByPass,getmsg(TLCOPKY),KEYSIZ);
               cedEnter(true);
          }
          break;
     case CEDEDP2:                 // enter channel to edit
          chanName=cncall();
          if (sameas(chanName,"?")) {   // list channels
               VChannels();
               prfmsg(CEDEDP2);
          }                             // no such channel to edit
          else if ((pChannel=tlcAPI->chanGetByName(chanName)) == NULL
                || (pChannel->GetType()&CHAN_TYPE_NOEDIT)) {
               prfmsg(CEDBDC2);
               prfmsg(CEDEDP2);
          }
          else {                        // enter FSD
               stlcpy(cedctrl.channel,chanName,CHAN_MAX_SIZE);
               memcpy(&cedtemp,pChannel->GetConfig(),sizeof(CHANINFO));
               cedEnter(false);
          }
          break;
     case CEDDLP2:                 // enter channel to delete
          chanName=cncall();
          if (sameas(chanName,"?")) {   // list channels
               VChannels();
               prfmsg(CEDDLP2);
          }                             // no such channel can be deleted
          else if ((pChannel=tlcAPI->chanGetByName(chanName)) == NULL
                || (pChannel->GetType()&CHAN_TYPE_NOEDIT)) {
               prfmsg(CEDBDC2);
               prfmsg(CEDDLP2);
          }                             // can't delete default channel
          else if (sameas(pChannel->GetName(),mainchan)) {
               prfmsg(CEDNDLM2);
               prfmsg(CEDDLP2);
          }
          else {                        // confirm delete
               stlcpy(cedctrl.channel,chanName,CHAN_MAX_SIZE);
               prfmsg(usrptr->substt=CEDDLSR2,pChannel->GetName());
          }
          break;
     case CEDDLSR2:                // confirm deletion of channel
          if (cncyesno() == 'Y') {
               tlcAPI->chanMoveAllUsers(cedctrl.channel,dftchan
                                       ,CHAN_SWITCH_DELETED,SCMSG_CHAN_DELETE);
               tlcAPI->chanRemoveByName(cedctrl.channel,TRUE);
               prfmsg(CEDDLSC2);
          }
          else {
               prfmsg(CEDNDL2,cedctrl.channel);
          }
          cncall();
          RetChanEditor();
          break;
     }
}

VOID
RetChanEditor(VOID)                // return to channel editor menu
{
     if (shortm) {                 // shortm actually means long menu
          prfmsg(CEDHDR2);
     }
     prfmsg(usrptr->substt=CEDHDRP3);
}

static VOID
VChannels()                        // display list of channels
{
     CTlcChannel* pChannel;

     pChannel=tlcAPI->chanGetFirst();
     prfmsg(LSTHDR4);
     while (pChannel != NULL) {
          if (!(pChannel->GetType()&CHAN_TYPE_PRIVATE)
           && !(pChannel->GetType()&CHAN_TYPE_NOEDIT)) {
               prfmsg(LSTCHN5,pChannel->GetName()
                     ,*pChannel->GetTopic() == '\0' ? "<no topic>"
                                                    : pChannel->GetTopic()
                     ,pChannel->GetNumVisible());
          }
          pChannel=tlcAPI->chanGetNext();
     }
}

/* List Editor Functions */

static VOID
ListEditor()                       // handle action list editor selections
{
     CTlcAList* pList;
     CHAR* listName;

     switch (usrptr->substt) {
     case LEDHDRP2:                // main list editor menu
          switch (cncchr()) {
          case 'L':
               cncall();
               VLists();
               RetListEditor();
               break;
          case 'V':
               prfmsg(usrptr->substt=LEDVWP2);
               break;
          case 'E':
               prfmsg(usrptr->substt=LEDEDP2);
               break;
          case 'D':
               prfmsg(usrptr->substt=LEDDLP2);
               break;
          case 'C':
               prfmsg(usrptr->substt=LEDCRP2);
               break;
          case 'W':
               prfmsg(usrptr->substt=LEDWDP2);
               break;
          default:
               cncall();
               prfmsg(SAYWHA2);
               prfmsg(LEDHDRP2);
               break;
          }
          break;
     case LEDVWP2:                 // enter list to view
          listName=cncall();
          if (sameas(listName,"?")) {   // list lists
               VLists();
               prfmsg(LEDVWP2);
          }                             // no such list
          else if ((pList=tlcAPI->actGetByName(listName)) == NULL) {
               prfmsg(LEDBDL2);
               prfmsg(LEDVWP2);
          }
          else {                        // display list info
               memcpy(&ledtemp,pList->GetConfigPtr(),sizeof(ACTLST));
               ledprp(LEDFSINF,-1);
               fsddsp(tfsdpft());
               RetListEditor();
          }
          break;
     case LEDCRP2:                 // enter name of new list
          listName=cncall();
          if (!chkalph(listName)) {     // name not valid
               prfmsg(LEDNOA2,listName);
               prfmsg(LEDCRP2);
          }                             // list already exists
          else if ((pList=tlcAPI->actGetByName(listName)) != NULL) {
               prfmsg(LEDALRX2,pList->GetName());
               prfmsg(LEDCRP2);
          }
          else {                        // enter FSD
               memset(&ledtemp,0,sizeof(ACTLST));
               stlcpy(ledtemp.name,strupr(listName),ACTSIZ);
               ledEnter(true);
          }
          break;
     case LEDEDP2:                 // enter list to edit
          listName=cncall();
          if (sameas(listName,"?")) {   // list lists
               VLists();
               prfmsg(LEDEDP2);
          }                             // no such list to edit
          else if ((pList=tlcAPI->actGetByName(listName)) == NULL) {
               prfmsg(LEDBDL2);
               prfmsg(LEDEDP2);
          }
          else {                        // enter FSD
               memcpy(&ledtemp,pList->GetConfigPtr(),sizeof(ACTLST));
               ledEnter(false);
          }
          break;
     case LEDDLP2:                 // enter list to delete
          listName=cncall();
          if (sameas(listName,"?")) {   // list lists
               VLists();
               prfmsg(LEDEDP2);
          }                             // no such list to delete
          else if ((pList=tlcAPI->actGetByName(listName)) == NULL) {
               prfmsg(LEDBDL2);
               prfmsg(LEDDLP2);
          }
          else {                        // confirm delete
               stlcpy(aedtemp.list,pList->GetName(),ACTSIZ);
               prfmsg(usrptr->substt=LEDDLSR2,aedtemp.list);
          }
          break;
     case LEDDLSR2:                // confirm deletion of list
          pList=tlcAPI->actGetByName(aedtemp.list);
          if (cncyesno() == 'Y') {
               if (pList != NULL) {
                    pList->Delete();
               }
               prfmsg(LEDDLSC2);
          }
          else {
               prfmsg(LEDNDL2,pList->GetName());
          }
          cncall();
          RetListEditor();
          break;
     case LEDWDP2:                 // enter list to edit words
          listName=cncall();
          if (sameas(listName,"?")) {   // list lists
               VLists();
               prfmsg(LEDWDP2);
          }                             // no such list to edit
          else if ((pList=tlcAPI->actGetByName(listName)) == NULL) {
               prfmsg(LEDBDL2);
               prfmsg(LEDWDP2);
          }
          else {                        // go to word editor menu
               stlcpy(aedtemp.list,pList->GetName(),ACTSIZ);
               prfmsg(AEDHDR2);
               prfmsg(usrptr->substt=AEDHDRP2);
          }
          break;
     }
}

VOID
RetListEditor(VOID)                // return to action list editor menu
{
     if (shortm) {                 // shortm actually means long menu
          prfmsg(LEDHDR2);
     }
     prfmsg(usrptr->substt=LEDHDRP2);
}

static VOID
VLists()                           // display list of action lists
{
     CTlcAList* pList;
     INT i;

     prfmsg(ACTLLST2);
     for (pList=tlcAPI->actGetFirst(),i=0
        ; pList != NULL
        ; pList=tlcAPI->actGetNext()) {
          if (++i > 4) {
               i=1;
               prf("\r");
          }
          prfmsg(ACT2,pList->GetName());
     }
     prf("\r");
}

/* Action Editor Functions */

static VOID
ActionEditor()                     // handle action editor selections
{
     CHAR *wordName;
     CTlcAList* pList=tlcAPI->actGetByName(aedtemp.list);
     ASSERT(pList != NULL);
     switch (usrptr->substt) {
     case AEDHDRP2:                // main action editor menu
          switch (cncchr()) {
          case 'L':
               cncall();
               ALists(pList);
               RetActionEditor();
               break;
          case 'V':
               prfmsg(usrptr->substt=AEDVWP2);
               break;
          case 'E':
               prfmsg(usrptr->substt=AEDEDP2);
               break;
          case 'D':
               prfmsg(usrptr->substt=AEDDLP2);
               break;
          case 'C':
               prfmsg(usrptr->substt=AEDCRP2);
               break;
          default:
               cncall();
               prfmsg(SAYWHA2);
               prfmsg(AEDHDRP2);
          }
          break;
     case AEDVWP2:                 // enter word to view
          wordName=cncall();
          if (sameas(wordName,"?")) {   // list words
               ALists(pList);
               prfmsg(AEDVWP2);
          }                             // display word info
          else if (pList->wordExists(wordName)) {
               memcpy(&aedtemp,pList->actionGetCurrentPtr(),sizeof(ACTION));
               aedprp(AEDFSIN2,-1);
               fsddsp(tfsdpft());
               RetActionEditor();
          }
          else {                        // no such word
               prfmsg(AEDBDW2);
               prfmsg(AEDVWP2);
          }
          break;
     case AEDCRP2:                 // enter word to create
          wordName=cncall();
          if (!chkalph(wordName)) {     // invalid name
               prfmsg(AEDNOA3,wordName);
               prfmsg(AEDCRP2);
          }                             // word already exists
          else if (pList->wordExists(wordName)) {
               prfmsg(AEDALRX2,wordName);
               prfmsg(AEDCRP2);
          }
          else {                        // enter FSD
               stlcpy(aedtemp.name,wordName,ACTSIZ);
               stlcpy(ledtemp.name,aedtemp.list,ACTSIZ);
               aedtemp.simple[0]='\0';
               aedtemp.complx[0]='\0';
               aedtemp.resp[0]='\0';
               aedtemp.isyell='0';
               aedtemp.actkey[0]='\0';
               aedEnter(true);
          }
          break;
     case AEDEDP2:                 // enter word to edit
          wordName=cncall();
          if (sameas(wordName,"?")) {   // list words
               ALists(pList);
               prfmsg(AEDEDP2);
          }                             // enter FSD
          else if (pList->wordExists(wordName)) {
               stlcpy(ledtemp.name,aedtemp.list,ACTSIZ);
               memcpy(&aedtemp,pList->actionGetCurrentPtr(),sizeof(ACTION));
               aedEnter(false);
          }
          else {                        // no such word
               prfmsg(AEDBDL2);
               prfmsg(AEDEDP2);
          }
          break;
     case AEDDLP2:                 // enter word to delete
          wordName=cncall();
          if (sameas(wordName,"?")) {   // list words
               ALists(pList);
               prfmsg(AEDDLP2);
          }                             // confirm delete
          else if (pList->wordExists(wordName)) {
               memcpy(&aedtemp,pList->actionGetCurrentPtr(),sizeof(ACTION));
               prfmsg(usrptr->substt=AEDDLSR2,aedtemp.name);
          }
          else {                        // no such word
               prfmsg(AEDBDL2);
               prfmsg(AEDDLP2);
          }
          break;
     case AEDDLSR2:                // confirm deletion of word
          if (cncyesno() == 'Y') {
               dfaSetBlk(dfaAct);
               if (dfaAcqEQ(NULL,&aedtemp,0)) {
                    dfaDelete();
                    prfmsg(AEDDLSC2);
               }
               else {
                    prfmsg(AEDDLER2);
               }
               dfaRstBlk();
          }
          else {
               prfmsg(AEDNDL2,aedtemp.name);
          }
          cncall();
          RetActionEditor();
          break;
     }
}

VOID
RetActionEditor(VOID)              // return to action word editor menu
{
     if (shortm) {                 // shortm actually means long menu
          prfmsg(AEDHDR2);
     }
     prfmsg(usrptr->substt=AEDHDRP2);
}

static VOID
ALists(                            // display list of words
CTlcAList* pList)                  //   in this action list
{
     const CHAR* pszAction;
     INT i;

     for (pszAction=pList->wordGetFirst(),i=0
        ; pszAction != NULL
        ; pszAction=pList->wordGetNext()) {
          if (++i > 4) {
               i=1;
               prf("\r");
          }
          prfmsg(ACT2,pszAction);
     }
     prf("\r");
}

/* Utility Functions */

VOID
tmReturnChannel(                   // return to channel from chat/menu
CTlcUser* pUser,                   //   user's user object
CTlcChannel* pChannel)             //   user's channel object
{
     tmEnterChannel(pUser);
     pChannel->SendWelcome(pUser);
     clrprf();
}

VOID
tmEnterChannel(                    // set up everything on entry to channel
CTlcUser* pUser)                   //   user's user object
{
     usrptr->substt=TLC_STT_INTLC;
     pUser->m_fFlags|=USR_INCHAN;
     btumil(usrnum,tinpsz);
     btuxnf(usrnum,0,19);
}

VOID
tmExitChannel(                     // set up everything when exiting a channel
CTlcUser* pUser)                   //   user's user object
{
     pUser->m_fFlags&=~USR_INCHAN;
     btumil(usrnum,DFTIMX);
     rstrxf();
}

static const CHAR*
tmTextOrNone(                      // display a string or "(none)"
const CHAR* text)                  //   text to display (if not empty)
{
     if (NULSTR(text)) {
          return(TextNone);
     }
     return(text);
}

INT
GetTeleState(VOID)                 // get terminal-mode tele module state
{
     return(tlcModuleState);
}

VOID
outtele(                           // output text to a terminal-mode user
INT unum)
{
     if (FLAG_TEST(PmtFlagArr,unum)) {
          FLAG_CLEAR(PmtFlagArr,unum);
          btuxmt(unum,delim);
     }
     FLAG_SET(OutFlagArr,unum);
     outprf(unum);
}

CHAR*                              //   pointer to allocated buffer
optString(                         // read a string option
INT OptNum)                        //   option number to read
{
     const CHAR* pOpt=rawmsg(OptNum);
     CHAR* pRet=new CHAR[strlen(pOpt)+1];
     return(strcpy(pRet,pOpt));
}
