{**************************************************}
{                    Chart 1.0                     }
{                    Written in                    }
{             Turbo Pascal for Windows             }
{                Copyright (c) 1991                }
{                  Zack Urlocker                   }
{                    05/02/91                      }
{**************************************************}

program PCharts;

{ This is a simple implementation of a charting program written
  in Turbo Pascal for Windows using the ObjectWindows application
  framework.  The program is divided into several object types:

  TChartApplication      --creates and shows the main window
  TChartDialog           --allows editing of data items
  TChartWindow           --responds to Windows messages, menu commands,
                           keyboard and mouse events
  TChart and descendants --chart objects that can draw, rescale etc
                           these are in the Charts unit
  TDict and TAssoc       --data management objects
                           these are in the Dicts unit
}

{$R PChart.res}        { Link in resources }

{$IFDEF Final}        { Remove debug code for final version}
{$D-,I-,L-,R-,S-}
{$ELSE}
{$D+,I+,L+,R+,S+}
{$ENDIF}

uses Dicts, WObjects, WinTypes, WinProcs, Strings, StdDlgs, Charts;

const
 cm_New    = 501;       { Menu items }
 cm_Open   = 502;
 cm_Save   = 503;
 cm_SaveAs = 504;
 cm_Exit   = 508;
 cm_About  = 509;
 cm_HBar   = 555;
 cm_VBar   = 556;
 cm_V3DBar = 557;
 cm_Pie    = 558;
 cm_Change = 552;
 cm_SetName= 553;
 cm_Help   = 600;
 cm_CmdMode= 601;      { For Lotus style slash (/) key commands }

 id_Label  = 101;       { Dialog box fields}
 id_Value  = 102;
 id_Delete = 104;
 fieldLen  = 16;

type

  { The application defines startup behavior for the window. }
  TChartApplication = object(TApplication)
    procedure InitInstance; virtual;
    procedure InitMainWindow; virtual;
  end;

  { Dialog transfer record }
  ItemTransferBuffer = record
    LabelStr, ValueStr : array[0..FieldLen-1] of char;
  end;

  { The dialog is used for input of new data items. }
  PChartDialog = ^TChartDialog;
  TChartDialog = object(TDialog)
    LabelEdit, valueEdit : PEdit;
    constructor Init(AParent: PWindowsObject; ATitle:PChar);
    procedure Delete(var Msg:TMessage); virtual id_First + id_Delete;
  end;

  { The window responds to messages and controls the game board. }
  PChartWindow = ^TChartWindow;
  TChartWindow = object(TWindow)
    Name : PChar;     { Name for file I/O     }
    Chart : PChart;   { Pointer to a chartl   }
    Saved : Boolean;  { has chart been saved? }
    ItemBuffer : ItemTransferBuffer; { for ChartDialog }
    constructor Init(AParent: PWindowsObject; ATitle: PChar);
    procedure GetWindowClass(var WndClass: TWndClass); virtual;
    procedure redraw;
    function CanClose: Boolean; virtual;
    procedure IOError(ErrMessage : PChar);
    procedure SetCaption(FName : PChar);
    function Read(fName : PChar): Boolean;
    function Write(fName : PChar): Boolean;

    { menu response methods }
    procedure NewFile(var Msg: TMessage); virtual cm_First + cm_New;
    procedure Open(var Msg: TMessage); virtual cm_First + cm_Open;
    procedure Save(var Msg: TMessage); virtual cm_First + cm_Save;
    procedure SaveAs(var Msg: TMessage); virtual cm_First + cm_SaveAs;
    procedure Exit(var Msg: TMessage); virtual cm_First + cm_Exit;
    procedure HBar(var Msg: TMessage); virtual cm_First + cm_HBar;
    procedure VBar(var Msg: TMessage); virtual cm_First + cm_VBar;
    procedure V3DBar(var Msg: TMessage); virtual cm_First + cm_V3DBar;
    procedure Pie(var Msg: TMessage); virtual cm_First + cm_Pie;
    procedure Change(var Msg: TMessage); virtual cm_First + cm_Change;
    procedure SetName(var Msg: TMessage); virtual cm_First + cm_SetName;
    procedure About(var Msg: TMessage); virtual cm_First + cm_About;
    procedure Help(var Msg: TMessage); virtual cm_First + cm_Help;
    procedure CmdMode(var Msg: TMessage); virtual cm_First + cm_CmdMode;

    { windows message response methods }
    procedure Paint(DC: HDC; var PaintInfo: TPaintStruct); virtual;
    procedure wmSetFocus(var Msg: TMessage); virtual wm_SetFocus;
    procedure wmKillFocus(var Msg: TMessage); virtual wm_KillFocus;
    procedure wmLButtonDown(var Msg: TMessage); virtual wm_LButtonDown;
    procedure wmKeyDown(var Msg: TMessage); virtual wm_KeyDown;
    procedure wmSize(var Msg: TMessage); virtual wm_Size;
  end;


{--------------------------------------------------}
{ TChartApplication's method implementations:      }
{--------------------------------------------------}

{ Load the accelerator table for hotkeys }
procedure TChartApplication.InitInstance;
begin
  Tapplication.InitInstance;
  HAccTable := LoadAccelerators(HInstance, 'ChartKeys');
end;

{ Start the main window }
procedure TChartApplication.InitMainWindow;
begin
  MainWindow := New(PChartWindow,
                Init(nil, 'PChart : (untitled)'));
end;


{--------------------------------------------------}
{ TChartDialog method implementations:             }
{--------------------------------------------------}

{ The edit controls will contain the transfer data. }
constructor TChartDialog.Init(AParent: PWindowsObject; ATitle:PChar);
begin
  TDialog.Init(AParent, ATitle);
  new(LabelEdit, initResource(@Self, id_Label, fieldLen));
  new(ValueEdit, initResource(@Self, id_Value, fieldLen));
end;

{ Respond to Delete Button by transfering data out.
  This is automatically done if the user presses Ok. }
procedure TChartDialog.Delete(var Msg:TMessage);
begin
  TransferData(tf_GetData);
  EndDlg(id_Delete);
end;


{--------------------------------------------------}
{ TChartWindow's method implementations:           }
{--------------------------------------------------}

{ Initialize all fields to starting values }
constructor TChartWindow.Init(AParent: PWindowsObject; ATitle: PChar);
var Msg : TMessage;
begin
  TWindow.Init(AParent, ATitle);
  Chart := new(PVbarChart, init);
  Saved := True;
  getMem(Name, 255);
  StrPcopy(ItemBuffer.LabelStr, 'Item');
  StrPCopy(ItemBuffer.ValueStr, '50');
  redraw;
  with attr do
  begin
    w:=400;          { Force window size }
    h:=300;
  end;
end;

{ Override default cursor, icon, menu }
procedure TChartWindow.GetWindowClass(var WndClass: TWndClass);
begin
  TWindow.GetWindowClass(WndClass);
  WndClass.Style := 0;
  WndClass.hCursor := LoadCursor(hInstance, 'ChartCur');
  WndClass.hIcon := LoadIcon(hInstance, 'ChartIco');
  WndClass.lpszMenuName := 'ChartMenu';
end;

{ Update the chart by rescaling, redrawing }
procedure TChartWindow.redraw;
begin
  Chart^.area.x := attr.w;
  Chart^.area.y := attr.h;
  Chart^.reScale;
  invalidateRect(HWindow, nil, True);
end;

{ Make sure the user has saved his work before closing }
function TChartWindow.CanClose: Boolean;
var Reply : Integer;
    Msg : TMessage;
begin
  if not Saved then
  begin
    Reply := MessageBox(HWindow, 'File has not been saved. Save file before closing?',
             'Warning', mb_IconStop or mb_YesNoCancel);
    if Reply = id_Yes then
      Save(Msg);
  end;
  CanClose := Saved or (Reply <> id_Cancel);
end;

{ Create a New chart }
procedure TChartWindow.NewFile(var Msg: TMessage);
begin
  Chart := new(PVbarChart, init);
  Saved := True;
  StrDispose(Name);
  GetMem(Name, 255);
  setName(Msg);
  StrPcopy(ItemBuffer.LabelStr, 'Item');
  StrPCopy(ItemBuffer.ValueStr, '50');
  redraw;
end;

{ Open a chart file }
procedure TChartWindow.Open(var Msg: TMessage);
var FName : PChar;
begin
  GetMem(FName, 255);
  strPCopy(FName, '*.cht');
  if application^.execDialog(New(PFileDialog,
   init(@Self, PChar(sd_FileOpen), FName))) = ID_Ok then
   begin
     Chart := new(PChart, init);
     StrCopy(Name, FName);
     if Read(FName) then
       redraw
     else
       newFile(Msg);
   end;
  Strdispose(FName);
end;

{ Save the chart with existing name.  Call SaveAs if necessary. }
procedure TChartWindow.Save(var Msg: TMessage);
begin
  if strScan(Name, '.') = nil then
    strCat(Name, '.cht');
  if strLen(Name) > 4 then
    write(Name)
  else
    SaveAs(Msg);
end;

{ Save the chart under a new name }
procedure TChartWindow.SaveAs(var Msg: TMessage);
var len : Integer;
  OldName : PChar;  { in case user cancels command }
begin
  getMem(OldName, 255);
  strCopy(OldName, Name);
  { give a default name and extension }
  if strLen(Name) = 0 then
  begin
    len := StrLen(Chart^.Name);
    if len > 8 then len := 8;
    StrLCopy(Name, Chart^.Name, len);
  end;
  if StrScan(Name, '.') = nil then
    StrCat(Name, '.cht');
  if StrLen(Name) < 5 then
    StrPCopy(Name, 'Chart.cht');

  if application^.execDialog(New(PFileDialog,
     init(@Self, PChar(sd_FileSave), Name))) = ID_Ok then
       write(Name)
  else
       StrCopy(Name, OldName);
  strDispose(OldName);
end;

{ Report an I/O Error }
procedure TChartWindow.IOError(ErrMessage : PChar);
var Msg : Array[0..255] of Char;
begin
  MessageBeep(0);
  strCopy(Msg, ErrMessage);
  MessageBox(0, StrCat(Msg, Name), 'File Error', mb_IconExclamation);
end;

{ Set the caption of the window to the filename }
procedure TChartWindow.SetCaption(FName : PChar);
var Caption : PChar;
begin
  getMem(Caption, 255);
  strPCopy(Caption, 'PChart : ');
  SetWindowText(Hwindow, strCat(Caption, FName));
  strDispose(Caption);
end;

{ Read a chart from a file. }
function TChartWindow.Read(FName : PChar) : Boolean;
var S : TBufStream;
begin
  S.Init(FName, StOpenRead, 1024);
  if S.Status <> stOk then
    IOError('Can''t open file ')
  else
    begin
      Chart := PChart(S.Get);
      if S.Status <> stOk then
	IOError('Can''t read file ')
      else
        setCaption(Name);
    end;
  S.Done;
  Read := (S.Status = stOk);
end;

{ Store a chart onto a file by storing onto a stream. }
function TChartWindow.Write(FName : PChar) : Boolean;
var S : TBufStream;
begin
  S.Init(FName, stCreate, 1024);
  if S.Status <> stOk then
    IOError('Can''t create file ')
  else
    begin
      S.put(Chart);
      if S.Status <> stOk then
	IOError('Can''t write file ')
      else
      begin
        setCaption(Name);
        Saved := True;
      end;
    end;
  S.Done;
  Write := (S.status = StOk);
end;

{ Make it a Horizontal Bar chart }
procedure TChartWindow.HBar(var Msg: TMessage);
Var Chart2 : PChart;
begin
  Chart2 := new(PHBarChart, init);
  Chart2^.Items := Chart^.items;
  Chart2^.Name := Chart^.Name;
  Chart := PHBarChart(Chart2);
  redraw;
end;

{ Make it a Vertical Bar chart }
procedure TChartWindow.VBar(var Msg: TMessage);
Var Chart2 : PChart;
begin
  Chart2 := new(PVBarChart, init);
  Chart2^.Items := Chart^.items;
  Chart2^.Name := Chart^.Name;
  Chart := PVBarChart(Chart2);
  redraw;
end;

{ Make it a Vertical Bar chart }
procedure TChartWindow.V3DBar(var Msg: TMessage);
Var Chart2 : PChart;
begin
  Chart2 := new(PV3DBarChart, init);
  Chart2^.Items := Chart^.items;
  Chart2^.Name := Chart^.Name;
  Chart := PV3DBarChart(Chart2);
  redraw;
end;

{ Make it a Pie chart }
procedure TChartWindow.Pie(var Msg: TMessage);
Var Chart2 : PChart;
begin
  Chart2 := new(PPieChart, init);
  Chart2^.Items := Chart^.items;
  Chart2^.Name := Chart^.Name;
  Chart := PPieChart(Chart2);
  redraw;
end;

{ Change, add or delete an item }
procedure TChartWindow.Change(var Msg: TMessage);
var  Dlg: TChartDialog;
     Reply, Value, errorPos : Integer;
begin
  Dlg.Init(@Self, 'ChartDlg');
  Dlg.TransferBuffer := @ItemBuffer;
  Reply := Dlg.Execute;
  Dlg.Done;
  if Reply = id_Ok then
  begin
    { If valid, add the item to the chart }
    val(ItemBuffer.ValueStr, value, errorPos);
    if errorPos = 0 then
    begin
      if Chart = nil then
        Chart := new(PVBarChart, init);
      Chart^.add(ItemBuffer.LabelStr, Value);
    end
    else
      MessageBeep(0);
  end
  else if Reply = id_Delete then
     if Chart = nil then
       MessageBeep(0)
     else
       Chart^.Remove(ItemBuffer.LabelStr);
  { Adjust the chart }
  if Reply <> id_Cancel then
  begin
    redraw;
    Saved := False;
  end;
end;

{ Set or change the name of the chart }
procedure TChartWindow.SetName(var Msg: TMessage);
var TempName : PChar;
begin
  GetMem(TempName, 40);
  if Chart^.Name <> nil then
    strLCopy(TempName, Chart^.Name, 40);
  if application^.ExecDialog(New(PInputDialog,
      Init(@Self, 'Chart', 'Enter chart name:',
      TempName, 40))) = id_Ok then
  begin
     if chart^.Name <> nil then
       strDispose(Chart^.Name);
     getMem(Chart^.Name, 40);
     strCopy(Chart^.Name, TempName);
     redraw;
  end;
  strDispose(TempName);
end;

{ Display About box }
procedure TChartWindow.About(var Msg: TMessage);
var  Dlg: TDialog;
begin
  Dlg.Init(@Self, 'AboutDlg');
  Dlg.Execute;
  Dlg.Done;
end;

{ Display Help dialog }
procedure TChartWindow.Help(var Msg: TMessage);
var  Dlg: TDialog;
begin
  Dlg.Init(@Self, 'HelpDlg');
  Dlg.Execute;
  Dlg.Done;
end;

{ Respond to Lotus style commands from slash (/) accelerator }
procedure TChartWindow.CmdMode(var Msg: TMessage);
begin
  sendMessage(HWindow, WM_SYSCOMMAND, $F100, 0);
end;

{ Exit the program }
procedure TChartWindow.Exit(var Msg: TMessage);
begin
  if CanClose then postQuitMessage(0);
end;

{ Draw the chart if it exists }
procedure TChartWindow.Paint(DC: HDC; var PaintInfo: TPaintStruct);
var s : array[0..16] of Char;
begin
  if Chart <> nil then
    Chart^.draw(DC)
  else
  begin
    strPCopy(s, 'Error: No chart');
    TextOut(DC, 10, 10, s, strLen(s));
  end;
end;

{ Ensure that cursor is visible even when no mouse }
procedure TChartWindow.wmSetFocus(var Msg: TMessage);
begin
  ShowCursor(True);
end;

{ Return cursor to previous state for other windows }
procedure TChartWindow.wmKillFocus(var Msg: TMessage);
begin
  ShowCursor(False);
end;

{ Select and item in the chart and edit it }
procedure TChartWindow.wmLButtonDown(var Msg: TMessage);
var Item : PAssoc;
    S : String;
begin
{ First locate the item clicked on }
  Item := Chart^.getItem(Msg.LParamLo, Msg.LParamHi);
  if Item <> nil then
  begin
    { Update the edit buffer and edit }
    strLCopy(ItemBuffer.LabelStr, Item^.key, fieldLen-1);
    str(Item^.value,S);
    strPCopy(ItemBuffer.ValueStr, S);
    Change(Msg);
  end
  else
    MessageBeep(0);
end;

{ Simulate mouse movement with cursor keys }
procedure TChartWindow.wmKeyDown(var Msg: TMessage);
var x, y : Integer;
    pos : TPoint;
    key : word;
begin
  { Determine position of cursor in Window }
  getCursorPos(pos);
  screenToClient(HWindow, pos);
  x:=pos.x;
  y:=pos.y;
  { move the cursor position }
  key := Msg.WParam;
  case key of
    VK_UP    : y := y - 10;
    VK_DOWN  : y := y + 10;
    VK_RIGHT : x := x + 10;
    VK_LEFT  : x := x - 10;
    VK_HOME  :
      begin
	x := 10;
	y := 10;
      end;
    VK_END :
      begin
	x := attr.w - 10;
	y := attr.h - 10;
      end;
    VK_RETURN,
    VK_SPACE,
    VK_F2:
      begin
        { Simulate mouse pressing at cursor position }
        Msg.LParam := LongInt(pos);
	wmLButtonDown(Msg);
      end;
    end;
    { Update position of cursor in window with clipping }
    if x < 1 then x := 10;
    if y < 1 then y := 10;
    if x >= attr.w then x:= attr.w - 10;
    if y >= attr.h then y:= attr.h - 10;
    pos.x := x;
    pos.y := y;
    clientToScreen(HWindow, pos);
    setCursorPos(pos.x, pos.y);
end;

{ update internal information when resizing then redraw }
procedure TChartWindow.wmSize(var Msg: TMessage);
begin
  attr.h := Msg.lParamHi;
  attr.w := Msg.lParamLo;
  redraw
end;


{--------------------------------------------------}
{ Main program:                                    }
{--------------------------------------------------}

var
  ChartApp: TChartApplication;

begin
  ChartApp.Init('PChart');
  ChartApp.Run;
  ChartApp.Done;
end.
