Module: qstate.cpp

class QState
.h

constructorQState(string iname,QSEventFunc ievFunc,QSPaintFunc ipaintFunc, QSEnterFunc ienterFunc,QSLeaveFunc ileaveFunc)
destructor~QState()
SetPaintFuncvoid SetPaintFunc(QSPaintFunc npaintFunc)
ProcessEventbool ProcessEvent(QEvent *e)

Dispatch event to the event handler function

Paintvoid Paint()
Leavevoid Leave()
Entervoid Enter()

class QStateManager
.h

constructorQStateManager(QWindow *parent,QRect *ipos)

: QWindow(parent,ipos->x,ipos->y,ipos->wid,ipos->hgt)

destructor~QStateManager()
Addvoid Add(QState *s)

Add state to the list of known states

Removevoid Remove(QState *s)

Remove state

SearchQState *Search(string name)
SetGlobalStatevoid SetGlobalState(QState *gstate)
SetGlobalRWvoid SetGlobalRW(QSReadFunc rf,QSWriteFunc wf)
ProcessEventbool ProcessEvent(QEvent *e)
Paintvoid Paint(QRect *r)

VCR look

GotoStatevoid GotoState(QState *state,bool silent)
VCR_Recordbool VCR_Record()

Record a frame (curState)

VCR_SkipBackbool VCR_SkipBack()

Push the file pointer back 1 state; don't read or do anything else.
Returns TRUE is succesful (FALSE for example when no previous frames
exist)

VCR_SkipForwardbool VCR_SkipForward()

Returns TRUE if there is in fact a next frame

VCR_FrameReadbool VCR_FrameRead(bool fRewind)

Reads frame data and setup the application for that frame (ShowFrame)
If fRewind==TRUE, then the file ptr will be rewound
to the start of the frame data (used with FrameBack?)

VCR_FrameBackbool VCR_FrameBack()

Go back 1 state

VCR_FrameForwardbool VCR_FrameForward()

Advance 1 frame

VCR_UseFilebool VCR_UseFile(string fname)

Use a (probably prerecorded) state file
Keeps recording in the previous file if the new one could not be opened.

VCR_Resetvoid VCR_Reset()

Clear out the states and start all over again

VCR_SaveAsbool VCR_SaveAs(string fname)

Save current recording as 'fname'
DON'T switch to 'fname'
Makes an inline copy, taking care not to disturb the current still
position, which might not be the end of the file.



/*
 * QState - state handling (current & managing the VCR)
 * 10-04-97: Created!
 * 23-05-97: Loading/saving vcr files. (^L/^S)
 * NOTES:
 * - I still wonder whether it was a good idea to make the StateManager
 *   a subclass of QWindow. Now, it is always the visual VCR that you see.
 * (C) MarketGraph/RVG
 */

#include <qlib/state.h>
#include <qlib/app.h>
#include <qlib/button.h>
#include <qlib/keys.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <qlib/file.h>
#include <qlib/debug.h>
DEBUG_ENABLE

#define QSM_MAX_STATE	100		// Max #states in system
#define QSM_MAX_NAME_LEN 32		// State names

#define VCR_TEMP_FILE	"temptape.vcr"	// Working tape

/*********
* QSTATE *
*********/
QState::QState(string iname,QSEventFunc ievFunc,QSPaintFunc ipaintFunc,
QState::QState(string iname,QSEventFunc ievFunc,QSPaintFunc ipaintFunc,  QSEnterFunc ienterFunc,QSLeaveFunc ileaveFunc)
{
  name=iname;
  evFunc=ievFunc;
  paintFunc=ipaintFunc;
  enterFunc=ienterFunc;
  leaveFunc=ileaveFunc;
  app->GetStateManager()->Add(this);
}
QState::~QState()
{
  app->GetStateManager()->Remove(this);
}

void QState::SetPaintFunc(QSPaintFunc npaintFunc)
{
  paintFunc=npaintFunc;
}

bool QState::ProcessEvent(QEvent *e)
// Dispatch event to the event handler function
{ if(evFunc)
    return evFunc(e);
  return FALSE;
}

void QState::Paint()
{ if(paintFunc)
    paintFunc();
}
void QState::Leave()
{ if(leaveFunc)
    leaveFunc();
}
void QState::Enter()
{ if(enterFunc)
    enterFunc();
}

/*****************
* QSTATE MANAGER *
*****************/
QStateManager::QStateManager(QWindow *parent,QRect *ipos)
  : QWindow(parent,ipos->x,ipos->y,ipos->wid,ipos->hgt)
{ QRect pb;
  curState=0;
  globalState=0;
  stateListSize=QSM_MAX_STATE;
  stateList=(QState**)qcalloc(stateListSize*sizeof(QState*));
  stateCount=0;
  bRewind=0;
  bPlay=0;
  // etc. zeroing
#ifdef NOTDEF
  qdbg("pos=%p\n",pos);
  pb.x=pos->x; pb.y=pos->y;
  pb.wid=pos->wid; pb.hgt=pos->hgt;
  pb.wid=pos->wid/4;
  pb.wid=80; pb.hgt=80;
  qdbg("new bRewind\n");
#endif
  QRect r(0,0,100,10);		// Rebuilt during Paint()
  bRewind=new QButton(app->GetShell(),&r,QB_MAGIC_REWIND);
  bPlay=new QButton(app->GetShell(),&r,QB_MAGIC_PLAY);
#ifndef WIN32
  bPlay->ShortCut(QK_CTRL|QK_RIGHT);
#endif
  bForward=new QButton(app->GetShell(),&r,QB_MAGIC_FORWARD);
  bReverse=new QButton(app->GetShell(),&r,QB_MAGIC_REVERSE);
#ifndef WIN32
  bReverse->ShortCut(QK_CTRL|QK_LEFT);
#endif
  // VCR
  fVCR=new QFile(VCR_TEMP_FILE,QFile::WRITEREAD);
  if(!fVCR->IsOpen())
  { qerr("Can't open VCR file\n");
  }
  vcrFrames=vcrFrame=0;
  readFunc=0; writeFunc=0;

  //qdbg("QStateMgr::ctor ret\n");
}
QStateManager::~QStateManager()
{
#ifdef OBS
  if(stateList)
    qfree((void*)stateList);
#endif
  if(fVCR)delete fVCR;
}

void QStateManager::Add(QState *s)
// Add state to the list of known states
{ int i;
  //qdbg("QStateMgr::Add(%p)\n",s);
  // Check for an existing name
  if(Search(s->GetName()))
  { printf("Multiply defined state named '%s'\n",s->GetName());
    exit(0);
  }
  stateList[stateCount]=s;
  stateCount++;
}
void QStateManager::Remove(QState *s)
// Remove state
{
  int i;
  if(stateCount<1)
  { qerr("QStateManager:Remove without states");
    return;
  }
  if(stateList[stateCount-1]!=s)
  { qerr("QStateManager:Remove: state must be the last added");
    return;
  }
  stateCount--;
}

QState *QStateManager::Search(string name)
{ int i;
  for(i=0;i<stateCount;i++)
  { if(stateList[i])
    { if(!strcmp(name,stateList[i]->GetName()))
      { return stateList[i];
      }
    }
  }
  return 0;
}

void QStateManager::SetGlobalState(QState *gstate)
{ globalState=gstate;
}
void QStateManager::SetGlobalRW(QSReadFunc rf,QSWriteFunc wf)
{ readFunc=rf;
  writeFunc=wf;
}

bool QStateManager::ProcessEvent(QEvent *e)
{
  //qdbg("QStateMgr::ProcEvent %d\n",e->type);
  // First, handle the VCR buttons
  if(e->type==QEVENT_CLICK)
  { if(e->win==(void*)bReverse)
    { qdbg("Reverse 1 frame\n");
      VCR_FrameBack();
    } else if(e->win==(void*)bPlay)
    { VCR_FrameForward();
    }
  } else if(e->type==QEVENT_KEYPRESS)
  { 
#ifndef WIN32
    if(e->n==(QK_CTRL|QK_S))
    { VCR_SaveAs("bc.vcr");
      return TRUE;
    } else if(e->n==(QK_CTRL|QK_L))
    { if(VCR_UseFile("bc.vcr"))
      { // Don't mess on in the new file
        VCR_SaveAs(VCR_TEMP_FILE);
        VCR_UseFile(VCR_TEMP_FILE);
      }
      return TRUE;
    }
#endif
  }

  // Give the global state first chance to handle event
  if(globalState)
    if(globalState->ProcessEvent(e))
      return TRUE;
  // Otherwise send the event through to the current state
  if(curState)
    return curState->ProcessEvent(e);
  return FALSE;
}

void QStateManager::Paint(QRect *r)
// VCR look
{ QRect pb;
  QButton *b;
  QColor col(200,0,50);
  if(!bRewind)return;
  //qdbg("QSM:Paint; bRewi\n");
  // Reposition buttons
  QRect pos;
  GetPos(&pos); //pos.x=pos.y=0;

  pb.x=pos.x; pb.y=pos.y;
  pb.wid=pos.wid/4; pb.hgt=pos.hgt;
  b=bRewind; b->Move(pb.x,pb.y); b->Size(pb.wid,pb.hgt); b->Paint();
  pb.x+=pb.wid;
  b=bReverse; b->Move(pb.x,pb.y); b->Size(pb.wid,pb.hgt); b->Paint();
  pb.x+=pb.wid;
  b=bPlay; b->Move(pb.x,pb.y); b->Size(pb.wid,pb.hgt); b->Paint();
  pb.x+=pb.wid;
  b=bForward; b->Move(pb.x,pb.y); b->Size(pb.wid,pb.hgt); b->Paint();
  /*canvas->SetColor(&col);
  canvas->Rectfill(pos);*/
}

void QStateManager::GotoState(QState *state,bool silent)
{
  //qdbg("Goto state\n");
  if(curState)curState->Leave();
  curState=state;
  if(curState)state->Enter();
  // Save progress if wanted
  if(silent==FALSE)
    VCR_Record();
  //qdbg("  QSM:GotoState ret\n");
}

/**********
* VCR OPS *
**********/
bool QStateManager::VCR_Record()
// Record a frame (curState)
{ int pos;
  char buf[QSM_MAX_NAME_LEN];
  if(!curState)return TRUE;
  // Check curState
  //if(curState->GetWriteFunc())curState->Write(fVCR);

  // Write state header
  pos=fVCR->Tell();
  //qdbg("VCR_Record: pos=%d\n",pos);
  for(int i=0;i<QSM_MAX_NAME_LEN;i++)buf[i]=0;
  strncpy(buf,curState->GetName(),QSM_MAX_NAME_LEN-1);
  fVCR->Write(buf,QSM_MAX_NAME_LEN);

  // Use global writer
  if(writeFunc)writeFunc(fVCR);

  // Write location of previous state
  fVCR->Write(&pos,sizeof(pos));
  vcrFrame++;
  // Set #frames directly; otherwise vcrFrames may be inconsistent
  // when you just rewinded several frames.
  vcrFrames=vcrFrame;
  //qdbg("  frames: %d, prev=%d, current=%d\n",vcrFrames,pos,fVCR->Tell());
  return TRUE;
}

//
// VCR file seeking frames (file location)
//
bool QStateManager::VCR_SkipBack()
// Push the file pointer back 1 state; don't read or do anything else.
// Returns TRUE is succesful (FALSE for example when no previous frames
// exist)
{
  int pos,posPrev;
  // Any frames before this one?
  pos=fVCR->Tell();
  //qdbg("SkipBack: pos=%d\n",pos);
  if(pos<=0)return FALSE;
  // Find location of previous frame
  pos-=4;
  fVCR->Seek(pos);
  fVCR->Read(&posPrev,sizeof(posPrev));
  fVCR->Seek(posPrev);
  vcrFrame--;
  //qdbg("Skipped back to %d\n",posPrev);
  return TRUE;
}
bool QStateManager::VCR_SkipForward()
// Returns TRUE if there is in fact a next frame
{
  int oldPos,pos;
  //qdbg("zsm::vcrskipfwd nyi");
  // Any frames left?
  pos=fVCR->Tell();
  //printf("  vcr_skipfw: pos=%d, size=%d\n",pos,fVCR->Size());
  if(pos==fVCR->Size())return FALSE;
  return TRUE;
}

//
// Reading frames and moving between them
//
bool QStateManager::VCR_FrameRead(bool fRewind)
// Reads frame data and setup the application for that frame (ShowFrame)
// If fRewind==TRUE, then the file ptr will be rewound
// to the start of the frame data (used with FrameBack?)
{
  char buf[QSM_MAX_NAME_LEN];
  QState *newState;
  int oldPos;

  oldPos=fVCR->Tell();
  fVCR->Read(buf,QSM_MAX_NAME_LEN);
  //qdbg("QSM:FrameRead: %s\n",buf);
  newState=Search(buf);
  if(!newState)
  { qerr("State '%s' was not found in this application.",buf);
    return FALSE;
  }
  
  // Use global reader
  if(readFunc)readFunc(fVCR);
  if(fRewind)
  { // Go back to start of state
    fVCR->Seek(oldPos);
  } else
  { // Skip the 'previous position' indicator
    fVCR->Seek(sizeof(oldPos),QFile::S_CUR);
    vcrFrame++;
  }
  // Paint the frame
  curState=newState;
  curState->Enter();
  curState->Paint();
  // Synchronize gels and such (don't allow too fast scrubbing)
  app->Sync();
  qdbg("new pos=%d\n",fVCR->Tell());
  return TRUE;
}

bool QStateManager::VCR_FrameBack()
// Go back 1 state
{ 
#ifdef OBSOLETE
  if(!VCR_SkipBack())return FALSE;
  if(!VCR_SkipBack())return FALSE;
#else
  // Don't cancel on failed backskipping to avoid the problem
  // that when forwarding again, the first frame is used twice
  VCR_SkipBack();
  VCR_SkipBack();
#endif
  return VCR_FrameRead();
}

bool QStateManager::VCR_FrameForward()
// Advance 1 frame
{ if(!VCR_SkipForward())return FALSE;
  return VCR_FrameRead();
}

//
// Loading and saving VCR files
//
bool QStateManager::VCR_UseFile(string fname)
// Use a (probably prerecorded) state file
// Keeps recording in the previous file if the new one could not be opened.
{ /*FILE *fNew;
  fNew=fopen(fname,"rb+");
  if(!fNew)return FALSE;
  if(fVCR)fclose(fVCR);
  fVCR=fNew;*/

  QFile *fNew;
  fNew=new QFile(fname,QFile::READWRITE);
  if(!fNew->IsOpen())
  { qwarn("VCR file not found"); return FALSE; }
  if(fVCR)delete fVCR;
  fVCR=fNew;
#ifdef NOTDEF
  fVCR=new QFile("zsmgr.vcr",QFile::WRITEREAD);
  if(!fVCR->IsOpen())
  { qerr("Can't open VCR file\n");
  }
#endif

  // Find #states in file
  //fseek(fVCR,0,SEEK_END);
  fVCR->Seek(0,QFile::S_END);
  for(vcrFrames=0;VCR_SkipBack();vcrFrames++);
  qdbg("VCR_UseFile: %d states in '%s'\n",vcrFrames,fname);

  // Use last recorded state in file (if any)
  fVCR->Seek(0,QFile::S_END);
  //fseek(fVCR,0,SEEK_END);
  vcrFrame=vcrFrames;
  if(VCR_SkipBack())
    VCR_FrameRead(FALSE);
  return TRUE;
}
void QStateManager::VCR_Reset()
// Clear out the states and start all over again
{
  // Reopen
  delete fVCR;
  fVCR=new QFile(VCR_TEMP_FILE,QFile::WRITEREAD);
  if(!fVCR->IsOpen())
  { qerr("Can't open VCR file\n");
  }
  vcrFrames=vcrFrame=0;
}

bool QStateManager::VCR_SaveAs(string fname)
// Save current recording as 'fname'
// DON'T switch to 'fname'
// Makes an inline copy, taking care not to disturb the current still
// position, which might not be the end of the file.
{ int i;
  FILE *fw;
  long oldPos;          // Old file position (==length of copy)
  char buf[256];
  char c;

  //oldPos=ftell(fVCR);
  oldPos=fVCR->Tell();
  qdbg("> Saving VCR recording as '%s'",fname);
  fw=fopen(fname,"wb");
  if(!fw){ qwarn("** VCR Save failed"); return FALSE; }

  //fseek(fVCR,0,SEEK_SET);
  fVCR->Seek(0);
  for(i=0;i<oldPos;i++)
  { //c=(char)fgetc(fVCR);
    c=fVCR->GetChar();
    fputc(c,fw);
  }
  fclose(fw);
  fVCR->Seek(oldPos);
  //fseek(fVCR,oldPos,SEEK_SET);  // Should be there already
  qdbg("\n");
  return TRUE;
}