|
The following describes the construction of a simple native application. Each step builds on the prior one.
- A C App
-
Add GTK+ Minimal Code
-
Basic ALP App Elements
-
Parse Launch Code Parameters
-
Separate UI Creation
-
Add Relaunch Handler
-
Userdata “Anchor Block”
-
(lib)Glade Integration
-
Saving and Restoring Application State
- Saving State in a File
Step 1: A C App
A “Hello World” application is just a little too bloated… Let’s start with a an even more minimal C program:
int main(int argc, char *argv[])
{
return 0;
} // main()
Step 2: Add GTK+ Minimal Code
See http://www.gtk.org/tutorial/ for a more exhaustive tutorial. As a generic GTK application, this example does not have any way of exiting, but it does not matter because within the context of ALP, an application does not need to exit itself. See the next section for a more appropriate ALP app.
Since we are using GLib/GDK/GTK, we should take advantage of its abstractions and use GLib types gint and gchar instead of int and char. For persistent data or wireline protocols, use types listed in types.h (e.g., !!!).
#include <gtk/gtk.h> // Gtk* gtk*()
gint main(gint argc, gchar *argv[])
{
GtkWidget *winmain; // App's main window
gtk_init(&argc, &argv); // Activate GTK UI
// Create the App's main window
winmain = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_show(winmain); // Show App Window
gtk_main(); // Run the UI; dispatch UI events
return 0;
} // main()
Step 3: Basic ALP App Elements
And if we want to build this for ALP, we need to start with the basics.
- Make directories called rsc and src
- Create an Icon
- Define the Manifest.xml
<manifest name="com.mydomain.apps.my_appl">
<application>
<name>My Appl</name>
<icon>myicon.png</icon>
</application>
</manifest>
Code changes include:
- Rename main() to alp_main()
- Add a handler for request to shutdown the application (OnAppExit()).
- Register the handler of the ALP request to exit the application via alp_app_add_exit_handler().
#include <alp/alp.h> // Alp* alp*() ALP*
#include <gtk/gtk.h> // Gtk* gtk*()
// Respond to ALP system request to shutdown the application, so save App UI
// state and shut down the UI.
void onAppExit()
{
gtk_main_quit(); // Tell GTK to exit its event loop
} // onAppExit()
gint alp_main(gint argc, gchar *argv[])
{
GtkWidget *winmain; // App's main window
alp_app_add_exit_handler(onAppExit, NULL); // Register app exit handler
gtk_init(&argc, &argv); // Activate GTK UI
// Create the App's main window
winmain = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_show(winmain); // Show App Window
gtk_main(); // Run the UI; dispatch UI events
return 0;
} // alp_main()
Step 4: Parse Launch Code Parameters
Now let’s prepare for a non-trival case…
gint alp_main(gint argc, gchar *argv[])
{
GtkWidget *winmain=null; // App's main window
alp_app_add_exit_handler(onAppExit, NULL); // Register app exit handler
while (argc--)
{
argv++; // Next parameter
if ( !strcmp(*argv, ALP_APP_PRIMARY) )
{
gtk_init(&argc, &argv); // Activate GTK UI
// Create the App's main window
winmain = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_show(winmain); // Show App Window
} // if ALP_APP_PRIMARY
} // while ()
if (winmain)
gtk_main(); // Run the UI; dispatch UI events
return 0;
} // alp_main()
Step 5: Separate UI Creation
GtkWindow *initializeUI( gpointer userdata )
{
GtkWidget *winmain; // App's main window
// Create the App's main window
winmain = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_show(winmain); // Show App Window
return winmain;
} // initUI()
gint alp_main(gint argc, gchar *argv[])
{
GtkWindow *winmain=NULL;
alp_app_add_exit_handler(onAppExit, NULL); // Register app exit handler
while (argc--)
{
argv++; // Next parameter
if ( !strcmp(argv, ALP_APP_PRIMARY) )
{
gtk_init(&argc, &argv); // Activate GTK UI
winmain = initializeUI( NULL );
} // if ALP_APP_PRIMARY
} // while ()
if (winmain)
gtk_main(); // Run the UI; dispatch UI events
return 0;
} // alp_main()
Step 6: Add Relaunch Handler
Notifications can be sent to an application whether it is running or not. If the program is running, it is shut down before the new launch codes are sent. To avoid disturbing the running application to handle the notification this inefficient way, we can register a relaunch handler callback function to handle such notifications while the program is running. (If the program is not running, then the notification launch code is passed to alp_main() in the fashion as shown above.
A relaunch handler accepts argc and argv parameters just like a main() does and the parsing of these parameters is exactly the same as alp_main() would, so we can move most of the code from the main function to the relaunch handler.
void onLaunchRelaunch( int argc, char *argv[], void *userdata )
{
while (argc--)
{
argv++; // Next parameter
if ( !strcmp(argv, ALP_APP_PRIMARY) )
{
gtk_init(&argc, &argv); // Activate GTK UI
*(GtkWindow **)userdata = initializeUI( NULL );
} // if ALP_APP_PRIMARY
} // while ()
} // void onLaunchRelaunch()
gint alp_main(gint argc, gchar *argv[])
{
GtkWindow *winmain=NULL;
alp_app_add_exit_handler(onAppExit, NULL); // Register app exit handler
alp_add_relaunch_handler( onLaunchRelaunch, &winmain );
onLaunchRelaunch( argc, argv, &winmain );
if (winmain)
gtk_main(); // Run the UI; dispatch UI events
return 0;
} // alp_main()
Step 7: Userdata “Anchor Block”
To consolidate application specific data and propogate it to the necessary functions without relying on globals, we use a common struct.
typedef struct UserData_s {
} UserData_t;
typedef struct UserData_s {
GtkWidget *winmain; // App's main window
} UserData_t;
void onLaunchRelaunch( int argc, char *argv[], void *data )
{
UserData_t *userdata = (UserData_t *)data;
while (argc--)
{
argv++; // Next parameter
if ( !strcmp(argv, ALP_APP_PRIMARY) )
{
gtk_init(&argc, &argv); // Activate GTK UI
userdata->winmain = initializeUI( data );
} // if ALP_APP_PRIMARY
} // while ()
} // void onLaunchRelaunch()
gint alp_main(gint argc, gchar *argv[])
{
UserData_t *userdata=g_malloc(sizeof(UserData_t));
memset(userdata, 0, sizeof(UserData_t)); // Commonly set fields to 0, NULL, false, etc.
alp_app_add_exit_handler(onAppExit, userdata); // Register app exit handler
alp_add_relaunch_handler( onLaunchRelaunch, userdata );
onLaunchRelaunch( argc, argv, userdata );
if (userdata->winmain)
gtk_main(); // Run the UI; dispatch UI events
g_free( userdata );
return 0;
} // alp_main()
Step 8: (lib)Glade Integration
We can be lazy about coding our UI and let Glade do it. It also means we can let someone else work on presentation while I code.
- Generate a project .glade file using Glade3.
- Add code to utilize the Glade definition file.
. . .
#include <glade/glade.h> // glade*()
. . .
GtkWindow *initializeUI( gpointer userdata )
{
// Load/activate UI defintion
GladeXML* glade; // Glade "DOM" instance
glade = alp_bundle_acquire_glade_xml("app_ui.glade", NULL );
// Enable any Glade defined signal handlers
glade_xml_signal_autoconnect(xml);
return GTK_WINDOW( glade_xml_widget_get( glade, "mainwindow" ) );
} // initializeUI()
. . .
Step 9: Saving and Restoring Application State
ALP application processes can be started and stopped at any time by the system. In order to give the user the impression that the app is running continuously, save and restore the state when the process is entered and exited. So, let’s add a couple of entry points to allow us to do that, generically, doInitializeApp() and doTerminateApp(). There is no prespecified method to store the application state; it can be saved in files, Global Settings, SQL, or elsewhere.
It might be convenient to represent the “live” version of the application state in the UserData_t struct.
void doInitializeApp( UserData_t *userdata )
{
} // doInitializeApp()
void doTerminateApp( UserData_t *userdata )
{
} // doTerminateApp()
gint alp_main(gint argc, gchar *argv[])
{
UserData_t *userdata=g_malloc(sizeof(UserData_t));
memset(userdata, 0, sizeof(UserData_t)); // Commonly set fields to 0, NULL, false, etc.
doInitializeApp( userdata );
alp_app_add_exit_handler(onAppExit, userdata); // Register app exit handler
alp_add_relaunch_handler( onLaunchRelaunch, userdata );
onLaunchRelaunch( argc, argv, userdata );
if (userdata->winmain)
gtk_main(); // Run the UI; dispatch UI events
doTerminateApp( userdata );
g_free( userdata );
return 0;
} // alp_main()
Step 9a: Saving State in a File
File data cannot just be stored anywhere, it has to be stored in an appication specific area in the filesystem. Rather than assume the location of the path, use alp_bundle_rw_pathname() to allow the system to return the path dynamically.
gchar* alp_bundle_rw_pathname(
AlpBundle index,
const char *file_path,
gboolean vivify
)
. . .
#define STATE_FILE "appstate.dat"
. . .
void doInitializeApp( UserData_t *userdata )
{
gchar *state_file=alp_bundle_rw_pathname( alp_bundle_application(), STATE_FILE, FALSE );
if ( state_file )
{
g_free(state_file);
}
} // doInitializeApp()
void doTerminateApp( UserData_t *userdata )
{
gchar *state_file=alp_bundle_rw_pathname( alp_bundle_application(), STATE_FILE, TRUE);
g_free(state_file);
} // doTerminateApp()
|