This is the code for the "RecordFile" framework. Although much simplified, the RecordFile framework does illustrate the working of programs built with frameworks like Symantec's THINK class library, Borland's Object Windows Library or Microsofts Foundation class library. There are a LARGE number of files. This is actually the StudentRecord program built on the framework. Program specific files*** My.h My.cp define program record, application and document classes main.cp Framework*** CmdHndl.h, CmdHndl.cp base "command handler" hierarchy Application.h, Application.cp Application (command handler subclass) Document.h, Document.cp Document (command handler subclass) (and Collection base class) BTDoc.h, BTDoc.cp specialized Document and Collection subclasses to work with Btree data storage Btree.h, Btree.cp a Btree class that stores fixed sized records on disk (two files, index and data records) commands.h the consts defining standard commands handled by Application and Document D.h, D.cp (dynamic array ) Keyd.h pure abstract class, base class for "records" hierarchy Record.h, Record.cp middle of "records" hierarchy! WindowRep.h, WindowRep.cp Window classes RecordWin.h, RecordWin.cp window for displaying records ========= main.cp ====== #include "My.h" int main() { MyApp a; a.Run(); return 0; } ========= My.h ======= #ifndef __MY__ #define __MY__ #include #include "Application.h" #include "BTDoc.h" #include "Record.h" #include "WindowRep.h" class MyApp : public Application { protected: virtual Document* DoMakeDocument(); }; class MyDoc : public BTDoc { protected: virtual Record *DoMakeRecord(long recnum); virtual Record *MakeEmptyRecord(); }; class MyRec : public Record { public: MyRec(long recnum); virtual void SetDisplayField(EditWindow *e); virtual void ReadDisplayField(EditWindow *e); virtual void ReadFrom(fstream& in); virtual void WriteTo(fstream& out) const; virtual long DiskSize(void) const ; protected: virtual void ConsistencyUpdate(EditWindow *e); virtual void AddFieldsToWindow(); char fStudentName[64]; long fMark1; long fMark2; long fMark3; long fMark4; long fMidSession; long fFinalExam; NumberItem *fN; long fTotal; }; #endif ======== My.cp ===== #include #include "My.h" #include "RecordWin.h" Document *MyApp::DoMakeDocument() { return new MyDoc; } Record *MyDoc::DoMakeRecord(long recnum) { return new MyRec(recnum); } Record *MyDoc::MakeEmptyRecord() { return new MyRec(0); } MyRec::MyRec(long recnum) : Record(recnum) { fMark1 = fMark2 = fMark3 = fMark4 = fMidSession = fFinalExam = 0; strcpy(fStudentName,"Nameless"); } void MyRec::SetDisplayField(EditWindow *e) { long id = e->Id(); switch(id) { case 1001: ((EditText*)e)->SetVal(fStudentName, 1); break; case 1002: ((EditNum*)e)->SetVal(fMark1,1); break; case 1003: ((EditNum*)e)->SetVal(fMark2,1); break; case 1004: ((EditNum*)e)->SetVal(fMark3,1); break; case 1005: ((EditNum*)e)->SetVal(fMark4,1); break; case 1006: ((EditNum*)e)->SetVal(fMidSession,1); break; case 1007: ((EditNum*)e)->SetVal(fFinalExam,1); break; } } void MyRec::ReadDisplayField(EditWindow *e) { long id = e->Id(); switch(id) { case 1001: char* ptr = ((EditText*)e)->GetVal(); strcpy(fStudentName, ptr); break; case 1002: fMark1 = ((EditNum*)e)->GetVal(); break; case 1003: fMark2 = ((EditNum*)e)->GetVal(); break; case 1004: fMark3 = ((EditNum*)e)->GetVal(); break; case 1005: fMark4 = ((EditNum*)e)->GetVal(); break; case 1006: fMidSession = ((EditNum*)e)->GetVal(); break; case 1007: fFinalExam = ((EditNum*)e)->GetVal(); break; } } void MyRec::AddFieldsToWindow() { Record::AddFieldsToWindow(); EditText *et = new EditText(1001, 5, 4, 60, "Student Name "); fRW->AddSubWindow(et); EditNum *en = new EditNum(1002, 5, 8, 30, "Assignment 1 (5) ", 0, 2000,1); fRW->AddSubWindow(en); en = new EditNum(1003, 5, 10, 30, "Assignment 2 (10) ", 0, 2000,1); fRW->AddSubWindow(en); en = new EditNum(1004, 5, 12, 30, "Assignment 3 (10) ", 0, 2000,1); fRW->AddSubWindow(en); en = new EditNum(1005, 5, 14, 30, "Assignment 4 (10) ", 0, 2000,1); fRW->AddSubWindow(en); en = new EditNum(1006, 40, 8, 30, "MidSession (15) ", 0, 2000,1); fRW->AddSubWindow(en); en = new EditNum(1007, 40, 10, 30, "Examination (50) ", 0, 2000,1); fRW->AddSubWindow(en); fTotal = fMark1 + fMark2 + fMark3 + fMark4 + fMidSession + fFinalExam; fN = new NumberItem(2000, 40, 14,30, "Total ", fTotal); fRW->AddSubWindow(fN); } void MyRec::ReadFrom(fstream& in) { in.read((char*)&fRecNum, sizeof(fRecNum)); in.read((char*)&fStudentName, sizeof(fStudentName)); in.read((char*)&fMark1, sizeof(fMark1)); in.read((char*)&fMark2, sizeof(fMark2)); in.read((char*)&fMark3, sizeof(fMark3)); in.read((char*)&fMark4, sizeof(fMark4)); in.read((char*)&fMidSession, sizeof(fMidSession)); in.read((char*)&fFinalExam, sizeof(fMidSession)); } void MyRec::WriteTo(fstream& out) const { out.write((char*)&fRecNum, sizeof(fRecNum)); out.write((char*)&fStudentName, sizeof(fStudentName)); out.write((char*)&fMark1, sizeof(fMark1)); out.write((char*)&fMark2, sizeof(fMark2)); out.write((char*)&fMark3, sizeof(fMark3)); out.write((char*)&fMark4, sizeof(fMark4)); out.write((char*)&fMidSession, sizeof(fMidSession)); out.write((char*)&fFinalExam, sizeof(fMidSession)); } long MyRec::DiskSize(void) const { return sizeof(fRecNum) + sizeof(fStudentName) + 6 * sizeof(long); } void MyRec::ConsistencyUpdate(EditWindow *e) { fTotal = fMark1 + fMark2 + fMark3 + fMark4 + fMidSession + fFinalExam; fN->SetVal(fTotal, 1); } ======== CmdHndl.h ==== #ifndef __COMMANDHANDLER__ #define __COMMANDHANDLER__ #include "commands.h" class MenuWindow; class CommandHandler { public: CommandHandler(int mainmenuid); virtual ~CommandHandler(); virtual void Run(); protected: virtual void MakeMenu() { } virtual void CommandLoop(); virtual void PrepareToRun() { } virtual void HandleCommand(int command) { } virtual void UpdateState() { } virtual void Finish() { } MenuWindow *fMenu; int fFinished; int fMenuID; }; #endif ==== CmdHndl.cp === #include "CmdHndl.h" #include "WindowRep.h" CommandHandler::CommandHandler(int mainmenuid) { fMenuID = mainmenuid; fMenu = new MenuWindow(fMenuID); fFinished = 0; } CommandHandler::~CommandHandler() { delete fMenu; } void CommandHandler::Run() { this->MakeMenu(); this->PrepareToRun(); this->CommandLoop(); this->Finish(); } void CommandHandler::CommandLoop() { while(!fFinished) { this->UpdateState(); int c = fMenu->PoseModally(); this->HandleCommand(c); } } ======= Application.h ----- #ifndef __APPLICATION__ #define __APPLICATION__ #ifndef __COMMANDHANDLER__ #include "CmdHndl.h" #endif class Document; class MenuWindow; class Application : public CommandHandler { public: Application(); virtual ~Application(); protected: virtual void MakeMenu(); virtual void HandleCommand(int command); virtual Document* DoMakeDocument() = 0; Document *fDoc; }; #endif ===== Application.cp -------- #include "Application.h" #include "Document.h" #include "WindowRep.h" const int kAPPMENU_ID = 100; Application::Application() : CommandHandler(kAPPMENU_ID) { } Application::~Application() { } void Application::HandleCommand(int cmdnum) { switch(cmdnum) { case cNEW: fDoc = this->DoMakeDocument(); fDoc->DoInitialState(); fDoc->OpenNew(); fDoc->Run(); delete fDoc; break; case cOPEN: fDoc = this->DoMakeDocument(); fDoc->DoInitialState(); fDoc->OpenOld(); fDoc->Run(); delete fDoc; break; case cQUIT: fFinished = 1; break; } } void Application::MakeMenu() { fMenu->AddMenuItem("New", cNEW); fMenu->AddMenuItem("Open",cOPEN); fMenu->AddMenuItem("Quit",cQUIT); } ======== Document.h #ifndef __DOCUMENT__ #define __DOCUMENT__ #include #ifndef __COMMANDHANDLER__ #include "CmdHndl.h" #endif class Record; class Collection; class NumberItem; class Document : public CommandHandler { public: Document(); virtual ~Document(); virtual void OpenNew(); virtual void OpenOld(); virtual void DoInitialState(); virtual Record *MakeEmptyRecord() = 0; protected: virtual void PrepareToRun(); virtual void UpdateState(); virtual Collection *DoMakeCollection() = 0; virtual void MakeMenu(); virtual void HandleCommand(int command); virtual void InitializeNewFile() = 0; virtual void OpenOldFile() = 0; virtual void DoCloseDoc() = 0; virtual void DoNewRecord(); virtual void DoDeleteRecord(); virtual void DoViewEditRecord(); virtual Record *DoLoadRecord(long recnum); virtual void DoEditRecord(Record *); virtual long GetKey(); virtual Record *DoMakeRecord(long recnum) = 0; long GetExistingRecordNum(); char fFileName[64]; NumberItem *fNumDisplay; Collection *fStore; int fVerifyInput; }; class Collection { public: Collection(Document *d) { this->fDoc = d; } virtual ~Collection() { } virtual void Append(Record *r) = 0; virtual int Delete(long recnum) = 0; virtual Record *Find(long recnum) = 0; virtual void Save(Record *r) { } virtual long Size() = 0; protected: Document *fDoc; }; #endif ========= Document.cp ---------- #include #include #include "Document.h" #include "Record.h" #include "RecordWin.h" const int kDOCMENU_ID = 200; const char * const NoRecMsg = "No record with that id."; Document::Document() : CommandHandler(kDOCMENU_ID) { fStore = NULL; fFileName[0] = '\0'; fVerifyInput = 1; } Document::~Document() { if(fStore != NULL) delete fStore; } void Document::DoInitialState() { fStore = DoMakeCollection(); } void Document::OpenNew() { TextDialog onew("Name for new file"); onew.PoseModally("example",fFileName); InitializeNewFile(); } void Document::OpenOld() { InputFileDialog oold; oold.PoseModally("example",fFileName, fVerifyInput); OpenOldFile(); } void Document::MakeMenu() { fMenu->AddMenuItem("New record", cNEWREC); fMenu->AddMenuItem("Delete record",cDELREC); fMenu->AddMenuItem("View/edit record",cVIEW); fMenu->AddMenuItem("Close file",cCLOSE); } void Document::PrepareToRun() { fNumDisplay = new NumberItem(0, 31, 1, 40, "Number of records:",0); fMenu->AddSubWindow(fNumDisplay); } void Document::UpdateState() { fNumDisplay->SetVal(fStore->Size()); fMenu->ShowText(fFileName, 2, 2, 30, 0, 1); } void Document::HandleCommand(int cmdnum) { switch(cmdnum) { case cNEWREC: DoNewRecord(); break; case cDELREC: DoDeleteRecord(); break; case cVIEW: DoViewEditRecord(); break; case cCLOSE: DoCloseDoc(); fFinished = 1; break; } } long Document::GetKey() { NumberDialog n("Record identifier", 1, LONG_MAX); long k = n.PoseModally(1); if(NULL == DoLoadRecord(k)) return k; Alert("Key already used"); return -1; } void Document::DoNewRecord() { long key = GetKey(); if(key<=0) return; Record *r = DoMakeRecord(key); DoEditRecord(r); fStore->Append(r); fStore->Save(r); } void Document::DoDeleteRecord() { long recnum = GetExistingRecordNum(); if(recnum<0) return; if(!fStore->Delete(recnum)) Alert(NoRecMsg); } void Document::DoViewEditRecord() { long recnum = GetExistingRecordNum(); if(recnum<0) return; Record *r = DoLoadRecord(recnum); if(r == NULL) { Alert(NoRecMsg); return; } DoEditRecord(r); fStore->Save(r); } long Document::GetExistingRecordNum() { if(fStore->Size() == 0) { Alert("No records defined."); return -1; } NumberDialog n("Record number", 1, LONG_MAX); return n.PoseModally(1); } Record *Document::DoLoadRecord(long recnum) { return fStore->Find(recnum); } void Document::DoEditRecord(Record *r) { RecordWindow *rw = r->DoMakeRecordWindow(); rw->PoseModally(); delete rw; } ====== BTDoc.h ----- #ifndef __BTDDOC__ #define __BTDDOC__ #include "Document.h" #include "BTree.h" class BTDoc : public Document { public: BTDoc(); protected: virtual Collection *DoMakeCollection(); virtual void InitializeNewFile(); virtual void OpenOldFile(); virtual void DoCloseDoc(); }; class BTCollection : public Collection { public: BTCollection(BTDoc *d) : Collection(d) { } void OpenBTree(char* filename); void CloseBTree(); virtual void Append(Record *r); virtual int Delete(long recnum); virtual Record *Find(long recnum); virtual long Size(); virtual void Save(Record *r); private: BTree *fBTree; }; #endif ======= BTDoc.cp ----- #include #include "BTDoc.h" #include "Record.h" #include "RecordWin.h" BTDoc::BTDoc() { fVerifyInput = 0; } Collection *BTDoc::DoMakeCollection() { return new BTCollection(this); } void BTDoc::InitializeNewFile() { ((BTCollection*)fStore)->OpenBTree(fFileName); } void BTDoc::OpenOldFile() { ((BTCollection*)fStore)->OpenBTree(fFileName); } void BTDoc::DoCloseDoc() { ((BTCollection*)fStore)->CloseBTree(); } void BTCollection::OpenBTree(char* filename) { fBTree = new BTree(filename); } void BTCollection::CloseBTree() { delete fBTree; } void BTCollection::Save(Record *r) { fBTree->Add(*r); // rewrite if changed, // redundant if just appended delete r; } void BTCollection::Append(Record *r) { fBTree->Add(*r); } Record *BTCollection::Find(long recnum) { Record *r = fDoc->MakeEmptyRecord(); int success = fBTree->Find(recnum, *r); if(success) return r; delete r; return NULL; } long BTCollection::Size() { return fBTree->NumItems(); } int BTCollection::Delete(long recnum) { Record *r = Find(recnum); if(r != NULL) { delete r; fBTree->Remove(recnum); return 1; } else return 0; } ====== Btree.h ----- #ifndef __BTREE__ #define __BTREE__ #include #include #include "Keyd.h" /* The type daddr_t is normally defined in one of the system's header files; but the file used will vary. It is simply a a typedef allowing a distinct type to be used to indicate an address of a record on disk. Really, it is just a long integer. */ typedef long daddr_t ; #define NO_DADDR ((daddr_t) (-1))/* not a legal disk address */ /* Parameters controlling form of BTree MIN Minimun no. of keys that can be in any node except the root. (should be 100s for a real BTree) MAX = 2*MIN (from definition of BTree) When testing, you should make these constants very small (minimum MIN should be 2 or 3). If you make them small, you increase the frequency of operations involving splitting and combining nodes; so it becomes easier to test that all the code works. Once the code is tested, the values should be increased. The optimal value is system dependent. Ideally you would chose a value so that a BNode was exactly the same size as the "block" most conveniently transferred to/from disk. In practice that isn't practical (for example, on many systems you will find that your different disk drives use different blocksizes). */ #define MIN 3 #define MAX (2 * MIN) class BTreeNode; struct KLRec; class BTree { public: BTree(const char* filename); ~BTree(); int NumItems(void) const; void Add(KeyedStorableItem& d); int Find(long key, KeyedStorableItem& rec); void Remove(long key); #ifdef DEBUGGING void PrintTreeFile(void); #endif private: struct HK { daddr_t fRoot; long fNumItems; long fLastKey; }; /* Disk i/o group */ void GetBTreeNode(BTreeNode& bnrec, daddr_t diskpos); void SaveBTreeNode(BTreeNode& bnrec, daddr_t diskpos); void GetDataRecord(KeyedStorableItem& datarec, daddr_t diskpos); void SaveDataRecord(KeyedStorableItem& datarec, daddr_t diskpos); daddr_t MakeNewDiskBNode(BTreeNode& bnode); daddr_t MakeNewDataRecord(KeyedStorableItem& data); /* Auxiliary functions for Add() */ void DoAdd(KeyedStorableItem& newData, daddr_t filepos, int& return_flag, KLRec& return_KLRec, daddr_t& return_diskpos); void Split(BTreeNode& nodetosplit, KLRec& extradata, daddr_t extralink, int index, KLRec& return_KLRec, daddr_t& return_diskpos); void SplitInsertLeft(BTreeNode& nodetosplit, KLRec& extradata, daddr_t extralink, int index, KLRec& return_KLRec, daddr_t& return_diskpos); void SplitInsertMiddle(BTreeNode& nodetosplit, KLRec& extradata, daddr_t extralink, int index, KLRec& return_KLRec, daddr_t& return_diskpos); void SplitInsertRight(BTreeNode& nodetosplit, KLRec& extradata, daddr_t extralink, int index, KLRec& return_KLRec, daddr_t& return_diskpos); /* Auxiliary functions for Remove() */ int DoRemove(long badkey, BTreeNode& cNode); void DeleteKeyInNode(BTreeNode& aNode, int index); KLRec Successor(daddr_t subtree); void DeleteInLeaf(BTreeNode& leaf, int index); void Restore(BTreeNode& parent, BTreeNode& deficient, int index); void MergeOrCombineRight(BTreeNode& parent, BTreeNode& deficient, int index); void MergeOrCombineLeft(BTreeNode& parent, BTreeNode& deficient, int index); void MoveRight(BTreeNode& parent, BTreeNode& left, BTreeNode& right, int index); void MoveLeft(BTreeNode& parent, BTreeNode& left, BTreeNode& right, int index); void Combine(BTreeNode& parent, BTreeNode& left, BTreeNode& right, int index); /* Data */ HK fHouseKeeping; void SaveHK(void); void LoadHK(void); fstream fTreeFile; fstream fDataFile; long fTreefile_size; long fDatafile_size; }; #endif ======= Btree.cp ----- #include #include #include "BTree.h" struct KLRec { daddr_t fLocation; long fKey; }; class BTreeNode { friend class BTree; private: int SearchInNode(long keysought, int& index); void InsertInNode(KLRec& data, daddr_t, int); void InsertAtLeft(KLRec& data, daddr_t downlink); void InsertAtRight(KLRec& data, daddr_t downlink); void ShiftLeft(void); void Compress(int index); int NotFull() { return (n_data==MAX) ? 0 : 1; } int Deficient() { return (n_dataMIN) ? 1 : 0; } int n_data; KLRec data[MAX]; /* 0..n_data-1 are filled */ daddr_t links[MAX+1]; /* 0..n_data are filled */ }; int BTreeNode::SearchInNode (long keysought, int& index) { for (index = 0; index < n_data; index++) if (data[index].fKey == keysought) return 1; else if (data[index].fKey > keysought) return 0; return 0; } void BTreeNode::InsertInNode(KLRec& info, daddr_t diskpos,int index) { /* An additional key etc has to be inserted into this node, together with an additional link down to some subtree. They are to go at position 'index'. Move existing entries to right to make room. */ for (int i = n_data - 1; i >= index; i--) { data[i+1] = data [i]; links[i+2] = links[i+1]; } data[index] = info; links[index+1] = diskpos; n_data++; } void BTreeNode::InsertAtLeft(KLRec& info, daddr_t downlink) { for(int i = n_data - 1; i >= 0; i--) { data[i+1] = data[i]; links[i+2] = links[i+1]; } links[1] = links [0]; data[0] = info; links[0] = downlink; n_data++; } void BTreeNode::InsertAtRight(KLRec& info, daddr_t downlink) { data[n_data] = info; links[n_data+1] = downlink; n_data++; } void BTreeNode::ShiftLeft(void) { n_data--; links[0] = links [1]; for(int i = 0; i < n_data; i++) { data[i] = data[i+1]; links[i+1] = links[i+2]; } } void BTreeNode::Compress(int index) { n_data--; for(int i = index; i < n_data; i++) { data[i] = data[i+1]; links[i+1] = links[i+2]; } } /* BTree Constructor opens (creates if necessary) two input-output files. The filenames are based on the character string given as an argument. A file with extension .ndx holds the BTree structure itself. A file with extension .dat holds the data records. The code doesn't do any name validation, if the name is illegal or if for any other reason a file cannot be opened the program is terminated. */ BTree::BTree(const char* filename) { char buff[100]; strcpy(buff,filename); strcat(buff,".ndx"); fTreeFile.open(buff, ios::in | ios::out); if(!fTreeFile.good()) { cerr << "Sorry, can't open BTree index file." << endl; exit(1); } strcpy(buff,filename); strcat(buff,".dat"); fDataFile.open(buff, ios::in | ios::out); if(!fDataFile.good()) { cerr << "Sorry, can't open BTree data file." << endl; exit(1); } /* If the files are new, they will be zero sized. If previously created they should be non-zero. */ fTreeFile.seekg(0, ios::end); long len = fTreeFile.tellg(); /* If new, zero sized, have to write an empty housekeeping record to the index file. If existing, need to read the existing housekeeping record */ if(len == 0) { fHouseKeeping.fNumItems = 0; fHouseKeeping.fRoot = NO_DADDR; fHouseKeeping.fLastKey = 0; SaveHK(); fTreefile_size = sizeof(HK); fDatafile_size = 0; } else { LoadHK(); fTreefile_size = len; fDataFile.seekg(0, ios::end); fDatafile_size = fDataFile.tellg(); } } BTree::~BTree() { SaveHK(); fTreeFile.close(); fDataFile.close(); } void BTree::SaveHK(void) { fTreeFile.seekp(0); fTreeFile.write((char*)&fHouseKeeping, sizeof(HK)); } void BTree::LoadHK(void) { fTreeFile.seekg(0); fTreeFile.read((char*)&fHouseKeeping, sizeof(HK)); } void BTree::GetBTreeNode(BTreeNode& bnrec, daddr_t diskpos) { fTreeFile.seekg(diskpos); fTreeFile.read((char*)&bnrec, sizeof(BTreeNode)); } void BTree::SaveBTreeNode(BTreeNode& bnrec, daddr_t diskpos) { fTreeFile.seekp(diskpos); fTreeFile.write((char*)&bnrec, sizeof(BTreeNode)); } void BTree::GetDataRecord(KeyedStorableItem& datarec, daddr_t diskpos) { fDataFile.seekg(diskpos); datarec.ReadFrom(fDataFile); } void BTree::SaveDataRecord(KeyedStorableItem& datarec, daddr_t diskpos) { fDataFile.seekp(diskpos); datarec.WriteTo(fDataFile); } daddr_t BTree::MakeNewDiskBNode(BTreeNode& bnode) { SaveBTreeNode(bnode, fTreefile_size); daddr_t diskpos = fTreefile_size; fTreefile_size += sizeof(BTreeNode); return diskpos; } daddr_t BTree::MakeNewDataRecord(KeyedStorableItem& data) { SaveDataRecord(data,fDatafile_size); daddr_t diskpos = fDatafile_size; fDatafile_size += data.DiskSize(); return diskpos; } int BTree::NumItems(void) const { return fHouseKeeping.fNumItems; } void BTree::Add(KeyedStorableItem& d) { KLRec rec_returned; daddr_t filepos_returned; int workflag; DoAdd(d, fHouseKeeping.fRoot, workflag, rec_returned, filepos_returned); if(workflag != 0) { /* Have to "grow" new root for the BTree. Existing root record was "split": the old root record is now to be the root of the left subtree, the address of the right subtree was returned in parameter filepos_returned, the new root will have one entry containing the key/location pair returned in rec_returned. */ BTreeNode new_root; new_root.n_data = 1; new_root.links[0] = fHouseKeeping.fRoot; new_root.data[0] = rec_returned; new_root.links [1] = filepos_returned; /* Add the record to the file */ fHouseKeeping.fRoot = MakeNewDiskBNode(new_root); } } void BTree::DoAdd(KeyedStorableItem& newData, daddr_t filepos, int& return_flag, KLRec& return_KLRec, daddr_t& return_diskpos) { int index; BTreeNode current; long key = newData.Key(); /* Initialize return flag --- most often will not want to pass any work back to caller. */ return_flag = 0; if (filepos == NO_DADDR) { /* Write data to data file, get caller to insert key/location record in its BTreeNode */ return_KLRec.fKey = key; return_KLRec.fLocation = MakeNewDataRecord(newData); return_diskpos = NO_DADDR; return_flag = 1; fHouseKeeping.fNumItems +=1; // one more record return; } /* Tree points to a node, see what's there. */ GetBTreeNode(current, filepos); int found = current.SearchInNode(key, index); if(found) { /* A record with the given key is already in the data file. Replace with new data */ daddr_t location = current.data[index].fLocation; SaveDataRecord(newData, location); return; } /* recursively insert new_data into subtree at current.link [index] */ KLRec rec_coming_up; daddr_t diskpos_coming_up; int need_insert_or_split; DoAdd(newData, current.links[index], need_insert_or_split, rec_coming_up, diskpos_coming_up); /* Most often, work all done at lower levels, and will be able to return */ if (need_insert_or_split == 0) return; /* However, will get cases where a record is moved out of next lower level and needs to be inserted at this level. */ if (current.NotFull()) current.InsertInNode(rec_coming_up, diskpos_coming_up, index); else { /* No room in current node, so split it... */ Split(current, rec_coming_up, diskpos_coming_up, index, return_KLRec, return_diskpos); /* If split a node, one record gets passed back to calling level; so set that flag. */ return_flag = 1; } /* Have changed current BTreeNode by adding to it, or by splitting it, so need to write it to disk */ SaveBTreeNode(current, filepos); } void BTree::Split(BTreeNode& nodetosplit, KLRec& extradata, daddr_t extralink, int index, KLRec& return_KLRec, daddr_t& return_diskpos) { if (index > MIN) SplitInsertRight(nodetosplit, extradata, extralink,index, return_KLRec, return_diskpos); else if (index == MIN) SplitInsertMiddle(nodetosplit, extradata, extralink,index, return_KLRec, return_diskpos); else SplitInsertLeft(nodetosplit, extradata, extralink,index, return_KLRec, return_diskpos); } void BTree::SplitInsertMiddle(BTreeNode& nodetosplit, KLRec& extradata, daddr_t extralink, int index, KLRec& return_KLRec, daddr_t& return_diskpos) { BTreeNode newNode; int i,j; /* Copy the contents of the top half of the node being split into the new BTreeNode. */ for (i = MAX-1, j = MIN-1; i >= MIN; i--, j--) { newNode.links[j+1] = nodetosplit.links[i+1]; newNode.data[j] = nodetosplit.data[i]; } newNode.links[0] = extralink; /* Both TreeNodes now have minimum number of elements */ newNode.n_data = nodetosplit.n_data = MIN; return_KLRec = extradata; return_diskpos = MakeNewDiskBNode(newNode); } void BTree::SplitInsertRight(BTreeNode& nodetosplit, KLRec& extradata, daddr_t extralink, int index, KLRec& return_KLRec, daddr_t& return_diskpos) { BTreeNode newNode; int i,j; /* Copy existing entries with keys greater than that of extra data into the new BTreeNode. Fill it in from the middle and work to lower index numbers. */ for (i = MAX-1, j = MIN-1; i >= index; i--, j--) { newNode.links[j+1] = nodetosplit.links [i+1]; newNode.data [j] = nodetosplit.data [i]; } /* put in extra data item */ newNode.links [j+1] = extralink; newNode.data[j] = extradata; /* and complete filling out the node */ for (j--; i > MIN; i--, j--) { newNode.links[j+1] = nodetosplit.links [i+1]; newNode.data[j] = nodetosplit.data [i]; } newNode.links[0] = nodetosplit.links[MIN+1]; return_KLRec = nodetosplit.data [MIN]; /* Both TreeNodes now have minimum number of elements */ newNode.n_data = nodetosplit.n_data = MIN; return_diskpos = MakeNewDiskBNode(newNode); } void BTree::SplitInsertLeft(BTreeNode& nodetosplit, KLRec& extradata, daddr_t extralink, int index, KLRec& return_KLRec, daddr_t& return_diskpos) { BTreeNode newNode; int i,j; /* Copy the contents of top half of node-to-split into the new node. */ for (i = MAX-1, j = MIN-1; i >= MIN; i--, j--) { newNode.links[j+1] = nodetosplit.links[i+1]; newNode.data[j] = nodetosplit.data[i]; } newNode.links [0] = nodetosplit.links [MIN]; /* Entry with largest key has to be extracted, as it is being moved through the tree toward the root. */ return_KLRec = nodetosplit.data[MIN-1]; /* Extra data item and link must fit into old node, move up some entries to make room. */ for (i--; i >= index; i--) { nodetosplit.links[i+2] = nodetosplit.links[i+1]; nodetosplit.data[i+1] = nodetosplit.data[i]; } /* Slot in the new information. */ nodetosplit.links[index+1] = extralink; nodetosplit.data[index] = extradata; /* Both TreeNodes now have minimum number of elements */ newNode.n_data = nodetosplit.n_data = MIN; return_diskpos = MakeNewDiskBNode(newNode); } int BTree::Find(long key, KeyedStorableItem& rec) { daddr_t diskpos = fHouseKeeping.fRoot; /* Iterative search down some vine of the overall tree */ while( diskpos != NO_DADDR) { int index; BTreeNode current; GetBTreeNode(current, diskpos); if (current.SearchInNode(key, index)) { daddr_t loc = current.data[index].fLocation; GetDataRecord(rec,loc); return 1; } diskpos = current.links[index]; } return 0; } #ifdef DEBUGGING void BTree::PrintTreeFile(void) { /* Rather than print the tree as a tree, this debug function displays the information in the index file. */ cout << "Index file information: " << endl; cout << "'Root' block is at disk location " << fHouseKeeping.fRoot << endl; cout << "There are " << fHouseKeeping.fNumItems << " data records." << endl; daddr_t startpos = sizeof(HK); fTreeFile.seekg(0, ios::end); daddr_t endpos = fTreeFile.tellg(); cout << "File length is " << endpos << " bytes." << endl; int numrecs = (endpos - startpos) / sizeof(BTreeNode); cout << "That should correspond to " << numrecs << " BTreeNodes." << endl; for(int i = 0; i < numrecs; i++) { BTreeNode aNode; GetBTreeNode(aNode, startpos); cout << "BTreeNode at " << startpos << endl; cout << "\t" << aNode.n_data << " keyrecords" << endl; for(int j = 0; j < aNode.n_data; j++) { cout << "!" << aNode.links[j] << "! "; cout << "[ " << aNode.data[j].fKey << ", "; cout << aNode.data[j].fLocation << "] "; } cout << "!" << aNode.links[aNode.n_data] << "!" << endl; startpos += sizeof(BTreeNode); char ch; cout << ">"; cin >> ch; } } #endif void BTree::Remove(long key) { if(fHouseKeeping.fRoot == NO_DADDR) return ; BTreeNode root_node; GetBTreeNode(root_node, fHouseKeeping.fRoot); (void) DoRemove(key, root_node); /* If all records removed from root node, have to replace it. */ if (root_node.n_data == 0) { /* Bypass the old root node */ fHouseKeeping.fRoot = root_node.links [0]; } else SaveBTreeNode(root_node, fHouseKeeping.fRoot); } int BTree::DoRemove(long bad_key, BTreeNode& cNode) { int index; int found = cNode.SearchInNode(bad_key, index); if(found) { DeleteKeyInNode(cNode,index); fHouseKeeping.fNumItems--; return cNode.Deficient(); } /* If data isn't at cNode->data[index], then it should be in the subtreee accessed via corresponding link */ daddr_t subtree = cNode.links[index]; /* if that link is null then the sought key can not be present, silently ignored. Maybe should be some kind of error. */ if(subtree == NO_DADDR) return 0; BTreeNode nextNode; int repairsneeded; GetBTreeNode(nextNode, subtree); repairsneeded = DoRemove(bad_key, nextNode); if (repairsneeded) Restore(cNode, nextNode, index); else SaveBTreeNode(nextNode, subtree); return cNode.Deficient(); } void BTree::DeleteKeyInNode(BTreeNode& aNode, int index) { /* Have found 'bad' record in a node. If it is a leaf node (easy to recognise because all the links will be 'NULL' (i.e. NO_DADDR)), then just tidy up the node. */ if (aNode.links [index+1] == NO_DADDR) { DeleteInLeaf(aNode, index); return; } /* But if it is not a leaf node, have to perform one of those rituals of 'promoting' a suitable data record from somewhere lower in the tree. Usual sort of thing, promote the "left most of the right subtree" So, find the data that are to be promoted. */ /* Start with right subtree */ daddr_t subtree = aNode.links[index + 1]; /* Run down left links, get back data in leftmost record and use that data to overwrite data associated with bad key */ aNode.data[index] = Successor(subtree); /* Of course, the original copy of the data just promoted is still in its place down at the end of the right subtree. It had better be removed. How? Call DoRemove specifying key of promoted data! */ long promotedskey = aNode.data[index].fKey; BTreeNode nxtNode; GetBTreeNode(nxtNode, subtree); int repairsneeded = DoRemove(promotedskey, nxtNode); if (repairsneeded) Restore (aNode, nxtNode, index+1); else SaveBTreeNode(nxtNode, subtree); } KLRec BTree::Successor(daddr_t subtree) { BTreeNode aNode; while(subtree != NO_DADDR) { GetBTreeNode(aNode, subtree); subtree = aNode.links[0]; } return aNode.data[0]; } void BTree::DeleteInLeaf(BTreeNode& leaf, int index) { leaf.n_data--; for(int i = index; i < leaf.n_data; i++) leaf.data[i] = leaf.data[i+1]; } void BTree::Restore(BTreeNode& parent, BTreeNode& deficient, int index) { if(index == 0) MergeOrCombineRight(parent, deficient, 0); else if(index == parent.n_data) MergeOrCombineLeft(parent, deficient, index); else { BTreeNode temp; daddr_t nbr_address; /* Look at BTreeNode to left of the one that has just become deficient */ nbr_address = parent.links[index-1]; GetBTreeNode(temp, nbr_address); int left_num = temp.n_data; /* Look at BTreeNode to right of the one that has just become deficient */ nbr_address = parent.links[index+1]; GetBTreeNode(temp, nbr_address); int right_num = temp.n_data; if(left_num >= right_num) MergeOrCombineLeft(parent, deficient, index); else MergeOrCombineRight(parent, deficient, index); } } void BTree::MergeOrCombineLeft(BTreeNode& parent, BTreeNode& deficient, int index) { BTreeNode left_nbr; daddr_t left_daddr = parent.links[index-1]; GetBTreeNode(left_nbr, left_daddr); if(left_nbr.MoreThanMinFilled()) { MoveRight (parent, left_nbr, deficient, index-1); SaveBTreeNode(left_nbr, left_daddr); SaveBTreeNode(deficient, parent.links[index]); } else { Combine(parent, left_nbr, deficient, index-1); SaveBTreeNode(left_nbr, left_daddr); } } void BTree::MergeOrCombineRight(BTreeNode& parent, BTreeNode& deficient, int index) { BTreeNode right_nbr; daddr_t right_daddr = parent.links[index+1]; GetBTreeNode(right_nbr, right_daddr); if (right_nbr.MoreThanMinFilled()) { MoveLeft(parent, deficient, right_nbr, index); SaveBTreeNode(deficient, parent.links[index]); SaveBTreeNode(right_nbr, right_daddr); } else { Combine(parent, deficient, right_nbr, index); SaveBTreeNode(deficient, parent.links[index]); } } void BTree::MoveRight(BTreeNode& parent, BTreeNode& left, BTreeNode& right, int index) { KLRec rec_from_parent = parent.data[index]; daddr_t downlink = left.links[left.n_data]; right.InsertAtLeft(rec_from_parent, downlink); parent.data[index] = left.data[left.n_data-1]; left.n_data--; } void BTree::MoveLeft(BTreeNode& parent, BTreeNode& left, BTreeNode& right, int index) { KLRec rec_from_parent = parent.data[index]; daddr_t downlink = right.links[0]; left.InsertAtRight(rec_from_parent, downlink); parent.data[index] = right.data[0]; right.ShiftLeft(); } void BTree::Combine(BTreeNode& parent, BTreeNode& left, BTreeNode& right, int index) { KLRec rec_from_parent = parent.data[index]; daddr_t downlink = right.links[0]; left.InsertAtRight(rec_from_parent, downlink); for(int j = 0; j < right.n_data; j++) left.InsertAtRight(right.data[j], right.links[j+1]); parent.Compress(index); } ======== commands.h ------ #ifndef __CMNDNUMS__ // Command identifier numbers const int cQUIT = 0; const int cNEW = 1; const int cOPEN = 2; const int cCLOSE = 3; const int cERR = -1; const int cNEWREC = 100; const int cDELREC = 101; const int cVIEW = 102; const int cSEARCH = 103; #endif ======= D.h ----- #ifndef __MYDYNAM__ #define __MYDYNAM__ class DynamicArray { public: DynamicArray(int size = 10, int inc = 5); int Length(void) const; int Position(void *item) const; void *Nth(int n) const; void Append(void *item); void *Remove(void *item); void *Remove(int itempos); private: void Grow(int amount); int fNum; int fSize; int fCSize; int fInc; void **fItems; }; inline int DynamicArray::Length(void) const { return fNum; } #endif ======= D.cp ------ #include #include #include "D.h" const int kLARGE = 10000; const int kINCL = 5000; DynamicArray::DynamicArray(int size, int inc) { assert((size > 1) && (size < kLARGE)); assert((inc > 1) && (inc < kINCL)); fNum = 0; fCSize = fSize = size; fInc = inc; fItems = new void* [size]; } int DynamicArray::Position(void *item) const { for(int i = 0; i< fNum; i++) if(fItems[i] == item) return i+1; return 0; } void *DynamicArray::Nth(int n) const { if((n < 1) || (n > fNum)) return NULL; n--; return fItems[n]; } void DynamicArray::Append(void *item) { if(fNum == fCSize) Grow(fInc); fItems[fNum] = item; fNum++; } void DynamicArray::Grow(int delta) { int newsize = fCSize + delta; void **temp = new void* [newsize]; fCSize = newsize; for(int i=0;i < fNum; i++) temp[i] = fItems[i]; delete [] fItems; fItems = temp; } void *DynamicArray::Remove(void *item) { int where = Position(item); return Remove(where); } void *DynamicArray::Remove(int itempos) { if((itempos < 1) || (itempos > fNum)) return NULL; itempos--; void *tmp = fItems[itempos]; for(int i = itempos + 1; i < fNum; i++) fItems[i-1] = fItems[i]; fNum--; if((fNum > fSize) && (fNum < (fCSize / 2))) Grow(-fInc); return tmp; } ======= Keyd.h ----- #ifndef __KEYEDSTORABLE__ #define __KEYEDSTORABLE__ #include class KeyedStorableItem { public: virtual ~KeyedStorableItem() { } virtual long Key(void) const = 0; virtual void PrintOn(ostream& out) const { } virtual long DiskSize(void) const = 0; virtual void ReadFrom(fstream& in) = 0; virtual void WriteTo(fstream& out) const = 0; }; inline ostream& operator<<(ostream& out, const KeyedStorableItem& d) { d.PrintOn(out); return out; } inline ostream& operator<<(ostream& out, const KeyedStorableItem* p_d) { p_d->PrintOn(out); return out; } #endif ====== Record.h ----- #ifndef __RECORD__ #define __RECORD__ #include #include "Keyd.h" class RecordWindow; class EditWindow; class Record : public KeyedStorableItem { public: Record(long recNum) { this->fRecNum = recNum; } virtual ~Record() { } virtual RecordWindow *DoMakeRecordWindow(); virtual long Key() const { return this->fRecNum; } virtual void SetDisplayField(EditWindow *e); virtual void ReadDisplayField(EditWindow *e); virtual void ConsistencyUpdate(EditWindow *e) { } protected: virtual void CreateWindow(); virtual void AddFieldsToWindow(); long fRecNum; RecordWindow *fRW; }; #endif ==== Record.cp ---- #include "Record.h" #include "RecordWin.h" RecordWindow *Record::DoMakeRecordWindow() { CreateWindow(); AddFieldsToWindow(); return fRW; } void Record::CreateWindow() { fRW = new RecordWindow(this); } void Record::AddFieldsToWindow() { fRW->ShowText("Record identifier ", 2, 2, 20, 0); fRW->ShowNumber(fRecNum, 24, 2, 10); // Then add data fields // typical code // new EditNum(id1, É); // rw->AddSubWindow() // new EditText(É) // rw->AddSubWindow() } void Record::SetDisplayField(EditWindow *e) { long id = e->Id(); switch(id) { // Typical code: // case 1: (1 is a number field) // ((EditNum*)e)->SetVal(numeric_data1, redraw); // break; // case 2: (2 is text field) // ((EditText*)e)->SetVal(characterstring, redraw); // break; } } void Record::ReadDisplayField(EditWindow *e) { long id = e->Id(); switch(id) { // Typical code: // case 1: (1 is a number field) // numericdata1 = ((EditNum*)e)->GetVal(); // break; // case 2: (2 is text field) // char* ptr = ((EditText*)e)->GetVal(); // strcpy(characterstring, ptr); // break; } } ======= WindowRep.h ----- #ifndef __MYWINDOWREP__ #define __MYWINDOWREP__ #include #include "D.h" const int CG_WIDTH = 78; const int CG_HEIGHT = 24; const int kNO_ID = 0; class WindowRep { public: static WindowRep *Instance(); void CloseDown(); void PutCharacter(char ch, int x, int y); void Clear(); void Delay(int seconds) const; char GetChar(); void MoveCursor(int x, int y); void Beep(); private: WindowRep(); void Initialize(); void PutCharacter(char ch); static WindowRep *sWindowRep; char fImage[CG_HEIGHT][CG_WIDTH]; int fXC; int fYC; }; class Window { public: Window(int id, int x, int y, int width, int height, char bkgd = ' ', int framed = 1); virtual ~Window(); void Clear(int x, int y); void Set(int x, int y, char ch); void SetBkgd(int x, int y, char ch); int X() const; int Y() const; int Width() const; int Height() const; int Id() const; void ShowAll() const; void ShowContent() const; void PrepareContent(); virtual void PrepareToDisplay() { } virtual void SetPromptPos(int x, int y); virtual int CanHandleInput() { return 0; } void DisplayWindow(); void ShowText(const char* str, int x, int y, int width, int multiline =0, int bkgd = 1); void ShowNumber(long value, int x, int y, int width, int bkgd = 1); void ClearArea(int x0, int y0, int x1, int y1, int bkgd); void AddSubWindow(Window*); protected: void Offset(int x, int y); void Change(int x, int y, char ch); int Valid(int x, int y) const; char Get(int x, int y, char **img) const; void SetFrame(); DynamicArray fSubWindows; char **fBkgd; char **fCurrentImg; int fX; int fY; int fWidth; int fHeight; int fFramed; int fId; }; inline int Window::Id() const { return fId; } inline int Window::Valid(int x, int y) const { return ((x>0) && (x<= fWidth) && (y>0) &&(y<=fHeight)); } class NumberItem : public Window { public: NumberItem(int id, int x, int y, int width, char *label, long initval = 0); void SetVal(long newVal, int redraw = 0); long GetVal() { return fVal; } virtual void PrepareToDisplay(); private: void ShowValue(int redraw); long fVal; int fLabelWidth; }; class EditWindow : public Window { public: EditWindow(int id, int x, int y, int width, int height = 3, int mustValidate = 0); virtual char GetInput(); virtual int CanHandleInput() { return 1; } virtual int ContentChanged(); protected: virtual void InitializeInput(); virtual void SetCursorPosition(); virtual int HandleCharacter(char ch); virtual void TerminateInput(); virtual void DoNotification(); virtual int Validate(); int fEntry; int fMustValidate; }; class EditText: public EditWindow { public: EditText(int id, int x, int y, int width, char *label, short size = 256, int mustValidate = 0); void SetVal(char* val, int redraw = 0); char* GetVal() { return fBuf; } virtual int ContentChanged(); protected: virtual void InitializeInput(); virtual void SetCursorPosition(); virtual int HandleCharacter(char ch); virtual void TerminateInput(); void ShowValue(int redraw); int fLabelWidth; char fBuf[256]; int fSize; int fEntry; int fChanged; }; class EditNum: public EditWindow { public: EditNum(int id, int x, int y, int width, char *label, long min, long max, int mustValidate = 0); void SetVal(long, int redraw = 0); long GetVal() { return fVal; } virtual int ContentChanged(); protected: void ShowValue(int redraw); virtual void InitializeInput(); virtual void SetCursorPosition(); virtual int HandleCharacter(char ch); virtual void TerminateInput(); virtual int Validate(); int fLabelWidth; long fMin; long fMax; long fSetVal; long fVal; int fsign; }; const int kMAXCHOICES = 6; class MenuWindow : public EditWindow { public: MenuWindow(int id, int x = 1, int y = 1, int width = 70, int height = 20); int AddMenuItem(const char *txt, int num); int PoseModally(); protected: virtual void SetCursorPosition(); void ClearCursorPosition(); virtual int HandleCharacter(char ch); int fCmds[kMAXCHOICES]; int fChoices; int fChosen; }; class NumberDialog : public EditWindow { public: NumberDialog(const char* msg, long min = LONG_MIN, long max = LONG_MAX); long PoseModally(long current); protected: EditNum *fE; }; class TextDialog : public EditWindow { public: TextDialog(const char* msg); virtual void PoseModally(char *current, char newdata[]); protected: EditText *fE; }; class InputFileDialog : public TextDialog { public: InputFileDialog(); void PoseModally(char *current, char newdata[], int checked = 1); }; class YesNoDialog : public TextDialog { public: YesNoDialog(char *question); int PoseModally(); }; void Alert(const char* msg); #endif ====== WindowRep.cp ----- #include #include #include #include #define SYMANTEC const char kTABC = 0xCA; #if defined(SYMANTEC) /* stdio is needed for fputc etc; console is Symantec's set of functions like gotoxy unix for sleep function */ #include #include #include #endif #if defined(DOS) /* conio has Borland's cursor graphics primitives dos needed for sleep function */ #include #include #endif #if defined(EASYWIN) /* Still need conio, but can't use sleep, achieve delay by alternative mechanism */ #include #endif #ifndef __MYWINDOWREP__ #include "WindowRep.h" #endif WindowRep *WindowRep::sWindowRep = NULL; WindowRep *WindowRep::Instance() { if(sWindowRep == NULL) sWindowRep = new WindowRep; return sWindowRep; } WindowRep::WindowRep() { Initialize(); for(int row = 0; row < CG_HEIGHT; row++) for(int col = 0; col< CG_WIDTH; col++) fImage[row][col] = ' '; Clear(); } void WindowRep::Initialize() { #if defined(SYMANTEC) /* Have to change the "mode" for the 'console' screen. Putting it in C_CBREAK allows characters to be read one by one as they are typed */ csetmode(C_CBREAK, stdin); #else /* No special initializations are needed for Borland environments */ #endif } void WindowRep::CloseDown() { Clear(); #if defined(SYMANTEC) csetmode(C_ECHO, stdin); #endif sWindowRep = NULL; Delay(2); delete this; } void WindowRep::MoveCursor(int x, int y) { if((x<1) || (x>CG_WIDTH)) return; if((y<1) || (y>CG_HEIGHT)) return; #if defined(SYMANTEC) cgotoxy(x,y,stdout); #else gotoxy(x,y); #endif fXC = x; fYC = y; } void WindowRep::PutCharacter(char ch) { #if defined(SYMANTEC) fputc(ch, stdout); fflush(stdout); #elif putch(ch); #endif fXC++; } void WindowRep::PutCharacter(char ch, int x, int y) { if((x<1) || (x>CG_WIDTH)) return; if((y<1) || (y>CG_HEIGHT)) return; if(ch != fImage[y-1][x-1]) { MoveCursor(x,y); PutCharacter(ch); fImage[y-1][x-1] = ch; } } void WindowRep::Clear() { for(int y=1;y<=CG_HEIGHT;y++) for(int x=1; x<=CG_WIDTH;x++) { MoveCursor(x, y); PutCharacter(' '); } } void WindowRep::Delay(int seconds) const { #if defined(EASYWIN) /* The EasyWin environment does not allow use of dos's sleep() function. So here do a "computational delay" The value 5000 will have to be adjusted to suit machine */ const long fudgefactor = 5000; double x; long lim = seconds*fudgefactor; while(lim>0) { x = 1.0/lim; lim--; } #else sleep(seconds); #endif } char WindowRep::GetChar() { char ch; #if defined(SYMANTEC) ch = fgetc(stdin); #elif ch = getche(); #endif fImage[fYC-1][fXC-1] = ch; fXC++; return ch; } void WindowRep::Beep() { for(int i=0;i<5;i++) PutCharacter('\a'); } Window::Window(int id, int x, int y, int width, int height, char bkgd, int framed ) { fId = id; fX = x-1; fY = y-1; fWidth = width; fHeight = height; fFramed = framed; fBkgd = new char* [height]; fCurrentImg = new char* [height]; for(int row = 0; row < height; row++) { fBkgd[row] = new char[width]; fCurrentImg[row] = new char[width]; for(int col = 0; col < width; col++) fBkgd[row][col] = bkgd; } } Window::~Window() { for(int row = 0; row < fHeight; row++) { delete [] fCurrentImg[row]; delete [] fBkgd[row]; } delete [] fCurrentImg; delete [] fBkgd; int n = fSubWindows.Length(); for(int i = n; i>0; i--) { Window* w = (Window*) fSubWindows.Nth(i); delete w; } } void Window::Offset(int x, int y) { fX += x; fY += y; } void Window::Clear(int x, int y) { if(Valid(x,y)) Change(x,y,Get(x,y,fBkgd)); } void Window::Set(int x, int y, char ch) { if(Valid(x,y)) Change(x, y, ch); } void Window::SetBkgd(int x, int y, char ch) { if(Valid(x,y)) { x--; y--; fBkgd[y][x] = ch; } } void Window::PrepareContent() { for(int row = 0; row < fHeight; row++) for(int col = 0; col < fWidth; col++) fCurrentImg[row][col] = fBkgd[row][col]; if(fFramed) SetFrame(); } char Window::Get(int x, int y, char **img) const { x--; y--; return img[y][x]; } void Window::SetFrame() { for(int x=1; xPutCharacter(fCurrentImg[row-1][col-1], fX+col, fY+row); } void Window::ShowContent() const { for(int row=2;rowPutCharacter(fCurrentImg[row-1][col-1], fX+col, fY+row); } void Window::Change(int x, int y, char ch) { if(fFramed) { if((x == 1) || (x == fWidth)) return; if((y == 1) || (y == fHeight)) return; } WindowRep::Instance()->PutCharacter(ch, x + fX, y + fY); x--; y--; fCurrentImg[y][x] = ch; } void Window::SetPromptPos(int x, int y) { WindowRep::Instance()->MoveCursor(x + fX , y + fY ); } void Window::ShowText(const char* str, int x, int y, int width, int multiline, int bkgd) { if(str == NULL) return; int right = x+width; right = (right < fWidth - 1) ? right : fWidth-1; int x1 = x; int y1 = y; int len = strlen(str); for(int i=0;i=right) { x1 = x; y1++; } else if((ch == ' ') && (x1>right-5)) { x1 = x; y1++; } } if(x1< right) { if(bkgd) this->SetBkgd(x1,y1,ch); else this->Set(x1,y1,ch); x1++; } } } void Window::ShowNumber(long value, int x, int y, int width, int bkgd) { char buff[16]; int w = (width < 15) ? width : 15; for(int i = 0; i< w;i++) buff[i] = ' '; buff[w] = '\0'; int pos = w-1; long val = value; if(val<0) val = -val; if(val == 0) buff[pos] = '0'; while(val > 0) { int d = val % 10; val = val / 10; char ch = d + '0'; buff[pos] = ch; pos--; } if(value<0) buff[pos] = '-'; ShowText(buff, x, y, w, 0, bkgd); } void Window::ClearArea(int x0, int y0, int x1, int y1, int bkgd) { for(int x=x0; x<=x1;x++) for(int y=y0; y<= y1;y++) if(bkgd) SetBkgd(x, y, ' '); else Clear(x,y); } void Window::AddSubWindow(Window *w) { fSubWindows.Append(w); w->Offset(fX, fY); } void Window::DisplayWindow() { PrepareContent(); PrepareToDisplay(); ShowAll(); int n = fSubWindows.Length(); for(int i=1; i<=n; i++) { Window* sub = (Window*) fSubWindows.Nth(i); sub->DisplayWindow(); } } EditWindow::EditWindow(int id, int x, int y, int width, int height, int mustValidate) : Window(id, x, y, width, height) { fMustValidate = mustValidate; } char EditWindow::GetInput() { int v; char ch; InitializeInput(); do { SetCursorPosition(); fEntry = 0; ch = WindowRep::Instance()->GetChar(); while(ch != '\n') { if(!HandleCharacter(ch)) break; ch = WindowRep::Instance()->GetChar(); } TerminateInput(); v = Validate(); } while (fMustValidate && !v); if(ContentChanged()) DoNotification(); return ch; } void EditWindow::InitializeInput() { PrepareToDisplay(); // ShowContent(); } void EditWindow::SetCursorPosition() { SetPromptPos(fWidth-1, fHeight-1); } int EditWindow::HandleCharacter(char ch) { return 1; } void EditWindow::TerminateInput() { } int EditWindow::Validate() { return 1; } int EditWindow::ContentChanged() { return 0; } void EditWindow::DoNotification() { } NumberItem::NumberItem(int id, int x, int y, int width, char *label, long initval) :Window(id, x, y, width,3) { fVal = initval; fLabelWidth = 0; int s = (label == NULL) ? 1 : strlen(label)+1; width -= 6; fLabelWidth = (s < width) ? s : width; ShowText(label, 2, 2, fLabelWidth); } void NumberItem::SetVal(long newVal, int redraw) { fVal = newVal; ShowValue(redraw); } void NumberItem::ShowValue(int redraw) { ShowNumber(fVal,fLabelWidth +1, 2, fWidth - fLabelWidth -2); if(redraw) { PrepareContent(); ShowContent(); } } void NumberItem::PrepareToDisplay() { ShowValue(1); } EditText::EditText(int id, int x, int y, int width, char *label, short size, int mustValidate) : EditWindow(id, x, y, width, 3, mustValidate) { fSize = size; fLabelWidth = 0; int s = (label == NULL) ? 2 : strlen(label)+2; width -= 6; fLabelWidth = (s < width) ? s : width; ShowText(label, 2, 2, fLabelWidth); fBuf[0] = '\0'; fChanged = 0; } void EditText::SetVal(char* val, int redraw) { int n = strlen(val); if(n>254) n = 254; strncpy(fBuf,val,n); fBuf[n] = '\0'; ShowValue(redraw); fChanged = 0; } void EditText::ShowValue(int redraw) { int left = fLabelWidth; int i,j; for(i=left; i fMax) val = fMax; if(val < fMin) val = fMin; fSetVal = fVal = val; ShowValue(redraw); } void EditNum::InitializeInput() { EditWindow::InitializeInput(); fsign = 1; } void EditNum::ShowValue(int redraw) { int left = fLabelWidth; int pos = fWidth - 1; long val = fVal; for(int i = left; i<= pos; i++) fCurrentImg[1][i-1] = ' '; if(val<0) val = -val; if(val == 0) fCurrentImg[1][pos-1] = '0'; while(val > 0) { int d = val % 10; val = val / 10; char ch = d + '0'; fCurrentImg[1][pos-1] = ch; pos--; if(pos <= left) break; } if(pos<=left) for(i=left; iBeep(); WindowRep::Instance()->Delay(1); fVal = fSetVal; ClearArea(fLabelWidth, 2, fWidth-1, 2, 0); ShowValue(1); return 0; } else return 1; } int EditNum::ContentChanged() { return fVal != fSetVal; } MenuWindow::MenuWindow(int id, int x, int y, int width, int height) : EditWindow(id, x, y, width, height) { fChoices = 0; ShowText("(use 'option-space' to switch between choices, 'enter' to select)", 2, height-1, width-4); } int MenuWindow::AddMenuItem(const char *txt, int num) { if(fChoices == kMAXCHOICES) return 0; int len = strlen(txt); fCmds[fChoices] = num; int x = 10; int y = 4 + 3*fChoices; ShowText(txt, x, y, fWidth-14); fChoices++; return 1; } int MenuWindow::PoseModally() { DisplayWindow(); fChosen = 0; GetInput(); return fCmds[fChosen]; } void MenuWindow::SetCursorPosition() { int x = 5; int y = 4 + 3*fChosen; ShowText("==>", x, y, 4,0,0); SetPromptPos(x,y); } void MenuWindow::ClearCursorPosition() { int x = 5; int y = 4 + 3*fChosen; ShowText(" ", x, y, 4, 0, 0); } int MenuWindow::HandleCharacter(char ch) { if(ch != kTABC) return 1; ClearCursorPosition(); fChosen++; if(fChosen==fChoices) fChosen = 0; SetCursorPosition(); return 1; } NumberDialog::NumberDialog(const char*msg, long min, long max) : EditWindow(kNO_ID, 15, 5, 35,10) { fE = new EditNum(kNO_ID, 5, 5, 20, NULL, min, max, 1); ShowText(msg, 2, 2, 30); AddSubWindow(fE); } long NumberDialog::PoseModally(long current) { DisplayWindow(); fE->SetVal(current, 1); fE->GetInput(); return fE->GetVal(); } TextDialog::TextDialog(const char*msg) : EditWindow(kNO_ID, 15, 5, 35,10) { fE = new EditText(kNO_ID, 5, 5, 20, NULL, 63, 1); ShowText(msg, 2, 2, 30); AddSubWindow(fE); } void TextDialog::PoseModally(char *current, char newdata[]) { DisplayWindow(); fE->SetVal(current, 1); fE->GetInput(); strcpy(newdata,fE->GetVal()); } InputFileDialog::InputFileDialog() : TextDialog("Name of input file") { } void InputFileDialog::PoseModally(char *current, char newdata[], int checked) { DisplayWindow(); for(;;) { fE->SetVal(current, 1); fE->GetInput(); strcpy(newdata,fE->GetVal()); if(!checked) return; ifstream in; in.open(newdata,ios::in | ios::nocreate); int state = in.good(); in.close(); if(state) return; WindowRep::Instance()->Beep(); fE->SetVal("File not found", 1); WindowRep::Instance()->Delay(1); } } void Alert(const char*msg) { EditWindow e(kNO_ID, 15, 5, 35, 10); e.DisplayWindow(); e.ShowText(msg, 2, 2, 30, 0, 0); e.ShowText("OK", 18, 6, 3, 0, 0); e.GetInput(); } ------ #ifndef __RECORDWIN__ #define __RECORDWIN__ #ifndef __MYWINDOWREP__ #include "WindowRep.h" #endif class Record; class RecordWindow : public EditWindow { public: RecordWindow(Record *r); void PoseModally(); protected: void CountEditWindows(); void NextEditWindow(); virtual void InitEditWindows(); Record *fRecord; int fNumEdits; int fCurrent; EditWindow *fEWin; }; #endif --------- #include "RecordWin.h" #include "Record.h" RecordWindow::RecordWindow(Record *r) : EditWindow(0, 1, 1, 70, 20) { fRecord = r; } void RecordWindow::PoseModally() { char ch; DisplayWindow(); CountEditWindows(); InitEditWindows(); fCurrent = fNumEdits; do { NextEditWindow(); fRecord->SetDisplayField(fEWin); ch = fEWin->GetInput(); if(fEWin->ContentChanged()) { fRecord->ReadDisplayField(fEWin); fRecord->ConsistencyUpdate(fEWin); } } while(ch != '\n'); } void RecordWindow::CountEditWindows() { fNumEdits = 0; int nsub = fSubWindows.Length(); for(int i = 1; i <= nsub; i++) { Window* w = (Window*) fSubWindows.Nth(i); if(w->CanHandleInput()) fNumEdits++; } } void RecordWindow::InitEditWindows() { int nsub = fSubWindows.Length(); for(int i = 1; i <= nsub; i++) { Window* w = (Window*) fSubWindows.Nth(i); if(w->CanHandleInput()) fRecord->SetDisplayField((EditWindow*)w); } } void RecordWindow::NextEditWindow() { if(fCurrent == fNumEdits) fCurrent = 1; else fCurrent++; int nsub = fSubWindows.Length(); for(int i = 1, j= 0; i <= nsub; i++) { Window* w = (Window*) fSubWindows.Nth(i); if(w->CanHandleInput()) { j++; if(j == fCurrent) { fEWin = (EditWindow*) w; return; } } } }