Saturday 26 April 2014

Writing a StrPix-like program with Lazarus – 7

We will delete Edit1 we placed on the form in the previous post since it was only to test that we could access data from the Wad.version and not part of our program’s design.

Go to Window>Form1 to display the form.

Click on Edit1 to select it and then press the delete key to delete it.

Deleting the control does not remove the code that references it in TForm1.FileOpenAcccept so go to that procedure and delete the two lines we added to display the version number in Edit1.

The method should look like this.

procedure TForm1.FileOpen1Accept(Sender: TObject);
var
  err: string;
  valid: integer;
begin
  // Processing all windows messages before and after using the
  // dialog box keeps the user interface clean.
  Application.ProcessMessages;
  ////////////// if not Main.OpenDialogWad.Execute then
  //////////////  exit;
  Application.ProcessMessages;
  // Make sure that wads will not accumulate in memory.
  // Free is successful even if the memory stream was not yet created.
  streamWAD.Free;
  streamWAD := TMemoryStream.Create;
  streamWAD.LoadFromFile(FileOpen1.Dialog.FileName);
  ////////////// Main.TextBox.Caption := Main.OpenDialogWad.FileName;
  // Crawl through the file for validity and find the main sections.
  valid := WADCrawler.IsValidWad(streamWAD, err);
  if valid < 0 then
  begin
    MessageDlg('File is not a valid WAD.' + #10 + err,
      mtWarning,
      [mbOK],
      0);
    exit;
  end
  else if valid > 0 then
    MessageDlg('File is not a regular WAD.' + #10 + err,
      mtWarning,
      [mbOK],
      0);
  // The main sections were already detected by the crawler.
  // Now parse the WAD file and extract the data tables.
  WADParser.ExtractAll(streamWAD);
  // Build the Wad Tree.
  //////////////Main.MakeWadTree;
  //////////////Main.TreeViewWad.FullExpand;
  // Prepare the user data interface.
  //////////////Main.PrepareWadDataForDisplay;
  // Enable the other file options in the popup.
  //Main.SaveWad.Enabled   := TRUE;
  //Main.SaveAsWad.Enabled := TRUE;
end;

We see that after the data has been extracted from the *.wad file, we commented a line that calls a procedure that builds a TreeView.

We want to build a TreeView also, so we will create a procedure called MakeWadTree that builds the TreeView. This means we can uncomment the line and use it as long as we either remove “Main.” or replace “Main” with “Form1”. Remember that Main is the name of WadExplorer’s main form whereas ours is called Form1.

We will study WadExplorer’s MakeWadTree method to see what code we can use in ours.

Go to Window>Source Editor to display the Source Editor window.

Click on the TombRaiderWAD tab to make it the currently displayed code.

Go to Open and select and open WadExplorer’s MainUnit.pas.

Go to Search>Procedure List and find TMainMakeWadTree and double click on it.

Another file is opened in the Source Editor and the MakeWadTree method is displayed.

//============================================================================//
//=== BUILD THE WAD TREE =====================================================//
//============================================================================//
procedure TMain.MakeWadTree;
var
fname:string;
                    //--- Function-in-procedure -----------
                    function AddWadTreeNode
                    ( parent: TTreeNode; s: string ): TTreeNode;
                    begin
                        Result := Main.TreeViewWad.Items.AddChild( parent, s );
                    end;
begin
    Main.TreeViewWad.Items.Clear;
    fname := ExtractFileName( Main.OpenDialogWad.FileName )
           + ' = '
           + FormatFloat('#,', streamWAD.Size )
           + ' bytes.';
    Main.root          := AddWadTreeNode( nil, fname );
    //
    Main.version       := AddWadTreeNode( root, 'Version'    );
    Main.textures      := AddWadTreeNode( root, 'Textures'   );
    Main.meshes        := AddWadTreeNode( root, 'Meshes'     );
    Main.animations    := AddWadTreeNode( root, 'Animations' );
    Main.models        := AddWadTreeNode( root, 'Models'     );
    //
    Main.wadVersion    := AddWadTreeNode( version, 'File_ID' );
    //
    Main.numTxtrs      := AddWadTreeNode( textures,'Num_Texture_Samples'   );
    Main.txtrTable     := AddWadTreeNode( textures,'Texture_Samples_Table' );
    Main.txtrDataSize  := AddWadTreeNode( textures,'Texture_Data_Size'     );
    Main.txtrPackage   := AddWadTreeNode( textures,'Texture_Data_Package'  );
    //
    Main.numPointers   := AddWadTreeNode( meshes, 'Num_Mesh_Pointers'   );
    Main.pointersTable := AddWadTreeNode( meshes, 'Mesh_Pointers_List'  );
    Main.meshDataSize  := AddWadTreeNode( meshes, 'Meshes_Data_Size'    );
    Main.meshPackage   := AddWadTreeNode( meshes, 'Meshes_Data_Package' );
    //
    Main.numAnims      := AddWadTreeNode( animations, 'Num_Animations'         );
    Main.animAnims     := AddWadTreeNode( animations, 'Animations_Table'       );
    Main.numChanges    := AddWadTreeNode( animations, 'Num_State_Changes'      );
    Main.animStates    := AddWadTreeNode( animations, 'State_Changes_Table'    );
    Main.numDispats    := AddWadTreeNode( animations, 'Num_Dispatches'         );
    Main.animDispats   := AddWadTreeNode( animations, 'Dispatches_Table'       );
    Main.commSize      := AddWadTreeNode( animations, 'Commands_Data_Size'     );
    Main.animComms     := AddWadTreeNode( animations, 'Commands_Data_Package'  );
    Main.linksDataSize := AddWadTreeNode( animations, 'Links_Data_Size  '      );
    Main.linksPackage  := AddWadTreeNode( animations, 'Links_Data_Package'     );
    Main.keysDataSize  := AddWadTreeNode( animations, 'Keyframes_Data_Size'    );
    Main.keysPackage   := AddWadTreeNode( animations, 'Keyframes_Data_Package' );
    //
    Main.numMovables   := AddWadTreeNode( models, 'Num_Movables'   );
    Main.movablesTable := AddWadTreeNode( models, 'Movables_Table' );
    Main.numStatics    := AddWadTreeNode( models, 'Num_Statics'    );
    Main.staticsTable  := AddWadTreeNode( models, 'Statics_Table'  );
end;

From studying the code we see that
  • TTreeView.Items.AddChild( parent, s ) is the method to build the tree
  • We need some variables of the TTreeNode type to act as parent nodes

Unlike IceBerg we will make our TTreeNode variables local variables.

Go to Window>Form1 to display our form.

Click on the TTreeView component in the Common Controls tab of the component palette and then click anywhere on the form.

TreeView

While TreeView1 control is still selected go to the Object Inspector window and select the properties tab then click on Align.

Click on the arrow button that appears and select alLeft from the list.

This will make TreeView1 expand so it is joined to the Menu bar at the top of the form, the StatusBar at the bottom and the left hand side of the form.

AlignLeft

Don’t worry about the MainMenu1 or ActionList1 controls being placed above the TreeView because they are non visual and do not display on the form when the program is run.

Now to add our MakeWadTree method to the code.

Display the Source Editor and select the unit1 tab.

Scroll to the top of the unit and find the declaration of the TForm1 class.

Type the following code under { private declarations } and above the word public.

procedure MakeWadTree;

so that the class declaration becomes

TForm1 = class(TForm)
  ActionList1: TActionList;
  FileExit1: TFileExit;
  FileOpen1: TFileOpen;
  MainMenu1: TMainMenu;
  MenuItem1: TMenuItem;
  MenuItem10: TMenuItem;
  MenuItem2: TMenuItem;
  MenuItem3: TMenuItem;
  MenuItem4: TMenuItem;
  MenuItem5: TMenuItem;
  MenuItem6: TMenuItem;
  MenuItem7: TMenuItem;
  MenuItem8: TMenuItem;
  MenuItem9: TMenuItem;
  StatusBar1: TStatusBar;
  TreeView1: TTreeView;
  procedure FileOpen1Accept(Sender: TObject);
  procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
private
  { private declarations }
  procedure MakeWadTree;
public
  { public declarations }
end;

While the cursor is at the words we typed click on Source>Complete Code.

This will create an empty procedure called TForm1.MakeWadTree which the Source Editor will jump to.

We just need to add our code to build the TreeView to this procedure.

Here is the procedure with the necessary code added.

procedure TForm1.MakeWadTree;
var
  s : string;
  root, n1 : TTreeNode;
  i : integer;
begin
  // Clear the old tree
  TreeView1.Items.Clear;
  // Extract the filename from the full path to the file
  s := ExtractFileName(FileOpen1.Dialog.FileName);
  // Make the first node and caption it with the filename
  root := TreeView1.Items.AddChild(nil, s);
  // create a child node under root and caption it "Size"
  s := 'Size';
  n1 := TreeView1.Items.AddChild(root, s);
  // create a child node under Size caption it with the file's size
  // there will be no further child nodes so don't need the returned node
  s := Format('%.0n bytes',[streamWAD.Size*1.0]); // fancy way to convert number to string
  TreeView1.Items.AddChild(n1, s);
  // create a child node under root with caption "Version"
  s := 'Version';
  n1 := TreeView1.Items.AddChild(root, s);
  // create a child node under Version and caption it with Wad.version
  // there will be no further child nodes so don't need the returned node
  s := Format('%d',[Wad.version]);
  TreeView1.Items.AddChild(n1, s);
  // create a child node under root with caption "Moveables"
  s := Format('Moveables (%d)',[Wad.numMovables]);
  n1 := TreeView1.Items.AddChild(root, s);
  // create a node for each moveable in the wad and caption with the slot number
  // each node will be a child of Moveables
  for i:=1 to Wad.numMovables do
  begin
    s := Format('Moveable %d',[Wad.movablesTableData[i-1].obj_ID]);
    TreeView1.Items.AddChild(n1, s);
  end;
  // create a child node under root with caption "Statics"
  s := Format('Statics (%d)',[Wad.numStatics]);
  n1 := TreeView1.Items.AddChild(root, s);
  // create a node for each static in the wad and caption with static Id
  // each node will be a child of Statics
  for i := 1 to Wad.numStatics do
  begin
    s := Format('Static %d',[Wad.staticsTableData[i-1].obj_ID]);
    TreeView1.Items.AddChild(n1, s);
  end;
  // expand the root node
  root.Expand(False);
end;

Next we need to call our MakeWadTree procedure in the FileOpen1Accept procedure to execute the code in the MakeWadTree procedure.

Uncomment the line by deleting all the slashes at the start of the line and also delete “Main.” not forgetting to delete the dot.

The procedure should now look like this.

procedure TForm1.FileOpen1Accept(Sender: TObject);
var
  err: string;
  valid: integer;
begin
  // Processing all windows messages before and after using the
  // dialog box keeps the user interface clean.
  Application.ProcessMessages;
  ////////////// if not Main.OpenDialogWad.Execute then
  //////////////  exit;
  Application.ProcessMessages;
  // Make sure that wads will not accumulate in memory.
  // Free is successful even if the memory stream was not yet created.
  streamWAD.Free;
  streamWAD := TMemoryStream.Create;
  streamWAD.LoadFromFile(FileOpen1.Dialog.FileName);
  ////////////// Main.TextBox.Caption := Main.OpenDialogWad.FileName;
  // Crawl through the file for validity and find the main sections.
  valid := WADCrawler.IsValidWad(streamWAD, err);
  if valid < 0 then
  begin
    MessageDlg('File is not a valid WAD.' + #10 + err,
      mtWarning,
      [mbOK],
      0);
    exit;
  end
  else if valid > 0 then
    MessageDlg('File is not a regular WAD.' + #10 + err,
      mtWarning,
      [mbOK],
      0);
  // The main sections were already detected by the crawler.
  // Now parse the WAD file and extract the data tables.
  WADParser.ExtractAll(streamWAD);
  // Build the Wad Tree.
  MakeWadTree;
  //////////////Main.TreeViewWad.FullExpand;
  // Prepare the user data interface.
  //////////////Main.PrepareWadDataForDisplay;
  // Enable the other file options in the popup.
  //Main.SaveWad.Enabled   := TRUE;
  //Main.SaveAsWad.Enabled := TRUE;
end;

Build and run the program.

In the following screenshot I have clicked on the Statics node to expand it. When a *.wad file is opened the nodes will be collapsed except for the root node.

TreeViewDisplay

The TreeView is not wide enough to display the node captions fully. We could just make the TreeView wider on our form in the designer but we will add another component that allows the user to widen the TreeView when the program is running.

Stop the program and go to Window>Form1 to display the form.

Click on Treeview1 on the form to select it. Place the mouse pointer over the size handle that appears on the right in the middle and click and drag to the right to make the TreeView wider.

Next click on the TSplitter control in the Additional tab of the component palette and then click on the form anywhere to the right of the TreeView1 control to add a Splitter. The Splitter should align alLeft automatically, attaching itself to the TreeView1 control.

Splitter

With Splitter1 selected go to the Properties tab of the Object Inspector and click on ResizeStyle. Click on the arrow that appears and select rsLine from the dropdown list.

Use any of the other ResizeStyle options you wish. I just think rsLine looks the best.

SplitterResizeStyle

Now click on the TreeView1 control on the form to select it again.

In the Properties tab of the Object Inspector scroll down and click on ReadOnly.

Click on the arrow button that appears to the right and select True from the dropdown list. This will disable editing of our captions which is not required for our program.

ReadOnly

Build and run the program and see that the TreeView is now resizeable.

Our program uses Windows theme manifests on WindowsXP and up. If you want the program to have an old style you can change the following setting.

Go to Project>Project Options. Click Project Options>Application and click “Use manifest to enable themes” to uncheck it. Click OK to apply the change.

Build the program but this time use the Build button or the Run>Build menu so that the program is built freshly with the new project options. Then click the Run button to run the program.

ThemeDisabled

Next post we will learn to open a text file containing slot names and use the slot names in the TreeView instead of the ID numbers.



prev | next

No comments:

Post a Comment