Oh, if you are touching it: being able to handle Zip files would be
great. :) Zip files are the common method to transfer an entire
manufacturing batch.
Yeah that would be great as well, but one step at a time. I am
relatively new to the Gtk based development (I am rather familiar with
Qt), so it takes some time to figure out things properly.
BTW I have not really found ZIP file support in glib. If this is true
what library would be the preferred formanaging ZIP files?
I think when opening an already opened layer file a dialog should be
shown with the following options:
- Reload the affected layer(s)
- Open as a new layer
- Skip
Sounds good to me.
I have attached a patch for the following changes:
- merge open menus
- the reload popup mentioned above
- and the drop file support
Any review comment will be highly appericiated! If necessary I can
create separate patches for the features above to ease the review.
Best regards,
Miklos Marton
diff --git a/src/callbacks.c b/src/callbacks.c
index 51ea349..0a6f4e5 100644
--- a/src/callbacks.c
+++ b/src/callbacks.c
@@ -56,6 +56,7 @@
#include "attribute.h"
#include "render.h"
#include "table.h"
+#include "project.h"
#include "selection.h"
#include "draw-gdk.h"
@@ -162,13 +163,8 @@ callbacks_new_project_activate (GtkMenuItem *menuitem, gpointer user_data)
* project file.
*
*/
-void
-callbacks_open_project_activate (GtkMenuItem *menuitem,
- gpointer user_data)
+void open_project(char *project_filename)
{
- gchar *filename=NULL;
- GtkFileFilter * filter;
-
if (mainProject->last_loaded >= 0) {
if (!interface_get_alert_dialog_response (
_("Do you want to close any open layers and load "
@@ -179,66 +175,99 @@ callbacks_open_project_activate (GtkMenuItem *menuitem,
FALSE, NULL, GTK_STOCK_CLOSE, GTK_STOCK_CANCEL))
return;
}
-
- screen.win.gerber =
- gtk_file_chooser_dialog_new (_("Open project file..."),
- NULL,
- GTK_FILE_CHOOSER_ACTION_OPEN,
- GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
- GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
- NULL);
- gtk_file_chooser_set_current_folder ((GtkFileChooser *) screen.win.gerber,
- mainProject->path);
- filter = gtk_file_filter_new();
- gtk_file_filter_set_name(filter, _(gerbv_project_file_name));
- gtk_file_filter_add_pattern(filter, gerbv_project_file_pat);
- gtk_file_chooser_add_filter ((GtkFileChooser *) screen.win.gerber,
- filter);
+ /* update the last folder */
+ g_free (mainProject->path);
+ mainProject->path = project_filename;
- filter = gtk_file_filter_new();
- gtk_file_filter_set_name(filter, _("All"));
- gtk_file_filter_add_pattern(filter, "*");
- gtk_file_chooser_add_filter ((GtkFileChooser *) screen.win.gerber,
- filter);
+ gerbv_unload_all_layers (mainProject);
+ main_open_project_from_filename (mainProject, project_filename);
+}
- gtk_widget_show (screen.win.gerber);
- if (gtk_dialog_run ((GtkDialog*)screen.win.gerber) == GTK_RESPONSE_ACCEPT) {
- filename =
- gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (screen.win.gerber));
- /* update the last folder */
- g_free (mainProject->path);
- mainProject->path = gtk_file_chooser_get_current_folder ((GtkFileChooser *) screen.win.gerber);
+
+/* --------------------------------------------------------- */
+/**
+ * File -> open action requested
+ * or file drop event happened.
+ * Open a layer (or layers) or one gerbv project from the files.
+ * This function will show a question if the layer to be opened
+ * is already open.
+ */
+void open_files(GSList *filenames)
+{
+ char *project_filename = NULL;
+ GSList *filename = NULL;
+ gint fileIndex = 0;
+ gboolean already_loaded = FALSE;
+
+ if (filenames == NULL)
+ return;
+
+ /* Check if there is a gerbv project in the list */
+ /* If there is least open only that and ignore the rest */
+ for (filename=filenames; filename; filename=filename->next) {
+ gboolean is_project = FALSE;
+ if (project_is_gerbv_p(filename->data, &is_project) == 0 && is_project == TRUE) {
+ project_filename = filename->data;
+ break;
+ }
}
- gtk_widget_destroy (screen.win.gerber);
- if (filename) {
- gerbv_unload_all_layers (mainProject);
- main_open_project_from_filename (mainProject, filename);
+ if (project_filename != NULL) {
+ open_project(project_filename);
+ } else {
+ /* Now try to open all gerbers specified */
+ for (filename=filenames; filename; filename=filename->next) {
+ /* First check if the same file already loaded */
+ already_loaded = FALSE;
+
+ for (fileIndex = 0; fileIndex <= mainProject->last_loaded; ++fileIndex) {
+ if ((strlen(filename->data) == strlen(mainProject->file[fileIndex]->fullPathname)) &&
+ g_ascii_strncasecmp (mainProject->file[fileIndex]->fullPathname, filename->data, strlen(filename->data)) == 0) {
+ already_loaded = TRUE;
+ break;
+ }
+ }
+
+ if (already_loaded) {
+ gint answer = interface_reopen_question ((char*)filename->data);
+ if (answer == GTK_RESPONSE_YES) {
+ /* Reload was selected -> reload all loaded layers */
+ for (fileIndex = 0; fileIndex <= mainProject->last_loaded; ++fileIndex) {
+ if ((strlen(filename->data) == strlen(mainProject->file[fileIndex]->fullPathname)) &&
+ g_ascii_strncasecmp (mainProject->file[fileIndex]->fullPathname, filename->data, strlen(filename->data)) == 0) {
+ gerbv_revert_file(mainProject, fileIndex);
+ }
+ }
+ } else if (answer == GTK_RESPONSE_APPLY) {
+ /* Open as a new layer was selected */
+ gerbv_open_layer_from_filename (mainProject, filename->data);
+ }
+ } else {
+ gerbv_open_layer_from_filename (mainProject, filename->data);
+ }
+ }
}
+ g_slist_free(filenames);
+
gerbv_render_zoom_to_fit_display (mainProject, &screenRenderInfo);
render_refresh_rendered_image_on_screen();
callbacks_update_layer_tree();
-
- return;
}
-
/* --------------------------------------------------------- */
/**
- * The file -> open layer menu item was selected. Open a
- * layer (or layers) from a file.
+ * The file -> open action was selected. Open a
+ * layer (or layers) or a project file.
*
*/
void
-callbacks_open_layer_activate (GtkMenuItem *menuitem,
+callbacks_open_activate (GtkMenuItem *menuitem,
gpointer user_data)
{
GSList *filenames=NULL;
- GSList *filename=NULL;
-
screen.win.gerber =
- gtk_file_chooser_dialog_new (_("Open Gerber, drill, or pick & place file(s)..."),
+ gtk_file_chooser_dialog_new (_("Open gerbv project, Gerber, drill, or pick & place file(s)..."),
NULL,
GTK_FILE_CHOOSER_ACTION_OPEN,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
@@ -258,17 +287,7 @@ callbacks_open_layer_activate (GtkMenuItem *menuitem,
}
gtk_widget_destroy (screen.win.gerber);
- /* Now try to open all gerbers specified */
- for (filename=filenames; filename; filename=filename->next) {
- gerbv_open_layer_from_filename (mainProject, filename->data);
- }
- g_slist_free(filenames);
-
- gerbv_render_zoom_to_fit_display (mainProject, &screenRenderInfo);
- render_refresh_rendered_image_on_screen();
- callbacks_update_layer_tree();
-
- return;
+ open_files(filenames);
}
/* --------------------------------------------------------- */
@@ -1833,7 +1852,7 @@ callbacks_get_col_num_from_tree_view_col (GtkTreeViewColumn *col)
/* --------------------------------------------------------- */
void
callbacks_add_layer_button_clicked (GtkButton *button, gpointer user_data) {
- callbacks_open_layer_activate (NULL, NULL);
+ callbacks_open_activate (NULL, NULL);
}
/* --------------------------------------------------------- */
@@ -2366,6 +2385,38 @@ callbacks_change_layer_format_clicked (GtkButton *button, gpointer user_data)
/* --------------------------------------------------------------------------- */
gboolean
+callbacks_file_drop_event (GtkWidget *widget,
+ GdkDragContext *dc,
+ gint x,
+ gint y,
+ GtkSelectionData *data,
+ guint info,
+ guint time,
+ gpointer p)
+{
+ const gchar *urilist = NULL;
+ gchar **uris;
+ gchar **uri;
+ GSList *filenames = NULL;
+
+ urilist = (const gchar *)gtk_selection_data_get_data (data);
+ if (!urilist)
+ return FALSE;
+
+ uris = g_strsplit (urilist, "\r\n", -1);
+
+ for (uri = uris; *uri; uri++) {
+ if (g_strrstr(*uri, "file://") == *uri) {
+ filenames = g_slist_append(filenames, *uri + 7);
+ }
+ }
+ open_files(filenames);
+ g_strfreev (uris);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------- */
+gboolean
callbacks_layer_tree_key_press (GtkWidget *widget, GdkEventKey *event, gpointer user_data) {
/* if space is pressed while a color picker icon is in focus,
show the color picker dialog. */
diff --git a/src/callbacks.h b/src/callbacks.h
index 7043acd..9c44bd7 100644
--- a/src/callbacks.h
+++ b/src/callbacks.h
@@ -52,13 +52,14 @@ callbacks_new_project_activate (GtkMenuItem *menuitem,
gpointer user_data);
void
-callbacks_open_project_activate (GtkMenuItem *menuitem,
- gpointer user_data);
+open_project (char *project_filename);
void
-callbacks_open_layer_activate (GtkMenuItem *menuitem,
+callbacks_open_activate (GtkMenuItem *menuitem,
gpointer user_data);
+void open_files(GSList *filenames);
+
void
callbacks_revert_activate (GtkMenuItem *menuitem,
gpointer user_data);
@@ -222,6 +223,16 @@ gboolean
callbacks_layer_tree_button_press (GtkWidget *widget, GdkEventButton *event,
gpointer user_data);
+gboolean
+callbacks_file_drop_event (GtkWidget *widget,
+ GdkDragContext *dc,
+ gint x,
+ gint y,
+ GtkSelectionData *data,
+ guint info,
+ guint time,
+ gpointer p);
+
void callbacks_add_layer_button_clicked (GtkButton *button, gpointer user_data);
void callbacks_remove_layer_button_clicked (GtkButton *button, gpointer user_data);
diff --git a/src/gerbv.c b/src/gerbv.c
index cd8b8fb..bc52330 100644
--- a/src/gerbv.c
+++ b/src/gerbv.c
@@ -390,13 +390,13 @@ gerbv_add_parsed_image_to_project (gerbv_project_t *gerbvProject, gerbv_image_t
* a new memory before we define anything more.
*/
if (reload) {
- gerbv_destroy_image(gerbvProject->file[idx]->image);
- gerbvProject->file[idx]->image = parsed_image;
- return 0;
+ gerbv_destroy_image(gerbvProject->file[idx]->image);
+ gerbvProject->file[idx]->image = parsed_image;
+ return 0;
} else {
- /* Load new file. */
- gerbvProject->file[idx] = (gerbv_fileinfo_t *) g_new0 (gerbv_fileinfo_t, 1);
- gerbvProject->file[idx]->image = parsed_image;
+ /* Load new file. */
+ gerbvProject->file[idx] = (gerbv_fileinfo_t *) g_new0 (gerbv_fileinfo_t, 1);
+ gerbvProject->file[idx]->image = parsed_image;
}
/*
@@ -454,20 +454,20 @@ gerbv_open_image(gerbv_project_t *gerbvProject, char *filename, int idx, int rel
/* if we don't have enough spots, then grow the file list by 2 to account for the possible
loading of two images for PNP files */
if ((idx+1) >= gerbvProject->max_files) {
- gerbvProject->file = g_renew (gerbv_fileinfo_t *,
- gerbvProject->file, gerbvProject->max_files + 2);
+ gerbvProject->file = g_renew (gerbv_fileinfo_t *,
+ gerbvProject->file, gerbvProject->max_files + 2);
- gerbvProject->file[gerbvProject->max_files] = NULL;
- gerbvProject->file[gerbvProject->max_files+1] = NULL;
- gerbvProject->max_files += 2;
+ gerbvProject->file[gerbvProject->max_files] = NULL;
+ gerbvProject->file[gerbvProject->max_files+1] = NULL;
+ gerbvProject->max_files += 2;
}
dprintf("In open_image, about to try opening filename = %s\n", filename);
fd = gerb_fopen(filename);
if (fd == NULL) {
- GERB_MESSAGE(_("Trying to open %s: %s"), filename, strerror(errno));
- return -1;
+ GERB_MESSAGE(_("Trying to open %s: %s"), filename, strerror(errno));
+ return -1;
}
/* Store filename info fd for further use */
diff --git a/src/interface.c b/src/interface.c
index 935eb9b..3cdf16a 100644
--- a/src/interface.c
+++ b/src/interface.c
@@ -139,8 +139,7 @@ interface_create_gui (int req_width, int req_height)
GtkWidget *menuitem_file;
GtkWidget *menuitem_file_menu;
GtkWidget *new_project;
- GtkWidget *open_project;
- GtkWidget *open_layer;
+ GtkWidget *open;
GtkWidget *revert;
GtkWidget *save;
GtkWidget *save_as;
@@ -307,6 +306,11 @@ interface_create_gui (int req_width, int req_height)
GtkWidget *tempImage;
gchar str_coord[MAX_COORDLEN];
+
+ static GtkTargetEntry dragTargetentries[] =
+ {
+ { "text/uri-list", 0, 1 },
+ };
pointerpixbuf = gdk_pixbuf_new_from_inline(-1, pointer, FALSE, NULL);
movepixbuf = gdk_pixbuf_new_from_inline(-1, move, FALSE, NULL);
@@ -325,6 +329,14 @@ interface_create_gui (int req_width, int req_height)
menubar1 = gtk_menu_bar_new ();
gtk_box_pack_start (GTK_BOX (vbox1), menubar1, FALSE, FALSE, 0);
+ gtk_drag_dest_set (mainWindow,
+ GTK_DEST_DEFAULT_ALL,
+ dragTargetentries,
+ 1,
+ GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK);
+ gtk_signal_connect(GTK_OBJECT(mainWindow), "drag-data-received",
+ GTK_SIGNAL_FUNC(callbacks_file_drop_event), NULL);
+
/* --- File menu --- */
menuitem_file = gtk_menu_item_new_with_mnemonic (_("_File"));
gtk_container_add (GTK_CONTAINER (menubar1), menuitem_file);
@@ -336,10 +348,10 @@ interface_create_gui (int req_width, int req_height)
/* File menu items dealing individual layers. */
- open_layer = gtk_menu_item_new_with_mnemonic (_("Open _layer(s)..."));
- SET_ACCELS (open_layer, ACCEL_FILE_OPEN_LAYER);
- gtk_container_add (GTK_CONTAINER (menuitem_file_menu), open_layer);
- gtk_tooltips_set_tip (tooltips, open_layer, _("Open Gerber, drill, or pick and place file(s)"), NULL);
+ open = gtk_menu_item_new_with_mnemonic (_("Open"));
+ SET_ACCELS (open, ACCEL_FILE_OPEN_LAYER);
+ gtk_container_add (GTK_CONTAINER (menuitem_file_menu), open);
+ gtk_tooltips_set_tip (tooltips, open, _("Open gerbv project, Gerber, drill, or pick and place file(s)"), NULL);
save_layer = gtk_menu_item_new_with_mnemonic (_("_Save active layer"));
screen.win.curFileMenuItem[0] = save_layer;
@@ -423,12 +435,6 @@ interface_create_gui (int req_width, int req_height)
tempImage = gtk_image_new_from_stock (GTK_STOCK_NEW, GTK_ICON_SIZE_MENU);
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (new_project), tempImage);
- open_project = gtk_image_menu_item_new_with_mnemonic (_("_Open project..."));
- gtk_container_add (GTK_CONTAINER (menuitem_file_menu), open_project);
- gtk_tooltips_set_tip (tooltips, open_project, _("Open an existing Gerbv project"), NULL);
- tempImage = gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU);
- gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (open_project), tempImage);
-
save = gtk_image_menu_item_new_with_mnemonic (_("Save project"));
screen.win.curFileMenuItem[4] = save;
gtk_tooltips_set_tip (tooltips, save, _("Save the current project"), NULL);
@@ -1170,11 +1176,8 @@ interface_create_gui (int req_width, int req_height)
g_signal_connect ((gpointer) new_project, "activate",
G_CALLBACK (callbacks_new_project_activate),
NULL);
- g_signal_connect ((gpointer) open_project, "activate",
- G_CALLBACK (callbacks_open_project_activate),
- NULL);
- g_signal_connect ((gpointer) open_layer, "activate",
- G_CALLBACK (callbacks_open_layer_activate),
+ g_signal_connect ((gpointer) open, "activate",
+ G_CALLBACK (callbacks_open_activate),
NULL);
g_signal_connect ((gpointer) revert, "activate",
G_CALLBACK (callbacks_revert_activate),
@@ -1380,7 +1383,7 @@ interface_create_gui (int req_width, int req_height)
G_CALLBACK (callbacks_save_project_activate),
NULL);
g_signal_connect ((gpointer) toolbutton_open, "clicked",
- G_CALLBACK (callbacks_open_project_activate),
+ G_CALLBACK (callbacks_open_activate),
NULL);
g_signal_connect ((gpointer) toolbutton_revert, "clicked",
G_CALLBACK (callbacks_revert_activate),
@@ -1784,6 +1787,80 @@ interface_get_alert_dialog_response (const gchar *primaryText,
/* ---------------------------------------------------- */
/**
+ * This dialog box shows a textmessage with three buttons in the case
+ * if the file to be open was already loaded:
+ * "Reload" (the already loaded file)
+ * "Open as new layer"
+ * "Skip loading"
+ */
+int
+interface_reopen_question(const gchar *filename)
+{
+ GtkWidget *dialog1;
+ GtkWidget *dialog_vbox1;
+ GtkWidget *hbox1;
+ GtkWidget *image1;
+ GtkWidget *label1;
+ GtkWidget *dialog_action_area1;
+ GtkWidget *reloadButton, *openAsNewButton, *skipButton;
+ gint ret = 0;
+ GString *title = g_string_new(NULL);
+ g_string_printf (title, _("The %s layer file is already loaded"), filename);
+
+ dialog1 = gtk_dialog_new ();
+ gtk_container_set_border_width (GTK_CONTAINER (dialog1), 6);
+ gtk_window_set_resizable (GTK_WINDOW (dialog1), FALSE);
+ gtk_window_set_type_hint (GTK_WINDOW (dialog1), GDK_WINDOW_TYPE_HINT_DIALOG);
+ gtk_dialog_set_has_separator (GTK_DIALOG (dialog1), FALSE);
+
+ dialog_vbox1 = GTK_DIALOG (dialog1)->vbox;
+
+ hbox1 = gtk_hbox_new (FALSE, 12);
+ gtk_box_pack_start (GTK_BOX (dialog_vbox1), hbox1, TRUE, TRUE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox1), 6);
+
+ image1 = gtk_image_new_from_icon_name (GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_DIALOG);
+ gtk_box_pack_start (GTK_BOX (hbox1), image1, TRUE, TRUE, 0);
+ gtk_misc_set_alignment (GTK_MISC (image1), 0.5, 0);
+
+ gchar *labelMessage = g_strconcat ("<span weight=\"bold\" size=\"larger\">", title->str,
+ "</span>\n<span/>\n", NULL);
+ label1 = gtk_label_new (labelMessage);
+ g_free (labelMessage);
+
+ GtkWidget *vbox9 = gtk_vbox_new (FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (vbox9), label1, FALSE, FALSE, 0);
+ gtk_label_set_use_markup (GTK_LABEL (label1), TRUE);
+ gtk_label_set_line_wrap (GTK_LABEL (label1), TRUE);
+ gtk_box_pack_start (GTK_BOX (hbox1), vbox9, FALSE, FALSE, 0);
+
+ GtkWidget *hbox2 = gtk_hbox_new (FALSE, 12);
+ gtk_box_pack_start (GTK_BOX (vbox9), hbox2, FALSE, FALSE, 12);
+
+ dialog_action_area1 = GTK_DIALOG (dialog1)->action_area;
+ gtk_button_box_set_layout (GTK_BUTTON_BOX (dialog_action_area1), GTK_BUTTONBOX_END);
+
+ reloadButton = gtk_button_new_with_mnemonic (_("Reload"));
+ gtk_dialog_add_action_widget (GTK_DIALOG (dialog1), reloadButton, GTK_RESPONSE_YES);
+ GTK_WIDGET_SET_FLAGS (reloadButton, GTK_CAN_DEFAULT);
+
+ openAsNewButton = gtk_button_new_with_mnemonic (_("Open as new layer"));
+ gtk_dialog_add_action_widget (GTK_DIALOG (dialog1), openAsNewButton, GTK_RESPONSE_APPLY);
+
+ skipButton = gtk_button_new_with_mnemonic (_("Skip loading"));
+ gtk_dialog_add_action_widget (GTK_DIALOG (dialog1), skipButton, GTK_RESPONSE_CANCEL);
+
+ gtk_widget_show_all (dialog1);
+
+ ret = gtk_dialog_run (GTK_DIALOG(dialog1));
+ gtk_widget_destroy (dialog1);
+
+ return ret;
+}
+
+
+/* ---------------------------------------------------- */
+/**
* This dialog box shows a textmessage and one button:
* "OK". It does not return anything.
*
diff --git a/src/interface.h b/src/interface.h
index 2d95172..e26c387 100644
--- a/src/interface.h
+++ b/src/interface.h
@@ -187,6 +187,9 @@ interface_get_alert_dialog_response (const gchar *primaryText,
const gchar *true_button_label,
const gchar *false_button_label);
+int
+interface_reopen_question (const gchar *filename);
+
void
interface_show_alert_dialog (gchar *primaryText,
gchar *secondaryText,
diff --git a/src/project.c b/src/project.c
index 8119044..6e410e2 100644
--- a/src/project.c
+++ b/src/project.c
@@ -56,6 +56,7 @@
#endif
#include <errno.h>
+#include <glib/gstdio.h>
#include <locale.h>
#include <math.h>
@@ -92,6 +93,7 @@
*/
#define GERBV_DEFAULT_PROJECT_FILE_VERSION "1.9A"
+#define MAXL 200
/*
* List of versions that we can load with this version of
* gerbv
@@ -894,6 +896,35 @@ gerbv_file_version(scheme *sc, pointer args)
return sc->NIL;
} /* gerbv_file_version */
+/** Checks whether the supplied file look like a gerbv project by
+ * reading the first line and checking if it contains gerbv-file-version
+ *
+ * Returns 0 on success -1 on open error
+ */
+int
+project_is_gerbv_p(const char *filename, gboolean *ret)
+{
+ FILE *fd;
+ *ret = FALSE;
+ char *buf;
+
+ fd = g_fopen(filename, "rb");
+ if (fd == NULL) {
+ GERB_MESSAGE(_("Failed to open %s for reading: %s"), filename, strerror(errno));
+ return -1;
+ }
+
+ buf = (char *) g_malloc(MAXL);
+ if (buf == NULL)
+ GERB_FATAL_ERROR(_("malloc buf failed while checking for gerbv project"));
+
+ if (fgets(buf, MAXL, fd) != NULL) {
+ *ret = (g_strrstr(buf, "gerbv-file-version") != NULL);
+ }
+ fclose(fd);
+ free(buf);
+ return 0;
+}
/** Reads the content of a project file.
* Global:\n
diff --git a/src/project.h b/src/project.h
index 3ffaa7d..1ad3642 100644
--- a/src/project.h
+++ b/src/project.h
@@ -56,6 +56,7 @@ enum conv_type {
UNIX_MINGW = 1
};
+int project_is_gerbv_p(const char *filename, gboolean *ret);
/*
* Reads a project from a file and returns a linked list describing the project
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Gerbv-devel mailing list
Gerbv-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/gerbv-devel