void open_files(GSList *filenames)
{
	GSList *fns = NULL;		/* File names to ask */
	GSList *fns_is_mod = NULL;	/* File name layer is modified */
	GSList *fns_cnt = NULL;		/* File names count */
	GSList *fns_lay_num = NULL;	/* Layer number for fns */
	GSList *cnt = NULL;		/* File names count unsorted by layers,
					   0 -- file not yet loaded as layer */
	gint answer;

	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 (GSList *fn = filenames; fn; fn = fn->next) {
		gboolean is_project = FALSE;
		if (0 == project_is_gerbv_project(fn->data, &is_project)
		&&  is_project) {
			open_project(fn->data);

			gerbv_render_zoom_to_fit_display(mainProject,
					&screenRenderInfo);
			render_refresh_rendered_image_on_screen();
			callbacks_update_layer_tree();

			return;
		}
	}

	/* Count opened filenames and place result in list */
	for (GSList *fn = filenames; fn; fn = fn->next) {
		gint c = 0;

		for (gint fidx = 0; fidx <= mainProject->last_loaded; ++fidx) {
			gchar *fpn = mainProject->file[fidx]->fullPathname;

			if (strlen(fpn) == strlen(fn->data)
			&&  0 == g_ascii_strncasecmp(fpn, fn->data,
						strlen(fn->data))) {
				c++;
			}
		}

		cnt = g_slist_append(cnt, GINT_TO_POINTER(c));
	}

	/* Make fns, fns_is_mod and fns_cnt lists sorted by layers */
	for (gint fidx = 0; fidx <= mainProject->last_loaded; ++fidx) {
		gchar *fpn = mainProject->file[fidx]->fullPathname;

		for (GSList *fn = filenames; fn; fn = fn->next) {
			if (strlen(fpn) == strlen(fn->data)
			&&  0 == g_ascii_strncasecmp(fpn, fn->data,
					strlen(fn->data))) {
				fns = g_slist_append(fns, fn->data);
				fns_is_mod = g_slist_append(fns_is_mod,
						GINT_TO_POINTER(mainProject->
							file[fidx]->
							layer_dirty));
				fns_cnt = g_slist_append(fns_cnt,
						g_slist_nth_data(cnt,
							g_slist_position(
								filenames,
								fn)));
				fns_lay_num = g_slist_append(fns_lay_num,
						GINT_TO_POINTER(fidx));

				break;
			}
		}
	}

	answer = GTK_RESPONSE_NONE;
	if (g_slist_length(fns) > 0)
		answer = interface_reopen_question(fns, fns_is_mod,
							fns_cnt, fns_lay_num);

	switch (answer) {

	case GTK_RESPONSE_CANCEL:
	case GTK_RESPONSE_NONE:
	case GTK_RESPONSE_DELETE_EVENT:
		/* Dialog is closed or Esc is pressed, skip all */
		break;

	case GTK_RESPONSE_YES: /* Reload layer was selected */
		for (GSList *fn = fns; fn; fn = fn->next) {
			if (fn->data != NULL)
				gerbv_revert_file(mainProject,
					GPOINTER_TO_INT(
						g_slist_nth_data (fns_lay_num,
							g_slist_position (fns,
									fn))));
		}
		break;

	case GTK_RESPONSE_OK: /* Open as a new layer was selected */
		/* To open as new only _one_ instance of file, check filenames
		 * by selected files in fns */
		for (GSList *fn = filenames; fn; fn = fn->next) {
			if (NULL != g_slist_find (fns, fn->data))
				gerbv_open_layer_from_filename(mainProject,
								fn->data);
		}
		break;
	}

	/* Add not loaded files (cnt == 0) in the end */
	for (GSList *fn = filenames; fn; fn = fn->next) {
		if (0 == GPOINTER_TO_INT(g_slist_nth_data(cnt,
					g_slist_position(filenames, fn))))
			gerbv_open_layer_from_filename (mainProject, fn->data);
	}

	g_slist_free(fns);
	g_slist_free(fns_is_mod);
	g_slist_free(fns_cnt);
	g_slist_free(fns_lay_num);
	g_slist_free(cnt);

	gerbv_render_zoom_to_fit_display (mainProject, &screenRenderInfo);
	render_refresh_rendered_image_on_screen();
	callbacks_update_layer_tree();
}


Remove local alias to parameter array

Normalizing access to `gerbv_simplified_amacro_t::parameter` as a step to fix CVE-2021-40402

callbacks_update_statusbar_measured_distance (gdouble dx, gdouble dy){
	gdouble delta = hypot(dx, dy);

	if (screen.unit == GERBV_MILS) {
	    utf8_snprintf(screen.statusbar.diststr, MAX_DISTLEN,
		     _("Measured distance: %8.2f mils (%8.2f x, %8.2f y)"),
		     COORD2MILS(delta), COORD2MILS(dx), COORD2MILS(dy));
	}
	else if (screen.unit == GERBV_MMS) {
	    utf8_snprintf(screen.statusbar.diststr, MAX_DISTLEN,
		     _("Measured distance: %8.3f mm (%8.3f x, %8.3f y)"),
		     COORD2MMS(delta), COORD2MMS(dx), COORD2MMS(dy));
	}
	else {
	    utf8_snprintf(screen.statusbar.diststr, MAX_DISTLEN,
		     _("Measured distance: %4.5f inches (%4.5f x, %4.5f y)"),
		     COORD2INS(delta), COORD2INS(dx), COORD2INS(dy));
	}
	callbacks_update_statusbar();
}


callbacks_drawingarea_expose_event (GtkWidget *widget, GdkEventExpose *event)
{
	if (screenRenderInfo.renderType <= GERBV_RENDER_TYPE_GDK_XOR) {
		GdkPixmap *new_pixmap;
		GdkGC *gc = gdk_gc_new(widget->window);

		/*
		* Create a pixmap with default background
		*/
		new_pixmap = gdk_pixmap_new(widget->window,
					widget->allocation.width,
					widget->allocation.height,
					-1);

		gdk_gc_set_foreground(gc, &mainProject->background);

		gdk_draw_rectangle(new_pixmap, gc, TRUE,
			       event->area.x, event->area.y,
			       event->area.width, event->area.height);

		/*
		* Copy gerber pixmap onto background if we have one to copy.
		* Do translation at the same time.
		*/
		if (screen.pixmap != NULL) {
		gdk_draw_pixmap(new_pixmap,
				widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
				screen.pixmap,
				event->area.x - screen.off_x,
				event->area.y - screen.off_y,
				event->area.x, event->area.y,
				event->area.width, event->area.height);
		}

		/*
		* Draw the whole thing onto screen
		*/
		gdk_draw_pixmap(widget->window,
			    widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
			    new_pixmap,
			    event->area.x, event->area.y,
			    event->area.x, event->area.y,
			    event->area.width, event->area.height);

		gdk_pixmap_unref(new_pixmap);
		gdk_gc_unref(gc);

		/*
		* Draw Zooming outline if we are in that mode
		*/
		if (screen.state == IN_ZOOM_OUTLINE) {
			render_draw_zoom_outline(screen.centered_outline_zoom);
		}
		else if (screen.state == IN_MEASURE) {
			render_draw_measure_distance();
		}
		if (screen.tool == MEASURE && screen.state != IN_MEASURE) {
			render_toggle_measure_line();
		}

		return FALSE;
	}

	cairo_t *cr;
	int width, height;
	int x_off=0, y_off=0;
	GdkDrawable *drawable = widget->window;

	if (GDK_IS_WINDOW(widget->window)) {
	      /* query the window's backbuffer if it has one */
		GdkWindow *window = GDK_WINDOW(widget->window);
	      gdk_window_get_internal_paint_info (window,
	                                          &drawable, &x_off, &y_off);
	}
	gdk_drawable_get_size (drawable, &width, &height);

#if defined(WIN32) || defined(QUARTZ)
	/* FIXME */
	cr = gdk_cairo_create (GDK_WINDOW(widget->window));
#else
	cairo_surface_t *buffert;

	GdkVisual *visual = gdk_drawable_get_visual (drawable);
	buffert = (gpointer) cairo_xlib_surface_create (GDK_DRAWABLE_XDISPLAY (drawable),
	                                          GDK_DRAWABLE_XID (drawable),
	                                          GDK_VISUAL_XVISUAL (visual),
	                                          event->area.width, event->area.height);
	cr = cairo_create (buffert);
#endif
	cairo_translate (cr, -event->area.x + screen.off_x, -event->area.y + screen.off_y);
	render_project_to_cairo_target (cr);
	cairo_destroy (cr);
#if !defined(WIN32) && !defined(QUARTZ)
	cairo_surface_destroy (buffert);
#endif

	if (screen.tool == MEASURE)
		render_toggle_measure_line();
	return FALSE;
}


callbacks_change_background_color_clicked  (GtkButton *button, gpointer   user_data) {
	callbacks_show_color_picker_dialog (-1);
}


void callbacks_force_expose_event_for_screen (void)
{
	GdkRectangle update_rect;

	update_rect.x = 0;
	update_rect.y = 0;
	update_rect.width = screenRenderInfo.displayWidth;
	update_rect.height = screenRenderInfo.displayHeight;

	/* Calls expose_event */
	gdk_window_invalidate_rect (screen.drawing_area->window, &update_rect, FALSE);

	/* update other gui things that could have changed */
	callbacks_update_ruler_scales ();
	callbacks_update_scrollbar_limits ();
	callbacks_update_scrollbar_positions ();
}


callbacks_viewmenu_units_changed (GtkCheckMenuItem *widget, gpointer user_data) {
	gerbv_gui_unit_t unit = GPOINTER_TO_INT(user_data);

	if (unit < 0 || unit == screen.unit)
		return;

	dprintf ("%s():  unit = %d, screen.unit = %d\n", __FUNCTION__, unit, screen.unit);

	callbacks_units_changed (unit);
}


callbacks_show_selection_on_invisible (GtkMenuItem *menuitem, gpointer user_data)
{
	mainProject->show_invisible_selection = GTK_CHECK_MENU_ITEM(menuitem)->active;
	render_refresh_rendered_image_on_screen();
}


callbacks_drawingarea_button_press_event (GtkWidget *widget, GdkEventButton *event)
{
	GdkWindow *drawing_area_window = screen.drawing_area->window;
	GdkCursor *cursor;

	switch (event->button) {
		case 1 :
			if (screen.tool == POINTER) {
				/* select */
				/* selection will only work with cairo, so do nothing if it's
				   not compiled */
				screen.state = IN_SELECTION_DRAG;
				screen.start_x = event->x;
				screen.start_y = event->y;
			}
			else if (screen.tool == PAN) {
				/* Plain panning */
				screen.state = IN_MOVE;
				screen.last_x = event->x;
				screen.last_y = event->y;
			}
			else if (screen.tool == ZOOM) {
				screen.state = IN_ZOOM_OUTLINE;
				/* Zoom outline mode initiated */
				screen.start_x = event->x;
				screen.start_y = event->y;
				screen.centered_outline_zoom = event->state;
			}
			else if (screen.tool == MEASURE) {
				screen.state = IN_MEASURE;
				callbacks_screen2board(&(screen.measure_start_x), &(screen.measure_start_y),
								event->x, event->y);
				screen.measure_stop_x = screen.measure_start_x;
				screen.measure_stop_y = screen.measure_start_y;
				/* force an expose event to clear any previous measure lines */
				callbacks_force_expose_event_for_screen ();
			}
			break;
		case 2 :
			screen.state = IN_MOVE;
			screen.last_x = event->x;
			screen.last_y = event->y;
			cursor = gdk_cursor_new(GDK_FLEUR);
			gdk_window_set_cursor(drawing_area_window, cursor);
			gdk_cursor_destroy(cursor);
			break;
		case 3 :
			if (screen.tool == POINTER) {
				/* if no items are selected, try and find the item the user
				   is pointing at */
				if (selection_length (&screen.selectionInfo) == 0) {
					gint index=callbacks_get_selected_row_index();
					if ((index >= 0) &&
					    (index <= mainProject->last_loaded) &&
					    (mainProject->file[index]->isVisible)) {
					  render_fill_selection_buffer_from_mouse_click(
							  event->x, event->y,
							  index, SELECTION_REPLACE);
					} else {
					    selection_clear (&screen.selectionInfo);
					    update_selected_object_message (FALSE);
					    render_refresh_rendered_image_on_screen ();
					}
				}

				/* only show the popup if we actually have something selected now */
				if (selection_length (&screen.selectionInfo) != 0) {
					update_selected_object_message (TRUE);
					gtk_menu_popup(GTK_MENU(screen.win.drawWindowPopupMenu), NULL, NULL, NULL, NULL,
							event->button, event->time);
				}

			} else {
				/* Zoom outline mode initiated */
				screen.state = IN_ZOOM_OUTLINE;
				screen.start_x = event->x;
				screen.start_y = event->y;
				screen.centered_outline_zoom = event->state & GDK_SHIFT_MASK;
				cursor = gdk_cursor_new(GDK_SIZING);
				gdk_window_set_cursor(drawing_area_window, cursor);
				gdk_cursor_destroy(cursor);
			}
			break;
		case 4 : /* Scroll wheel */
			render_zoom_display (ZOOM_IN_CMOUSE, 0, event->x, event->y);
			break;
		case 5 :  /* Scroll wheel */
			render_zoom_display (ZOOM_OUT_CMOUSE, 0, event->x, event->y);
			break;
		default:
			break;
	}
	callbacks_switch_to_correct_cursor ();
	return TRUE;
}


static void show_no_layers_warning (void) {
	gchar *str = g_new(gchar, MAX_DISTLEN);
	utf8_strncpy(str, _("No layers are currently loaded. A layer must be loaded first."), MAX_DISTLEN - 7);
	utf8_snprintf(screen.statusbar.diststr, MAX_DISTLEN, "<b>%s</b>", str);
	g_free(str);

	callbacks_update_statusbar();
}


callbacks_move_layer_up_menu_activate (GtkMenuItem *menuitem, gpointer user_data) {
	callbacks_move_layer_up_button_clicked (NULL, NULL);
	gtk_widget_grab_focus (screen.win.layerTree);
}


callbacks_get_col_num_from_tree_view_col (GtkTreeViewColumn *col)
{
	GList *cols;
	gint   num;

	g_return_val_if_fail ( col != NULL, -1 );
	g_return_val_if_fail ( col->tree_view != NULL, -1 );
	cols = gtk_tree_view_get_columns(GTK_TREE_VIEW(col->tree_view));
	num = g_list_index(cols, (gpointer) col);
	g_list_free(cols);
	return num;
}


void utf8_snprintf(gchar *dst, gsize byte_len, const gchar *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	gchar *str = g_strdup_vprintf(fmt, ap);
	va_end(ap);
	utf8_strncpy(dst, str, byte_len);
	g_free(str);
}


callbacks_reload_layer_clicked (GtkButton *button, gpointer user_data)
{
	gint index = callbacks_get_selected_row_index ();

	if (index < 0) {
		show_no_layers_warning ();
		return;
	}

	render_remove_selected_objects_belonging_to_layer (
			&screen.selectionInfo, mainProject->file[index]->image);
	update_selected_object_message (FALSE);

	gerbv_revert_file (mainProject, index);
	render_refresh_rendered_image_on_screen ();
	callbacks_update_layer_tree();
}


callbacks_control_gerber_options_activate     (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{

}


callbacks_analyze_active_gerbers_activate(GtkMenuItem *menuitem,
					gpointer user_data)
{
	gerbv_aperture_list_t *aperture_list;
	gchar *str;
	int i;

	/* Get a report of stats & errors accumulated from all layers */
	gerbv_stats_t *stat = generate_gerber_analysis();

	/* General info report */
	GString *general_report_str = g_string_new(NULL);
	if (stat->layer_count == 0) {
		g_string_printf(general_report_str,
				_("No Gerber layers visible!"));
	} else {
		if (stat->error_list->error_text == NULL) {
			g_string_printf(general_report_str,
				ngettext("No errors found in %d visible "
					"Gerber layer.",
					"No errors found in %d visible "
					"Gerber layers.",
					stat->layer_count),
				stat->layer_count);
		} else {
			g_string_printf(general_report_str,
				ngettext("Found errors in %d visible "
					"Gerber layer.",
					"Found errors in %d visible "
					"Gerber layers.",
					stat->layer_count),
				stat->layer_count);
		}
	}

	GtkWidget *general_label = gtk_label_new(general_report_str->str);
	g_string_free(general_report_str, TRUE);
	gtk_misc_set_alignment(GTK_MISC(general_label), 0, 0);
	gtk_misc_set_padding(GTK_MISC(general_label), 7, 7);
	gtk_label_set_selectable(GTK_LABEL(general_label), TRUE);

	struct table *general_table;

	general_table = table_new_with_columns(3,
			_("Layer"), G_TYPE_UINT, _("Type"), G_TYPE_STRING,
			_("Description"), G_TYPE_STRING);
	table_set_column_align(general_table, 0, 1.0);

	for (i = 0; i <= mainProject->last_loaded; i++) {
		gerbv_fileinfo_t **files = mainProject->file;

		if (!files[i]
		||  !files[i]->isVisible
		||   files[i]->image->layertype != GERBV_LAYERTYPE_RS274X)
			continue;

		table_add_row(general_table, i + 1,
				_("RS-274X file"), files[i]->fullPathname);

		str = g_strdup_printf(_("%g x %g %s"),
				screen_units(fabs(files[i]->image->info->max_x -
						files[i]->image->info->min_x)),
				screen_units(fabs(files[i]->image->info->max_y -
						files[i]->image->info->min_y)),
				screen_units_str());
		table_add_row(general_table, i + 1, _("Bounding size"), str);
		g_free(str);

		/* Check error report on layer */
		if (stat->layer_count > 0
		&&  stat->error_list->error_text != NULL) {
			for (gerbv_error_list_t *err_list = stat->error_list;
					err_list != NULL;
					err_list = err_list->next) {

				if (i != err_list->layer - 1)
					continue;

				table_add_row(general_table, err_list->layer,
					error_type_string(err_list->type),
					err_list->error_text);
			}
		}
	}

	/* G codes on active layers */
	GtkWidget *G_report_window = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(G_report_window),
			GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

	struct table *G_table =
		table_new_with_columns(3, _("Code"), G_TYPE_STRING,
			pgettext("table", "Count"), G_TYPE_UINT,
			_("Note"), G_TYPE_STRING);
	table_set_column_align(G_table, 0, 1.0);
	table_set_column_align(G_table, 1, 1.0);
	gtk_tree_view_set_headers_clickable(
			GTK_TREE_VIEW(G_table->widget), TRUE);

	table_add_row(G_table, "G00", stat->G0,  _(gerber_g_code_name(0)));
	table_add_row(G_table, "G01", stat->G1,  _(gerber_g_code_name(1)));
	table_add_row(G_table, "G02", stat->G2,  _(gerber_g_code_name(2)));
	table_add_row(G_table, "G03", stat->G3,  _(gerber_g_code_name(3)));
	table_add_row(G_table, "G04", stat->G4,  _(gerber_g_code_name(4)));
	table_add_row(G_table, "G10", stat->G10, _(gerber_g_code_name(10)));
	table_add_row(G_table, "G11", stat->G11, _(gerber_g_code_name(11)));
	table_add_row(G_table, "G12", stat->G12, _(gerber_g_code_name(12)));
	table_add_row(G_table, "G36", stat->G36, _(gerber_g_code_name(36)));
	table_add_row(G_table, "G37", stat->G37, _(gerber_g_code_name(37)));
	table_add_row(G_table, "G54", stat->G54, _(gerber_g_code_name(54)));
	table_add_row(G_table, "G55", stat->G55, _(gerber_g_code_name(55)));
	table_add_row(G_table, "G70", stat->G70, _(gerber_g_code_name(70)));
	table_add_row(G_table, "G71", stat->G71, _(gerber_g_code_name(71)));
	table_add_row(G_table, "G74", stat->G74, _(gerber_g_code_name(74)));
	table_add_row(G_table, "G75", stat->G75, _(gerber_g_code_name(75)));
	table_add_row(G_table, "G90", stat->G90, _(gerber_g_code_name(90)));
	table_add_row(G_table, "G91", stat->G91, _(gerber_g_code_name(91)));
	table_add_row(G_table, "", stat->G_unknown, _("unknown G-codes"));

	table_set_sortable(G_table);
	gtk_container_add(GTK_CONTAINER(G_report_window), G_table->widget);

	/* D codes on active layers */
	GtkWidget *D_report_window = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(D_report_window),
			GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

	struct table *D_table =
		table_new_with_columns(3, _("Code"), G_TYPE_STRING,
			pgettext("table", "Count"), G_TYPE_UINT,
			_("Note"), G_TYPE_STRING);
	table_set_column_align(D_table, 0, 1.0);
	table_set_column_align(D_table, 1, 1.0);
	gtk_tree_view_set_headers_clickable(
			GTK_TREE_VIEW(D_table->widget), TRUE);
	table_add_row(D_table, "D01", stat->D1, _(gerber_d_code_name(1)));
	table_add_row(D_table, "D02", stat->D2, _(gerber_d_code_name(2)));
	table_add_row(D_table, "D03", stat->D3, _(gerber_d_code_name(3)));
	table_add_row(D_table, "", stat->D_unknown, _("unknown D-codes"));
	table_add_row(D_table, "", stat->D_error, _("D-code errors"));

	table_set_sortable(D_table);
	gtk_container_add(GTK_CONTAINER(D_report_window), D_table->widget);

	/* M codes on active layers */
	GtkWidget *M_report_window = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(M_report_window),
			GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

	struct table *M_table =
		table_new_with_columns(3, _("Code"), G_TYPE_STRING,
			pgettext("table", "Count"), G_TYPE_UINT,
			_("Note"), G_TYPE_STRING);
	table_set_column_align(M_table, 0, 1.0);
	table_set_column_align(M_table, 1, 1.0);
	gtk_tree_view_set_headers_clickable(
			GTK_TREE_VIEW(M_table->widget), TRUE);
	table_add_row(M_table, "M00", stat->M0, _(gerber_m_code_name(0)));
	table_add_row(M_table, "M01", stat->M1, _(gerber_m_code_name(1)));
	table_add_row(M_table, "M02", stat->M2, _(gerber_m_code_name(2)));
	table_add_row(M_table, "", stat->M_unknown, _("unknown M-codes"));

	table_set_sortable(M_table);
	gtk_container_add(GTK_CONTAINER(M_report_window), M_table->widget);

	/* Misc codes */
	GtkWidget *misc_report_window = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(misc_report_window),
			GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

	struct table *misc_table =
		table_new_with_columns(2, _("Code"), G_TYPE_STRING,
				pgettext("table", "Count"), G_TYPE_UINT);
	table_set_column_align(misc_table, 1, 1.0);
	gtk_tree_view_set_headers_clickable(
			GTK_TREE_VIEW(misc_table->widget), TRUE);
	table_add_row(misc_table, "X", stat->X);
	table_add_row(misc_table, "Y", stat->Y);
	table_add_row(misc_table, "I", stat->I);
	table_add_row(misc_table, "J", stat->J);
	table_add_row(misc_table, "*", stat->star);
	table_add_row(misc_table, _("Unknown"), stat->unknown);

	table_set_sortable(misc_table);
	gtk_container_add(GTK_CONTAINER(misc_report_window),
			misc_table->widget);

	/* Apertures definition in input files */
	GtkWidget *aperture_def_report_window =
		gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(
			GTK_SCROLLED_WINDOW(aperture_def_report_window),
			GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

	if (stat->aperture_list->number == -1) {
		GtkWidget *aperture_def_label = gtk_label_new(
			_("No aperture definitions found in active Gerber file(s)!"));
		gtk_misc_set_alignment(GTK_MISC(aperture_def_label), 0, 0);
		gtk_misc_set_padding(GTK_MISC(aperture_def_label), 7, 7);
		gtk_label_set_selectable(GTK_LABEL(aperture_def_label), TRUE);
		gtk_scrolled_window_add_with_viewport(
				GTK_SCROLLED_WINDOW(aperture_def_report_window),
				aperture_def_label);
	} else {
		struct table *aperture_def_table = table_new_with_columns(6,
				_("Layer"), G_TYPE_UINT,
				_("D code"), G_TYPE_STRING,
				_("Aperture"), G_TYPE_STRING,
				_("Param[0]"), G_TYPE_DOUBLE,
				_("Param[1]"), G_TYPE_DOUBLE,
				_("Param[2]"), G_TYPE_DOUBLE);
		table_set_column_align(aperture_def_table, 0, 1.0);
		table_set_column_align(aperture_def_table, 1, 1.0);
		gtk_tree_view_set_headers_clickable(
				GTK_TREE_VIEW(aperture_def_table->widget), TRUE);
		gtk_tree_view_set_search_column(
				GTK_TREE_VIEW(aperture_def_table->widget), 1);

		GString *gstr = g_string_new(NULL);
		for (aperture_list = stat->aperture_list;
				aperture_list != NULL;
				aperture_list = aperture_list->next) {
			g_string_printf(gstr, "D%d", aperture_list->number);
			table_add_row(aperture_def_table,
				aperture_list->layer,
				gstr->str,
				_(gerbv_aperture_type_name(
					aperture_list->type)),
				aperture_list->parameter[0],
				aperture_list->parameter[1],
				aperture_list->parameter[2]);
		}
		g_string_free(gstr, TRUE);
		table_set_sortable(aperture_def_table);
		gtk_container_add(GTK_CONTAINER(aperture_def_report_window),
				aperture_def_table->widget);
	}

	/* Gerber aperture usage on active layers */
	GtkWidget *aperture_usage_report_window =
		gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(
			GTK_SCROLLED_WINDOW(aperture_usage_report_window),
			GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

	unsigned int aperture_count = 0;

	if (stat->D_code_list->number == -1) {
		GtkWidget *aperture_usage_label = gtk_label_new(
			_("No apertures used in Gerber file(s)!"));
		gtk_misc_set_alignment(GTK_MISC(aperture_usage_label), 0, 0);
		gtk_misc_set_padding(GTK_MISC(aperture_usage_label), 7, 7);
		gtk_label_set_selectable(GTK_LABEL(aperture_usage_label), TRUE);
		gtk_scrolled_window_add_with_viewport(
			GTK_SCROLLED_WINDOW(aperture_usage_report_window),
			aperture_usage_label);
	} else {
		struct table *aperture_usage_table = table_new_with_columns(2,
				_("Code"), G_TYPE_STRING,
				pgettext("table", "Count"), G_TYPE_UINT);
		table_set_column_align(aperture_usage_table, 0, 1.0);
		table_set_column_align(aperture_usage_table, 1, 1.0);
		gtk_tree_view_set_headers_clickable(
			GTK_TREE_VIEW(aperture_usage_table->widget), TRUE);

		GString *gstr = g_string_new(NULL);
		for (aperture_list = stat->D_code_list;
				aperture_list != NULL;
				aperture_list = aperture_list->next) {
			g_string_printf(gstr, "D%d", aperture_list->number);
			table_add_row(aperture_usage_table,
					gstr->str,
					aperture_list->count);
			aperture_count += aperture_list->count;
		}
		g_string_free(gstr, TRUE);
		table_add_row(aperture_usage_table, _("Total"), aperture_count);
		table_set_sortable(aperture_usage_table);
		gtk_container_add( GTK_CONTAINER(aperture_usage_report_window),
				aperture_usage_table->widget);
	}

	/* Create top level dialog window for report */
	GtkWidget *analyze_active_gerbers;
	analyze_active_gerbers = gtk_dialog_new_with_buttons(
			_("Gerber codes report on visible layers"),
			NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
			GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
	gtk_container_set_border_width(GTK_CONTAINER (analyze_active_gerbers), 5);

	gtk_dialog_set_default_response (GTK_DIALOG(analyze_active_gerbers),
			GTK_RESPONSE_ACCEPT);
	g_signal_connect_after (G_OBJECT(analyze_active_gerbers),
			"response",
			G_CALLBACK (gtk_widget_destroy),
			GTK_WIDGET(analyze_active_gerbers));

	/* Put general report text into scrolled window */
	GtkWidget *general_report_window =
		gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(
			GTK_SCROLLED_WINDOW(general_report_window),
			GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

	GtkWidget *vbox = gtk_vbox_new(0, 0);
	gtk_box_pack_start(GTK_BOX(vbox), general_label, 0, 0, 0);
	gtk_box_pack_start(GTK_BOX(vbox), general_table->widget, 0, 0, 0);
	gtk_scrolled_window_add_with_viewport(
			GTK_SCROLLED_WINDOW(general_report_window), vbox);

	/* Create tabbed notebook widget and add report widgets. */
	GtkWidget *notebook = gtk_notebook_new();

	gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
			GTK_WIDGET(general_report_window),
			gtk_label_new(_("General")));

	gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
			GTK_WIDGET(G_report_window),
			gtk_label_new(_("G codes")));

	gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
			GTK_WIDGET(D_report_window),
			gtk_label_new(_("D codes")));

	gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
			GTK_WIDGET(M_report_window),
			gtk_label_new(_("M codes")));

	gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
			GTK_WIDGET(misc_report_window),
			gtk_label_new(_("Misc. codes")));

	gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
			GTK_WIDGET(aperture_def_report_window),
			gtk_label_new(_("Aperture definitions")));

	gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
			GTK_WIDGET(aperture_usage_report_window),
			gtk_label_new(_("Aperture usage")));

	/* Now put notebook into dialog window and show the whole thing */
	gtk_container_add(
			GTK_CONTAINER(GTK_DIALOG(analyze_active_gerbers)->vbox),
			GTK_WIDGET(notebook));

	if (screen.settings) {
		analyze_window_size_restore(analyze_active_gerbers);
		g_signal_connect (G_OBJECT(analyze_active_gerbers), "response",
				G_CALLBACK (analyze_window_size_store),
				GTK_WIDGET(analyze_active_gerbers));
	} else {
		gtk_window_set_default_size(GTK_WINDOW(analyze_active_gerbers),
						640, 320);
	}

	gtk_widget_show_all(analyze_active_gerbers);

	gerbv_stats_destroy(stat);
}


callbacks_analyze_active_drill_activate(GtkMenuItem *menuitem,
					gpointer user_data)
{
	gchar *str;
	int i;

	gerbv_drill_stats_t *stat = generate_drill_analysis();

	/* General info report */
	GString *general_report_str = g_string_new(NULL);
	if (stat->layer_count == 0) {
		g_string_printf(general_report_str,
				_("No drill layers visible!"));
	} else {
		if (stat->error_list->error_text == NULL) {
			g_string_printf(general_report_str,
				ngettext("No errors found in %d visible "
					"drill layer.",
					"No errors found in %d visible "
					"drill layers.",
					stat->layer_count),
				stat->layer_count);
		} else {
			g_string_printf(general_report_str,
				ngettext("Found errors found in %d visible "
					"drill layer.",
					"Found errors found in %d visible "
					"drill layers.",
					stat->layer_count),
				stat->layer_count);
		}
	}

	GtkWidget *general_label = gtk_label_new(general_report_str->str);
	g_string_free(general_report_str, TRUE);
	gtk_misc_set_alignment(GTK_MISC(general_label), 0, 0);
	gtk_misc_set_padding(GTK_MISC(general_label), 7, 7);
	gtk_label_set_selectable(GTK_LABEL(general_label), TRUE);

	struct table *general_table;

	general_table = table_new_with_columns(3,
			_("Layer"), G_TYPE_UINT, _("Type"), G_TYPE_STRING,
			_("Description"), G_TYPE_STRING);
	table_set_column_align(general_table, 0, 1.0);

	for (i = 0; i <= mainProject->last_loaded; i++) {
		gerbv_fileinfo_t **files = mainProject->file;

		if (!files[i]
		||  !files[i]->isVisible
		||   files[i]->image->layertype != GERBV_LAYERTYPE_DRILL)
			continue;

		table_add_row(general_table, i + 1,
				_("Excellon file"), files[i]->fullPathname);

		str = g_strdup_printf(_("%g x %g %s"),
				screen_units(fabs(files[i]->image->info->max_x -
						files[i]->image->info->min_x)),
				screen_units(fabs(files[i]->image->info->max_y -
						files[i]->image->info->min_y)),
				screen_units_str());
		table_add_row(general_table, i + 1, _("Bounding size"), str);
		g_free(str);

		/* Check error report on layer */
		if (stat->layer_count > 0
		&&  stat->error_list->error_text != NULL) {
			for (gerbv_error_list_t *err_list = stat->error_list;
					err_list != NULL;
					err_list = err_list->next) {

				if (i != err_list->layer - 1)
					continue;

				table_add_row(general_table, err_list->layer,
					error_type_string(err_list->type),
					err_list->error_text);
			}
		}
	}

	/* G codes on active layers */
	GtkWidget *G_report_window = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(G_report_window),
			GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

	struct table *G_table =
		table_new_with_columns(3, _("Code"), G_TYPE_STRING,
			pgettext("table", "Count"), G_TYPE_UINT,
			_("Note"), G_TYPE_STRING);
	table_set_column_align(G_table, 0, 1.0);
	table_set_column_align(G_table, 1, 1.0);
	gtk_tree_view_set_headers_clickable(
			GTK_TREE_VIEW(G_table->widget), TRUE);

	table_add_row(G_table, "G00", stat->G00, _(drill_g_code_name(0)));
	table_add_row(G_table, "G01", stat->G01, _(drill_g_code_name(1)));
	table_add_row(G_table, "G02", stat->G02, _(drill_g_code_name(2)));
	table_add_row(G_table, "G03", stat->G03, _(drill_g_code_name(3)));
	table_add_row(G_table, "G04", stat->G04, _(drill_g_code_name(4)));
	table_add_row(G_table, "G05", stat->G05, _(drill_g_code_name(5)));
	table_add_row(G_table, "G85", stat->G85, _(drill_g_code_name(85)));
	table_add_row(G_table, "G90", stat->G90, _(drill_g_code_name(90)));
	table_add_row(G_table, "G91", stat->G91, _(drill_g_code_name(91)));
	table_add_row(G_table, "G93", stat->G93, _(drill_g_code_name(93)));
	table_add_row(G_table, "", stat->G_unknown, _("unknown G-codes"));

	table_set_sortable(G_table);
	gtk_container_add(GTK_CONTAINER(G_report_window), G_table->widget);

	/* M codes on active layers */
	GtkWidget *M_report_window = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(M_report_window),
			GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

	struct table *M_table =
		table_new_with_columns(3, _("Code"), G_TYPE_STRING,
			pgettext("table", "Count"), G_TYPE_UINT,
			_("Note"), G_TYPE_STRING);
	table_set_column_align(M_table, 0, 1.0);
	table_set_column_align(M_table, 1, 1.0);
	gtk_tree_view_set_headers_clickable(
			GTK_TREE_VIEW(M_table->widget), TRUE);
	table_add_row(M_table, "M00", stat->M00, _(drill_m_code_name(0)));
	table_add_row(M_table, "M01", stat->M01, _(drill_m_code_name(1)));
	table_add_row(M_table, "M18", stat->M18, _(drill_m_code_name(18)));
	table_add_row(M_table, "M25", stat->M25, _(drill_m_code_name(25)));
	table_add_row(M_table, "M30", stat->M30, _(drill_m_code_name(30)));
	table_add_row(M_table, "M45", stat->M45, _(drill_m_code_name(45)));
	table_add_row(M_table, "M47", stat->M47, _(drill_m_code_name(47)));
	table_add_row(M_table, "M48", stat->M48, _(drill_m_code_name(48)));
	table_add_row(M_table, "M71", stat->M71, _(drill_m_code_name(71)));
	table_add_row(M_table, "M72", stat->M72, _(drill_m_code_name(72)));
	table_add_row(M_table, "M95", stat->M95, _(drill_m_code_name(95)));
	table_add_row(M_table, "M97", stat->M97, _(drill_m_code_name(97)));
	table_add_row(M_table, "M98", stat->M98, _(drill_m_code_name(98)));
	table_add_row(M_table, "", stat->M_unknown, _("unknown M-codes"));

	table_set_sortable(M_table);
	gtk_container_add(GTK_CONTAINER(M_report_window), M_table->widget);

	/* Misc codes */
	GtkWidget *misc_report_window = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(misc_report_window),
			GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

	struct table *misc_table =
		table_new_with_columns(2,
				/* Count is string for value hide. */
				pgettext("table", "Count"), G_TYPE_STRING,
				_("Code"), G_TYPE_STRING);
	table_set_column_align(misc_table, 0, 1.0);
	str = g_strdup_printf("%d", stat->comment);
	table_add_row(misc_table, str,_("Comments"));
	g_free(str);
	str = g_strdup_printf("%d", stat->unknown);
	table_add_row(misc_table, str, _("Unknown codes"));
	g_free(str);
	str = g_strdup_printf("%d", stat->R);
	table_add_row(misc_table, str, _("Repeat hole (R)"));
	g_free(str);
	if (stat->detect != NULL ) {
		table_add_row(misc_table, "", stat->detect);
	}

	table_set_sortable(misc_table);
	gtk_container_add(GTK_CONTAINER(misc_report_window),
			misc_table->widget);

	/* Drill usage on active layers */
	GtkWidget *drill_usage_report_window =
			gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(
			GTK_SCROLLED_WINDOW(drill_usage_report_window),
			GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

	struct table *drill_usage_table = table_new_with_columns(4,
			_("Drill no."), G_TYPE_UINT,
			_("Dia."), G_TYPE_DOUBLE,
			_("Units"), G_TYPE_STRING,
			pgettext("table", "Count"), G_TYPE_UINT);

	table_set_column_align(drill_usage_table, 0, 1.0);
	table_set_column_align(drill_usage_table, 3, 1.0);
	gtk_tree_view_set_headers_clickable(
		GTK_TREE_VIEW(drill_usage_table->widget), TRUE);

	gerbv_drill_list_t *drill_list;
	for (drill_list = stat->drill_list;
			drill_list != NULL; drill_list = drill_list->next) {
		if (drill_list->drill_num == -1)
			break;	/* No drill list */

		table_add_row(drill_usage_table,
				drill_list->drill_num,
				drill_list->drill_size,
				drill_list->drill_unit,
				drill_list->drill_count);
	}

	table_set_sortable(drill_usage_table);
	gtk_container_add(GTK_CONTAINER(drill_usage_report_window),
			drill_usage_table->widget);

	/* Create top level dialog window for report */
	GtkWidget *analyze_active_drill;
	analyze_active_drill = gtk_dialog_new_with_buttons(
			_("Drill codes report on visible layers"),
			NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
			GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
	gtk_container_set_border_width (GTK_CONTAINER (analyze_active_drill), 5);

	gtk_dialog_set_default_response (GTK_DIALOG(analyze_active_drill),
			GTK_RESPONSE_ACCEPT);
	g_signal_connect_after (G_OBJECT(analyze_active_drill),
			"response",
			G_CALLBACK (gtk_widget_destroy),
			GTK_WIDGET(analyze_active_drill));

	/* Put general report text into scrolled window */
	GtkWidget *general_report_window =
		gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(
			GTK_SCROLLED_WINDOW(general_report_window),
			GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

	GtkWidget *vbox = gtk_vbox_new(0, 0);
	gtk_box_pack_start(GTK_BOX(vbox), general_label, 0, 0, 0);
	gtk_box_pack_start(GTK_BOX(vbox), general_table->widget, 0, 0, 0);
	gtk_scrolled_window_add_with_viewport(
			GTK_SCROLLED_WINDOW(general_report_window), vbox);

	/* Create tabbed notebook widget and add report widgets. */
	GtkWidget *notebook = gtk_notebook_new();

	gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
			GTK_WIDGET(general_report_window),
			gtk_label_new(_("General")));

	gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
			GTK_WIDGET(G_report_window),
			gtk_label_new(_("G codes")));

	gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
			GTK_WIDGET(M_report_window),
			gtk_label_new(_("M codes")));

	gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
			GTK_WIDGET(misc_report_window),
			gtk_label_new(_("Misc. codes")));

	gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
			GTK_WIDGET(drill_usage_report_window),
			gtk_label_new(_("Drill usage")));

	/* Now put notebook into dialog window and show the whole thing */
	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(analyze_active_drill)->vbox),
			GTK_WIDGET(notebook));

	if (screen.settings) {
		analyze_window_size_restore(analyze_active_drill);
		g_signal_connect (G_OBJECT(analyze_active_drill), "response",
				G_CALLBACK (analyze_window_size_store),
				GTK_WIDGET(analyze_active_drill));
	} else {
		gtk_window_set_default_size(GTK_WINDOW(analyze_active_drill),
						640, 320);
	}

	gtk_widget_show_all(analyze_active_drill);

	gerbv_drill_stats_destroy(stat);
}


static void aperture_state_report (gerbv_net_t *net,
		gerbv_image_t *img, gerbv_project_t *prj)
{
	gerbv_layertype_t layer_type = img->layertype;

	gboolean show_length = FALSE;
	gboolean aperture_is_valid = FALSE;
	double x, y, len = 0;

	if (net->aperture > 0)
		aperture_is_valid = TRUE;

	switch (net->aperture_state) {

	case GERBV_APERTURE_STATE_OFF:
		break;

	case GERBV_APERTURE_STATE_ON:
		switch (net->interpolation) {

		case GERBV_INTERPOLATION_LINEARx1:
		case GERBV_INTERPOLATION_LINEARx10:
		case GERBV_INTERPOLATION_LINEARx01:
		case GERBV_INTERPOLATION_LINEARx001:
			if (layer_type != GERBV_LAYERTYPE_DRILL)
				g_message (_("Object type: Line"));
			else
				g_message (_("Object type: Slot (drilled)"));

			len = line_length(net->start_x, net->start_y,
					net->stop_x, net->stop_y);
			show_length = 1;

			break;

		case GERBV_INTERPOLATION_CW_CIRCULAR:
		case GERBV_INTERPOLATION_CCW_CIRCULAR:
			g_message (_("Object type: Arc"));
			len = arc_length(net->cirseg->width,
					fabs(net->cirseg->angle1 -
						net->cirseg->angle2));
			show_length = 1;

			break;
		default:
			g_message (_("Object type: Unknown"));
			break;
		}

		if (layer_type != GERBV_LAYERTYPE_DRILL)
			g_message (_("    Exposure: On"));

		if (aperture_is_valid) {
			if (layer_type != GERBV_LAYERTYPE_DRILL)
				aperture_report(img->aperture, net->aperture,
						net->start_x, net->start_y,
						img, prj);
			else
				drill_report(img->aperture, net->aperture);
		}

		x = net->start_x;
		y = net->start_y;
		gerbv_transform_coord_for_image(&x, &y, img, prj);
		g_message (_("    Start: (%g, %g) %s"),
				screen_units(x),
				screen_units(y),
				screen_units_str());

		x = net->stop_x;
		y = net->stop_y;
		gerbv_transform_coord_for_image(&x, &y, img, prj);
		g_message (_("    Stop: (%g, %g) %s"),
				screen_units(x),
				screen_units(y),
				screen_units_str());

		switch (net->interpolation) {

		case GERBV_INTERPOLATION_CW_CIRCULAR:
		case GERBV_INTERPOLATION_CCW_CIRCULAR:
			x = net->cirseg->cp_x;
			y = net->cirseg->cp_y;
			gerbv_transform_coord_for_image(&x, &y, img, prj);
			g_message (_("    Center: (%g, %g) %s"),
					screen_units(x),
					screen_units(y),
					screen_units_str());

			x = net->cirseg->width/2;
			y = x;
			gerbv_transform_coord_for_image(&x, &y, img, prj);
			g_message (_("    Radius: %g %s"),
					screen_units(x),
					screen_units_str());

			g_message (_("    Angle: %g deg"),
					fabs(net->cirseg->angle1 -
						net->cirseg->angle2));
			g_message (_("    Angles: (%g, %g) deg"),
					net->cirseg->angle1,
					net->cirseg->angle2);
			g_message (_("    Direction: %s"),
					(net->interpolation ==
					 GERBV_INTERPOLATION_CW_CIRCULAR)?
						_("CW"): _("CCW"));
			break;

		default:
			break;
		}

		if (show_length) {
			gerbv_aperture_t *aper = img->aperture[net->aperture];

			if (layer_type == GERBV_LAYERTYPE_DRILL
			&&  aperture_is_valid
			&&  aper->type == GERBV_APTYPE_CIRCLE) {
				double dia = aper->parameter[0];
				g_message (_("    Slot length: %g %s"),
						screen_units(len + dia),
						screen_units_str());
			}

			screen.length_sum += len;
			g_message (_("    Length: %g (sum: %g) %s"),
					screen_units(len),
					screen_units(screen.length_sum),
					screen_units_str());
		}

		net_layer_file_report (net, img, prj);

		break;

	case GERBV_APERTURE_STATE_FLASH:
		if (layer_type != GERBV_LAYERTYPE_DRILL)
			g_message (_("Object type: Flashed aperture"));
		else
			g_message (_("Object type: Drill"));

		if (aperture_is_valid) {
			if (layer_type != GERBV_LAYERTYPE_DRILL)
				aperture_report(img->aperture, net->aperture,
						net->stop_x, net->stop_y,
						img, prj);
			else
				drill_report(img->aperture, net->aperture);
		}

		x = net->stop_x;
		y = net->stop_y;
		gerbv_transform_coord_for_image(&x, &y, img, prj);
		g_message (_("    Location: (%g, %g) %s"),
				screen_units(x),
				screen_units(y),
				screen_units_str());

		net_layer_file_report (net, img, prj);

		break;
	}
}


callbacks_zoom_in_activate                    (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{
	render_zoom_display (ZOOM_IN, 0, 0, 0);
}


callbacks_screen2board(gdouble *X, gdouble *Y, gint x, gint y) {

	/* make sure we don't divide by zero (which is possible if the gui
	   isn't displayed yet */
	if ((screenRenderInfo.scaleFactorX > 0.001)||(screenRenderInfo.scaleFactorY > 0.001)) {
		*X = screenRenderInfo.lowerLeftX + (x / screenRenderInfo.scaleFactorX);
		*Y = screenRenderInfo.lowerLeftY + ((screenRenderInfo.displayHeight - y)
			/ screenRenderInfo.scaleFactorY);
	}
	else {
		*X = *Y = 0.0;
	}
}


callbacks_revert_activate (GtkMenuItem *menuitem, gpointer user_data)
{
	gerbv_revert_all_files (mainProject);
	selection_clear (&screen.selectionInfo);
	update_selected_object_message (FALSE);
	render_refresh_rendered_image_on_screen ();
	callbacks_update_layer_tree ();
}


callbacks_delete_objects_clicked (GtkButton *button, gpointer user_data)
{
	if (selection_length (&screen.selectionInfo) == 0) {
		interface_show_alert_dialog (
			_("No object is currently selected"),
			_("Objects must be selected using the pointer tool "
				"before they can be deleted."),
			FALSE,
			NULL);
		return;
	}

	gint index = callbacks_get_selected_row_index ();
	if (index < 0)
		return;

	if (mainProject->check_before_delete) {
		if (!interface_get_alert_dialog_response (
			_("Do you want to permanently delete "
			"the selected objects from <i>visible</i> layers?"),
			_("Gerbv currently has no undo function, so "
			"this action cannot be undone. This action "
			"will not change the saved file unless you "
			"save the file afterwards."),
			TRUE, &(mainProject->check_before_delete),
			GTK_STOCK_DELETE, GTK_STOCK_CANCEL)) {
				return;
		}
	}

	guint i;
	for (i = 0; i < selection_length (&screen.selectionInfo);) {
		gerbv_selection_item_t sel_item =
			selection_get_item_by_index (&screen.selectionInfo, i);
		gerbv_fileinfo_t *file_info =
			gerbv_get_fileinfo_for_image(sel_item.image, mainProject);

		/* Preserve currently invisible selection from deletion */
		if (!file_info->isVisible) {
			i++;
			continue;
		}

		file_info->layer_dirty = TRUE;
		selection_clear_item_by_index (&screen.selectionInfo, i);
		gerbv_image_delete_net (sel_item.net);
	}
	update_selected_object_message (FALSE);

	render_refresh_rendered_image_on_screen ();
	callbacks_update_layer_tree();
}


callbacks_scrollbar_button_pressed (GtkWidget *widget, GdkEventButton *event){
	//screen.last_x = ((GtkAdjustment *)screen.win.hAdjustment)->value;
	screen.state = SCROLLBAR;
	return FALSE;
}


callbacks_print_render_page (GtkPrintOperation *operation,
           GtkPrintContext   *context,
           gint               page_nr,
           gpointer           user_data)
{
	GtkPrintSettings *pSettings = gtk_print_operation_get_print_settings (operation);
	gerbv_render_info_t renderInfo = {1.0, 1.0, 0, 0,
		GERBV_RENDER_TYPE_CAIRO_HIGH_QUALITY,
		(gint) gtk_print_context_get_width (context),
		(gint) gtk_print_context_get_height (context)};
	cairo_t *cr;

	/* have to assume x and y resolutions are the same for now, since we
	   don't support differing scales in the gerb_render_info_t struct yet */
	gdouble xres = gtk_print_context_get_dpi_x (context);
	gdouble yres = gtk_print_context_get_dpi_y (context);
	gdouble scalePercentage = gtk_print_settings_get_scale (pSettings);
	renderInfo.scaleFactorX = scalePercentage / 100 * xres;
	renderInfo.scaleFactorY = scalePercentage / 100 * yres;

	gerbv_render_translate_to_fit_display (mainProject, &renderInfo);
	cr = gtk_print_context_get_cairo_context (context);
	gerbv_render_all_layers_to_cairo_target_for_vector_output (mainProject, cr, &renderInfo);
}


callbacks_switch_to_correct_cursor (void)
{
	GdkWindow *drawing_area_window = screen.drawing_area->window;
	GdkCursor *cursor;

	if (screen.state == IN_MOVE) {
		cursor = gdk_cursor_new(GDK_FLEUR);
		gdk_window_set_cursor(drawing_area_window, cursor);
		gdk_cursor_destroy(cursor);
		return;
	}
	else if (screen.state == IN_ZOOM_OUTLINE) {
		cursor = gdk_cursor_new(GDK_SIZING);
		gdk_window_set_cursor(drawing_area_window, cursor);
		gdk_cursor_destroy(cursor);
		return;
	}
	callbacks_switch_to_normal_tool_cursor (screen.tool);
}


void callbacks_layer_tree_row_inserted (GtkTreeModel *tree_model, GtkTreePath  *path,
                              GtkTreeIter  *oIter, gpointer user_data) {
	gint *indices=NULL,oldPosition,newPosition;

	if ((!screen.win.treeIsUpdating)&&(path != NULL)) {
		indices = gtk_tree_path_get_indices (path);
		if (indices) {
			newPosition = indices[0];
			oldPosition = callbacks_get_selected_row_index ();
			/* compensate for the fact that the old row has already
			   been removed */
			if (oldPosition < newPosition)
				newPosition--;
			else
				oldPosition--;
			gerbv_change_layer_order (mainProject, oldPosition, newPosition);

			if (screenRenderInfo.renderType <= GERBV_RENDER_TYPE_GDK_XOR) {
				render_refresh_rendered_image_on_screen();
			}
			else {
				render_recreate_composite_surface (screen.drawing_area);
				callbacks_force_expose_event_for_screen ();
			}
			/* select the new line */
			GtkTreeSelection *selection;
			GtkTreeIter iter;
			GtkListStore *list_store = (GtkListStore *) gtk_tree_view_get_model
				((GtkTreeView *) screen.win.layerTree);

			selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(screen.win.layerTree));
			if (gtk_tree_model_get_iter ((GtkTreeModel *)list_store, &iter, path))
				gtk_tree_selection_select_iter (selection, &iter);
		}
	}
}


callbacks_update_statusbar_coordinates (gint x, gint y)
{
	gdouble X, Y;

	callbacks_screen2board (&X, &Y, x, y);

	switch (screen.unit) {
	case GERBV_MILS:
		utf8_snprintf (screen.statusbar.coordstr, MAX_COORDLEN,
				_(gerbv_coords_pattern_mils_str),
				COORD2MILS (X), COORD2MILS (Y));
		break;
	case GERBV_MMS:
		utf8_snprintf (screen.statusbar.coordstr, MAX_COORDLEN,
				_("%8.3f %8.3f"),
				COORD2MMS (X), COORD2MMS (Y));
		break;
	default:
		utf8_snprintf (screen.statusbar.coordstr, MAX_COORDLEN,
				_("%4.5f %4.5f"),
				COORD2INS (X), COORD2INS (Y));
	}

	callbacks_update_statusbar();
}


gerbv_image_t *merge_images (int type)
{
	gint i, filecount, img;
	gerbv_image_t *out;
	gerbv_layertype_t layertype;
	struct l_image_info {
		gerbv_image_t *image;
		gerbv_user_transformation_t *transform;
	} *images;

	images=(struct l_image_info *)g_new0(struct l_image_info,1);
	out = NULL;
	switch(type) {
	case CALLBACKS_SAVE_FILE_DRILLM:
		layertype = GERBV_LAYERTYPE_DRILL;
		break;
	case CALLBACKS_SAVE_FILE_RS274XM:
		layertype = GERBV_LAYERTYPE_RS274X;
		break;
	default:
		GERB_COMPILE_ERROR(_("Unknown Layer type for merge"));
		goto err;
	}
	dprintf("Looking for matching files\n");
	for (i = img = filecount = 0; i < mainProject->max_files; ++i) {
		if (mainProject->file[i] &&  mainProject->file[i]->isVisible &&
		(mainProject->file[i]->image->layertype == layertype)) {
			++filecount;
			dprintf("Adding '%s'\n", mainProject->file[i]->name);
			images[img].image=mainProject->file[i]->image;
			images[img++].transform=&mainProject->file[i]->transform;
			images = (struct l_image_info *)g_renew(struct l_image_info, images, img+1);
		}
	}
	if (filecount < 2) {
		GERB_COMPILE_ERROR(_("Not Enough Files of same type to merge"));
		goto err;
	}
	dprintf("Now merging files\n");
	for (i = 0; i < img; ++i) {
		gerbv_user_transformation_t *thisTransform;
		gerbv_user_transformation_t identityTransform = {0,0,1,1,0,FALSE,FALSE,FALSE};
		thisTransform=images[i].transform;
		if (NULL == thisTransform)
			thisTransform = &identityTransform;
		if (0 == i)
			out = gerbv_image_duplicate_image(images[i].image, thisTransform);
		else
			gerbv_image_copy_image(images[i].image, thisTransform, out);
	}
err:
	g_free(images);
	return out;
}


callbacks_move_layer_down_button_clicked  (GtkButton *button, gpointer   user_data) {
	gint index=callbacks_get_selected_row_index();
	if (index < 0) {
		show_no_layers_warning ();
		return;
	}

	if (index < mainProject->last_loaded) {
		gerbv_change_layer_order (mainProject, index, index + 1);
		callbacks_update_layer_tree ();
		callbacks_select_layer_row (index + 1);

		if (screenRenderInfo.renderType <= GERBV_RENDER_TYPE_GDK_XOR) {
			render_refresh_rendered_image_on_screen ();
		}
		else {
			render_recreate_composite_surface (screen.drawing_area);
			callbacks_force_expose_event_for_screen ();
		}
	}
}


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. */

	if(event->keyval == GDK_space){
		GtkTreeView *tree;
		GtkTreePath *path;
		GtkTreeViewColumn *col;
		gint *indices;
		gint idx;

		tree = (GtkTreeView *) screen.win.layerTree;
		gtk_tree_view_get_cursor (tree, &path, &col);
		if (path) {
			indices = gtk_tree_path_get_indices (path);
			if (indices) {
				idx = callbacks_get_col_num_from_tree_view_col (col);
				if ((idx == 1) && (indices[0] <= mainProject->last_loaded)){
					callbacks_show_color_picker_dialog (indices[0]);
				}
			}
			gtk_tree_path_free (path);
		}
	}
	/* by default propagate the key press */
	return FALSE;
}


static void net_layer_file_report(gerbv_net_t *net,
		gerbv_image_t *img, gerbv_project_t *prj)
{
	/* Don't report "no net" to keep log short */
	if (net->label != NULL)
		g_message (_("    Net label: %s"), net->label->str);

	/* Don't report "no layer name" to keep log short */
	if (net->layer->name != NULL)
		g_message (_("    Layer name: %s"), net->layer->name);

	/* Search file name in project files array */
	for (int i = 0; i <= prj->last_loaded; i++) {
		if (img == prj->file[i]->image)
			g_message (_("    In file: %s"), prj->file[i]->name);
	}
}


callbacks_window_scroll_event(GtkWidget *widget, GdkEventScroll *event)
{
	switch (event->direction) {
		case GDK_SCROLL_UP:
			render_zoom_display (ZOOM_IN_CMOUSE, 0, event->x, event->y);
			break;
		case GDK_SCROLL_DOWN:
			render_zoom_display (ZOOM_OUT_CMOUSE, 0, event->x, event->y);
			break;
		case GDK_SCROLL_LEFT:
			/* Ignore */
		case GDK_SCROLL_RIGHT:
			/* Ignore */
		default:
			return TRUE;
	}
	return TRUE;
} /* scroll_event */


analyze_window_size_restore(GtkWidget *win)
{
	GVariant *var;
	const gint32 *xy;
	gsize num;

	if (!screen.settings)
		return;

	var = g_settings_get_value (screen.settings, "analyze-window-size");
	xy = g_variant_get_fixed_array (var, &num, sizeof (*xy));
	if (num == 2)
		gtk_window_set_default_size (GTK_WINDOW (win), xy[0], xy[1]);
	g_variant_unref (var);

	var = g_settings_get_value (screen.settings, "analyze-window-position");
	xy = g_variant_get_fixed_array (var, &num, sizeof (*xy));
	if (num == 2)
		gtk_window_move (GTK_WINDOW (win), xy[0], xy[1]);
	g_variant_unref (var);
}


callbacks_scrollbar_button_released (GtkWidget *widget, GdkEventButton *event){
	screen.off_x = 0;
	screen.off_y = 0;
	screen.state = NORMAL;
	render_refresh_rendered_image_on_screen();
	return FALSE;
}


visible_file_name(gchar **file_name, gchar **dir_name,
		gerbv_layertype_t layer_type, /* -1 for all types */
		const gchar *file_extension,
		const gchar *untitled_file_extension)
{
	unsigned int count = 0;
	gerbv_fileinfo_t *first_vis_file = NULL;

	for (int i = 0; i < mainProject->max_files; ++i) {
		if (mainProject->file[i]
		&&  mainProject->file[i]->isVisible
		&&  (layer_type == (gerbv_layertype_t)-1
		  || layer_type == mainProject->file[i]->image->layertype)) {
			if (first_vis_file == NULL) {
				first_vis_file = mainProject->file[i];
				/* Always directory of first visible file */
				if (dir_name)
					*dir_name = g_path_get_dirname (
						first_vis_file->fullPathname);
			}

			if (++count == 2 && file_name) {
				*file_name = g_strdup_printf("%s%s",
						pgettext("file name",
							"untitled"),
						untitled_file_extension);
			}
		}
	}

	if (count == 1 && file_name)
		*file_name = g_strdup_printf("%s%s",
			first_vis_file->name, file_extension);

	return count;
}


callbacks_layer_tree_visibility_toggled (gint index)
{
	mainProject->file[index]->isVisible =
		!mainProject->file[index]->isVisible;

	callbacks_update_layer_tree ();
	if (screenRenderInfo.renderType <= GERBV_RENDER_TYPE_GDK_XOR) {
		render_refresh_rendered_image_on_screen ();
	} else {
		render_recreate_composite_surface (screen.drawing_area);
		callbacks_force_expose_event_for_screen ();
	}
}


callbacks_fit_to_window_activate              (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{
	gerbv_render_zoom_to_fit_display (mainProject, &screenRenderInfo);
	render_refresh_rendered_image_on_screen();
}


callbacks_get_selected_row_index (void)
{
	GtkTreeSelection *selection;
	GtkTreeIter       iter;
	GtkListStore *list_store = (GtkListStore *) gtk_tree_view_get_model
			((GtkTreeView *) screen.win.layerTree);
	gint index=-1,i=0;

	/* This will only work in single or browse selection mode! */
	selection = gtk_tree_view_get_selection((GtkTreeView *) screen.win.layerTree);
	if (gtk_tree_selection_get_selected(selection, NULL, &iter)) {
		while (gtk_tree_model_iter_nth_child ((GtkTreeModel *)list_store,
				&iter, NULL, i)){
			if (gtk_tree_selection_iter_is_selected (selection, &iter)) {
				return i;
			}
			i++;
     		}
	}
	return index;
}


void callbacks_update_ruler_pointers (void) {
	double xPosition, yPosition;
	xPosition = screenRenderInfo.lowerLeftX + (screen.last_x / screenRenderInfo.scaleFactorX);
	yPosition = screenRenderInfo.lowerLeftY + ((screenRenderInfo.displayHeight - screen.last_y) / screenRenderInfo.scaleFactorY);

	if (!((screen.unit == GERBV_MILS) && ((screenRenderInfo.scaleFactorX < 80)||(screenRenderInfo.scaleFactorY < 80)))) {
		xPosition = callbacks_calculate_actual_distance (xPosition);
		yPosition = callbacks_calculate_actual_distance (yPosition);
	}
	g_object_set (G_OBJECT (screen.win.hRuler), "position", xPosition, NULL);
	g_object_set (G_OBJECT (screen.win.vRuler), "position", yPosition, NULL);
}


callbacks_show_color_picker_dialog (gint index){
	screen.win.colorSelectionDialog = NULL;
	GtkColorSelectionDialog *cs= (GtkColorSelectionDialog *) gtk_color_selection_dialog_new (_("Select a color"));
	GtkColorSelection *colorsel = (GtkColorSelection *) cs->colorsel;

	screen.win.colorSelectionDialog = (GtkWidget *) cs;
	screen.win.colorSelectionIndex = index;
	if (index >= 0)
		gtk_color_selection_set_current_color (colorsel, &mainProject->file[index]->color);
	else
		gtk_color_selection_set_current_color (colorsel, &mainProject->background);
	if ((screenRenderInfo.renderType >= GERBV_RENDER_TYPE_CAIRO_NORMAL)&&(index >= 0)) {
		gtk_color_selection_set_has_opacity_control (colorsel, TRUE);
		gtk_color_selection_set_current_alpha (colorsel, mainProject->file[index]->alpha);
	}
	gtk_widget_show_all((GtkWidget *)cs);
	if (gtk_dialog_run ((GtkDialog*)cs) == GTK_RESPONSE_OK) {
		GtkColorSelection *colorsel = (GtkColorSelection *) cs->colorsel;
		gint rowIndex = screen.win.colorSelectionIndex;

		if (index >= 0) {
			gtk_color_selection_get_current_color (colorsel, &mainProject->file[rowIndex]->color);
			gdk_colormap_alloc_color(gdk_colormap_get_system(), &mainProject->file[rowIndex]->color, FALSE, TRUE);
		}
		else {
			gtk_color_selection_get_current_color (colorsel, &mainProject->background);
			gdk_colormap_alloc_color(gdk_colormap_get_system(), &mainProject->background, FALSE, TRUE);
		}
		if ((screenRenderInfo.renderType >= GERBV_RENDER_TYPE_CAIRO_NORMAL)&&(index >= 0)) {
			mainProject->file[rowIndex]->alpha = gtk_color_selection_get_current_alpha (colorsel);
		}

		callbacks_update_layer_tree ();
		render_refresh_rendered_image_on_screen();
	}
	gtk_widget_destroy ((GtkWidget *)cs);
	screen.win.colorSelectionDialog = NULL;
}


aperture_report(gerbv_aperture_t *apertures[], int aperture_num,
		double x, double y, gerbv_image_t *img, gerbv_project_t *prj)
{
	gerbv_aperture_type_t type = apertures[aperture_num]->type;
	double *params = apertures[aperture_num]->parameter;
	gerbv_simplified_amacro_t *sam = apertures[aperture_num]->simplified;

	g_message (_("    Aperture used: D%d"), aperture_num);
	g_message (_("    Aperture type: %s"),
		(type == GERBV_APTYPE_MACRO)?
			_(gerbv_aperture_type_name(sam->type)):
			_(gerbv_aperture_type_name(type)));

	switch (type) {
	case GERBV_APTYPE_CIRCLE:
		g_message (_("    Diameter: %g %s"),
			screen_units(params[0]),
			screen_units_str());
		break;
	case GERBV_APTYPE_RECTANGLE:
	case GERBV_APTYPE_OVAL:
		g_message (_("    Dimensions: %gx%g %s"),
			screen_units(params[0]),
			screen_units(params[1]),
			screen_units_str());
		break;
	case GERBV_APTYPE_MACRO: {
		switch (sam->type) {
		case GERBV_APTYPE_MACRO_CIRCLE:
			g_message (_("    Diameter: %g %s"),
				screen_units(sam->parameter[CIRCLE_DIAMETER]),
				screen_units_str());
			x += sam->parameter[CIRCLE_CENTER_X];
			y += sam->parameter[CIRCLE_CENTER_Y];
			gerbv_transform_coord_for_image(&x, &y, img, prj);
			g_message (_("    Center: (%g, %g) %s"),
				screen_units(x), screen_units(y),
				screen_units_str());
			break;

		case GERBV_APTYPE_MACRO_OUTLINE:
			g_message (_("    Number of points: %g"),
				sam->parameter[OUTLINE_NUMBER_OF_POINTS]);
			x += sam->parameter[OUTLINE_FIRST_X];
			y += sam->parameter[OUTLINE_FIRST_Y];
			gerbv_transform_coord_for_image(&x, &y, img, prj);
			g_message (_("    Start: (%g, %g) %s"),
				screen_units(x), screen_units(y),
				screen_units_str());
			g_message (_("    Rotation: %g deg"),
				sam->parameter[OUTLINE_ROTATION_IDX(sam->parameter)]);
			break;

		case GERBV_APTYPE_MACRO_POLYGON:
			g_message (_("    Number of points: %g"),
				sam->parameter[POLYGON_NUMBER_OF_POINTS]);
			g_message (_("    Diameter: %g %s"),
				screen_units(sam->parameter[POLYGON_DIAMETER]),
				screen_units_str());
			x += sam->parameter[POLYGON_CENTER_X];
			y += sam->parameter[POLYGON_CENTER_Y];
			gerbv_transform_coord_for_image(&x, &y, img, prj);
			g_message (_("    Center: (%g, %g) %s"),
				screen_units(x), screen_units(y),
				screen_units_str());
			g_message (_("    Rotation: %g deg"),
				sam->parameter[POLYGON_ROTATION]);
			break;

		case GERBV_APTYPE_MACRO_MOIRE:
			g_message (_("    Outside diameter: %g %s"),
				screen_units(sam->parameter[MOIRE_OUTSIDE_DIAMETER]),
				screen_units_str());
			g_message (_("    Ring thickness: %g %s"),
				screen_units(sam->parameter[MOIRE_CIRCLE_THICKNESS]),
				screen_units_str());
			g_message (_("    Gap width: %g %s"),
				screen_units(sam->parameter[MOIRE_GAP_WIDTH]),
				screen_units_str());
			g_message (_("    Number of rings: %g"),
				sam->parameter[MOIRE_NUMBER_OF_CIRCLES]);
			g_message (_("    Crosshair thickness: %g %s"),
				screen_units(
					sam->parameter[MOIRE_CROSSHAIR_THICKNESS]),
				screen_units_str());
			g_message (_("    Crosshair length: %g %s"),
				screen_units(sam->parameter[MOIRE_CROSSHAIR_LENGTH]),
				screen_units_str());
			x += sam->parameter[MOIRE_CENTER_X];
			y += sam->parameter[MOIRE_CENTER_Y];
			gerbv_transform_coord_for_image(&x, &y, img, prj);
			g_message (_("    Center: (%g, %g) %s"),
				screen_units(x), screen_units(y),
				screen_units_str());
			g_message (_("    Rotation: %g deg"),
				sam->parameter[MOIRE_ROTATION]);
			break;

		case GERBV_APTYPE_MACRO_THERMAL:
			g_message (_("    Outside diameter: %g %s"),
				screen_units(sam->parameter[THERMAL_OUTSIDE_DIAMETER]),
				screen_units_str());
			g_message (_("    Inside diameter: %g %s"),
				screen_units(sam->parameter[THERMAL_INSIDE_DIAMETER]),
				screen_units_str());
			g_message (_("    Crosshair thickness: %g %s"),
				screen_units(
					sam->parameter[THERMAL_CROSSHAIR_THICKNESS]),
				screen_units_str());
			x += sam->parameter[THERMAL_CENTER_X];
			y += sam->parameter[THERMAL_CENTER_Y];
			gerbv_transform_coord_for_image(&x, &y, img, prj);
			g_message (_("    Center: (%g, %g) %s"),
				screen_units(x), screen_units(y),
				screen_units_str());
			g_message (_("    Rotation: %g deg"),
				sam->parameter[THERMAL_ROTATION]);
			break;

		case GERBV_APTYPE_MACRO_LINE20:
			g_message (_("    Width: %g %s"),
				screen_units(sam->parameter[LINE20_WIDTH]),
				screen_units_str());
			x += sam->parameter[LINE20_START_X];
			y += sam->parameter[LINE20_START_Y];
			gerbv_transform_coord_for_image(&x, &y, img, prj);
			g_message (_("    Start: (%g, %g) %s"),
				screen_units(x), screen_units(y),
				screen_units_str());
			x += sam->parameter[LINE20_END_X];
			y += sam->parameter[LINE20_END_Y];
			gerbv_transform_coord_for_image(&x, &y, img, prj);
			g_message (_("    Stop: (%g, %g) %s"),
				screen_units(x), screen_units(y),
				screen_units_str());
			g_message (_("    Rotation: %g deg"),
					sam->parameter[LINE20_ROTATION]);
			break;

		case GERBV_APTYPE_MACRO_LINE21:
			g_message (_("    Width: %g %s"),
				screen_units(sam->parameter[LINE21_WIDTH]),
				screen_units_str());
			g_message (_("    Height: %g %s"),
				screen_units(sam->parameter[LINE21_HEIGHT]),
				screen_units_str());
			x += sam->parameter[LINE21_CENTER_X];
			y += sam->parameter[LINE21_CENTER_Y];
			gerbv_transform_coord_for_image(&x, &y, img, prj);
			g_message (_("    Center: (%g, %g) %s"),
				screen_units(x), screen_units(y),
				screen_units_str());
			g_message (_("    Rotation: %g deg"),
					sam->parameter[LINE21_ROTATION]);
			break;

		case GERBV_APTYPE_MACRO_LINE22:
			g_message (_("    Width: %g %s"),
				screen_units(sam->parameter[LINE22_WIDTH]),
				screen_units_str());
			g_message (_("    Height: %g %s"),
				screen_units(sam->parameter[LINE22_HEIGHT]),
				screen_units_str());
			x += sam->parameter[LINE22_LOWER_LEFT_X];
			y += sam->parameter[LINE22_LOWER_LEFT_Y];
			gerbv_transform_coord_for_image(&x, &y, img, prj);
			g_message (_("    Lower left: (%g, %g) %s"),
				screen_units(x), screen_units(y),
				screen_units_str());
			g_message (_("    Rotation: %g deg"),
					sam->parameter[LINE22_ROTATION]);
			break;

		default:
			break;
		}
		break;
	}
	default:
		break;
	}
}


callbacks_reduce_object_area_clicked  (GtkButton *button, gpointer user_data){
	/* for testing, just hard code in some parameters */
	gerbv_image_reduce_area_of_selected_objects (screen.selectionInfo.selectedNodeArray, 0.20, 3, 3, 0.01);
	selection_clear (&screen.selectionInfo);
	update_selected_object_message (FALSE);
	render_refresh_rendered_image_on_screen ();
}


callbacks_viewmenu_rendertype_changed (GtkCheckMenuItem *widget, gpointer user_data) {
	gerbv_render_types_t type = GPOINTER_TO_INT(user_data);

	if (type == screenRenderInfo.renderType)
		return;

	dprintf ("%s():  type = %d\n", __FUNCTION__, type);

	screenRenderInfo.renderType = type;
	callbacks_render_type_changed ();
}


callbacks_show_cross_on_drill_holes (GtkMenuItem *menuitem, gpointer user_data)
{
	screenRenderInfo.show_cross_on_drill_holes = GTK_CHECK_MENU_ITEM(menuitem)->active;
	render_refresh_rendered_image_on_screen();
}


callbacks_edit_object_properties_clicked (GtkButton *button, gpointer   user_data){
}


callbacks_save_layer_activate                       (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{
  /* first figure out which layer in the layer side menu is selected */
  gint index=callbacks_get_selected_row_index();

  /* Now save that layer */
  if (index >= 0) {
    if (!gerbv_save_layer_from_index (mainProject, index, mainProject->file[index]->fullPathname)) {
      interface_show_alert_dialog(_("Gerbv cannot export this file type"),
				  NULL,
				  FALSE,
				  NULL);
      mainProject->file[index]->layer_dirty = FALSE;
      callbacks_update_layer_tree();
      return;
    }
  }
  callbacks_update_layer_tree();
  return;
}


callbacks_online_manual_activate              (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{

}


callbacks_update_ruler_scales (void) {
	double xStart, xEnd, yStart, yEnd;

	xStart = screenRenderInfo.lowerLeftX;
	yStart = screenRenderInfo.lowerLeftY;
	xEnd = screenRenderInfo.lowerLeftX + (screenRenderInfo.displayWidth / screenRenderInfo.scaleFactorX);
	yEnd = screenRenderInfo.lowerLeftY + (screenRenderInfo.displayHeight / screenRenderInfo.scaleFactorY);
	/* mils can get super crowded with large boards, but inches are too
	   large for most boards.  So, we leave mils in for now and just switch
	   to inches if the scale factor gets too small */
	if (!((screen.unit == GERBV_MILS) && ((screenRenderInfo.scaleFactorX < 80)||(screenRenderInfo.scaleFactorY < 80)))) {
		xStart = callbacks_calculate_actual_distance (xStart);
		xEnd = callbacks_calculate_actual_distance (xEnd);
		yStart = callbacks_calculate_actual_distance (yStart);
		yEnd = callbacks_calculate_actual_distance (yEnd);
	}
	/* make sure the widgets actually exist before setting (in case this gets
	   called before everything is realized */
	if (screen.win.hRuler)
		gtk_ruler_set_range (GTK_RULER (screen.win.hRuler), xStart, xEnd, 0, xEnd - xStart);
	/* reverse y min and max, since the ruler starts at the top */
	if (screen.win.vRuler)
		gtk_ruler_set_range (GTK_RULER (screen.win.vRuler), yEnd, yStart, 0, yEnd - yStart);
}


callbacks_live_edit (GtkWidget *button, gpointer user_data){
	GtkDialog *toplevel = GTK_DIALOG(gtk_widget_get_toplevel (button));
	gtk_dialog_response(toplevel, GTK_RESPONSE_APPLY);
}


callbacks_drawingarea_motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
{
	int x, y;
	GdkModifierType state;

	if (event->is_hint)
		gdk_window_get_pointer (event->window, &x, &y, &state);
	else {
		x = event->x;
		y = event->y;
		state = event->state;
	}

	switch (screen.state) {
		case IN_MOVE: {
		    if (screen.last_x != 0 || screen.last_y != 0) {
			/* Move pixmap to get a snappier feel of movement */
			screen.off_x += x - screen.last_x;
			screen.off_y += y - screen.last_y;
		    }
    		    screenRenderInfo.lowerLeftX -= ((x - screen.last_x) / screenRenderInfo.scaleFactorX);
		    screenRenderInfo.lowerLeftY += ((y - screen.last_y) / screenRenderInfo.scaleFactorY);
		    callbacks_force_expose_event_for_screen ();
		    callbacks_update_scrollbar_positions ();
			screen.last_x = x;
			screen.last_y = y;
		    break;
		}
		case IN_ZOOM_OUTLINE: {
			if (screen.last_x || screen.last_y)
				render_draw_zoom_outline(screen.centered_outline_zoom);
			screen.last_x = x;
			screen.last_y = y;
			render_draw_zoom_outline(screen.centered_outline_zoom);
			break;
		}
		case IN_MEASURE: {
			/* clear the previous drawn line by drawing over it */
			render_toggle_measure_line();
			callbacks_screen2board(&(screen.measure_stop_x),
			    &(screen.measure_stop_y), x, y);
			/* screen.last_[xy] are updated to move the ruler pointers */
			screen.last_x = x;
			screen.last_y = y;
			/* draw the new line and write the new distance */
			render_draw_measure_distance();
			break;
		}
		case IN_SELECTION_DRAG: {
			if (screen.last_x || screen.last_y)
				render_draw_selection_box_outline();
			screen.last_x = x;
			screen.last_y = y;
			render_draw_selection_box_outline();
			break;
		}
		default:
			screen.last_x = x;
			screen.last_y = y;
			break;
	}
	callbacks_update_statusbar_coordinates (x, y);
	callbacks_update_ruler_pointers ();
	return TRUE;
} /* motion_notify_event */


callbacks_drawingarea_configure_event (GtkWidget *widget, GdkEventConfigure *event)
{
	GdkDrawable *drawable = widget->window;

	gdk_drawable_get_size (drawable, &screenRenderInfo.displayWidth, &screenRenderInfo.displayHeight);

	/* set this up if cairo is compiled, since we may need to switch over to
	   using the surface at any time */
	int x_off=0, y_off=0;

	if (GDK_IS_WINDOW(widget->window)) {
	      /* query the window's backbuffer if it has one */
		GdkWindow *window = GDK_WINDOW(widget->window);
	      gdk_window_get_internal_paint_info (window, &drawable, &x_off, &y_off);
	}
	if (screen.windowSurface)
		cairo_surface_destroy ((cairo_surface_t *)
			screen.windowSurface);

#if defined(WIN32) || defined(QUARTZ)
	cairo_t *cairoTarget = gdk_cairo_create (GDK_WINDOW(widget->window));

	screen.windowSurface = cairo_get_target (cairoTarget);
	/* increase surface reference by one so it isn't freed when the cairo_t
	   is destroyed next */
	screen.windowSurface = cairo_surface_reference (screen.windowSurface);
	cairo_destroy (cairoTarget);
#else
	GdkVisual *visual = gdk_drawable_get_visual (drawable);
	screen.windowSurface = (gpointer) cairo_xlib_surface_create (GDK_DRAWABLE_XDISPLAY (drawable),
                                          GDK_DRAWABLE_XID (drawable),
                                          GDK_VISUAL_XVISUAL (visual),
                                          screenRenderInfo.displayWidth,
                                          screenRenderInfo.displayHeight);
#endif
	/* if this is the first time, go ahead and call autoscale even if we don't
	   have a model loaded */
	if ((screenRenderInfo.scaleFactorX < 0.001)||(screenRenderInfo.scaleFactorY < 0.001)) {
		gerbv_render_zoom_to_fit_display (mainProject, &screenRenderInfo);
	}
	render_refresh_rendered_image_on_screen();
	return TRUE;
}


callbacks_unselect_all_tool_buttons (void) {

}


callbacks_change_layer_edit_clicked  (GtkButton *button, gpointer userData)
{
	gint index = callbacks_get_selected_row_index();
	gerbv_fileinfo_t **files = mainProject->file;
	gerbv_user_transformation_t **transforms;
	int i, j;

	if (index < 0) {
		show_no_layers_warning ();
		return;
	}

	/* last_loaded == 0 if only one file is loaded */
	transforms = g_new (gerbv_user_transformation_t *,
			mainProject->last_loaded +
			2 /* layer + NULL */ +
			1 /* if selected layer is visible */);

	/* [0] is selected layer */
	transforms[0] = &mainProject->file[index]->transform;

	/* Get visible Gerber files transformations */
	j = 1;	/* [0] is alerady used */
	for (i = 0; i <= mainProject->last_loaded; i++) {
		if (files[i] && files[i]->isVisible)
			transforms[j++] = &files[i]->transform;
	}

	/* Terminate array with NULL */
	transforms[j] = NULL;

	interface_show_layer_edit_dialog(transforms, screen.unit);
	g_free (transforms);
	render_refresh_rendered_image_on_screen ();
	callbacks_update_layer_tree ();
}


callbacks_show_sidepane_toggled (GtkMenuItem *menuitem, gpointer user_data)
{
	gtk_widget_set_visible (user_data, GTK_CHECK_MENU_ITEM(menuitem)->active);
}


callbacks_show_toolbar_toggled (GtkMenuItem *menuitem, gpointer user_data)
{
	gtk_widget_set_visible (user_data, GTK_CHECK_MENU_ITEM(menuitem)->active);
}


callbacks_quit_activate                       (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{
  gboolean layers_dirty = FALSE;
  gint idx;

  for (idx = 0; idx<=mainProject->last_loaded; idx++) {
    if (mainProject->file[idx] == NULL) break;
    layers_dirty = layers_dirty || mainProject->file[idx]->layer_dirty;
  }

  if (layers_dirty &&
      !interface_get_alert_dialog_response(
		_("Do you want to close all open layers and quit the program?"),
		_("Quitting the program will cause any unsaved changes "
		"to be lost."),
		FALSE, NULL, GTK_STOCK_QUIT, GTK_STOCK_CANCEL)) {
    return TRUE; // stop propagation of the delete_event.
	// this would destroy the gui but not return from the gtk event loop.
  }

  /* Save background color */
  if (screen.settings && !screen.background_is_from_project) {
    guint clr;
    GdkColor *bg = &mainProject->background;

    clr = bg->red/257<<16 | bg->green/257<<8 | bg->blue/257;
    g_settings_set_uint (screen.settings, "background-color", clr);
  }

  /* Save main window size and postion */
  if (screen.settings) {
    GtkWindow *win = GTK_WINDOW(screen.win.topLevelWindow);
    gint32 xy[2];
    GVariant *var;
    gboolean is_max;

    is_max = FALSE != (GDK_WINDOW_STATE_MAXIMIZED & gdk_window_get_state (
			    gtk_widget_get_window (GTK_WIDGET(win))));
    g_settings_set_boolean (screen.settings, "window-maximized", is_max);

    if (!is_max) {
      gtk_window_get_size (win, (gint *)xy, (gint *)(xy+1));
      var = g_variant_new_fixed_array (G_VARIANT_TYPE_INT32, xy, 2,
		      sizeof (xy[0]));
      g_settings_set_value (screen.settings, "window-size", var);

      gtk_window_get_position (win, (gint *)xy, (gint *)(xy+1));
      var = g_variant_new_fixed_array (G_VARIANT_TYPE_INT32, xy, 2,
		      sizeof (xy[0]));
      g_settings_set_value (screen.settings, "window-position", var);
    }
  }

  gerbv_unload_all_layers (mainProject);
  gtk_main_quit();

  return FALSE;
}


callbacks_move_layer_down_menu_activate (GtkMenuItem *menuitem, gpointer user_data) {
	callbacks_move_layer_down_button_clicked(NULL, NULL);
	gtk_widget_grab_focus (screen.win.layerTree);
}


callbacks_align_files_from_sel_clicked (
		GtkMenuItem *menu_item, gpointer user_data)
{
	gerbv_fileinfo_t *fi[2];
	gerbv_selection_item_t item[2];
	gerbv_net_t *net;
	gerbv_selection_info_t *sel_info = &screen.selectionInfo;
	int align_second_to_first = GPOINTER_TO_INT(user_data);
	gdouble x[2], y[2];
	int i;

	if (selection_length (sel_info) != 2)
		return;

	item[0] = selection_get_item_by_index(sel_info, 0);
	item[1] = selection_get_item_by_index(sel_info, 1);

	fi[0] = gerbv_get_fileinfo_for_image (item[0].image, mainProject);
	fi[1] = gerbv_get_fileinfo_for_image (item[1].image, mainProject);

	if (fi[0] == NULL || fi[1] == NULL || fi[0] == fi[1])
		return;

	/* Calculate aligning coords */
	for (i = 0; i < 2; i++) {
		net = item[i].net;

		switch (net->aperture_state) {
		case GERBV_APERTURE_STATE_FLASH:
			x[i] = net->stop_x;
			y[i] = net->stop_y;
			break;
		case GERBV_APERTURE_STATE_ON:
			switch (net->interpolation) {
			case GERBV_INTERPOLATION_LINEARx1:
			case GERBV_INTERPOLATION_LINEARx10:
			case GERBV_INTERPOLATION_LINEARx01:
			case GERBV_INTERPOLATION_LINEARx001:
				x[i] = (net->stop_x + net->start_x)/2;
				y[i] = (net->stop_y + net->start_y)/2;
				break;
			case GERBV_INTERPOLATION_CW_CIRCULAR:
			case GERBV_INTERPOLATION_CCW_CIRCULAR:
				x[i] = net->cirseg->cp_x;
				y[i] = net->cirseg->cp_y;
				break;
			default:
				GERB_COMPILE_ERROR (_("Can't align by this "
							"type of object"));
				return;
			}
			break;
		default:
			GERB_COMPILE_ERROR (_("Can't align by this "
						"type of object"));
			return;
		}

		gerbv_transform_coord_for_image(x + i, y + i,
				item[i].image, mainProject);
	}

	if (align_second_to_first) {
		fi[1]->transform.translateX += x[0] - x[1];
		fi[1]->transform.translateY += y[0] - y[1];
	} else {
		fi[0]->transform.translateX += x[1] - x[0];
		fi[0]->transform.translateY += y[1] - y[0];
	}

	render_refresh_rendered_image_on_screen ();
	callbacks_update_layer_tree ();
}


callbacks_update_statusbar(void)
{
	if ((screen.statusbar.coordstr != NULL)&&(GTK_IS_LABEL(screen.win.statusMessageLeft))) {
		gtk_label_set_text(GTK_LABEL(screen.win.statusMessageLeft), screen.statusbar.coordstr);
	}
	if ((screen.statusbar.diststr != NULL)&&(GTK_IS_LABEL(screen.win.statusMessageRight))) {
		gtk_label_set_markup(GTK_LABEL(screen.win.statusMessageRight), screen.statusbar.diststr);
	}
}


static const char *error_type_string(gerbv_message_type_t type) {
	switch (type) {
	case GERBV_MESSAGE_FATAL:
		return _("FATAL");
	case GERBV_MESSAGE_ERROR:
		return _("ERROR");
	case GERBV_MESSAGE_WARNING:
		return _("Warning");
	case GERBV_MESSAGE_NOTE:
		return _("Note");
	default:
		return "Unknown";
	}
}


callbacks_bugs_activate (GtkMenuItem     *menuitem,
                             gpointer         user_data)
{
    int i;
    #include "bugs.c"

    /* Create the top level dialog widget with an OK button */
    GtkWidget *bugs_dialog = gtk_dialog_new_with_buttons(_("Known bugs in Gerbv"),
							 NULL,
							 GTK_DIALOG_DESTROY_WITH_PARENT,
							 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
							 NULL);
    gtk_container_set_border_width (GTK_CONTAINER (bugs_dialog), 5);
    gtk_dialog_set_default_response (GTK_DIALOG(bugs_dialog),
				     GTK_RESPONSE_ACCEPT);
    g_signal_connect (G_OBJECT(bugs_dialog), "response",
		      G_CALLBACK (gtk_widget_destroy), GTK_WIDGET(bugs_dialog));

    /* First create single bugs_string from bugs_string_array */
    GString *bugs_string = g_string_new(NULL);
    for (i=0; bugs_string_array[i] != NULL; i++) {
	/* gettext("") will return info string */
	g_string_append_printf(bugs_string, "%s\n",
		(bugs_string_array[i][0] == '\0') ? "" : _(bugs_string_array[i]));
    }

    /* Create GtkLabel to hold text */
    GtkWidget *bugs_label = gtk_label_new (bugs_string->str);
    g_string_free(bugs_string, FALSE);
    gtk_misc_set_alignment(GTK_MISC(bugs_label), 0, 0);
    gtk_misc_set_padding(GTK_MISC(bugs_label), 7, 7);

    /* Put text into scrolled window */
    GtkWidget *bugs_window = gtk_scrolled_window_new (NULL, NULL);
    gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(bugs_window),
                                          GTK_WIDGET(bugs_label));
    gtk_widget_set_size_request(bugs_window, 600, 300);
    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(bugs_dialog)->vbox),
                      GTK_WIDGET(bugs_window));

    gtk_widget_show_all(GTK_WIDGET(bugs_dialog));
    gtk_dialog_run(GTK_DIALOG(bugs_dialog));

}


callbacks_update_layer_tree (void)
{
	GtkListStore *list_store = (GtkListStore *) gtk_tree_view_get_model
			((GtkTreeView *) screen.win.layerTree);
	gint idx;
	GtkTreeIter iter;
	GtkTreeSelection *selection;
	gint oldSelectedRow;

	if (screen.win.treeIsUpdating)
		return;

	screen.win.treeIsUpdating = TRUE;

	oldSelectedRow = callbacks_get_selected_row_index();
	if (oldSelectedRow < 0)
		oldSelectedRow = 0;

	gtk_list_store_clear (list_store);

	for (idx = 0; idx <= mainProject->last_loaded; idx++) {
		GdkPixbuf *pixbuf, *blackPixbuf;
		unsigned char red, green, blue, alpha;
		guint32 color;
		gchar *layerName;
		gerbv_fileinfo_t *file;

		file = mainProject->file[idx];
		if (!file)
			continue;

		red =   (unsigned char) (file->color.red * 255 / G_MAXUINT16);
		green = (unsigned char) (file->color.green * 255 / G_MAXUINT16);
		blue =  (unsigned char) (file->color.blue *255 / G_MAXUINT16);
		alpha = (unsigned char) (file->alpha * 255 / G_MAXUINT16);

		color = red*(256*256*256) + green*(256*256) + blue*256 + alpha;
		pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 20, 15);
		gdk_pixbuf_fill (pixbuf, color);

		blackPixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 20, 15);
		color = 100*(256*256*256) + 100*(256*256) + 100*256 + 150;
		gdk_pixbuf_fill (blackPixbuf, color);

		/* copy the color area into the black pixbuf */
		gdk_pixbuf_copy_area  (pixbuf, 1, 1, 18, 13, blackPixbuf, 1, 1);
		g_object_unref(pixbuf);

		gtk_list_store_append (list_store, &iter);

		gchar startChar[2],*modifiedCode;
		/* terminate the letter string */
		startChar[1] = 0;

		gint numberOfModifications = 0;
		if (file->transform.inverted) {
			startChar[0] = 'I';
			numberOfModifications++;
		}
		if (file->transform.mirrorAroundX
		||  file->transform.mirrorAroundY) {
			startChar[0] = 'M';
			numberOfModifications++;
		}
		if (fabs(file->transform.translateX)
					> GERBV_PRECISION_LINEAR_INCH
		||  fabs(file->transform.translateY)
					> GERBV_PRECISION_LINEAR_INCH) {
			startChar[0] = 'T';
			numberOfModifications++;
		}
		if (fabs(file->transform.scaleX - 1)
					> GERBV_PRECISION_LINEAR_INCH
		||  fabs(file->transform.scaleY - 1)
					> GERBV_PRECISION_LINEAR_INCH) {
			startChar[0] = 'S';
			numberOfModifications++;
		}
		if (fabs(file->transform.rotation)
					> GERBV_PRECISION_ANGLE_RAD) {
			startChar[0] = 'R';
			numberOfModifications++;
		}
		if (numberOfModifications > 1)
			startChar[0] = '*';
		if (numberOfModifications == 0)
			modifiedCode = g_strdup ("");
		else
			modifiedCode = g_strdup (startChar);

		/* Display any unsaved layers differently to show the user they
		 * are unsaved */
		if (file->layer_dirty == TRUE) {
			/* The layer has unsaved changes in it. Show layer name
			 * in italics. */
			layerName = g_strconcat ("*", "<i>", file->name,
					"</i>", NULL);
		} else {
			/* Layer is clean. Show layer name using normal font. */
			layerName = g_strdup (file->name);
		}

		gtk_list_store_set (list_store, &iter,
				    0, file->isVisible,
				    1, blackPixbuf,
				    2, layerName,
				    3, modifiedCode,
				    -1);
		g_free (layerName);
		g_free (modifiedCode);
		/* pixbuf has a refcount of 2 now, as the list store has added its own reference */
		g_object_unref(blackPixbuf);
	}

	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(screen.win.layerTree));

	/* if no line is selected yet, select the first row (if it has data) */
	/* or, select the line that was previously selected */

	if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) {
		if (gtk_tree_model_iter_nth_child ((GtkTreeModel *) list_store,
						&iter, NULL, oldSelectedRow)) {
			gtk_tree_selection_select_iter (selection, &iter);
		}
	}
	gboolean showItems = (mainProject->last_loaded >= 0);
	gtk_widget_set_sensitive (screen.win.curLayerMenuItem, showItems);
	gtk_widget_set_sensitive (screen.win.curAnalyzeMenuItem, showItems);
	gtk_widget_set_sensitive (screen.win.curEditMenuItem, showItems);
	for (unsigned int i = 0;
			i < G_N_ELEMENTS(screen.win.curFileMenuItem); i++) {
		gtk_widget_set_sensitive (screen.win.curFileMenuItem[i], showItems);
	}
	screen.win.treeIsUpdating = FALSE;
}


callbacks_units_changed (gerbv_gui_unit_t unit)
{
	static gboolean isChanging = FALSE;

	if (isChanging)
		return;

	isChanging = TRUE;
	screen.unit = unit;

	if (unit == GERBV_MILS || unit == GERBV_MMS || unit == GERBV_INS) {
		gtk_combo_box_set_active (
				GTK_COMBO_BOX (screen.win.statusUnitComboBox),
				unit);
		gtk_check_menu_item_set_active (
				screen.win.menu_view_unit_group[unit], TRUE);
	}

	callbacks_update_ruler_scales ();
	callbacks_update_statusbar_coordinates (screen.last_x, screen.last_y);

	if (screen.tool == MEASURE)
		callbacks_update_statusbar_measured_distance (
				screen.measure_last_x,
				screen.measure_last_y);

	isChanging = FALSE;
}


void open_project(char *project_filename)
{

/* TODO: check if layers is modified and show it to user. */

	if (mainProject->last_loaded >= 0
	&&  !interface_get_alert_dialog_response (
		_("Do you want to close any open layers and load "
		"an existing project?"),
		_("Loading a project will cause all currently open "
		"layers to be closed. Any unsaved changes "
		"will be lost."),
		FALSE, NULL, GTK_STOCK_CLOSE, GTK_STOCK_CANCEL)) {

			return;
	}

	/* Update the last folder */
	g_free (mainProject->path);
	mainProject->path = g_strdup(project_filename);

	gerbv_unload_all_layers (mainProject);
	main_open_project_from_filename (mainProject, project_filename);
}


callbacks_invert_layer_clicked  (GtkButton *button, gpointer   user_data) {
	gint index=callbacks_get_selected_row_index();
	if (index < 0) {
		show_no_layers_warning ();
		return;
	}
	mainProject->file[index]->transform.inverted = !mainProject->file[index]->transform.inverted;
	render_refresh_rendered_image_on_screen ();
	callbacks_update_layer_tree ();
}


callbacks_move_objects_clicked (GtkButton *button, gpointer   user_data){
	/* for testing, just hard code in some translations here */
	gerbv_image_move_selected_objects (screen.selectionInfo.selectedNodeArray, -0.050, 0.050);
	callbacks_update_layer_tree();
	selection_clear (&screen.selectionInfo);
	update_selected_object_message (FALSE);
	render_refresh_rendered_image_on_screen ();
}


update_selected_object_message (gboolean userTriedToSelect)
{
	if (screen.tool != POINTER)
		return;

	gint selectionLength = selection_length (&screen.selectionInfo);

	if (selectionLength == 0) {
		if (userTriedToSelect) {
			/* Update status bar message to make sure the user
			 * knows about needing to select the layer */
			gchar *str = g_new(gchar, MAX_DISTLEN);
			utf8_strncpy(str,
					_("No object selected. Objects can "
					"only be selected in the active "
					"layer."),
					MAX_DISTLEN - 7);
			utf8_snprintf(screen.statusbar.diststr, MAX_DISTLEN,
					"<b>%s</b>", str);
			g_free(str);
		} else {
			utf8_strncpy(screen.statusbar.diststr,
					_("Click to select objects in the "
					"active layer. Middle click and drag "
					"to pan."),
					MAX_DISTLEN);
		}
	} else {
		utf8_snprintf(screen.statusbar.diststr, MAX_DISTLEN,
				ngettext("%d object are currently selected",
					"%d objects are currently selected",
					selectionLength), selectionLength);
	}

	callbacks_update_statusbar();
}


callbacks_print_activate (GtkMenuItem *menuitem, gpointer user_data)
{
	GtkPrintOperation *print;
	/*GtkPrintOperationResult res;*/

	print = gtk_print_operation_new ();

	g_signal_connect (print, "begin_print", G_CALLBACK (callbacks_begin_print), NULL);
	g_signal_connect (print, "draw_page", G_CALLBACK (callbacks_print_render_page), NULL);

	//GtkPrintSettings *pSettings = gtk_print_operation_get_print_settings (print);

	(void) gtk_print_operation_run (print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
	                              (GtkWindow *) screen.win.topLevelWindow , NULL);

	g_object_unref (print);
}


callbacks_remove_layer_button_clicked (GtkButton *button, gpointer user_data)
{
	gint index = callbacks_get_selected_row_index ();

	if ((index >= 0) && (index <= mainProject->last_loaded)) {
		render_remove_selected_objects_belonging_to_layer (
				&screen.selectionInfo,
				mainProject->file[index]->image);
		update_selected_object_message (FALSE);

		gerbv_unload_layer (mainProject, index);
		callbacks_update_layer_tree ();

		index = MAX(0, index - 1);
		callbacks_select_layer_row (index);

		if (screenRenderInfo.renderType <= GERBV_RENDER_TYPE_GDK_XOR) {
			render_refresh_rendered_image_on_screen ();
		} else {
			render_recreate_composite_surface (screen.drawing_area);
			callbacks_force_expose_event_for_screen ();
		}
	}
}


check_align_files_possibility (gerbv_selection_info_t *sel_info)
{
	gerbv_fileinfo_t **f = mainProject->file;
	GtkMenuItem **menu_items = (GtkMenuItem **) screen.win.curEditAlingItem;
	gerbv_selection_item_t si[2];
	int id[2] = {-1, -1};
	int i;

	/* If has two objects, then can do files aligning */
	if (selection_length (sel_info) == 2) {
		si[0] = selection_get_item_by_index(sel_info, 0);
		si[1] = selection_get_item_by_index(sel_info, 1);

		for (i = 0; i <= mainProject->last_loaded; i++) {
			if (f[i]->image == si[0].image)
				id[0] = i;

			if (f[i]->image == si[1].image)
				id[1] = i;
		}

		/* Can align if on different files */
		if (id[0]*id[1] >= 0 && id[0] != id[1]) {
			gchar *str;

/* TODO: add color boxes for layers as hint */

			/* Update align menu items */
			str = g_strdup_printf (_("#_%i %s  >  #%i %s"),
					id[0]+1, f[id[0]]->name,
					id[1]+1, f[id[1]]->name);
			gtk_menu_item_set_label (menu_items[0], str);
			g_free (str);

			str = g_strdup_printf (_("#_%i %s  >  #%i %s"),
					id[1]+1, f[id[1]]->name,
					id[0]+1, f[id[0]]->name);
			gtk_menu_item_set_label (menu_items[1], str);
			g_free (str);

			gtk_widget_set_sensitive (
				screen.win.curEditAlingMenuItem, TRUE);

			return TRUE;
		}
	}

	/* Can't align, disable align menu */
	gtk_widget_set_sensitive (screen.win.curEditAlingMenuItem, FALSE);
	gtk_menu_item_set_label (menu_items[0], "");
	gtk_menu_item_set_label (menu_items[1], "");

	return FALSE;
}


callbacks_window_key_release_event (GtkWidget *widget, GdkEventKey *event)
{
	return TRUE;
} /* key_release_event */


callbacks_save_project_activate                       (GtkMenuItem     *menuitem,
                                        gpointer         user_data)
{
  if (mainProject->project)
    main_save_project_from_filename (mainProject, mainProject->project);
  else
    callbacks_generic_save_activate (menuitem, (gpointer) CALLBACKS_SAVE_PROJECT_AS);
  callbacks_update_layer_tree();
  return;
}


static double screen_units(double d)
{
	switch (screen.unit) {
	case GERBV_INS:
		return COORD2INS(d);
		break;
	case GERBV_MILS:
		return COORD2MILS(d);
		break;
	case GERBV_MMS:
		return COORD2MMS(d);
		break;
	}

	return d;
}


callbacks_clear_messages_button_clicked  (GtkButton *button, gpointer   user_data) {
	GtkTextBuffer *textbuffer;
	GtkTextIter start, end;

	screen.length_sum = 0;

	textbuffer = gtk_text_view_get_buffer((GtkTextView*)screen.win.messageTextView);
	gtk_text_buffer_get_start_iter(textbuffer, &start);
	gtk_text_buffer_get_end_iter(textbuffer, &end);
	gtk_text_buffer_delete (textbuffer, &start, &end);
}


void callbacks_update_scrollbar_limits (void){
	gerbv_render_info_t tempRenderInfo = {0, 0, 0, 0,
		GERBV_RENDER_TYPE_CAIRO_HIGH_QUALITY,
		screenRenderInfo.displayWidth,
		screenRenderInfo.displayHeight};

	GtkAdjustment *hAdjust = (GtkAdjustment *)screen.win.hAdjustment;
	GtkAdjustment *vAdjust = (GtkAdjustment *)screen.win.vAdjustment;
	gerbv_render_zoom_to_fit_display (mainProject, &tempRenderInfo);
	hAdjust->lower = tempRenderInfo.lowerLeftX;
	hAdjust->page_increment = hAdjust->page_size;
	hAdjust->step_increment = hAdjust->page_size / 10.0;
	vAdjust->lower = tempRenderInfo.lowerLeftY;
	vAdjust->page_increment = vAdjust->page_size;
	vAdjust->step_increment = vAdjust->page_size / 10.0;
	hAdjust->upper = tempRenderInfo.lowerLeftX + (tempRenderInfo.displayWidth / tempRenderInfo.scaleFactorX);
	hAdjust->page_size = screenRenderInfo.displayWidth / screenRenderInfo.scaleFactorX;
	vAdjust->upper = tempRenderInfo.lowerLeftY + (tempRenderInfo.displayHeight / tempRenderInfo.scaleFactorY);
	vAdjust->page_size = screenRenderInfo.displayHeight / screenRenderInfo.scaleFactorY;
	callbacks_update_scrollbar_positions ();
}


static const char *screen_units_str(void)
{
	/* NOTE: in order of gerbv_gui_unit_t */
	const char *units_str[] = {N_("mil"), N_("mm"), N_("in")};

	return _(units_str[screen.unit]);
}


analyze_window_size_store(GtkWidget *win, gpointer user_data)
{
	gint32 xy[2];
	GVariant *var;
	gboolean is_max;

	if (!screen.settings)
		return;

	is_max = FALSE != (GDK_WINDOW_STATE_MAXIMIZED &
			gdk_window_get_state (gtk_widget_get_window (win)));
	if (is_max)
		return;

	gtk_window_get_size (GTK_WINDOW (win), (gint *)xy, (gint *)(xy+1));
	var = g_variant_new_fixed_array (G_VARIANT_TYPE_INT32,
			xy, 2, sizeof (xy[0]));
	g_settings_set_value (screen.settings, "analyze-window-size", var);

	gtk_window_get_position (GTK_WINDOW (win),
			(gint *)xy, (gint *)(xy+1));
	var = g_variant_new_fixed_array (G_VARIANT_TYPE_INT32,
			xy, 2, sizeof (xy[0]));
	g_settings_set_value (screen.settings, "analyze-window-position", var);
}


callbacks_display_object_properties_clicked (GtkButton *button, gpointer user_data)
{
	gint index = callbacks_get_selected_row_index ();
	guint i;

	if (index < 0 || selection_length (&screen.selectionInfo) == 0) {
		interface_show_alert_dialog(_("No object is currently selected"),
			_("Objects must be selected using the pointer tool "
			"before you can view the object properties."),
			FALSE, NULL);
		return;
	}

	for (i = 0; i < selection_length (&screen.selectionInfo); i++){
		gerbv_selection_item_t sItem =
			selection_get_item_by_index (&screen.selectionInfo, i);

		gerbv_net_t *net = sItem.net;
		gerbv_image_t *image = sItem.image;

		if (net->interpolation == GERBV_INTERPOLATION_PAREA_START) {
			/* Spacing for a pretty display */
			if (i != 0)
				g_message (" ");

			g_message (_("Object type: Polygon"));
			parea_report (net, image, mainProject);
			net_layer_file_report (net, image, mainProject);
		} else {
			switch (net->aperture_state) {

			case GERBV_APERTURE_STATE_ON:
			case GERBV_APERTURE_STATE_FLASH:
				/* Spacing for a pretty display */
				if (i != 0)
					g_message (" ");
				break;

			default:
				break;
			}

			aperture_state_report (net, image, mainProject);
		}
	}
	/* Use separator for different report requests */
	g_message ("---------------------------------------");
}


static double line_length(double x0, double y0, double x1, double y1)
{
	double dx = x0 - x1;
	double dy = y0 - y1;

	return hypot(dx, dy);
}


callbacks_begin_print (GtkPrintOperation *operation, GtkPrintContext   *context,
		gpointer user_data) {
	gtk_print_operation_set_n_pages     (operation, 1);
}


static void drill_report(gerbv_aperture_t *apertures[], int aperture_num)
{
	gerbv_aperture_type_t type = apertures[aperture_num]->type;
	double *params = apertures[aperture_num]->parameter;

	g_message (_("    Tool used: T%d"), aperture_num);
	if (type == GERBV_APTYPE_CIRCLE)
		g_message (_("    Diameter: %g %s"),
				screen_units(params[0]),
				screen_units_str());
}


callbacks_change_layer_color_clicked  (GtkButton *button, gpointer   user_data) {
	gint index=callbacks_get_selected_row_index();
	if (index < 0) {
		show_no_layers_warning ();
		return;
	}
	callbacks_show_color_picker_dialog (index);
}


callbacks_change_tool (GtkButton *button, gpointer   user_data) {
	gint toolNumber = GPOINTER_TO_INT (user_data);

	/* make sure se don't get caught in endless recursion here */
	if (screen.win.updatingTools)
		return;
	screen.win.updatingTools = TRUE;
	gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (screen.win.toolButtonPointer), FALSE);
	gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (screen.win.toolButtonPan), FALSE);
	gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (screen.win.toolButtonZoom), FALSE);
	gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (screen.win.toolButtonMeasure), FALSE);
	switch (toolNumber) {
		case POINTER:
			gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (screen.win.toolButtonPointer), TRUE);
			screen.tool = POINTER;
			screen.state = NORMAL;
			utf8_strncpy(screen.statusbar.diststr,
				_("Click to select objects in the active layer. "
					"Middle click and drag to pan."),
				MAX_DISTLEN);
			break;
		case PAN:
			gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (screen.win.toolButtonPan), TRUE);
			screen.tool = PAN;
			screen.state = NORMAL;
			utf8_strncpy(screen.statusbar.diststr,
				_("Click and drag to pan. Right click and drag to zoom."),
				MAX_DISTLEN);
			break;
		case ZOOM:
			gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (screen.win.toolButtonZoom), TRUE);
			screen.tool = ZOOM;
			screen.state = NORMAL;
			utf8_strncpy(screen.statusbar.diststr,
				_("Click and drag to zoom in. Shift+click to zoom out."),
				MAX_DISTLEN);
			break;

		case MEASURE:
			gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (screen.win.toolButtonMeasure), TRUE);
			screen.tool = MEASURE;
			screen.state = NORMAL;
			utf8_strncpy(screen.statusbar.diststr,
				_("Click and drag to measure a distance or select two apertures."),
				MAX_DISTLEN);

			/* To not show previous measure drag-line */
			screen.measure_start_x = 0;
			screen.measure_start_y = 0;
			screen.measure_stop_x =  0;
			screen.measure_stop_y =  0;

			/* If two items are selected, measure they distance */
			if (selection_length (&screen.selectionInfo) == 2) {
				gerbv_selection_item_t item[2];
				gerbv_net_t *net[2];

				item[0] = selection_get_item_by_index(
						&screen.selectionInfo, 0);
				item[1] = selection_get_item_by_index(
						&screen.selectionInfo, 1);
				net[0] = item[0].net;
				net[1] = item[1].net;

				if ((net[0]->aperture_state ==
						net[1]->aperture_state)
				 && (net[0]->aperture_state ==
						GERBV_APERTURE_STATE_FLASH)) {
					screen.measure_start_x = net[0]->stop_x;
					screen.measure_start_y = net[0]->stop_y;
					gerbv_transform_coord_for_image(
							&screen.measure_start_x,
							&screen.measure_start_y,
							item[0].image,
							mainProject);

					screen.measure_stop_x = net[1]->stop_x;
					screen.measure_stop_y = net[1]->stop_y;
					gerbv_transform_coord_for_image(
							&screen.measure_stop_x,
							&screen.measure_stop_y,
							item[1].image,
							mainProject);

					render_draw_measure_distance();
				}
			}
			break;
		default:
			break;
	}

	callbacks_switch_to_normal_tool_cursor (toolNumber);
	callbacks_update_statusbar();
	screen.win.updatingTools = FALSE;
	callbacks_force_expose_event_for_screen();
}