553 lines
14 KiB
Plaintext
553 lines
14 KiB
Plaintext
{************************************************}
|
|
{ }
|
|
{ Turbo Pascal 6.0 }
|
|
{ Turbo Vision Forms Demo }
|
|
{ Copyright (c) 1990 by Borland International }
|
|
{ }
|
|
{************************************************}
|
|
|
|
unit ListDlg;
|
|
|
|
{$F+,O+,X+,S-,D+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Dos, Objects, Memory, Drivers, Views, Dialogs, Stddlg,
|
|
DataColl, FormCmds;
|
|
|
|
type
|
|
PListKeyBox = ^TListKeyBox;
|
|
TListKeyBox = object(TSortedListBox)
|
|
function GetText(Item: Integer; MaxLen: Integer): String; virtual;
|
|
end;
|
|
|
|
PListDialog = ^TListDialog;
|
|
TListDialog = object(TDialog)
|
|
DataCollection: PDataCollection;
|
|
FileName: PString;
|
|
FormDataFile: PResourceFile;
|
|
IsValid: Boolean;
|
|
List: PListKeyBox;
|
|
Modified: Boolean;
|
|
constructor Init(RezName: PathStr);
|
|
destructor Done; virtual;
|
|
procedure Close; virtual;
|
|
procedure HandleEvent(var Event: TEvent); virtual;
|
|
function OpenDataFile(Name: PathStr;
|
|
var DataFile: PResourceFile; Mode: Word): Boolean;
|
|
function SaveList: Boolean;
|
|
function SaveForm(F: PDialog): Boolean;
|
|
procedure StackOnPrev(F: PDialog);
|
|
function Valid(Command: Word): Boolean; virtual;
|
|
end;
|
|
|
|
function FileExists(Name: PathStr): Boolean;
|
|
|
|
implementation
|
|
|
|
uses App, Forms, MsgBox;
|
|
|
|
function FileExists(Name: PathStr): Boolean;
|
|
var
|
|
SR: SearchRec;
|
|
begin
|
|
FindFirst(Name, 0, SR);
|
|
FileExists := DosError = 0;
|
|
end;
|
|
|
|
{ TListKeyBox }
|
|
function TListKeyBox.GetText(Item: Integer; MaxLen: Integer): String;
|
|
var
|
|
S: String;
|
|
begin
|
|
with PDataCollection(List)^ do
|
|
begin
|
|
case KeyType of
|
|
StringKey: GetText := TSortedListBox.GetText(Item, MaxLen);
|
|
LongIntKey:
|
|
begin
|
|
Str(LongInt(KeyOf(At(Item))^):MaxLen - 3, S);
|
|
GetText := Copy(S, 1, MaxLen);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ TListDialog }
|
|
constructor TListDialog.Init(RezName: PathStr);
|
|
const
|
|
ButtonCt = 4;
|
|
FormX = 2;
|
|
FormY = 2;
|
|
FormWd = 30;
|
|
FormHt = 13;
|
|
ListX = 2;
|
|
ListY = 3;
|
|
DefaultListWd = 12;
|
|
ListHt = ButtonCt * 2;
|
|
ButtonWd = 12;
|
|
ButtonY = ListY;
|
|
|
|
var
|
|
R: TRect;
|
|
SB: PScrollBar;
|
|
Y: Integer;
|
|
D: DirStr;
|
|
N: NameStr;
|
|
E: ExtStr;
|
|
F: PForm;
|
|
ListWd: Word;
|
|
ButtonX: Word;
|
|
begin
|
|
FSplit(FExpand(RezName), D, N, E);
|
|
R.Assign(FormX, FormY, FormX + FormWd, FormY + FormHt);
|
|
TDialog.Init(R, N);
|
|
|
|
FileName := NewStr(D + N + E);
|
|
{ Read data off resource stream }
|
|
if OpenDataFile(FileName^, FormDataFile, stOpen) then
|
|
begin
|
|
{ Get horizontal size of key field }
|
|
F := PForm(FormDataFile^.Get('FormDialog'));
|
|
if F = nil then
|
|
begin
|
|
MessageBox('Error accessing file data.', nil, mfError or mfOkButton);
|
|
Exit;
|
|
end;
|
|
|
|
{ Base listbox width on key width. Grow entire dialog if required }
|
|
if F^.KeyWidth > DefaultListWd then
|
|
begin
|
|
ListWd := F^.KeyWidth;
|
|
GrowTo(FormWd + ListWd - DefaultListWd, FormHt);
|
|
end
|
|
else ListWd := DefaultListWd;
|
|
|
|
{ Move to upper right corner of desktop }
|
|
Desktop^.GetExtent(R); { Desktop coordinates }
|
|
MoveTo(R.B.X - Size.X, 1);
|
|
|
|
Dispose(F, Done);
|
|
|
|
{ Read data collection into memory }
|
|
DataCollection := PDataCollection(FormDataFile^.Get('FormData'));
|
|
if DataCollection <> nil then
|
|
begin
|
|
{ Loaded successfully: build ListDialog dialog }
|
|
|
|
{ Scrollbar }
|
|
R.Assign(ListX + ListWd, ListY, ListX + ListWd + 1, ListY + ListHt);
|
|
SB := New(PScrollBar, Init(R));
|
|
Insert(SB);
|
|
|
|
{ List box }
|
|
R.Assign(ListX, ListY, ListX + ListWd, ListY + ListHt);
|
|
List := New(PListKeyBox, Init(R, 1, SB));
|
|
List^.NewList(DataCollection);
|
|
Insert(List);
|
|
|
|
{ Label }
|
|
R.Assign(ListX, ListY - 1, ListX + 10, ListY);
|
|
Insert(New(PLabel, Init(R, '~K~eys', List)));
|
|
|
|
{ Buttons }
|
|
ButtonX := ListX + ListWd + 2;
|
|
Y := ButtonY;
|
|
R.Assign(ButtonX, Y, ButtonX + ButtonWd, Y + 2);
|
|
Insert(New(PButton, Init(R, '~E~dit', cmFormEdit, bfDefault)));
|
|
|
|
Inc(Y, 2);
|
|
R.Assign(ButtonX, Y, ButtonX + ButtonWd, Y + 2);
|
|
Insert(New(PButton, Init(R, '~N~ew', cmFormNew, bfNormal)));
|
|
|
|
Inc(Y, 2);
|
|
R.Assign(ButtonX, Y, ButtonX + ButtonWd, Y + 2);
|
|
Insert(New(PButton, Init(R, '~D~elete', cmFormDel, bfNormal)));
|
|
|
|
Inc(Y, 2);
|
|
R.Assign(ButtonX, Y, ButtonX + ButtonWd, Y + 2);
|
|
Insert(New(PButton, Init(R, '~S~ave', cmListSave, bfNormal)));
|
|
|
|
SelectNext(False); { Select first field }
|
|
IsValid := True;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
destructor TListDialog.Done;
|
|
begin
|
|
if List <> nil then Dispose(List, Done);
|
|
if DataCollection <> nil then Dispose(DataCollection, Done);
|
|
if FormDataFile <> nil then Dispose(FormDataFile, Done);
|
|
if FileName <> nil then DisposeStr(FileName);
|
|
TDialog.Done;
|
|
end;
|
|
|
|
procedure TListDialog.Close;
|
|
begin
|
|
{ TDialog.Close calls Valid and then Free. Before calling
|
|
Free (which calls Done), tell all attached forms to close.
|
|
}
|
|
if Valid(cmClose) then
|
|
begin
|
|
{ Stop desktop video update in case there are scores of attached forms }
|
|
Desktop^.Lock;
|
|
Message(Desktop, evBroadcast, cmCloseForm, @Self);
|
|
Desktop^.Unlock;
|
|
Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TListDialog.HandleEvent(var Event: TEvent);
|
|
|
|
function EditingForm: PForm;
|
|
{ Return pointer to the form that is editing the current selection }
|
|
begin
|
|
EditingForm := Message(Desktop, evBroadcast,
|
|
cmEditingForm, DataCollection^.At(List^.Focused));
|
|
end;
|
|
|
|
procedure FormOpen(NewForm: Boolean);
|
|
var
|
|
F: PForm;
|
|
begin
|
|
if not NewForm then
|
|
begin
|
|
{ Empty collection? }
|
|
if DataCollection^.Count = 0 then Exit;
|
|
|
|
{ If selection is being edited, then bring its form to top }
|
|
F := EditingForm;
|
|
if F <> nil then
|
|
begin
|
|
F^.Select;
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
{ Selection is not being edited: open new form from the resource file }
|
|
F := PForm(FormDataFile^.Get('FormDialog'));
|
|
if F = nil then
|
|
MessageBox('Error opening form.', nil, mfError or mfOkButton)
|
|
else
|
|
begin
|
|
with F^ do
|
|
begin
|
|
ListDialog := @Self; { Form points back to List }
|
|
if NewForm then
|
|
PrevData := nil { Adding new form }
|
|
else
|
|
begin
|
|
{ Edit data from collection }
|
|
PrevData := DataCollection^.At(List^.Focused);
|
|
SetData(PrevData^);
|
|
end;
|
|
end;
|
|
if Application^.ValidView(F) <> nil then
|
|
begin
|
|
StackOnPrev(F);
|
|
if NewForm then Desktop^.Insert(F) { Insert & select }
|
|
else Desktop^.InsertBefore(F, Next); { Insert but keep focus }
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure DeleteSelection;
|
|
var
|
|
F: PForm;
|
|
begin
|
|
{ Empty collection? }
|
|
if DataCollection^.Count = 0 then Exit;
|
|
|
|
{ Don't allow delete of data already being edited }
|
|
F := EditingForm;
|
|
if F <> nil then
|
|
begin
|
|
F^.Select;
|
|
MessageBox('Data is already being edited. Close form before deleting.',
|
|
nil, mfWarning or mfOkButton);
|
|
Exit;
|
|
end;
|
|
|
|
{ Confirm delete }
|
|
if MessageBox('Are you sure you want to delete this item?', nil,
|
|
mfWarning or mfYesNoCancel) = cmYes then
|
|
begin
|
|
DataCollection^.AtFree(List^.Focused);
|
|
List^.SetRange(DataCollection^.Count);
|
|
List^.DrawView;
|
|
Modified := True;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
with Event do
|
|
if (What = evKeyDown) and (KeyCode = kbEsc) then
|
|
begin
|
|
What := Command;
|
|
Command := cmClose;
|
|
end;
|
|
|
|
TDialog.HandleEvent(Event);
|
|
|
|
case Event.What of
|
|
evCommand:
|
|
begin
|
|
case Event.Command of
|
|
cmFormEdit: FormOpen(False);
|
|
cmFormNew: FormOpen(True);
|
|
cmFormDel: DeleteSelection;
|
|
cmListSave: if Modified then SaveList;
|
|
else
|
|
Exit;
|
|
end;
|
|
ClearEvent(Event);
|
|
end;
|
|
evKeyDown:
|
|
begin
|
|
case Event.KeyCode of
|
|
kbIns: FormOpen(True);
|
|
else
|
|
Exit;
|
|
end;
|
|
ClearEvent(Event);
|
|
end;
|
|
evBroadcast:
|
|
begin
|
|
case Event.Command of
|
|
{ Respond to broadcast from TSortedListBox }
|
|
cmListItemSelected: FormOpen(False);
|
|
|
|
{ Keep file from being edited simultaneously by 2 lists }
|
|
cmEditingFile: if FileName^ = PString(Event.InfoPtr)^ then
|
|
ClearEvent(Event);
|
|
|
|
{ Respond to search for topmost list dialog }
|
|
cmTopList: ClearEvent(Event);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TListDialog.OpenDataFile(Name: PathStr;
|
|
var DataFile: PResourceFile; Mode: Word): Boolean;
|
|
var
|
|
S: PStream;
|
|
begin
|
|
S := New(PBufStream, Init(Name, Mode, 1024));
|
|
DataFile := New(PResourceFile, Init(S));
|
|
if S^.Status <> stOk then
|
|
begin
|
|
Dispose(DataFile, Done);
|
|
DataFile := nil;
|
|
OpenDataFile := False;
|
|
end
|
|
else OpenDataFile := True;
|
|
end;
|
|
|
|
function TListDialog.SaveList: Boolean;
|
|
var
|
|
S: PStream;
|
|
NewDataFile: PResourceFile;
|
|
Form: PForm;
|
|
D: DirStr;
|
|
N: NameStr;
|
|
E: ExtStr;
|
|
F: File;
|
|
begin
|
|
{ Empty collection? Unedited? }
|
|
if (DataCollection^.Count = 0) or not Modified then
|
|
begin
|
|
SaveList := True;
|
|
Exit;
|
|
end;
|
|
|
|
SaveList := False;
|
|
{ Read form definition out of original form file }
|
|
Form := PForm(FormDataFile^.Get('FormDialog'));
|
|
if Form = nil then
|
|
MessageBox('Cannot find original file. Data not saved.',
|
|
nil, mfError or mfOkButton)
|
|
else
|
|
begin
|
|
{ Create new data file }
|
|
FSplit(FileName^, D, N, E);
|
|
if not OpenDataFile(D + N + '.$$$', NewDataFile, stCreate) then
|
|
MessageBox('Cannot create file. Data not saved.',
|
|
nil, mfError or mfOkButton)
|
|
else
|
|
begin
|
|
{ Create new from form and collection in memory }
|
|
NewDataFile^.Put(Form, 'FormDialog');
|
|
NewDataFile^.Put(DataCollection, 'FormData');
|
|
NewDataFile^.Flush;
|
|
Dispose(NewDataFile, Done);
|
|
|
|
{ Close original file, rename to .BAK }
|
|
Dispose(FormDataFile, Done);
|
|
FormDataFile := nil;
|
|
{$I-}
|
|
if FileExists(D + N + '.BAK') then
|
|
begin
|
|
Assign(F, D + N + '.BAK');
|
|
Erase(F);
|
|
end;
|
|
Assign(F, FileName^);
|
|
Rename(F, D + N + '.BAK');
|
|
{$I+}
|
|
|
|
{ Error trying to erase old .BAK or rename original to .BAK? }
|
|
if IOResult <> 0 then
|
|
begin
|
|
MessageBox('Cannot create .BAK file. Data not saved.',
|
|
nil, mfError or mfOkButton);
|
|
|
|
{ Try to re-open original. New data will still be in memory }
|
|
if not OpenDataFile(FileName^, FormDataFile, stOpen) then
|
|
begin
|
|
MessageBox('Cannot re-open original file.',
|
|
nil, mfError or mfOkButton);
|
|
Free; { Cannot proceed. Free data and close window }
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
{ Rename temp file to original file and re-open }
|
|
Assign(F, D + N + '.$$$');
|
|
Rename(F, FileName^);
|
|
OpenDataFile(FileName^, FormDataFile, stOpen);
|
|
|
|
Modified := False;
|
|
SaveList := True;
|
|
end;
|
|
end;
|
|
Dispose(Form, Done);
|
|
end;
|
|
end;
|
|
|
|
function TListDialog.SaveForm(F: PDialog): Boolean;
|
|
var
|
|
i: Integer;
|
|
P: Pointer;
|
|
begin
|
|
SaveForm := False;
|
|
with PForm(F)^, DataCollection^ do
|
|
begin
|
|
{ Validate data before updating collection }
|
|
if not F^.Valid(cmFormSave) then Exit;
|
|
|
|
{ Extract data from form. Don't use safety pool. }
|
|
P := MemAlloc(ItemSize);
|
|
if P = nil then
|
|
begin
|
|
Application^.OutOfMemory;
|
|
Exit;
|
|
end;
|
|
|
|
GetData(P^);
|
|
{ If no duplicates, make sure not attempting to add duplicate key }
|
|
if not Duplicates and Search(KeyOf(P), i) then
|
|
if (PrevData = nil) or (PrevData <> At(i)) then
|
|
begin
|
|
FreeMem(P, ItemSize);
|
|
MessageBox('Duplicate keys are not allowed in this database.'+
|
|
' Delete duplicate record before saving this form.', nil,
|
|
mfError or mfOkButton);
|
|
Exit;
|
|
end;
|
|
|
|
{ Free previous data? }
|
|
if (PrevData <> nil) then Free(PrevData);
|
|
|
|
{ TDataCollection.Insert may fail because it doesn't use
|
|
the safety pool. Check status field after insert and cleanup
|
|
if necessary.
|
|
}
|
|
Insert(P);
|
|
if Status <> 0 then
|
|
begin
|
|
FreeMem(P, ItemSize);
|
|
Application^.OutOfMemory;
|
|
Exit;
|
|
end;
|
|
|
|
{ Success: store off original data pointer }
|
|
PrevData := P;
|
|
|
|
{ Redraw list }
|
|
List^.SetRange(Count);
|
|
List^.DrawView;
|
|
|
|
Modified := True;
|
|
SaveForm := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TListDialog.StackOnPrev(F: PDialog);
|
|
var
|
|
TopForm: PForm;
|
|
R: TRect;
|
|
begin
|
|
{ Stack on top topmost form or on top list if first form }
|
|
TopForm := Message(Owner, evBroadcast, cmTopForm, @Self);
|
|
if (TopForm <> nil) then
|
|
{ Stack on top previous topmost form }
|
|
with TopForm^.Origin do
|
|
F^.MoveTo(X + 1, Y + 1)
|
|
else
|
|
begin
|
|
{ Stack right or left of ListDialog }
|
|
if Origin.X > F^.Size.X then F^.Moveto(0, Origin.Y)
|
|
else F^.Moveto(Origin.X + Size.X + 1, Origin.Y);
|
|
end;
|
|
|
|
{ Visible on desktop? Make sure at least half of form is visible }
|
|
Owner^.GetExtent(R); { Desktop coordinates }
|
|
with F^, F^.Origin do { Keep stack on screen }
|
|
begin
|
|
if (X + Size.X div 2 > R.B.X) then F^.MoveTo(0, 1);
|
|
if (Y + Size.Y div 2 > R.B.Y) then F^.MoveTo(X, 1);
|
|
end;
|
|
end;
|
|
|
|
function TListDialog.Valid(Command: Word): Boolean;
|
|
var
|
|
Ok: Boolean;
|
|
Reply: Word;
|
|
begin
|
|
Ok := True;
|
|
case Command of
|
|
cmValid:
|
|
begin
|
|
Ok := IsValid;
|
|
if not Ok then
|
|
MessageBox('Error opening file (%S).',
|
|
@FileName, mfError or mfOkButton);
|
|
end;
|
|
cmQuit, cmClose:
|
|
begin
|
|
{ Any forms open that cannot close? }
|
|
Ok := Message(Desktop, evBroadcast, cmCanCloseForm, @Self) = nil;
|
|
|
|
{ Any data modified? }
|
|
if Ok and Modified then
|
|
begin
|
|
Select;
|
|
Reply := MessageBox('Database has been modified. Save? ', nil,
|
|
mfYesNoCancel);
|
|
case Reply of
|
|
cmYes: Ok := SaveList;
|
|
cmNo: Modified := False; { abandon changes }
|
|
else
|
|
Ok := False; { cancel close request }
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
if Ok then Valid := TDialog.Valid(Command)
|
|
else Valid := False;
|
|
end;
|
|
|
|
end.
|