2 * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
4 * Copyright (c) 2015, Rob Norris <rw_norris@hotmail.com>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 #include <glib/gstdio.h>
30 #include <glib/gi18n.h>
42 #include "preferences.h"
43 #include "icons/icons.h"
44 #include "mapnik_interface.h"
46 struct _VikMapnikLayerClass
48 VikLayerClass object_class;
51 static VikLayerParamData file_default ( void )
53 VikLayerParamData data;
58 static VikLayerParamData size_default ( void ) { return VIK_LPD_UINT ( 256 ); }
59 static VikLayerParamData alpha_default ( void ) { return VIK_LPD_UINT ( 255 ); }
61 static VikLayerParamScale scales[] = {
62 { 0, 255, 5, 0 }, // Alpha
63 { 64, 1024, 8, 0 }, // Tile size
66 VikLayerParam mapnik_layer_params[] = {
67 { VIK_LAYER_MAPNIK, "config-file-mml", VIK_LAYER_PARAM_STRING, VIK_LAYER_GROUP_NONE, N_("CSS (MML) Config File:"), VIK_LAYER_WIDGET_FILEENTRY, GINT_TO_POINTER(VF_FILTER_CARTO), NULL,
68 N_("CartoCSS configuration file"), file_default, NULL, NULL },
69 { VIK_LAYER_MAPNIK, "config-file-xml", VIK_LAYER_PARAM_STRING, VIK_LAYER_GROUP_NONE, N_("XML Config File:"), VIK_LAYER_WIDGET_FILEENTRY, GINT_TO_POINTER(VF_FILTER_XML), NULL,
70 N_("Mapnik XML configuration file"), file_default, NULL, NULL },
71 { VIK_LAYER_MAPNIK, "alpha", VIK_LAYER_PARAM_UINT, VIK_LAYER_GROUP_NONE, N_("Alpha:"), VIK_LAYER_WIDGET_HSCALE, &scales[0], NULL,
72 NULL, alpha_default, NULL, NULL },
81 static const gchar* mapnik_layer_tooltip ( VikMapnikLayer *vml );
82 static void mapnik_layer_marshall( VikMapnikLayer *vml, guint8 **data, gint *len );
83 static VikMapnikLayer *mapnik_layer_unmarshall( guint8 *data, gint len, VikViewport *vvp );
84 static gboolean mapnik_layer_set_param ( VikMapnikLayer *vml, guint16 id, VikLayerParamData data, VikViewport *vp, gboolean is_file_operation );
85 static VikLayerParamData mapnik_layer_get_param ( VikMapnikLayer *vml, guint16 id, gboolean is_file_operation );
86 static VikMapnikLayer *mapnik_layer_new ( VikViewport *vvp );
87 static VikMapnikLayer *mapnik_layer_create ( VikViewport *vp );
88 static void mapnik_layer_free ( VikMapnikLayer *vml );
89 static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vp );
90 static void mapnik_layer_add_menu_items ( VikMapnikLayer *vml, GtkMenu *menu, gpointer vlp );
92 // See comment in viktrwlayer.c for advice on values used
94 static VikToolInterface mapnik_tools[] = {
99 static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file);
101 VikLayerInterface vik_mapnik_layer_interface = {
103 N_("Mapnik Rendering"),
105 &vikmapniklayer_pixbuf, // icon
108 sizeof(mapnik_tools) / sizeof(VikToolInterface),
117 (VikLayerFuncCreate) mapnik_layer_create,
118 (VikLayerFuncRealize) NULL,
119 (VikLayerFuncPostRead) mapnik_layer_post_read,
120 (VikLayerFuncFree) mapnik_layer_free,
122 (VikLayerFuncProperties) NULL,
123 (VikLayerFuncDraw) mapnik_layer_draw,
124 (VikLayerFuncChangeCoordMode) NULL,
126 (VikLayerFuncSetMenuItemsSelection) NULL,
127 (VikLayerFuncGetMenuItemsSelection) NULL,
129 (VikLayerFuncAddMenuItems) mapnik_layer_add_menu_items,
130 (VikLayerFuncSublayerAddMenuItems) NULL,
132 (VikLayerFuncSublayerRenameRequest) NULL,
133 (VikLayerFuncSublayerToggleVisible) NULL,
134 (VikLayerFuncSublayerTooltip) NULL,
135 (VikLayerFuncLayerTooltip) mapnik_layer_tooltip,
136 (VikLayerFuncLayerSelected) NULL,
138 (VikLayerFuncMarshall) mapnik_layer_marshall,
139 (VikLayerFuncUnmarshall) mapnik_layer_unmarshall,
141 (VikLayerFuncSetParam) mapnik_layer_set_param,
142 (VikLayerFuncGetParam) mapnik_layer_get_param,
143 (VikLayerFuncChangeParam) NULL,
145 (VikLayerFuncReadFileData) NULL,
146 (VikLayerFuncWriteFileData) NULL,
148 (VikLayerFuncDeleteItem) NULL,
149 (VikLayerFuncCutItem) NULL,
150 (VikLayerFuncCopyItem) NULL,
151 (VikLayerFuncPasteItem) NULL,
152 (VikLayerFuncFreeCopiedItem) NULL,
153 (VikLayerFuncDragDropRequest) NULL,
155 (VikLayerFuncSelectClick) NULL,
156 (VikLayerFuncSelectMove) NULL,
157 (VikLayerFuncSelectRelease) NULL,
158 (VikLayerFuncSelectedViewportMenu) NULL,
161 struct _VikMapnikLayer {
163 gchar *filename_css; // CartoCSS MML File - use 'carto' to convert into xml
167 guint tile_size_x; // Y is the same as X ATM
172 #define MAPNIK_PREFS_GROUP_KEY "mapnik"
173 #define MAPNIK_PREFS_NAMESPACE "mapnik."
175 static VikLayerParamData plugins_default ( void )
177 VikLayerParamData data;
179 data.s = g_strdup ( "input" );
181 if ( g_file_test ( "/usr/lib/mapnik/input", G_FILE_TEST_EXISTS ) )
182 data.s = g_strdup ( "/usr/lib/mapnik/input" );
183 else if ( g_file_test ( "/usr/lib/mapnik/2.2/input", G_FILE_TEST_EXISTS ) )
184 // Current Debian location
185 data.s = g_strdup ( "/usr/lib/mapnik/2.2/input" );
187 data.s = g_strdup ( "" );
192 static VikLayerParamData fonts_default ( void )
194 // Possibly should be string list to allow loading from multiple directories
195 VikLayerParamData data;
197 data.s = g_strdup ( "C:\\Windows\\Fonts" );
198 #elif defined __APPLE__
199 data.s = g_strdup ( "/Library/Fonts" );
201 data.s = g_strdup ( "/usr/share/fonts" );
206 static VikLayerParam prefs[] = {
207 // Changing these values only applies before first mapnik layer is 'created'
208 { VIK_LAYER_NUM_TYPES, MAPNIK_PREFS_NAMESPACE"plugins_directory", VIK_LAYER_PARAM_STRING, VIK_LAYER_GROUP_NONE, N_("Plugins Directory:"), VIK_LAYER_WIDGET_FOLDERENTRY, NULL, NULL, N_("You need to restart Viking for a change to this value to be used"), plugins_default, NULL, NULL },
209 { VIK_LAYER_NUM_TYPES, MAPNIK_PREFS_NAMESPACE"fonts_directory", VIK_LAYER_PARAM_STRING, VIK_LAYER_GROUP_NONE, N_("Fonts Directory:"), VIK_LAYER_WIDGET_FOLDERENTRY, NULL, NULL, N_("You need to restart Viking for a change to this value to be used"), fonts_default, NULL, NULL },
210 { VIK_LAYER_NUM_TYPES, MAPNIK_PREFS_NAMESPACE"recurse_fonts_directory", VIK_LAYER_PARAM_BOOLEAN, VIK_LAYER_GROUP_NONE, N_("Recurse Fonts Directory:"), VIK_LAYER_WIDGET_CHECKBUTTON, NULL, NULL, N_("You need to restart Viking for a change to this value to be used"), vik_lpd_true_default, NULL, NULL },
211 // Changeable any time
212 { VIK_LAYER_NUM_TYPES, MAPNIK_PREFS_NAMESPACE"carto", VIK_LAYER_PARAM_STRING, VIK_LAYER_GROUP_NONE, N_("CartoCSS:"), VIK_LAYER_WIDGET_FILEENTRY, NULL, NULL, N_("The program to convert CartoCSS files into Mapnik XML"), NULL, NULL, NULL },
215 // NB Only performed once per program run
216 static void mapnik_layer_class_init ( VikMapnikLayerClass *klass )
218 mapnik_interface_initialize ( a_preferences_get (MAPNIK_PREFS_NAMESPACE"plugins_directory")->s,
219 a_preferences_get (MAPNIK_PREFS_NAMESPACE"fonts_directory")->s,
220 a_preferences_get (MAPNIK_PREFS_NAMESPACE"recurse_fonts_directory")->b );
223 void vik_mapnik_layer_init (void)
225 a_preferences_register_group ( MAPNIK_PREFS_GROUP_KEY, "Mapnik" );
228 VikLayerParamData tmp = plugins_default();
229 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
231 tmp = fonts_default();
232 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
235 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
238 a_preferences_register(&prefs[i++], tmp, MAPNIK_PREFS_GROUP_KEY);
241 GType vik_mapnik_layer_get_type ()
243 static GType vml_type = 0;
246 static const GTypeInfo vml_info = {
247 sizeof (VikMapnikLayerClass),
248 NULL, /* base_init */
249 NULL, /* base_finalize */
250 (GClassInitFunc) mapnik_layer_class_init, /* class init */
251 NULL, /* class_finalize */
252 NULL, /* class_data */
253 sizeof (VikMapnikLayer),
255 NULL /* instance init */
257 vml_type = g_type_register_static ( VIK_LAYER_TYPE, "VikMapnikLayer", &vml_info, 0 );
263 static const gchar* mapnik_layer_tooltip ( VikMapnikLayer *vml )
265 return vml->filename_xml;
268 static void mapnik_layer_set_file_xml ( VikMapnikLayer *vml, const gchar *name )
270 if ( vml->filename_xml )
271 g_free (vml->filename_xml);
272 vml->filename_xml = g_strdup (name);
275 static void mapnik_layer_set_file_css ( VikMapnikLayer *vml, const gchar *name )
277 if ( vml->filename_css )
278 g_free (vml->filename_css);
279 vml->filename_css = g_strdup (name);
282 static void mapnik_layer_marshall( VikMapnikLayer *vml, guint8 **data, gint *len )
284 vik_layer_marshall_params ( VIK_LAYER(vml), data, len );
287 static VikMapnikLayer *mapnik_layer_unmarshall( guint8 *data, gint len, VikViewport *vvp )
289 VikMapnikLayer *rv = mapnik_layer_new ( vvp );
290 vik_layer_unmarshall_params ( VIK_LAYER(rv), data, len, vvp );
294 static gboolean mapnik_layer_set_param ( VikMapnikLayer *vml, guint16 id, VikLayerParamData data, VikViewport *vp, gboolean is_file_operation )
297 case PARAM_CONFIG_CSS: mapnik_layer_set_file_css (vml, data.s); break;
298 case PARAM_CONFIG_XML: mapnik_layer_set_file_xml (vml, data.s); break;
299 case PARAM_ALPHA: if ( data.u <= 255 ) vml->alpha = data.u; break;
305 static VikLayerParamData mapnik_layer_get_param ( VikMapnikLayer *vml, guint16 id, gboolean is_file_operation )
307 VikLayerParamData data;
309 case PARAM_CONFIG_CSS: {
310 data.s = vml->filename_css;
311 gboolean set = FALSE;
312 if ( is_file_operation ) {
313 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
314 gchar *cwd = g_get_current_dir();
316 data.s = file_GetRelativeFilename ( cwd, vml->filename_css );
317 if ( !data.s ) data.s = "";
323 data.s = vml->filename_css ? vml->filename_css : "";
326 case PARAM_CONFIG_XML: {
327 data.s = vml->filename_xml;
328 gboolean set = FALSE;
329 if ( is_file_operation ) {
330 if ( a_vik_get_file_ref_format() == VIK_FILE_REF_FORMAT_RELATIVE ) {
331 gchar *cwd = g_get_current_dir();
333 data.s = file_GetRelativeFilename ( cwd, vml->filename_xml );
334 if ( !data.s ) data.s = "";
340 data.s = vml->filename_xml ? vml->filename_xml : "";
343 case PARAM_ALPHA: data.u = vml->alpha; break;
344 //case PARAM_TILE_X: data.u = vml->tile_size_x; break;
353 static VikMapnikLayer *mapnik_layer_new ( VikViewport *vvp )
355 VikMapnikLayer *vml = VIK_MAPNIK_LAYER ( g_object_new ( VIK_MAPNIK_LAYER_TYPE, NULL ) );
356 vik_layer_set_type ( VIK_LAYER(vml), VIK_LAYER_MAPNIK );
357 vik_layer_set_defaults ( VIK_LAYER(vml), vvp );
358 vml->tile_size_x = size_default().u; // FUTURE: Is there any use in this being configurable?
360 vml->mi = mapnik_interface_new();
366 * ATM don't have any version issues AFAIK
367 * Tested with carto 0.14.0
369 gboolean carto_load ( VikMapnikLayer *vml, VikViewport *vvp )
371 gchar *mystdout = NULL;
372 gchar *mystderr = NULL;
373 GError *error = NULL;
375 VikLayerParamData *vlpd = a_preferences_get(MAPNIK_PREFS_NAMESPACE"carto");
376 gchar *command = g_strdup_printf ( "%s %s", vlpd->s, vml->filename_css );
378 gboolean answer = TRUE;
379 //gchar *args[2]; args[0] = vlpd->s; args[1] = vml->filename_css;
381 //if ( g_spawn_async_with_pipes ( NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, NULL, &carto_stdout, &carto_error, &error ) ) {
382 // cf code in babel.c to handle stdout
384 // NB Running carto may take several seconds
385 // especially for large style sheets like the default OSM Mapnik style (~6 seconds on my system)
386 VikWindow *vw = VIK_WINDOW(VIK_GTK_WINDOW_FROM_WIDGET(vvp));
388 gchar *msg = g_strdup_printf ( "%s: %s", _("Running"), command );
389 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
390 vik_window_set_busy_cursor ( vw );
395 // You won't get a sensible timing measurement if running too old a GLIB
396 #if GLIB_CHECK_VERSION (2, 28, 0)
397 tt1 = g_get_real_time ();
400 if ( g_spawn_command_line_sync ( command, &mystdout, &mystderr, NULL, &error ) ) {
401 #if GLIB_CHECK_VERSION (2, 28, 0)
402 tt2 = g_get_real_time ();
405 if ( strlen(mystderr) > 1 ) {
406 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp), _("Error running carto command:\n%s"), mystderr );
410 // NB This will overwrite the specified XML file
411 if ( ! ( vml->filename_xml && strlen (vml->filename_xml) > 1 ) ) {
412 // XML Not specified so try to create based on CSS file name
413 GRegex *regex = g_regex_new ( "\\.mml$|\\.mss|\\.css$", G_REGEX_CASELESS, 0, &error );
415 g_critical ("%s: %s", __FUNCTION__, error->message );
416 if ( vml->filename_xml )
417 g_free (vml->filename_xml);
418 vml->filename_xml = g_regex_replace_literal ( regex, vml->filename_css, -1, 0, ".xml", 0, &error );
420 g_warning ("%s: %s", __FUNCTION__, error->message );
421 // Prevent overwriting self
422 if ( !g_strcmp0 ( vml->filename_xml, vml->filename_css ) ) {
423 vml->filename_xml = g_strconcat ( vml->filename_css, ".xml", NULL );
426 if ( !g_file_set_contents (vml->filename_xml, mystdout, -1, &error) ) {
427 g_warning ("%s: %s", __FUNCTION__, error->message );
428 g_error_free (error);
435 g_warning ("%s: %s", __FUNCTION__, error->message );
436 g_error_free (error);
441 gchar *msg = g_strdup_printf ( "%s %s %.1f %s", vlpd->s, _(" completed in "), (gdouble)(tt2-tt1)/G_USEC_PER_SEC, _("seconds") );
442 vik_window_statusbar_update ( vw, msg, VIK_STATUSBAR_INFO );
444 vik_window_clear_busy_cursor ( vw );
449 #if !GLIB_CHECK_VERSION(2,26,0)
450 typedef struct stat GStatBuf;
456 static void mapnik_layer_post_read (VikLayer *vl, VikViewport *vvp, gboolean from_file)
458 VikMapnikLayer *vml = VIK_MAPNIK_LAYER(vl);
460 // Determine if carto needs to be run
461 gboolean do_carto = FALSE;
462 if ( vml->filename_css && strlen(vml->filename_css) > 1 ) {
463 if ( vml->filename_xml && strlen(vml->filename_xml) > 1) {
464 // Compare timestamps
466 if ( g_stat ( vml->filename_xml, &gsb1 ) == 0 ) {
468 if ( g_stat ( vml->filename_css, &gsb2 ) == 0 ) {
469 // Is CSS file newer than the XML file
470 if ( gsb2.st_mtime > gsb1.st_mtime )
473 g_debug ( "No need to run carto" );
477 // XML file doesn't exist
482 // No XML specified thus need to generate
488 // Don't load the XML config if carto load fails
489 if ( !carto_load ( vml, vvp ) )
492 gchar* ans = mapnik_interface_load_map_file ( vml->mi, vml->filename_xml, vml->tile_size_x, vml->tile_size_x );
494 a_dialog_error_msg_extra ( VIK_GTK_WINDOW_FROM_WIDGET(vvp),
495 _("Mapnik error loading configuration file:\n%s"),
502 ui_add_recent_file ( vml->filename_xml );
509 static GdkPixbuf *get_pixbuf ( VikMapnikLayer *vml, MapCoord *ulm, MapCoord *brm )
511 VikCoord ul; VikCoord br;
514 map_utils_iTMS_to_vikcoord (ulm, &ul);
515 map_utils_iTMS_to_vikcoord (brm, &br);
517 pixbuf = a_mapcache_get ( ulm->x, ulm->y, ulm->z, MAP_ID_MAPNIK_RENDER, ulm->scale, vml->alpha, 0.0, 0.0, vml->filename_xml );
520 pixbuf = mapnik_interface_render ( vml->mi, ul.north_south, ul.east_west, br.north_south, br.east_west );
522 // NB Mapnik can apply alpha, but use our own function for now
523 if ( vml->alpha < 255 )
524 pixbuf = ui_pixbuf_set_alpha ( pixbuf, vml->alpha );
525 a_mapcache_add ( pixbuf, ulm->x, ulm->y, ulm->z, MAPNIK_LAYER_MAP_TYPE, ulm->scale, vml->alpha, 0.0, 0.0, vml->filename_xml );
535 static void mapnik_layer_draw ( VikMapnikLayer *vml, VikViewport *vvp )
540 if ( vik_viewport_get_drawmode(vvp) != VIK_VIEWPORT_DRAWMODE_MERCATOR ) {
541 vik_statusbar_set_message ( vik_window_get_statusbar (VIK_WINDOW(VIK_GTK_WINDOW_FROM_LAYER(vml))),
542 VIK_STATUSBAR_INFO, _("Mapnik Rendering must be in Mercator mode") );
547 ul.mode = VIK_COORD_LATLON;
548 br.mode = VIK_COORD_LATLON;
549 vik_viewport_screen_to_coord ( vvp, 0, 0, &ul );
550 vik_viewport_screen_to_coord ( vvp, vik_viewport_get_width(vvp), vik_viewport_get_height(vvp), &br );
552 gdouble xzoom = vik_viewport_get_xmpp ( vvp );
553 gdouble yzoom = vik_viewport_get_ympp ( vvp );
557 if ( map_utils_vikcoord_to_iTMS ( &ul, xzoom, yzoom, &ulm ) &&
558 map_utils_vikcoord_to_iTMS ( &br, xzoom, yzoom, &brm ) ) {
559 // TODO: Understand if tilesize != 256 does this need to use shrinkfactors??
564 gint xmin = MIN(ulm.x, brm.x), xmax = MAX(ulm.x, brm.x);
565 gint ymin = MIN(ulm.y, brm.y), ymax = MAX(ulm.y, brm.y);
567 // Split rendering into a grid for the current viewport
568 // thus each individual 'tile' can then be stored in the map cache
569 // TODO: Also potentially allows using multi threads for each individual tile
570 // this is more important for complicated stylesheets/datasources as each rendering may take some time
571 for (gint x = xmin; x <= xmax; x++ ) {
572 for (gint y = ymin; y <= ymax; y++ ) {
578 pixbuf = get_pixbuf ( vml, &ulm, &brm );
581 map_utils_iTMS_to_vikcoord ( &ulm, &coord );
582 vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
583 vik_viewport_draw_pixbuf ( vvp, pixbuf, 0, 0, xx, yy, vml->tile_size_x, vml->tile_size_x );
588 // Done after so drawn on top
589 // Just a handy guide to tile blocks.
590 if ( vik_debug && vik_verbose ) {
591 GdkGC *black_gc = GTK_WIDGET(vvp)->style->black_gc;
592 gint width = vik_viewport_get_width(vvp);
593 gint height = vik_viewport_get_height(vvp);
595 ulm.x = xmin; ulm.y = ymin;
596 map_utils_iTMS_to_center_vikcoord ( &ulm, &coord );
597 vik_viewport_coord_to_screen ( vvp, &coord, &xx, &yy );
598 xx = xx - (vml->tile_size_x/2);
599 yy = yy - (vml->tile_size_x/2); // Yes use X ATM
600 for (gint x = xmin; x <= xmax; x++ ) {
601 vik_viewport_draw_line ( vvp, black_gc, xx, 0, xx, height );
602 xx += vml->tile_size_x;
604 for (gint y = ymin; y <= ymax; y++ ) {
605 vik_viewport_draw_line ( vvp, black_gc, 0, yy, width, yy );
606 yy += vml->tile_size_x; // Yes use X ATM
615 static void mapnik_layer_free ( VikMapnikLayer *vml )
617 mapnik_interface_free ( vml->mi );
618 if ( vml->filename_css )
619 g_free ( vml->filename_css );
620 if ( vml->filename_xml )
621 g_free ( vml->filename_xml );
624 static VikMapnikLayer *mapnik_layer_create ( VikViewport *vp )
626 return mapnik_layer_new ( vp );
635 typedef gpointer menu_array_values[MA_LAST];
640 static void mapnik_layer_flush_memory ( menu_array_values values )
642 a_mapcache_flush_type ( MAP_ID_MAPNIK_RENDER );
648 static void mapnik_layer_about ( menu_array_values values )
650 VikMapnikLayer *vml = values[MA_VML];
651 gchar *msg = mapnik_interface_about();
652 a_dialog_info_msg ( VIK_GTK_WINDOW_FROM_LAYER(vml), msg );
659 static void mapnik_layer_add_menu_items ( VikMapnikLayer *vml, GtkMenu *menu, gpointer vlp )
661 static menu_array_values values;
662 values[MA_VML] = vml;
663 values[MA_VVP] = vik_layers_panel_get_viewport( VIK_LAYERS_PANEL(vlp) );
665 GtkWidget *item = gtk_menu_item_new();
666 gtk_menu_shell_append ( GTK_MENU_SHELL(menu), item );
667 gtk_widget_show ( item );
669 // Typical users shouldn't need to use this functionality - so debug only ATM
671 item = gtk_image_menu_item_new_with_mnemonic ( _("_Flush Memory Cache") );
672 gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU) );
673 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_flush_memory), values );
674 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
675 gtk_widget_show ( item );
678 item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_ABOUT, NULL );
679 g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(mapnik_layer_about), values );
680 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
681 gtk_widget_show ( item );