How to create a new window

From PSwiki
Jump to navigation Jump to search

In this tutorial, I will explain how to create a simple window using PAWS. The window we will be making here will do nothing but display the doll widget that displays your avatar. Keep in mind that the window we are making here has absolutely no practical use (unless you like watching yourself).

Our objectives are the following:

  1. Create a "doll" window that displays the inventory doll widget
  2. Create a command called /show doll so we can view it in psclient

If you are reading this tutorial, then:

  • You have some knowledge of C++ and XML
  • You can compile and run psclient

And thus we learn.

Creating the window

First off, we need to write the source code for the new window. This consists of two intermediate steps: Writing all the source files for the window itself and telling psengine.cpp to load it. I will break it down by source file. Also I should say I derived it mostly from the skill window.

pawsdollwindow.h

First off, we should go to PlaneShift's src/client/gui directory. See all of the source files there? Most of these contain the source code to widgets used in psclient. There are more in src/common/paws but let's skip that directory for now. Our first objective is to create a header file for our doll window.

So let's create a new file called pawsdollwindow.h and let's put the following in it:

#ifndef PAWS_DOLL_WINDOW_HEADER
#define PAWS_DOLL_WINDOW_HEADER

#include "paws/pawswidget.h"

class pawsObjectView;
class psCharAppearance;

/** This handles all the details about the doll window.
 */
class pawsDollWindow : public pawsWidget
{
public:
    pawsDollWindow();
    virtual ~pawsDollWindow();
	
    bool PostSetup();
protected:
    bool SetupDoll();
    
    psCharAppearance* charApp;  // character appearance for doll
};

CREATE_PAWS_FACTORY( pawsDollWindow );

#endif 

Our class called pawsDollWindow is the class for the actual window, and we are deriving it from pawsWidget since there is nothing special about it. Windows that have buttons associated with them on the toolbar are derived from pawsControlledWindow but we're not doing that in this tutorial.

The CREATE_PAWS_FACTORY macro does a whole bunch of necessary crap.

pawsdollwindow.cpp

OK, let's move on to the more complicated part. Instead of posting the entire source file like I did the last time, I'm going to break it apart into parts so we can understand what is going on. First, the includes:

#include <psconfig.h>
#include <imesh/spritecal3d.h>

#include "globals.h"
#include "pscelclient.h"
#include "../charapp.h"

// PAWS INCLUDES
#include "pawsdollwindow.h"
#include "pawschardescription.h"
#include "paws/pawsobjectview.h"

Nothing special here, other than we included the pawsdollwindow.h file we just made. Also, we added some Cal3D stuff which the doll widget itself will need.

pawsDollWindow::pawsDollWindow()
{
    charApp = new psCharAppearance(PawsManager::GetSingleton().GetObjectRegistry());
}

pawsDollWindow::~pawsDollWindow()
{
    delete charApp;
}

In the constructor, we create a new psCharAppearance to hold the state of the character. Of course we deinitialize it in the deconstructor.

bool pawsDollWindow::PostSetup()
{
    // Setup the Doll
    if (!SetupDoll())
    {
        return false;
    }

    return true;
}

In the PostSetup function, we call the protected function SetupDoll which we haven't written yet.

bool pawsDollWindow::SetupDoll()
{
    pawsObjectView* widget = dynamic_cast<pawsObjectView*>(FindWidget("Doll"));
    GEMClientActor* actor = psengine->GetCelClient()->GetMainPlayer();
    if (!widget || !actor)
    {
        return false;
    }

    csRef<iMeshWrapper> mesh = actor->GetMesh();
    if (!mesh)
    {
        return false;
    }

    // Set the doll view
    widget->View( mesh );
   
    // Register this doll for updates
    widget->SetID( actor->GetID() );

    csRef<iSpriteCal3DState> spstate = scfQueryInterface<iSpriteCal3DState>  (widget->GetObject()->GetMeshObject());
    if (spstate)
    {
        // Setup cal3d to select random 0 velocity anims
        spstate->SetVelocity(0.0,&psengine->GetRandomGen());
    }

    charApp->SetMesh(widget->GetObject());
    
    charApp->ApplyTraits(actor->traits);
    charApp->ApplyEquipment(actor->equipment);
    
    widget->EnableMouseControl(true);

    return true;
}

This part is probably the most confusing. First off, we tell psclient to go through dollwindow.xml which we have yet to write, and find the widget whose name is "Doll". All of the initializing and setup is done using the source code for pawsObjectView. Then it loads the mesh for your avatar, displays it, and registers it for updates (for example equip updates). The rest is Cal3D stuff.

dollwindow.xml

Next, we should probably write our dollwindow.xml. But before we do that, let's go to your data/gui directory. You will see a bunch of xml files here; most of these describe a window of some sort and this is where your dollwindow.xml file will go.

So, we're going to create a new file called dollwindow.xml here, and it should look something like this:

<widget_description>
	<widget name="DollWindow" factory="pawsDollWindow"
		visible="no" savepositions="yes" movable="yes" 
		resizable="no" configurable="yes">
		
		<frame x="100" y="100" width="116" height="173" border="yes" />
		<title resource="Blue Title" text="Doll" align="left" close_button="yes" />
		<bgimage resource="GM Background" alpha="128" />
		
		<widget name="Doll" factory="pawsInventoryDollView" >
			<frame x="4" y="4" width="108" height="165" border="no" />
			<map file="/planeshift/world/podium" sector="room" />
			<distance value="7" />
			<subscriptions>
				<subscribe data="sigActorUpdate" />
			</subscriptions>
		</widget>	
	</widget>
</widget_description>

First off, in the first widget node, we need to remember what we use as our value for name. The factory attribute should be called "pawsDollWindow" because that's what we named our class in pawsdollwindow.cpp and pawsdollwindow.h. Also I am using the "GM Background" image, which is a generic background, for the window itself.

Since we defined a widget named "Doll" in pawsdollwindow.cpp, we must also define it in this file. The map child node defines what map file and sector we are placing the doll in; in this case we are using the podium map.

psengine.cpp

Finally, let's go to the src/client directory. We are going to edit psengine.cpp so we can add our new widget. For this, we are only going to add one line.

First, add your pawsdollwindow.h to your includes:

#include "gui/pawsdollwindow.h"

Once you have your include, browse the file until you come across teh psEngine::DeclareExtraFactories function, which looks something like this:

void psEngine::DeclareExtraFactories()
{
   pawsWidgetFactory* factory;

   RegisterFactory (pawsInventoryDollViewFactory);
   RegisterFactory (pawsGlyphSlotFactory);
   ...
   RegisterFactory (pawsConfigShadowsFactory);

You're gonna want to add the factory to your new pawsDollWindow, so add the following line:

   RegisterFactory (pawsDollWindowFactory);

Now, browse the file until you come across something that looks like the following:

void psEngine::LoadGame()
{
   switch(loadstate)
   {
   ...

   case LS_CREATE_GUI:
   {
       ...

       LoadPawsWidget( "Status window",           "data/gui/infowindow.xml" );
       LoadPawsWidget( "Ignore window",           "data/gui/ignorewindow.xml" );
       ...
       LoadPawsWidget( "Writing window",          "data/gui/bookwriting.xml");

This is the part that initializes the GUI system when psclient loads the world. What we are going to do is add our new doll window here so it initializes with the rest of the GUI. So, let's add the following line to this huge list, shall we?

       LoadPawsWidget( "Doll window",             "data/gui/dollwindow.xml");

The second parameter points to the xml file that we just created.

Creating the /show doll command

What we've done so far is successfully create a new window that really doesn't do a whole lot but see your character. The only thing that we are missing now is a way to open the window and view it in psclient. So we are going to create a command called /show doll to do this for us. The /show command already exists (for example /show bag), so we are just going to add a doll option to it. The purpose of the /show command is to toggle between "show" and "hide" states of the window, so the same command also hides it too.

cmdusers.cpp

The actual source file that we need to modify is src/client/gui/pawscontrolwindow.cpp, but we're going to browse through src/client/cmdusers.cpp real quick for enrichment so this first part can be skipped.

Let us go to the src/client directory and open up cmdusers.cpp. You will see that it lists a ton of commands here in its constructor; one of them is /show. Further down, we have the psUserCommands::HandleCommand function which handles the individual commands. For the /show command, it has the following:

if (words[0] == "/show")
{
   if (words.GetCount() > 1)
   {
      pawsControlWindow* ctrlWindow = dynamic_cast<pawsControlWindow*>(PawsManager::GetSingleton().FindWidget("ControlWindow"));
      if (!ctrlWindow || ctrlWindow->HandleWindowName(words[1]))
         return NULL;

      else return "That window cannot be found.";
   }
   return "You need to specify a window to show";
}

What it does is it grabs some class called "ControlWindow" and calls its HandleWindowName() function. What this means is that the source file we need to edit is pawscontrolwindow.cpp and not this one. Also, pawsControlWindow is not to be confused with the aforementioned pawsControlledWindow. Anyways, on to the next part.

pawscontrolwindow.cpp

Let us finally go to the src/client/gui directory and open up pawscontrolwindow.cpp. We are interested in the HandleWindowName function, which takes in a string (in this case "doll" from /show doll), maps it to a valid widget name, and passes that string to HandleWindow which does the actual toggling between show and hide. We are looking for a bunch of code that looks like the following:

bool pawsControlWindow::HandleWindowName(csString widgetStr)
{
   csString widget;
   widgetStr.Downcase();
   if(widgetStr == "options" )
       widget = "ConfigWindow";
   else if(widgetStr == "stats" || widgetStr =="skills")
       widget = "SkillWindow";  
   ...
   else if(widgetStr == "talk" || widgetStr == "chat" || widgetStr == "communications")
       widget = "ChatWindow";
   else if(widgetStr == "quit")
   {
       HandleQuit();
       return true;
   }
   else if(widgetStr == "buy")
   {
       psengine->GetCmdHandler()->Execute("/buy");
       return true;
   }

Add the following line to here:

   else if(widgetStr == "doll")
       widget = "DollWindow";

What you assign to the widget string should be exactly the same as what you called it in dollwindow.xml; since this line says "DollWindow" it will look for a widget where name="DollWindow".

Alas, we are almost done. Save and recompile. Then fire up psclient, log into a server and try out the /show doll command to get your new fabulous window to appear!

Once you're done with this, you can move on to the next tutorial, where I show you how to bind the window you just made to the toolbar.