Friday, May 24, 2013

Cocos2d-x for iOS and Android with IntelliJ

Cocos2d-x is a C++ port of the Cocos2d library and it allows you to write cross-platform games that work on both iOS and Android (and a couple of other platforms as well).

Setting up a cross-platform project is a bit of work though. The C++ code you use for writing your games is the same for all platforms but you will need some platform-specific wrapper code and build-configuration to get things going on the different platforms you want to support.

Jean-Yves Mengant has written an in-depth guide on how to do just that and it offers everything you need to know to get going. That article focuses on getting the Android-specific code running in Eclipse. This makes sense, as Eclipse is the default IDE that even ships with the ADT Bundle Google offers. And even if you like another IDE better, you can ask yourself if it is even worth the trouble because the actual game will be written in C++, which can be done easier in Xcode anyhow (or AppCode :o).

IntelliJ has supported Android development for quite some time now, even in their free Community Edition. And Google has recently announced Android Studio, which is also based on IntelliJ. So I figured I'd give it a try and get a cross-platform project up-and-running using IntelliJ for the Android side of things.

Before we get started, I expect you to have installed IntelliJ, Xcode, Cocos2d-x, the Android SDK and the Android NDK so you are able to create and run both a Cocos2d-x iOS app in Xcode using the 'New Project'-wizard and a plain Android application in IntelliJ. In the next steps I'll show you how to setup a project that can be started from within Xcode (for iOS) and IntelliJ (for Android), using the same code base.

In Cocos2d-x we'll usually work iOS-first and then move on to other platforms. So let's first open Xcode and start a new project. Pick the 'cocos2dx' project template and click 'Next':



Now enter a Product Name and a Company Identifier for your project and click 'Next':


Then select your project folder to save your project in and click 'Create'. Xcode wil create the project for you and you're ready to go. You can immediately click 'Run' and the default Cocos2d-x 'Hello World'-application will fire up on whatever device or simulator you have configured as default in Xcode:


OK, time to move on to Android. Locate the folder where you extracted the Cocos2d-x framework. It should hold a file named 'create-android-project.sh'. Open this file in your favorite text-editor and change the parameters NDK_ROOT_LOCAL and ANDROID_SDK_ROOT_LOCAL and point them to the location you installed the Android NDK and the Android SDK on your system. Make sure you save the file.

Now open a console and step into the directory that holds the Cocos2d-x framework. Execute the script you just updated (./create-android-project.sh). You will be asked to provide a package for your application. Type in the package you'd like to use for your Android project and continue.


Now you'll get a list of available Android versions and you are asked to provide the input target id. I usually set this to the id matching "Google Inc.:Google APIs:14", which is Android API Level 14 and some additional Google libraries for USB and Google Maps. For me that translates to id 2:


And finally you are asked for a project name. I'm using 'Pong' here:


Your Android project will now be created for you and once the script is done, you'll see that there is a new folder inside the Cocos2d-x framework folder with the name of your project (in my case 'Pong'). If you take a look inside that folder, you'll find another folder named proj.android. This is the folder we are most interested in. Select the folder in Finder and copy it.

Now use Finder to browse to the iOS project you created using Xcode. In your project folder, Xcode should have created a new folder structure. If you also named your project 'Pong', you'll see a 'Pong' directory that holds another 'Pong' directory. Paste the proj.android folder you copied earlier into the innermost 'Pong' directory. You can now remove the Android 'Pong' folder inside the Cocos2d-x framework folder that we generated using the create-android-project.sh script.

While you're in the Cocos2d-x framework folder, select the folders 'cocos2dx', 'CocosDenshion' and 'extensions' and copy them. Again locate the Xcode iOS project folder using Finder and step into Pong/Pong/libs. Paste the folders you just copied into here. They should already be there but we need to make sure they are complete. So if asked to overwrite the existing file, do so.

Open the file /Pong/Pong/proj.android/build_native.sh in your favorite text editor. Directly after the first line that defines the APPNAME, set the NDK_ROOT:
NDK_ROOT="/path/to/android/ndk"

Then locate these lines:
# ... use paths relative to current directory
COCOS2DX_ROOT="$DIR/../.."

make sure you set the COCOS2DX_ROOT to the project libs folder:
COCOS2DX_ROOT="$DIR/../libs"

Make sure you save the build_native.sh file. Using your console you can step into the Android project directory (Pong/Pong/proj.android) and execute the script:
./build_native.sh

This will build the C++ code in the project. It'll take some time to complete on the first run.


Ok, great! We have just created an iOS project as well as an Android project and merged the projects into a single folder that shares the C++ code. We also built the native code for our Android version. Now let's move on to IntelliJ.

Open IntelliJ and create a new project. Set the project name and point to the existing 'Pong/Pong/proj.android' folder. Make sure you pick your Android SDK as the project SDK and click 'Next'.


IntelliJ will continue to the next panel of the 'New Project' wizard that lets you select additional technologies for your project. We don't need any other technologies, so we can click 'Finish' now to create the project.

As soon as the project opens, IntelliJ will usually detect you're using Android in your project and prompt you to configure the framework. Otherwise you can manually add the Android facet to your module (File -> Project Structure... -> Facets -> '+' -> 'Android').

Using IntelliJ now, you can open the AndroidManifest.xml file. Make sure you set the minSdkVersion to 14.

Next, look for the line:
 <application android:label="@string/app_name"
        android:debuggable="true"
        android:icon="@drawable/icon">

Make sure the name of the icon matches the actual icon name in the 'res' folder. Most likely this is wrong and should be update to look something like this:

    <application android:label="@string/app_name"
        android:debuggable="true"
        android:icon="@drawable/ic_launcher">

After updating the AndroidManifest.xml file, you'll need to tell IntelliJ where to find the Cocos2d-x JNI Java files, which are required for the Java wrapper-code of your Android project. You can do this by opening 'File' -> 'Project Structure...' from the menu. Now select 'Modules', 'proj.android' and make sure the 'Sources' tab is selected.


Now click 'Add Content Root' and select [Cocos2d-x root folder]/cocos2dx/platform/android/java/src and click 'OK' to confirm.


If you let IntelliJ configure Android for you when it detected it, you should already have a run-configuration added. Otherwise you'll have to add it manually. From the menu select 'Run' -> 'Edit Configurations...'. Then click the '+'-button and select 'Android Application'. Name your configuration (Pong) and select the proj.android module. You can also select your preferred Android Virtual Device here and then click 'OK':


You should now be able to use this Run-configuration to fire up the Cocos2d-x 'Hello World' application on your Android simulator:


Great. Now go back to IntelliJ and edit the Run Configuration you used to start the Android application. On the 'Before launch' panel click the '+'-button and select 'Run External tool'.
Here you can provide a name (Pong Native Build) a group (Android Native Builds) and a description (Builds the native code for Pong). Then you should select the build_native.sh script in your proj.android folder and make sure the Working directory is also set to your proj.android folder. Once you're done, click 'OK'.


You'll see you have created a new external tool and a group to hold it. Make sure 'Pong Native Build' is selected and click 'OK'.


Now select the newly created external tool in the 'Before launch' panel and click the up-arrow to move it to the top so it will be executed before 'Make'. If you are done, click 'OK' to confirm.


Now, let's make a small change in the C++ code. I'm going to use Xcode for this. Open the file HelloWorldScene.cpp and locate this line:

pCloseItem->setPosition( ccp(CCDirector::sharedDirector()->getWinSize().width - 20, 20) );

This line places the close-button in the 'Hello World'-app at the bottom right corner. Let's put it into the top left corner instead by changing the line into:

pCloseItem->setPosition( ccp(20, CCDirector::sharedDirector()->getWinSize().height - 20) );

Run the application in Xcode so you can verify the button is now indeed in the top left corner in the iPhone simulator or whatever iOS simulator or device you are using:


Return to IntelliJ. If you click run, the updated C++ code should automatically compile for your Android project and the app with the updated button-position should show up inside the Android Virtual Device:


I'll leave the implementation of the actual cross-platform 'Pong'-game up to you.

enjoy.

Sunday, November 4, 2012

Submitting PDF Forms in Java

For a client I had to add some prototyping functionality to an existing web application to allow the client to quickly upload PDF forms and assign them to specific users. Those users should then see the PDF embedded inside the browser window and be able to fill out the form and submit the data to the server.

This functionality would help the client to quickly try out different kinds of questionnaires and different approaches on groups of test users without depending on developers to develop new web pages in the application all the time. Only when they were fully satisfied with the feedback from the test group, developers would convert the PDF forms into actual HTML forms.

Of course, proper embedding inside the web page only works on a subset of platforms and browser and only if a PDF Reader plugin is installed that supports those features. For us this wasn't much of a problem, since the test group was in a controlled environment (using Windows + IE + Adobe Acrobat Reader).

In this blog I will create a simple Java web application that has this basic functionality. The project structure looks like this:


So there are just 2 servlets, 2 JSPs and a web.xml file. I also provide a pom-file so you can build and run the project using Maven.

Let's take a look at the web.xml first:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

   <display-name>PDFForms</display-name>

   <!-- This servlet generates a PDF form on the fly. -->
   <servlet>
      <servlet-name>PDFFormServlet</servlet-name>
      <servlet-class>nl.piraya.blog.PDFServlet</servlet-class>
   </servlet>
   <servlet-mapping>
      <servlet-name>PDFFormServlet</servlet-name>
      <url-pattern>/dynamic.pdf</url-pattern>
   </servlet-mapping>

   <!-- This PDF accepts submitted FDF data. -->
   <servlet>
      <servlet-name>FDFServlet</servlet-name>
      <servlet-class>nl.piraya.blog.FDFServlet</servlet-class>
   </servlet>
   <servlet-mapping>
      <servlet-name>FDFServlet</servlet-name>
      <url-pattern>/fdf</url-pattern>
   </servlet-mapping>

   <!-- Make sure the index.jsp file is opened when people access the application. -->
   <welcome-file-list>
      <welcome-file>index.jsp</welcome-file>
   </welcome-file-list>
</web-app>

Here we setup a Servlet that generates the PDF form dynamically and map it to the path /dynamic.pdf. We also setup a Servlet that accepts submitted FDF data and map it to the path /fdf. At the end we also make sure the index.jsp file is opened if the user visits our webapp.

Let's take a look at the index.jsp:
<!DOCTYPE html>
<html lang="en-us">
   <head>
      <meta charset="utf-8">
      <title>PDF Forms Example</title>
   </head>
   <body>
      <h1>Embedded PDF Form</h1>

      <!-- Embed a dynamically generated PDF that can submit its data back to the server. -->
      <object type="application/pdf" width="640" height="480" data="dynamic.pdf">
         <a href="dynamic.pdf">Dynamically Generated PDF</a>
      </object>
   </body>
</html>

Not much happening here. As a matter of fact, there's really no need to make this a JSP. An HTML file would have sufficed. The only mildly interesting part is the embedding code; an object tag is used that points to the PDF servlet. If PDF embedding is not supported, we still show a link so you can download the PDF instead. Although usually when embedding fails, you most likely don't have a PDF reader on your system that supports submitting data from a PDF form either.

Next up, the PDFServlet code:
package nl.piraya.blog;

import com.lowagie.text.*;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.*;
import com.lowagie.text.pdf.TextField;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * Servlet that uses iText to generate a PDF. The PDF that is created will hold a couple of text fields and a submit
 * button that submits the form data to the FDFServlet.
 *
 * @author Onno Scheffers
 */
public class PDFServlet extends javax.servlet.http.HttpServlet {
   /**
    * Handles GET requests to this servlet.
    *
    * @param request An {@link HttpServletRequest} object that contains the request the client has made of the servlet.
    * @param response An {@link HttpServletResponse} object that contains the response the servlet sends to the client.
    * @exception IOException If an input or output error is detected when the servlet handles the GET request.
    * @exception ServletException If the request for the GET could not be handled.
    */
   protected void doGet(
         final HttpServletRequest request,
         final HttpServletResponse response
   ) throws ServletException, IOException {

      // Setup a buffer for the PDF data
      ByteArrayOutputStream buffer = new ByteArrayOutputStream();
      try {
         // Create the new PDF document and add some content
         Document document = new Document(PageSize.A4);
         PdfWriter writer = PdfWriter.getInstance(document, buffer);
         document.open();
         addParagraph(document);
         addFields(writer);
         addSubmitButton(writer);
         document.close();

         // Handle the response
         response.setHeader("Expires", "0");
         response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
         response.setHeader("Pragma", "public");
         response.setContentType("application/pdf");
         response.setContentLength(buffer.size());
         OutputStream os = response.getOutputStream();
         buffer.writeTo(os);
         os.flush();
         os.close();
      } catch (Exception e) {
         throw new IOException("Problem during PDF creation", e);
      }
   }

   /**
    * Adds a paragraph of text to the document.
    *
    * @param document The PDF document.
    * @throws DocumentException If something goes wrong while adding the paragraph to the PDF document.
    */
   private void addParagraph(final Document document) throws DocumentException {
      Paragraph p = new Paragraph("Please fill out the fields below and click the submit button.");
      p.setAlignment(Element.ALIGN_CENTER);
      document.add(p);
   }

   /**
    * Adds a couple of text fields to the PDF document.
    *
    * @param writer The PdfWriter to use for adding text fields to the PDF document.
    * @throws IOException If something goes wrong while adding the text fields to the document.
    * @throws DocumentException If something goes wrong while creating the required PDF elements.
    */
   private void addFields(final PdfWriter writer) throws IOException, DocumentException {
      // Add some labels
      PdfContentByte cb = writer.getDirectContent();
      cb.beginText();
      cb.setFontAndSize(BaseFont.createFont(), 12);
      cb.showTextAligned(PdfContentByte.ALIGN_RIGHT, "First Name:", 164, 687, 0);
      cb.showTextAligned(PdfContentByte.ALIGN_RIGHT, "Last Name:", 164, 630, 0);
      cb.endText();

      // Add the text fields
      addTextField(writer, "firstName", 679);
      addTextField(writer, "lastName", 622);
   }

   /**
    * Adds a single text field to the PDF document.
    *
    * @param writer The PdfWriter to use for adding text fields to the PDF document.
    * @param fieldName The name of the field, which will be name to use for submitting the data in this text field.
    * @param y The y-position of the bottom of the text field in points, relative to the left bottom border of the page.
    * @throws IOException If something goes wrong while adding the text fields to the document.
    * @throws DocumentException If something goes wrong while creating the required PDF elements.
    */
   private void addTextField(
         final PdfWriter writer,
         final String fieldName,
         final float y) throws IOException, DocumentException {
      TextField textField = new TextField(writer, new Rectangle(170, y, 425, y + 21), fieldName);
      textField.setBorderColor(Color.BLACK);
      textField.setBackgroundColor(new GrayColor(0.9f));
      textField.setBorderWidth(1);
      textField.setBorderStyle(PdfBorderDictionary.STYLE_BEVELED);
      textField.setAlignment(Element.ALIGN_LEFT);
      textField.setOptions(TextField.REQUIRED);
      writer.addAnnotation(textField.getTextField());
   }

   /**
    * Adds a single submit button to the PDF document.
    *
    * @param writer The PdfWriter to use for adding text fields to the PDF document.
    * @throws IOException If something goes wrong while adding the text fields to the document.
    * @throws DocumentException If something goes wrong while creating the required PDF elements.
    */
   private void addSubmitButton(final PdfWriter writer) throws IOException, DocumentException {
      PushbuttonField button = new PushbuttonField(writer, new Rectangle(170, 558, 255, 586), "submit");
      button.setText("Submit");
      button.setBackgroundColor(new GrayColor(0.7f));
      button.setVisibility(PushbuttonField.VISIBLE_BUT_DOES_NOT_PRINT);
      PdfFormField submit = button.getField();
      submit.setAction(PdfAction.createSubmitForm("fdf", null, 0));
      writer.addAnnotation(submit);
   }
}

Most of this code is used for generating the PDF document with some text and some form elements. If you ignore all PDF creation code, there is really not much going on. We just generate the document and return it. The most important part here is the submit button that is added to the document in the addSubmitButton method. We configure it to post to the FDFServlet, which was mapped to the fdf path.

Let's take a look at that FDFServlet now:
package nl.piraya.blog;

import com.lowagie.text.pdf.FdfReader;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Set;

/**
 * Servlet that accepts POSTing binary FDF data. The form data will be parsed and the results outputted to System.out.
 *
 * @author Onno Scheffers
 */
public class FDFServlet extends HttpServlet {
   /**
    * Handles the POST to this servlet.
    *
    * @param request An {@link HttpServletRequest} object that contains the request the client has made of the servlet.
    * @param response An {@link HttpServletResponse} object that contains the response the servlet sends to the client.
    * @exception IOException If an input or output error is detected when the servlet handles the POST request.
    * @exception ServletException If the request for the POST could not be handled.
    */
   protected void doPost(
         final HttpServletRequest request,
         final HttpServletResponse response
   ) throws ServletException, IOException {

      // Check if we are getting FDF data
      String contentType = request.getContentType();
      if("application/vnd.fdf".equalsIgnoreCase(contentType)) {
         try {
            // Parse the FDF data using iText
            FdfReader reader = new FdfReader(request.getInputStream());
            HashMap map = reader.getFields();
            Set keys = map.keySet();
            for(Object key : keys) {
               // Simply output the keys and their values to System.out
               String value = reader.getFieldValue(key.toString());
               System.out.println(key + " = " + value);
            }
            reader.close();

            // Forward the user to the next page in the application
            // If you want to keep the user in the same PDF instead, you can simply return the FDF data you received
            response.sendRedirect("done.jsp");

            return;
         } catch (Exception e) {
            // Ignore
         }
      }
      throw new IOException("Unable to read FDF data from request");
   }
}

We implement the doPost method here to accept binary data. By default PDF will submit its form data as FDF, although it also supports submitting as HTML, XDF and as a full PDF (if the PDF reader supports it, that is).

We use iText here again. This time we use it to parse the incoming FDF data and extract all form fields. We simply output the field names and their values to System.out and then we redirect the user to the done.jsp page, which simply thanks the user for submitting his or her data:

<!DOCTYPE html>
<html lang="en-us">
   <head>
      <meta charset="utf-8">
      <title>PDF Forms Example</title>
   </head>
   <body>
      <h1>PDF Form was sent</h1>

      <p>
         Thanks for sending your PDF Form.
      </p>
      <a href="index.jsp">back</a>
   </body>
</html>


That's it! A simple web application that creates a PDF form, embeds it inside a web page, allows the user to submit the form data and then parses the submitted data.

I hope you enjoyed this article.

Get the source code

Saturday, October 20, 2012

First post

I finally got around to setting up a development blog :o)

Given time, I hope this place will become a central location for me to publish articles on any topic related to software development or technology that I find interesting, as well as tips, tricks and solutions to problems I encounter in my day-to-day life as a software developer.

I hope you will enjoy my articles.

- Onno