Tutorial #11: SystemDialog & WebControl

SystemDialogControl

Usually dialogs are created in accordance to the app's design. This means that dialogs are also built up by several graphic nodes such as all other elements of the application. In some situations, e.g. when querying, if an app should be closed, real system dialogs are preferred. The Murl Engine provides the possibility to display a simple system dialog and to query the corresponding user input via its ISystemDialogControl interface.

If a ISystemDialogControl is provided on the platform, a ISystemDialog object can be created via CreateSystemDialog(). A dialog is displayed via the Open() method and can be closed via Close().

For our example we use the 9 slice button from the previous tutorial and create three button instances.

  <Instance graphResourceId="package_main:init_button_9s"/>
  
  <!--Draw Buttons-->
  <Instance buttonId="button01" text="Show Dialog" posX="-100" posY="0" graphResourceId="package_main:graph_button_9s"/>
  <Instance buttonId="button02" text="+" posX="-170" posY="-100" sizeX="70" sizeY="70" graphResourceId="package_main:graph_button_9s"/>
  <Instance buttonId="button03" text="NEW" posX="-30" posY="-100" sizeX="70" sizeY="70" graphResourceId="package_main:graph_button_9s"/>

In the header file, we create member variables for the buttons and a pointer to store for the ISystemDialog object.

    Logic::ButtonNode mButton01;
    Logic::ButtonNode mButtonPlus;
    Logic::ButtonNode mButtonNew;

    ISystemDialog* mSystemDialog;

The mSystemDialog pointer is initialized to 0 in the constructor.

App::SystemDialogLogic::SystemDialogLogic(Logic::IFactory* factory)
: BaseProcessor(factory)
, mSystemDialog(0)
{
}

In the OnInit method, the Logic::ButtonNode member variables are initialized with the references of the button nodes. Additionally, a ISystemDialog object is created and the object pointer is stored in the mSystemDialog member variable.

Bool App::SystemDialogLogic::OnInit(const Logic::IState* state)
{
    state->GetLoader()->UnloadPackage("startup");
    
    Graph::IRoot* root = state->GetGraphRoot();
    AddGraphNode(mButton01.GetReference(root, "button01/button"));
    AddGraphNode(mButtonPlus.GetReference(root, "button02/button"));
    AddGraphNode(mButtonNew.GetReference(root, "button03/button"));

    if (!AreGraphNodesValid())
    {
        return false;
    }

    Logic::IDeviceHandler* deviceHandler = state->GetDeviceHandler();
    if (deviceHandler->IsSystemDialogControlAvailable())
    {
        mSystemDialog = deviceHandler->CreateSystemDialog("Test Dialog Title", 
            "Test Dialog\nChoose Action\n3rd Line Message", "CANCEL", "OK");
    }

    state->SetUserDebugMessage("SystemDialog Init succeeded!");
    return true;
}

We also pass the title and the info message of the dialog as parameters to the CreateSystemDialog() method.

The created ISystemDialog object has to be destroyed (released) with DestroySystemDialog(). This can be done in the OnDeInit method.

Bool App::SystemDialogLogic::OnDeInit(const Logic::IState* state)
{
    Logic::IDeviceHandler* deviceHandler = state->GetDeviceHandler();
    deviceHandler->DestroySystemDialog(mSystemDialog);
    deviceHandler->DestroyEMail(mEMail);
    deviceHandler->DestroyUrlRequest(mUrlRequest);
    return true;
}

The dialog should be opened when we click on Button01 or when we press the D key. The dialog window is automatically closed when we click on a dialog button. Additionally we implement an auto-close-timer which closes the dialog automatically after a timeout of five seconds.

    //SystemDialog
    if (mSystemDialog != 0)
    {
        static Double autoCloseTime;
        if (!mSystemDialog->IsOpen())
        {
            if (mButton01->WasReleasedInside() || deviceHandler->WasRawKeyPressed(RAWKEY_D))
            {
                mSystemDialog->Open();
                autoCloseTime = 5;
            }
        }
        else
        {
            autoCloseTime -= dt;
            if (autoCloseTime < 0)
            {
                mSystemDialog->Close(-1);
            }
        }

With GetClickedButtonIndex() or GetButtonLabel() we can find out onto which button was clicked.

        if (mSystemDialog->WasClosed())
        {
            SInt32 clickedButtonIndex = mSystemDialog->GetClickedButtonIndex();

            Debug::Trace("Clicked Button Index: "+Util::SInt32ToString(clickedButtonIndex));
            if (clickedButtonIndex < 0)
                Debug::Trace("Titlebar close button pressed or time out close!");
            else
                Debug::Trace("Clicked button label: "+mSystemDialog->GetButtonLabel(clickedButtonIndex));
        }

We use the mButtonPlus button in order to add another button to the dialog.

Note
Caution! On the Android operating system the maximum number of visible dialog buttons is limited to three.

The button mButtonNew destroys the current ISystemDialog object and creates a new one.

        if (!mSystemDialog->IsOpen())
        {
            if (mButtonPlus->WasReleasedInside())
            {
                mSystemDialog->AddButton("EXTRA-"+Util::UInt32ToString(mSystemDialog->GetNumberOfButtons()));
            }

            if (mButtonNew->WasReleasedInside())
            {
                deviceHandler->DestroySystemDialog(mSystemDialog);
                mSystemDialog = deviceHandler->CreateSystemDialog("Test Dialog Title", "New Dialog", "OK");
            }
        }
    }

If there is more than one button in the dialog, the order of the buttons depends on the current platform on which the application runs. The button 0 is always on the position on which the negative button (typically "CANCEL") would usually be located. The CreateSystemDialog("Test Dialog Title", "Test Dialog\nChoose Action\n3rd Line Message", "CANCEL", "OK"); call would provide the following results on different platforms:

tut0111_systemdialog_ios.png
System Dialog iOS
tut0111_systemdialog_android_lt11.png
System Dialog Android API Level < 11
tut0111_systemdialog_android_ge11.png
System Dialog Android API Level ≥ 11
tut0111_systemdialog_win.png
System Dialog Windows
tut0111_systemdialog_osx.png
System Dialog OS X

WebControl

The IWebControl provides basic web features:

  • Opening a website in the default browser
  • Sending emails
  • HTTP POST and HTTP GET calls

Again we use some additional 9 slice buttons and ButtonNode member variables to trigger the different IWebControl functions.

Opening a Website in a Browser

The method OpenUrlInSystemBrowser() with the web address as parameter can be used to open a website.

    //WebControl
    if (mButtonBrowser->WasReleasedInside())
    {
        if (deviceHandler->IsWebControlAvailable())
            deviceHandler->OpenUrlInSystemBrowser("http://murlengine.com");
    }

The entered web address is opened in the default browser.

Sending Emails

In order to send emails we first need to create an IEMail object. To reference this object we define an IEMail* mEMail; pointer in the header file and initialize it to 0 in the constructor.

The CreateEMail() method creates an IEMail object and returns a pointer to the object. With the Send() method we open the email client and the email can be sent by the user.

    // Create and send Email
    if (mButtonEmail->WasReleasedInside())
    {
        if (deviceHandler->IsWebControlAvailable())
        {
            mEMail = deviceHandler->CreateEMail("My Subject", "My Message", "office@murlengine.com");
            if (mEMail != 0)
            {
                mEMail->AddToRecipient("konrad@zuse.com");
                mEMail->AddCcRecipient("alan@turing.com");
                mEMail->AddCcRecipient("ivan@sutherland.com");
                mEMail->AddBccRecipient("harry@nyquist.com");
                mEMail->AddBccRecipient("john@neumann.com");
                Debug::Trace(t+": Start Send");
                mEMail->Send();
            }
        }
    }

The created IEMail object has to be destroyed (released) via DestroyEMail()! We release the object as soon as the sending process has finished. The current state can be queried directly from the IEMail object through one of the following methods:

    // Destroy Email Object
    if (mEMail != 0)
    {
        if (mEMail->WasSent() ||
            mEMail->WasSaved() ||
            mEMail->WasCancelled() ||
            mEMail->WasRejected())
        {
            deviceHandler->DestroyEMail(mEMail);
        }
    }

In order to track the status we display the current status as debug message:

    // Print Email status
    if (mEMail != 0)
    {
        if (mEMail->IsInQueue())
            Debug::Trace(t+": IsInQueue");
        else if (mEMail->IsSending())
            Debug::Trace(t+": IsSending");
        else if (mEMail->WasSent())
            Debug::Trace(t+": WasSent");
        else if (mEMail->WasSaved())
            Debug::Trace(t+": WasSaved");
        else if (mEMail->WasCancelled())
            Debug::Trace(t+": WasCancelled");
        else if (mEMail->WasRejected())
            Debug::Trace(t+": WasRejected");
        else
            Debug::Trace(t+": Unknown Status");
    }

Typically, the output looks as follows:

1.209361: Start Send
1.226596: IsSending
1.243769: IsSending
1.260958: IsSending
1.279752: IsSending
1.299660: IsSending
1.323538: WasSent
Note
Caution! The WasSaved(), WasCancelled() and WasRejected() methods give an accurate result only with iOS. All other platforms return for WasSent() true as soon as the email was successfully handed over to the email client. A verification, if the user has actually sent the message or if the email was e.g. discarded, is not possible on any other platform than iOS.

HTTP POST & HTTP GET

The IWebControl provides with the method CreateUrlRequest() the possibility to create an IUrlRequest object which can be used for HTTP calls. To reference the IUrlRequest object we define an IUrlRequest* mUrlRequest; pointer in the header file and initialize it to 0 in the constructor.

In the OnInit method we create an IUrlRequest object and store the pointer of the object in the mUrlRequest member variable.

mUrlRequest = deviceHandler->CreateUrlRequest(); 

The created IUrlRequest object has to be destroyed (released) with DestroyUrlRequest()! This can again be done in the OnDeInit method.

deviceHandler->DestroyUrlRequest(mUrlRequest);

In order to send an HTTP request with HTTP GET, the SendGet() method can be used. The HTTP address is passed as parameter.

    if (mButtonHttpGet->WasReleasedInside())
    {
        mUrlRequest->SendGet("http://murlengine.com");
        Debug::Trace("URL request HTTP GET Start: '%s'", mUrlRequest->GetUrlString().Begin());
    }

Of course the HTTP address can also contain HTTP GET parameters, e.g.

mUrlRequest->SendGet("http://murlengine.com/news/?murlpage=news&murllang=en");

The status of the HTTP request can be queried through the following methods:

    //URL Request
    if (mUrlRequest != 0)
    {
        if (mUrlRequest->IsPending())
        {
            Debug::Trace("URL request is pendind. Received %d bytes.",mUrlRequest->GetCurrentDataSize());
        }
        else if (mUrlRequest->WasRejected())
        {
            Debug::Trace("URL request was rejected.");
        }
        else if (mUrlRequest->WasFinished())
        {
            UInt32 receiveSize = mUrlRequest->GetCurrentDataSize();
            Debug::Trace("URL request has finished. Received %d bytes", receiveSize);
            UInt32 limitSize = receiveSize;
            const UInt32 LIMIT = 1024;
            if (limitSize > LIMIT)
                limitSize = LIMIT;
            String response = mUrlRequest->GetResponseData().GetString(limitSize);
            Debug::Trace("Response Content (first %d bytes):\n%s", limitSize, response.Begin());
        }

The size of the received data can be queried through GetCurrentDataSize() and the actual data through GetResponseData(). The example code above outputs the first 1024 bytes as debug message.

Typically, the output looks as follows:

URL request HTTP GET Start: 'http://murlengine.com'
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 3935 bytes.
URL request has finished. Received 8298 bytes
Response Content (first 1024 bytes):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
...

In order to send a HTTP request via HTTP POST, the SendPost() method can be used. The HTTP address, the post data and the content type are passed as parameters.

    if (mButtonHttpPost->WasReleasedInside())
    {
        //early version of URL Encoding with space is +
        Data postData("Name=Jonathan+Doe&Age=23&Formula=a+%2B+b+%3D%3D+13%25%21");
        //URL Encoding with space is %20
        //Data postData("Name=Jonathan%20Doe&Age=23&Formula=a%20%2B%20b%20%3D%3D%2013%25%21");
        mUrlRequest->SendPost("http://murlengine.com/xtest.php?id=42", postData, "application/x-www-form-urlencoded");
        Debug::Trace("URL request HTTP POST Start '%s'", mUrlRequest->GetUrlString().Begin());
    }

In the example code above the following parameter-value-pairs are transmitted:

Name: Jonathan Doe
Age: 23
Formula: a + b == 13%!

For transmission, all data first have to be URL encoded (URL encoding also known as Percent encoding; see en.wikipedia.org/wiki/Percent-encoding). The encoded string then looks as follows:

Name=Jonathan+Doe&Age=23&Formula=a+%2B+b+%3D%3D+13%25%21

This simple HTML/PHP script can be used to display the received data:

<html lang="en">
<head><meta charset="utf-8" /><title>xtest</title></head>
<body>
<h2>Post</h2>
<?php var_dump($_POST);?>
<h2>Get</h2>
<?php var_dump($_GET);?>
</body>
</html>

Typically, the output looks as follows:

URL request HTTP POST Start 'http://murlengine.com/xtest.php?id=42'
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request is pendind. Received 0 bytes.
URL request has finished. Received 294 bytes
Response Content (first 294 bytes):
<html lang="en">
<head><meta charset="utf-8" /><title>xtest</title></head>
<body>
<h2>Post</h2>
array(3) {
["Name"]=>
string(12) "Jonathan Doe"
["Age"]=>
string(2) "23"
["Formula"]=>
string(13) "a + b == 13%!"
}
<h2>Get</h2>
array(1) {
["id"]=>
string(2) "42"
}
</body>
</html>
tut0111_systemdialog_webcontrol.png
System Dialog & Web Control


Copyright © 2011-2017 Spraylight GmbH.