Guides/J8 Standalone

From J Wiki
Jump to: navigation, search

Standalone, cross-platform applications in J8

J provides an exceptionally powerful engine for complex data analysis. It is also an expressive and flexible general purpose programming language that makes it easy to develop desktop applications. The goal of this tutorial is to provide an example of taking a form-based J application that runs within the development environment to a standalone, desktop application that can be installed by end users on Windows, OSX, and Linux. This tutorial assumes that you've begun using the J8 qt ide (Jqt), and have some experience developing GUI applications in earlier versions of J. The sample application displays the contents of text files or URLs.


Converting J6 wd applications to J8

The GUI elements of J8 are provided by the Qt toolkit, accessed through the wd commands familiar to programmers using J6 (or earlier versions). Moving to the Qt toolkit provides several advantages over the earlier Jwd (Windows) and Jwdp (Java) front ends. Qt is widely used and well maintained on (among others) all three major desktop platforms and Android. Qt provides a native look and feel on all of the platforms it supports.

J8 implements essentially all of the functionality of the wd command set from earlier versions of J. Converting a J6 application to J8 is straightforward, but requires some effort due to differences in wd syntax. The biggest conceptual difference is in the way form layouts are constructed. In J6, xywh commands were used to position and size controls within the form, usually using the form editor. J8's Qt underpinnings use layout managers to display form controls. The example below provides the form definition for a simple dialog that allows the user to enter a URL in an edit control in Jwd and Jqt, as well as their display on Windows 7.

Jwd version

GETURL=: 0 : 0
pc geturl closeok dialog nomax nomenu nomin nosize owner;pn "Retrieve URL";
xywh 8 8 20 11;cc urlsrc static ss_right;cn "URL  ";
xywh 28 8 200 12;cc src edit;set src http://;
xywh 144 24 38 12;cc ok button;cn "&OK";
xywh 188 24 38 12;cc cancel button;cn "&Cancel";
pas 6 6;pcenter;
rem form end;
)

Img1.png

Jqt version

GETURL=: 0 : 0
pc geturl escclose closeok dialog nosize;pn "Retrieve URL";
bin vh;
minwh 40 22;cc urlsrc static right;cn "URL";
minwh 400 24;cc src edit; set src text http://;
bin z;
bin h s2;
cc ok button;cn "&OK";
cc cancel button;cn "&Cancel";
pcenter;
)

Img2.png

The biggest difference in these two definitions is replacement of the xywh commands in Jwd with the bin layout commands in Jqt. The bin vh line specifies a horizontal layout (for the label and edit control) nested within a vertical layout. The bin h s2 command pushes the two buttons to the right edge of the dialog. The minimum sizes of the controls in Jqt may still be set using minwh commands. Although the use of the layout manager takes a bit of practice, it has the advantage of giving a reasonable display when forms are resized, which was often not the case with the fixed positioning used in Jwd. During form development (since we don't have a visual form editor), defining a simple test function within the form locale allows for quick inspection of the form's appearance, without worrying about the initialization code normally present in the run or create verb.

test=: 3 : 0
wd GETURL
wd'pshow'
)

Finally, note the button appearance in Jqt matches the Windows 7 style, while Jwd draws buttons in the Windows 2000 style.

There are a variety of other wd syntax changes that will require attention when converting Jwd to Jqt, including pc and cc style names and command syntax. Two examples from above:

cc urlsrc static ss_right; is replaced by cc urlsrc static right;

set src http://; is replaced by set src text http://;

Review the J8 wd documentation (as well as the demos) to see the other syntax changes. The bottom line is that you will need to inspect every wd command in your J6 application, and likely make minor changes, in order to convert it to run on J8.

The sample application: Viewer

The example for our standalone conversion, Viewer, simply opens a text file or URL and displays the result in an editm window. The main window, below, has a menu, toolbar, and an editm control. In addition, it includes three dialogs: About, Preferences, and Retrieve URL. An archive of the J8 project folder may be downloaded from here: http://www.jsoftware.com/files/viewer-2.zip

Img3.png

The text file or URL is opened from File>Open file or File>Open URL, respectively, and then displayed in a readonly editm control. Choosing Edit>Annotate makes the editm editable and you can toggle it back to readonly using the same menu choice or toolbar button. The File>Preferences dialog allows you to set the fonts for the application controls, the editm control, as well as the margins and orientation for printing.

The first step is to make sure that your application is fully functional within the J IDE. Our project is in the folder viewer-2. The contents of the viewer-2.jproj file are:

NB. project: viewer-2
NB.
NB. defines list of source files.
NB. path defaults to project directory.

about.ijs
geturl.ijs
pref.ijs
req.ijs
sysdep.ijs
main.ijs

In addition to the main window and the three dialog forms mentioned above, source files include sysdep.ijs (system dependent code to deal with our three platforms), and req.ijs (some utility functions used by the program). The scripts have require statements (e.g. require 'general/misc/validate' in pref.ijs) to bring in required code from addons.

The build.ijs script consists of the line

writesource_jp_ '~Projects/viewer-2';'~Projects/viewer-2/viewer-2.ijs'

and run.ijs is (at the outset)

load jpath'~Projects/viewer-2/viewer-2.ijs'
view_run''

Let's assume that, at this point, you can run the project and the program does everything you want.

Anatomy of a standalone application

Our goal is to turn Viewer into a self-contained application that can be packaged for installation on Windows, OSX, or Linux by an end-user who does not have J installed on his or her system. The discussion below will focus first on Windows and then point out platform specifics.

After installation, our application folder structure will look like this:

Install folder
|-bin
|  |-icons

The bin folder will contain j.dll, jqt.dll, and jqt.exe (or their OSX or Linux equivalents); viewer.ijs (the script file for our application), toucan.bmp (copied from the bmp addon and used by the About dialog), curl.exe (on Windows), and the icons folder (which contains all of the png files used by the toolbar as well as the program icon app.png).

Because our application will doesn't use WebKit, OpenGL, or other advanced functions, we can use the "slim" versions for the jqt binaries (e.g., http://www.jsoftware.com/download/j802/qtide/jqt-winslim-x64.zip) and Qt libraries (e.g., http://www.jsoftware.com/download/j802/qtlib/qt53-win-slim-x64.zip). On OSX, the Qt libraries are included in the folder Qt in the install directory, and you can simply copy the necessary files from your J installation. On Linux, Qt is always installed as a set of system libraries and this folder is not present. The install folder will also contain a shell script (Linux) or a .app folder (OSX) that starts the application (see below).

First step: Making the application profile independent

Because our standalone application runs without a profile (or the rest of the J development environment), the paths to necessary files (e.g., the toolbar icons) need to be set at runtime. To get everything working, set up a folder like the Install folder above, and copy the necessary files into it (all except viewer.ijs, which we haven't made yet). In particular, you'll need to have the toolbar icons in bin/icons. Now modify run.ijs to replace view_run' ' with dbg' '. To the main.ijs script add the dbg function as below:

dbg=: 3 : 0
DBG=: 1
select. UNAME
  case. 'Win' do.
    b=. 'c:\Utility\Viewer\bin'
    1!:44 b
    p=. GetEnv'path'
    if. -.(';' taketo p) -: b do. SetEnv'PATH';(b,';',p) end.
  case. 'Darwin' do.
    b=. '/Users/drinkwater/Viewer/bin'
    1!:44 b
    p=. 2!:5'PATH'
    if. -.(':' taketo p) -: b do. SetEnv'PATH';(b,':',p) end.
  case. 'Linux' do.
    1!:44'/home/drinkwater/viewer/bin'
end.
cocurrent'base'
view_run''
)

On Windows, we've set up our application folder as c:\Utility\Viewer. We can now get everything working from within the J development environment. On Windows, dbg makes c:\Utility\Viewer\bin the current working directory, and executes view_run' '. The function also adds that directory to the path, which is necessary for Viewer on Windows because of the use of curl.exe.

The function view_run is in main.ijs:

IDE=: 1
showide''
sysinit''
SetDefaults''
readPref''
Title=: Data=: ''
wd'fontdef ',FFont__
wd VIEW
wd'set wrap checked; set vw wrap 1'
wd'picon "',Sdir__,'bin/icons/app.png"'
wd'set vw stylesheet *QPlainTextEdit {background-color:#eeeeee;',(font2css VFont__),'}'
wd'set vw text *',Data
TBP=: Bdir,'icons/'
viewtb''
wd 'pshow;'
Ed=: 0
WRAP=: 1
view_wrap_button''
)

For a standalone application, we don't want to see the jqt terminal window. The command wd'ide hide' will hide the terminal window and you can place it as the first line of the run function. I prefer to be able to toggle the terminal window, so view_run starts with

IDE=: 1
NB.showide''

Where showide is the function

showide=: 3 : 0
if. IDE do.
  IDE=: 0
  wd'ide hide'
else.
  IDE=: 1
  wd'ide show'
  wd'psel view;pactive'
end.
empty''
)

view_wctrl_fkey=: showide

The application will normally start with the terminal window hidden and it can be toggled with Ctrl-W. Note that we've commented out showide' ' for now so that we can see any errors. In view_run, sysinit' ' sets Bdir (as the current working directory Viewer/bin), and Sdir as the install folder). The SetDefaults and readPref functions (defined in pref.ijs) provide the fonts to be used for the form (FFont) and for the editm widget that will display the file/url contents (VFont). TBP contains the path to the toolbar icons (Bdir/icons) for use by viewtb, which adds the icons to the toolbar.

Once you're satisfied that everything is working, remove the NB. from the showide line.

Building the standalone script

For our standalone application, we need to build a single script file (viewer.ijs) that contains all of our source scripts as well as the system libraries and required addon scripts. Fortunately, this is easy to do using functions in the jp (jproject) locale. To our project folder we add a new script builds.ijs:

NB. builds.ijs

PDIR=: jpath'~Projects/viewer-2/'

LIBS=: 0 : 0
graphics/bmp
graphics/gl2
graphics/color/rgb
general/misc/validate
web/gethttp
)

TARGETHEADER=: 0 : 0
NB. Viewer 2.0

)

TARGETFILE=: 'viewer.ijs'

TARGETEXTRA=: 'view_run'''''


builds=: 3 : 0
load PDIR,'build.ijs'
out=. TARGETHEADER
out=. out, getstdenv_jp_''
out=. out, getlibs_jp_ LIBS
out=. out,freads PDIR,'viewer-2.ijs'
out=. out,TARGETEXTRA,LF
out fwrites PDIR,'viewer.ijs'
)

builds''

The build.ijs script (run by choosing Project>Build from the IDE edit window menu) collects all of our script files into viewer-2.ijs. The builds script defines the project folder path as PDIR. The other nouns include

LIBS, a list of required addon scripts

TARGETHEADER, copyright or other comments placed at the beginning of our standalone script

TARGETFILE, the name of our standalone script (different from the one used by build.ijs)

TARGETEXTRA, lines to be added at the end of the standalone (typically the run statement).

The builds function constructs our standalone script by pulling in the TARGETHEADER; adding the stdlib.ijs, task.ijs and the hostdef scripts; redefining the load, require, and script functions in the z locale to do nothing; adding the required addon scripts; adding our built source script, viewer-2.ijs; and finally adding the run statement. All of this is written out to our standalone script, viewer.ijs. Uncomment the showide' ' line in the run function of main.ijs and build the standalone script. Note that the resulting viewer.ijs is platform independent. You can simply copy this script from your Windows box to other platforms.

Running the standalone application

Copy viewer.ijs to the Viewer/bin directory of the standalone test folder (c:\Utility\Viewer for Windows). We will run the viewer application from a batch or script file. Instructions for the three platforms are given below.

Windows: To start the program, you could make a batch file, viewer.cmd with the following contents:

@echo off
setlocal
set PATH=%CD%\bin;%PATH%
cd bin
start jqt.exe -jprofile viewer.ijs

A simpler alternative is to rename viewer.ijs to profile.ijs. Now, double-clicking jqt.exe will start the Viewer application. You can make a shortcut on your desktop with jqt.exe as the target and specifying that the application starts in the Viewer\bin directory. If you place an icon file (viewer.ico) for the program (conveniently in the Viewer\bin folder), you can set it as the icon for the shortcut.

OSX: Set up the Viewer application folder in your home directory as discussed above. First make a shell script, viewer.command, with the following contents and place it in the Viewer/bin folder:

 #!/bin/sh
cd "`dirname "$0"`/"
if [ ! -e /Library/Frameworks/QtCore.framework ]
then
export QT_PLUGIN_PATH="$(pwd)"/../Qt/plugins
fi
PATH="$(pwd)":$PATH
export PATH
./jqt -jprofile viewer.ijs

You could start the application from a terminal window with this shell script, but a more Mac-friendly approach is to make a Viewer.app folder in the install folder. The easiest way to do this is to copy the jqt802.app folder (the big green J, note that the .app does not show up in Finder) from your J install directory to the Viewer folder. Rename it to Viewer, right-click on it and choose Show Package Contents. In the resulting finder window, traverse to Contents>MacOS and open the file apprun in a text editor. Replace jqt.command with viewer.command. You will probably want to change the icon as well. Make an OSX icon by converting app.png to app.icns (the free software img2icns will make that easy). Now traverse to Contents>Resources and replace jgreen.icns with app.icns. Go back to the Contents folder and open Info.plist. In the Property List Editor, change the entry for Icon file to app.icns and change the entry for Bundle name to Viewer. Close the Property List Editor and save the modified plist. Double clicking Viewer in the install folder will now start the program. As a final point, following the above directions will yield a Viewer menu in the menubar that displays "About jqt" and "Quit jqt". To have these display properly (About Viewer), you can rename the jqt executable to Viewer, and change the last line of the script above to

./Viewer -jprofile viewer.ijs

Linux: Set up the Viewer application folder as described above. In the install folder create a shell script with the following contents:

 #!/bin/sh
abspath=$(cd ${0%/*} && echo $PWD/${0##*/})
APP_PATH=`dirname "$abspath"`
cd $APP_PATH/bin
PATH=$APP_PATH/bin:$PATH
export PATH
jqt -jprofile viewer.ijs

Make sure that the script is set as executable. You can now start the application by running ~/Viewer/viewer from the terminal.

Debugging your standalone application

Even if your application worked fine within the J development environment, you may encounter an error when you try to run it as a standalone. Most often, the application will simply fail to open for no obvious reason. These errors most often result from a missing file, misconfigured paths, or failing to add a required library to builds.ijs. Debugging standalone applications is much easier with Jqt than it was with Jwd. Open the application script (viewer.ijs in our example), search for showide' ' (or wd'ide hide' if you used that) and comment it out. You'll now get a terminal window when you try to start the application and the error it displays should allow you to figure out how to fix the program.

Reducing the size of the Qt libraries

The set of Qt libraries installed by default on Windows and OSX is quite large, exceeding 90 Mb for 64-bit Windows. Many applications (including Viewer) only require the Qt5Core, Qt5Gui, Qt5Widgets, Qt5PrinSupport libraries, together with the platforms and printsupport plugin folders and the MSVC 2013 runtime libraries. You can reduce the size of your standalone installation by eliminating QtWebKit and the other unnecessary libraries. The sizes of the reduced Qt library set is less than 20 Mb for 64-bit Windows.

The slim versions of the jqt binaries are available from http://www.jsoftware.com/download/j802/qtide/ and the reduced library set for Windows from http://www.jsoftware.com/download/j802/qtlib/ . In addition, the Viewer installers provided as part of this tutorial include the recompiled jqt and reduced set of Qt libraries for 64-bit platforms. Note that links to these installers are on the last page of this tutorial.

Making 32- and 64-bit applications

The J, jqt, and Qt libraries are specific for either 32-bit or 64-bit architectures. To make standalone applications for both architectures, you will need to construct the application folders separately for each architecture. Note that the rest of the files (viewer.ijs, the icon files, the shell scripts) are identical for the two architectures. On OSX, only 64-bit applications are possible.

Making an installer for distribution

Having constructed our standalone Viewer folders by following the directions above, you could simply zip up the folder and distribute the application as a compressed archive. Indeed, on Linux that is likely all you would want to do. Depending on how widely you intend to distribute your application, you may want to construct installers for Windows and OSX that are more user-friendly. On Windows, I've used NSIS to make installers. While NSIS is a bit long in the tooth, it has several advantages, including the ability to run from within Eclipse and the ability to make a single installer that works on both 32- and 64-bit computers. The file Viewersetup.nsi in the install directory of the project folder can be used to make a Windows installer. On OSX, I've used DropDMG to make dmg archives that contain the application folder. Opening the dmg file will display a license file of your choosing and the application folder can then be copied to the Applications folder or the user's Home folder. In any case, it would be appropriate to include a copy of the Jsoftware license for the J executables in your distribution, or a link to it (http://www.jsoftware.com/docs/help802/user/warranty.htm) in your program's documentation.

Final words and a few platform specific quirks

The combination of J and Qt is as close to write once run everywhere as I've been able to get. I have run across a handful of problems, mostly in getting applications to work on OSX. For OSX, the appropriate shared libraries are installed in the Qt folder within the installation directory as described above. Because of the way OSX handles shared libraries, you will have a conflict if you have already installed Qt to the /Library/Frameworks directory (not a common occurrence outside of the developer community). To test for that conflict, open a terminal window, change directories to the Viewer/bin folder, and enter ./viewer. If you have duplicate libraries, you will see a string of error messages indicating that fact in the terminal window. Simply rename the Qt folder in the installation directory to Qt.0 to resolve the problem.

A more general Qt issue is in the way it sizes edit controls. The wd'minwh ' command sets the minimum size for controls and static controls default to that size. However, edit controls always default to a size sufficient to handle 15-20 characters, which may be much larger than you want if you have a form with many edit controls, each of which is intended to hold a small amount of text (like a single number). The wd'set editname wh...' command was added to wd to handle that issue:

cc editname edit;set editname wh 60 _1;

Now the control will start at a width of 60 pixels and the default height.

Viewer installer files

Installer files for Windows, OSX, and Linux may be downloaded from the links below.

Windows (32- and 64-bit): http://www.jsoftware.com/files/Viewer-setup.exe

OSX (64-bit): http://www.jsoftware.com/files/Viewer-mac-x64.dmg

Linux (64-bit): http://www.jsoftware.com/files/Viewer-x64.tar.gz

Contributed by Norman Drinkwater, 7/2014