X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Fui%2Fgui%2Fpsppire-output-view.c;h=6d704ae1bccf1bba19eaa468cbddaf6eacbca03f;hb=29917c4f5908454803e663d2ad78bca4bc35e805;hp=6b9445b404e6991ff097582b95e825d45394f4d2;hpb=5b5099296b3c7212623991de8920e1459e234922;p=pspp diff --git a/src/ui/gui/psppire-output-view.c b/src/ui/gui/psppire-output-view.c index 6b9445b404..6d704ae1bc 100644 --- a/src/ui/gui/psppire-output-view.c +++ b/src/ui/gui/psppire-output-view.c @@ -1,5 +1,5 @@ /* PSPPIRE - a graphical user interface for PSPP. - Copyright (C) 2008-2014 Free Software Foundation. + Copyright (C) 2008-2015, 2016 Free Software Foundation. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,23 +18,22 @@ #include "ui/gui/psppire-output-view.h" +#include #include #include #include "libpspp/assertion.h" #include "libpspp/string-map.h" -#include "output/cairo.h" +#include "output/cairo-fsm.h" +#include "output/cairo-pager.h" #include "output/driver-provider.h" #include "output/driver.h" -#include "output/chart-item.h" -#include "output/message-item.h" #include "output/output-item.h" -#include "output/table-item.h" -#include "output/text-item.h" +#include "output/pivot-table.h" #include "gl/c-xvasprintf.h" #include "gl/minmax.h" -#include "gl/tmpdir.h" +#include "gl/clean-temp.h" #include "gl/xalloc.h" #include @@ -48,27 +47,31 @@ struct output_view_item struct psppire_output_view { - struct xr_driver *xr; - int font_height; + struct xr_fsm_style *style; + int object_spacing; GtkLayout *output; int render_width; int max_width; - int y; + glong y; - struct string_map render_opts; GtkTreeView *overview; - GtkTreeIter cur_command; - bool in_command; + GtkTreePath *cur_group; GtkWidget *toplevel; + guint buttontime; /* Time of the button event */ + struct output_view_item *items; size_t n_items, allocated_items; + struct output_view_item *selected_item; /* Variables pertaining to printing */ GtkPrintSettings *print_settings; - struct xr_driver *print_xrd; + + struct xr_fsm_style *fsm_style; + struct xr_page_style *page_style; + struct xr_pager *pager; int print_item; int print_n_pages; gboolean paginated; @@ -76,279 +79,415 @@ struct psppire_output_view enum { - COL_NAME, /* Table name. */ + COL_LABEL, /* Output item label. */ COL_ADDR, /* Pointer to the table */ - COL_Y, /* Y position of top of name. */ + COL_Y, /* Y position of top of object. */ N_COLS }; -static void on_dwgarea_realize (GtkWidget *widget, gpointer data); +static GtkTargetList *build_target_list (const struct output_item *item); +static void clipboard_get_cb (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + guint info, + gpointer data); +/* Draws a white background on the GtkLayout to match the white background of + each of the output items. */ static gboolean -expose_event_callback (GtkWidget *widget, GdkEventExpose *event, gpointer data) +layout_draw_callback (GtkWidget *widget, cairo_t *cr, gpointer data) { - struct psppire_output_view *view = data; - struct xr_rendering *r = g_object_get_data (G_OBJECT (widget), "rendering"); - cairo_t *cr = gdk_cairo_create (widget->window); + int width = gtk_widget_get_allocated_width (widget); + int height = gtk_widget_get_allocated_height (widget); + GtkStyleContext *context = gtk_widget_get_style_context (widget); + gtk_render_background (context, cr, 0, 0, width, height); + return FALSE; /* Continue drawing the GtkDrawingAreas. */ +} - const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (view->output)); +static gboolean +draw_callback (GtkWidget *widget, cairo_t *cr, gpointer data) +{ + GdkRectangle clip; + if (!gdk_cairo_get_clip_rectangle (cr, &clip)) + return TRUE; + + struct xr_fsm *fsm = g_object_get_data (G_OBJECT (widget), "fsm"); + + /* Draw the background based on the state of the widget + which can be selected or not selected */ + GtkStyleContext *context = gtk_widget_get_style_context (widget); + gtk_render_background (context, cr, clip.x, clip.y, + clip.x + clip.width, clip.y + clip.height); + /* Select the default foreground color based on current style + and state of the widget */ + GtkStateFlags state = gtk_widget_get_state_flags (widget); + GdkRGBA color; + gtk_style_context_get_color (context, state, &color); + cairo_set_source_rgba (cr, color.red, color.green, color.blue, color.alpha); + xr_fsm_draw_region (fsm, cr, clip.x, clip.y, clip.width, clip.height); - PangoFontDescription *font_desc; - char *font_name; + return TRUE; +} - gchar *fgc = - gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (view->output))]); +static void +free_fsm (gpointer fsm_) +{ + struct xr_fsm *fsm = fsm_; + xr_fsm_destroy (fsm); +} - string_map_replace (&view->render_opts, "foreground-color", fgc); +static struct xr_fsm_style * +get_xr_fsm_style (struct psppire_output_view *view) +{ + GtkStyleContext *context + = gtk_widget_get_style_context (GTK_WIDGET (view->output)); + GtkStateFlags state = gtk_widget_get_state_flags (GTK_WIDGET (view->output)); + + int xr_width = view->render_width * 1000; + + PangoFontDescription *pf; + gtk_style_context_get (context, state, "font", &pf, NULL); + + struct xr_fsm_style *style = xmalloc (sizeof *style); + *style = (struct xr_fsm_style) { + .ref_cnt = 1, + .size = { [TABLE_HORZ] = xr_width, [TABLE_VERT] = INT_MAX }, + .min_break = { [TABLE_HORZ] = xr_width / 2, [TABLE_VERT] = 0 }, + .font = pf, + .use_system_colors = true, + .object_spacing = XR_POINT * 12, + .font_resolution = 96.0, + }; + + return style; +} + +/* Return the horizontal position to place a widget whose + width is CHILD_WIDTH */ +static gint +get_xpos (const struct psppire_output_view *view, gint child_width) +{ + GdkWindow *gdkw = gtk_widget_get_window (GTK_WIDGET (view->output)); + guint w = gdk_window_get_width (gdkw); + int gutter = 0; + g_object_get (view->output, "border-width", &gutter, NULL); + return (gtk_widget_get_direction (GTK_WIDGET (view->output)) == GTK_TEXT_DIR_RTL) ? w - child_width - gutter: gutter; +} - free (fgc); +static struct output_view_item * +find_selected_item (struct psppire_output_view *view) +{ + struct output_view_item *item = NULL; + if (view == NULL) + return NULL; + if (view->items == NULL) + return NULL; + + for (item = view->items; item < &view->items[view->n_items]; item++) + { + GtkWidget *widget = GTK_WIDGET (item->drawing_area); + if (GTK_IS_WIDGET (widget)) + { + GtkStateFlags state = gtk_widget_get_state_flags (widget); + if (state & GTK_STATE_FLAG_SELECTED) + return item; + } + } + return NULL; +} - /* Use GTK+ default font as proportional font. */ - font_name = pango_font_description_to_string (style->font_desc); - string_map_replace (&view->render_opts, "prop-font", font_name); - g_free (font_name); - /* Derived emphasized font from proportional font. */ - font_desc = pango_font_description_copy (style->font_desc); - pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC); - font_name = pango_font_description_to_string (font_desc); - string_map_replace (&view->render_opts, "emph-font", font_name); - g_free (font_name); - pango_font_description_free (font_desc); +static void +set_copy_action (struct psppire_output_view *view, + gboolean state) +{ + GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view->output)); + GAction *copy_action = g_action_map_lookup_action (G_ACTION_MAP (toplevel), + "copy"); + g_object_set (copy_action, + "enabled", state, + NULL); +} - xr_rendering_apply_options (r, &view->render_opts); +static void +clear_selection (struct psppire_output_view *view) +{ + if (view == NULL) + return; + struct output_view_item *item = find_selected_item (view); + if (item == NULL) + return; + set_copy_action (view, FALSE); + GtkWidget *widget = GTK_WIDGET (item->drawing_area); + if (GTK_IS_WIDGET (widget)) + { + gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_SELECTED); + gtk_widget_queue_draw (widget); + } +} - xr_rendering_draw (r, cr, event->area.x, event->area.y, - event->area.width, event->area.height); - cairo_destroy (cr); +static gboolean +off_item_button_press_event_cb (GtkWidget *widget, + GdkEventButton *event, + struct psppire_output_view *view) +{ + /* buttontime is set by button_press_event_cb + If our event->time is equal to the time from the + button_press_event_cb, then we handle the same event. + In that case we must not clear the selection because + it was just set by button_press_event_cb from the item */ + if (event->time != view->buttontime) + clear_selection (view); + return FALSE; /* Forward the event -> DragNDrop */ +} - return TRUE; +static gboolean +button_press_event_cb (GtkWidget *widget, + GdkEventButton *event, + struct psppire_output_view *view) +{ + view->buttontime = event->time; + clear_selection (view); + set_copy_action (view, TRUE); + gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_SELECTED, FALSE); + gtk_widget_queue_draw (widget); + return FALSE; /* Forward Event -> off_item will trigger */ } static void -free_rendering (gpointer rendering_) +drag_data_get_cb (GtkWidget *widget, GdkDragContext *context, + GtkSelectionData *selection_data, + guint target_type, guint time, + struct psppire_output_view *view) { - struct xr_rendering *rendering = rendering_; - xr_rendering_destroy (rendering); + view->selected_item = find_selected_item (view); + clipboard_get_cb (NULL, selection_data, target_type, view); } static void -create_xr (struct psppire_output_view *view) -{ - const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (view->output)); - struct text_item *text_item; - PangoFontDescription *font_desc; - struct xr_rendering *r; - char *font_name; - int font_width; - cairo_t *cr; - gchar *fgc; - - cr = gdk_cairo_create (GTK_WIDGET (view->output)->window); - - /* Set the widget's text color as the foreground color for the output driver */ - fgc = gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (view->output))]); - - string_map_insert (&view->render_opts, "foreground-color", fgc); - g_free (fgc); - - /* Use GTK+ default font as proportional font. */ - font_name = pango_font_description_to_string (style->font_desc); - string_map_insert (&view->render_opts, "prop-font", font_name); - g_free (font_name); - - /* Derived emphasized font from proportional font. */ - font_desc = pango_font_description_copy (style->font_desc); - pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC); - font_name = pango_font_description_to_string (font_desc); - string_map_insert (&view->render_opts, "emph-font", font_name); - g_free (font_name); - pango_font_description_free (font_desc); - - /* Pretend that the "page" has a reasonable width and a very big length, - so that most tables can be conveniently viewed on-screen with vertical - scrolling only. (The length should not be increased very much because - it is already close enough to INT_MAX when expressed as thousands of a - point.) */ - string_map_insert_nocopy (&view->render_opts, xstrdup ("paper-size"), - xasprintf ("%dx1000000pt", view->render_width)); - string_map_insert (&view->render_opts, "left-margin", "0"); - string_map_insert (&view->render_opts, "right-margin", "0"); - string_map_insert (&view->render_opts, "top-margin", "0"); - string_map_insert (&view->render_opts, "bottom-margin", "0"); - - view->xr = xr_driver_create (cr, &view->render_opts); - - text_item = text_item_create (TEXT_ITEM_PARAGRAPH, "X"); - r = xr_rendering_create (view->xr, text_item_super (text_item), cr); - xr_rendering_measure (r, &font_width, &view->font_height); - /* xr_rendering_destroy (r); */ - text_item_unref (text_item); +create_drawing_area (struct psppire_output_view *view, + GtkWidget *drawing_area, struct xr_fsm *r, + int tw, int th, const struct output_item *item) +{ + g_object_set_data_full (G_OBJECT (drawing_area), + "fsm", r, free_fsm); + g_signal_connect (drawing_area, "button-press-event", + G_CALLBACK (button_press_event_cb), view); + gtk_widget_add_events (drawing_area, GDK_BUTTON_PRESS_MASK); + + { /* Drag and Drop */ + GtkTargetList *tl = build_target_list (item); + g_assert (tl); + gtk_drag_source_set (drawing_area, GDK_BUTTON1_MASK, NULL, 0, GDK_ACTION_COPY); + gtk_drag_source_set_target_list (drawing_area, tl); + gtk_target_list_unref (tl); + g_signal_connect (drawing_area, "drag-data-get", + G_CALLBACK (drag_data_get_cb), view); + } + GtkStyleContext *context = gtk_widget_get_style_context (drawing_area); + gtk_style_context_add_class (context, + GTK_STYLE_CLASS_VIEW); + g_signal_connect (drawing_area, "draw", + G_CALLBACK (draw_callback), view); - cairo_destroy (cr); + gtk_widget_set_size_request (drawing_area, tw, th); + gint xpos = get_xpos (view, tw); + + gtk_layout_put (view->output, drawing_area, xpos, view->y); + + gtk_widget_show (drawing_area); } static void rerender (struct psppire_output_view *view) { struct output_view_item *item; - cairo_t *cr; + GdkWindow *gdkw = gtk_widget_get_window (GTK_WIDGET (view->output)); - if (!view->n_items) + if (!view->n_items || ! gdkw) return; - string_map_clear (&view->render_opts); - xr_driver_destroy (view->xr); - create_xr (view); + if (!view->style) + view->style = get_xr_fsm_style (view); - cr = gdk_cairo_create (GTK_WIDGET (view->output)->window); + GdkWindow *win = gtk_layout_get_bin_window (view->output); + cairo_region_t *region = gdk_window_get_visible_region (win); + GdkDrawingContext *ctx = gdk_window_begin_draw_frame (win, region); + cairo_t *cr = gdk_drawing_context_get_cairo_context (ctx); view->y = 0; view->max_width = 0; for (item = view->items; item < &view->items[view->n_items]; item++) { - struct xr_rendering *r; + struct xr_fsm *r; + GtkAllocation alloc; int tw, th; if (view->y > 0) - view->y += view->font_height / 2; + view->y += view->object_spacing; + + if (item->item->type == OUTPUT_ITEM_GROUP_OPEN) + continue; - r = xr_rendering_create (view->xr, item->item, cr); + r = xr_fsm_create_for_scrolling (item->item, view->style, cr); if (r == NULL) { g_warn_if_reached (); continue; } - xr_rendering_measure (r, &tw, &th); - g_object_set_data_full (G_OBJECT (item->drawing_area), - "rendering", r, free_rendering); - gtk_widget_set_size_request (item->drawing_area, tw, th); - gtk_layout_move (view->output, item->drawing_area, 0, view->y); + xr_fsm_measure (r, cr, &tw, &th); + + gint xpos = get_xpos (view, tw); + + if (!item->drawing_area) + { + item->drawing_area = gtk_drawing_area_new (); + create_drawing_area (view, item->drawing_area, r, tw, th, item->item); + } + else + { + g_object_set_data_full (G_OBJECT (item->drawing_area), + "fsm", r, free_fsm); + gtk_widget_set_size_request (item->drawing_area, tw, th); + gtk_layout_move (view->output, item->drawing_area, xpos, view->y); + } + + if (item->item->type == OUTPUT_ITEM_TABLE) + gtk_widget_set_tooltip_text (item->drawing_area, + item->item->table->notes); + + { + gint minw; + gint minh; + /* This code probably doesn't bring us anthing, but Gtk + shows warnings if get_preferred_width/height is not + called before the size_allocate below is called. */ + gtk_widget_get_preferred_width (item->drawing_area, &minw, NULL); + gtk_widget_get_preferred_height (item->drawing_area, &minh, NULL); + if (th > minh) th = minh; + if (tw > minw) tw = minw; + } + alloc.x = xpos; + alloc.y = view->y; + alloc.width = tw; + alloc.height = th; + + gtk_widget_size_allocate (item->drawing_area, &alloc); if (view->max_width < tw) view->max_width = tw; view->y += th; } - gtk_layout_set_size (view->output, view->max_width, view->y); - cairo_destroy (cr); + gtk_layout_set_size (view->output, + view->max_width + view->object_spacing, + view->y + view->object_spacing); + + gdk_window_end_draw_frame (win, ctx); + cairo_region_destroy (region); } + void psppire_output_view_put (struct psppire_output_view *view, const struct output_item *item) { + struct output_view_item *view_item; GtkWidget *drawing_area; - struct xr_rendering *r; - struct string name; - GtkTreeStore *store; - GtkTreePath *path; - GtkTreeIter iter; - cairo_t *cr; int tw, th; - if (is_text_item (item)) + if (item->type == OUTPUT_ITEM_GROUP_CLOSE) { - const struct text_item *text_item = to_text_item (item); - enum text_item_type type = text_item_get_type (text_item); - const char *text = text_item_get_text (text_item); - - if (type == TEXT_ITEM_COMMAND_CLOSE) + if (view->cur_group) { - view->in_command = false; - return; + if (!gtk_tree_path_up (view->cur_group)) + { + gtk_tree_path_free (view->cur_group); + view->cur_group = NULL; + } } - else if (text[0] == '\0') + return; + } + else if (item->type == OUTPUT_ITEM_TEXT) + { + char *text = text_item_get_plain_text (item); + bool text_is_empty = text[0] == '\0'; + free (text); + if (text_is_empty) return; } if (view->n_items >= view->allocated_items) view->items = x2nrealloc (view->items, &view->allocated_items, sizeof *view->items); - view->items[view->n_items].item = output_item_ref (item); - view->items[view->n_items].drawing_area = drawing_area = gtk_drawing_area_new (); - view->n_items++; - - cr = gdk_cairo_create (GTK_WIDGET (view->output)->window); - if (view->xr == NULL) - create_xr (view); - - if (view->y > 0) - view->y += view->font_height / 2; + view_item = &view->items[view->n_items++]; + view_item->item = output_item_ref (item); + view_item->drawing_area = NULL; + + GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (view->output)); + if (item->type == OUTPUT_ITEM_GROUP_OPEN) + tw = th = 0; + else if (win) + { + view_item->drawing_area = drawing_area = gtk_drawing_area_new (); - r = xr_rendering_create (view->xr, item, cr); - if (r == NULL) - goto done; + if (!view->style) + view->style = get_xr_fsm_style (view); - xr_rendering_measure (r, &tw, &th); + cairo_region_t *region = gdk_window_get_visible_region (win); + GdkDrawingContext *ctx = gdk_window_begin_draw_frame (win, region); + cairo_t *cr = gdk_drawing_context_get_cairo_context (ctx); - g_object_set_data_full (G_OBJECT (drawing_area), "rendering", r, free_rendering); - g_signal_connect (drawing_area, "realize", - G_CALLBACK (on_dwgarea_realize), view); - g_signal_connect (drawing_area, "expose_event", - G_CALLBACK (expose_event_callback), view); + if (view->y > 0) + view->y += view->object_spacing; - gtk_widget_set_size_request (drawing_area, tw, th); - gtk_layout_put (view->output, drawing_area, 0, view->y); + struct xr_fsm *r = xr_fsm_create_for_scrolling (item, view->style, cr); + if (r == NULL) + { + gdk_window_end_draw_frame (win, ctx); + cairo_region_destroy (region); + return; + } - gtk_widget_show (drawing_area); + xr_fsm_measure (r, cr, &tw, &th); + create_drawing_area (view, drawing_area, r, tw, th, item); + gdk_window_end_draw_frame (win, ctx); + cairo_region_destroy (region); + } + else + tw = th = 0; - if (view->overview - && (!is_text_item (item) - || text_item_get_type (to_text_item (item)) != TEXT_ITEM_SYNTAX - || !view->in_command)) + if (view->overview) { - store = GTK_TREE_STORE (gtk_tree_view_get_model (view->overview)); + GtkTreeStore *store = GTK_TREE_STORE ( + gtk_tree_view_get_model (view->overview)); - ds_init_empty (&name); - if (is_text_item (item) - && text_item_get_type (to_text_item (item)) == TEXT_ITEM_COMMAND_OPEN) - { - gtk_tree_store_append (store, &iter, NULL); - view->cur_command = iter; /* XXX shouldn't save a GtkTreeIter */ - view->in_command = true; - } + /* Create a new node in the tree and puts a reference to it in 'iter'. */ + GtkTreeIter iter; + GtkTreeIter parent; + if (view->cur_group + && gtk_tree_path_get_depth (view->cur_group) > 0 + && gtk_tree_model_get_iter (GTK_TREE_MODEL (store), + &parent, view->cur_group)) + gtk_tree_store_append (store, &iter, &parent); else - { - GtkTreeIter *p = view->in_command ? &view->cur_command : NULL; - gtk_tree_store_append (store, &iter, p); - } + gtk_tree_store_append (store, &iter, NULL); - ds_clear (&name); - if (is_text_item (item)) - ds_put_cstr (&name, text_item_get_text (to_text_item (item))); - else if (is_message_item (item)) - { - const struct message_item *msg_item = to_message_item (item); - const struct msg *msg = message_item_get_msg (msg_item); - ds_put_format (&name, "%s: %s", _("Message"), - msg_severity_to_string (msg->severity)); - } - else if (is_table_item (item)) + if (item->type == OUTPUT_ITEM_GROUP_OPEN) { - const char *title = table_item_get_title (to_table_item (item)); - if (title != NULL) - ds_put_format (&name, "Table: %s", title); - else - ds_put_cstr (&name, "Table"); - } - else if (is_chart_item (item)) - { - const char *s = chart_item_get_title (to_chart_item (item)); - if (s != NULL) - ds_put_format (&name, "Chart: %s", s); - else - ds_put_cstr (&name, "Chart"); + gtk_tree_path_free (view->cur_group); + view->cur_group = gtk_tree_model_get_path (GTK_TREE_MODEL (store), + &iter); } + gtk_tree_store_set (store, &iter, - COL_NAME, ds_cstr (&name), + COL_LABEL, output_item_get_label (item), COL_ADDR, item, COL_Y, view->y, -1); - ds_destroy (&name); - path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter); + GtkTreePath *path = gtk_tree_model_get_path ( + GTK_TREE_MODEL (store), &iter); gtk_tree_view_expand_row (view->overview, path, TRUE); gtk_tree_path_free (path); } @@ -358,9 +497,6 @@ psppire_output_view_put (struct psppire_output_view *view, view->y += th; gtk_layout_set_size (view->output, view->max_width, view->y); - -done: - cairo_destroy (cr); } static void @@ -383,9 +519,9 @@ on_row_activate (GtkTreeView *overview, y = g_value_get_long (&value); g_value_unset (&value); - vadj = gtk_layout_get_vadjustment (view->output); - min = vadj->lower; - max = vadj->upper - vadj->page_size; + vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (view->output)); + min = gtk_adjustment_get_lower (vadj); + max = gtk_adjustment_get_upper (vadj) - gtk_adjustment_get_page_size (vadj); if (y < min) y = min; else if (y > max) @@ -394,32 +530,22 @@ on_row_activate (GtkTreeView *overview, } static void -copy_base_to_bg (GtkWidget *dest, GtkWidget *src) +on_style_updated (GtkWidget *toplevel, struct psppire_output_view *view) { - int i; - for (i = 0; i < 5; ++i) + if (!view->n_items || !gtk_widget_get_window (GTK_WIDGET (view->output))) + return; + + /* GTK+ fires this signal for trivial changes like the mouse moving in or out + of the window. Check whether the actual fsm options changed and + re-render only if they did. */ + struct xr_fsm_style *style = get_xr_fsm_style (view); + if (!view->style || !xr_fsm_style_equals (style, view->style)) { - gtk_widget_modify_bg (dest, i, >k_widget_get_style (src)->base[i]); - gtk_widget_modify_fg (dest, i, >k_widget_get_style (src)->text[i]); + xr_fsm_style_unref (view->style); + view->style = xr_fsm_style_ref (style); + rerender (view); } -} - -/* Copy the base style from the parent widget to the container and all its - children. We do this because the container's primary purpose is to display - text. This way psppire appears to follow the chosen gnome theme. */ -static void -on_style_set (GtkWidget *toplevel, GtkStyle *prev, - struct psppire_output_view *view) -{ - copy_base_to_bg (GTK_WIDGET (view->output), toplevel); - gtk_container_foreach (GTK_CONTAINER (view->output), - (GtkCallback) copy_base_to_bg, view->output); -} - -static void -on_dwgarea_realize (GtkWidget *dwg_area, gpointer data) -{ - copy_base_to_bg (dwg_area, gtk_widget_get_toplevel (dwg_area)); + xr_fsm_style_unref (style); } enum { @@ -427,14 +553,22 @@ enum { SELECT_FMT_TEXT, SELECT_FMT_UTF8, SELECT_FMT_HTML, + SELECT_FMT_SVG, + SELECT_FMT_IMG, SELECT_FMT_ODT }; -/* GNU Hurd doesn't have PATH_MAX. Use a fallback. - Temporary directory names are usually not that long. */ -#ifndef PATH_MAX -# define PATH_MAX 1024 -#endif +static void +clear_rectangle (cairo_surface_t *surface, + double x0, double y0, double x1, double y1) +{ + cairo_t *cr = cairo_create (surface); + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_new_path (cr); + cairo_rectangle (cr, x0, y0, x1 - x0, y1 - y0); + cairo_fill (cr); + cairo_destroy (cr); +} static void clipboard_get_cb (GtkClipboard *clipboard, @@ -447,25 +581,20 @@ clipboard_get_cb (GtkClipboard *clipboard, gsize length; gchar *text = NULL; struct output_driver *driver = NULL; - char dirname[PATH_MAX], *filename; + char *filename; struct string_map options; + struct temp_dir *td = NULL; - GtkTreeSelection *sel = gtk_tree_view_get_selection (view->overview); - GtkTreeModel *model = gtk_tree_view_get_model (view->overview); - - GList *rows = gtk_tree_selection_get_selected_rows (sel, &model); - GList *n = rows; - - if ( n == NULL) + if (view->selected_item == NULL) return; - if (path_search (dirname, sizeof dirname, NULL, NULL, true) - || mkdtemp (dirname) == NULL) + td = create_temp_dir ("pspp", NULL, false); + if (td == NULL) { msg_error (errno, _("failed to create temporary directory during clipboard operation")); return; } - filename = xasprintf ("%s/clip.tmp", dirname); + filename = xasprintf ("%s/clip.tmp", td->dir_name); string_map_init (&options); string_map_insert (&options, "output-file", filename); @@ -478,6 +607,7 @@ clipboard_get_cb (GtkClipboard *clipboard, case SELECT_FMT_TEXT: string_map_insert (&options, "format", "txt"); + string_map_insert (&options, "width", "1000"); break; case SELECT_FMT_HTML: @@ -486,6 +616,11 @@ clipboard_get_cb (GtkClipboard *clipboard, string_map_insert (&options, "css", "false"); break; + case SELECT_FMT_SVG: + case SELECT_FMT_IMG: + /* see below */ + break; + case SELECT_FMT_ODT: string_map_insert (&options, "format", "odt"); break; @@ -496,40 +631,66 @@ clipboard_get_cb (GtkClipboard *clipboard, break; } - driver = output_driver_create (&options); - if (driver == NULL) - goto finish; - - while (n) + if ((info == SELECT_FMT_IMG) || + (info == SELECT_FMT_SVG) ) { - GtkTreePath *path = n->data ; - GtkTreeIter iter; - struct output_item *item ; - - gtk_tree_model_get_iter (model, &iter, path); - gtk_tree_model_get (model, &iter, COL_ADDR, &item, -1); - - driver->class->submit (driver, item); - - n = n->next; + GtkWidget *widget = view->selected_item->drawing_area; + struct xr_fsm *fsm = g_object_get_data (G_OBJECT (widget), "fsm"); + + GdkWindow *win = gtk_layout_get_bin_window (view->output); + cairo_region_t *region = gdk_window_get_visible_region (win); + GdkDrawingContext *ctx = gdk_window_begin_draw_frame (win, region); + cairo_t *cr = gdk_drawing_context_get_cairo_context (ctx); + + int w, h; + xr_fsm_measure (fsm, cr, &w, &h); + + gdk_window_end_draw_frame (win, ctx); + cairo_region_destroy (region); + + cairo_surface_t *surface + = (info == SELECT_FMT_SVG + ? cairo_svg_surface_create (filename, w, h) + : cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h)); + clear_rectangle (surface, 0, 0, w, h); + cairo_t *cr2 = cairo_create (surface); + xr_fsm_draw_all (fsm, cr2); + cairo_destroy (cr2); + if (info == SELECT_FMT_IMG) + { + GdkPixbuf *pixbuf = gdk_pixbuf_get_from_surface (surface, + 0, 0, w, h); + if (pixbuf) + { + gtk_selection_data_set_pixbuf (selection_data, pixbuf); + g_object_unref (pixbuf); + } + } + cairo_surface_destroy (surface); } + else + { + driver = output_driver_create (&options); + if (driver == NULL) + goto finish; - if ( driver->class->flush) - driver->class->flush (driver); - + driver->class->submit (driver, view->selected_item->item); - /* Some drivers (eg: the odt one) don't write anything until they - are closed */ - output_driver_destroy (driver); - driver = NULL; + if (driver->class->flush) + driver->class->flush (driver); - if ( g_file_get_contents (filename, &text, &length, NULL) ) - { - gtk_selection_data_set (selection_data, selection_data->target, - 8, - (const guchar *) text, length); + /* Some drivers (eg: the odt one) don't write anything until they + are closed */ + output_driver_destroy (driver); + driver = NULL; } + if (info != SELECT_FMT_IMG + && g_file_get_contents (filename, &text, &length, NULL)) + gtk_selection_data_set (selection_data, + gtk_selection_data_get_target (selection_data), + 8, (const guchar *) text, length); + finish: if (driver != NULL) @@ -539,9 +700,7 @@ clipboard_get_cb (GtkClipboard *clipboard, unlink (filename); free (filename); - rmdir (dirname); - - g_list_free (rows); + cleanup_temp_dir (td); } static void @@ -550,124 +709,140 @@ clipboard_clear_cb (GtkClipboard *clipboard, { } -static const GtkTargetEntry targets[] = { - - { "STRING", 0, SELECT_FMT_TEXT }, - { "TEXT", 0, SELECT_FMT_TEXT }, - { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT }, - { "text/plain", 0, SELECT_FMT_TEXT }, - - { "UTF8_STRING", 0, SELECT_FMT_UTF8 }, - { "text/plain;charset=utf-8", 0, SELECT_FMT_UTF8 }, - - { "text/html", 0, SELECT_FMT_HTML }, +#define CBTARGETS \ +CT ( ctn1, "STRING", 0, SELECT_FMT_TEXT ) \ +CT ( ctn2, "TEXT", 0, SELECT_FMT_TEXT ) \ +CT ( ctn3, "COMPOUND_TEXT", 0, SELECT_FMT_TEXT ) \ +CT ( ctn4, "text/plain", 0, SELECT_FMT_TEXT ) \ +CT ( ctn5, "UTF8_STRING", 0, SELECT_FMT_UTF8 ) \ +CT ( ctn6, "text/plain;charset=utf-8", 0, SELECT_FMT_UTF8 ) \ +CT ( ctn7, "text/html", 0, SELECT_FMT_HTML ) \ +CT ( ctn8, "image/svg+xml", 0, SELECT_FMT_SVG ) + +#define CT(ID, TARGET, FLAGS, INFO) static gchar ID[] = TARGET; +CBTARGETS +#undef CT +static gchar ctnlast[] = "application/vnd.oasis.opendocument.text"; - { "application/vnd.oasis.opendocument.text", 0, SELECT_FMT_ODT } +static const GtkTargetEntry targets[] = { +#define CT(ID, TARGET, FLAGS, INFO) { ID, FLAGS, INFO }, + CBTARGETS +#undef CT + { ctnlast, 0, SELECT_FMT_ODT } }; +static GtkTargetList * +build_target_list (const struct output_item *item) +{ + GtkTargetList *tl = gtk_target_list_new (targets, G_N_ELEMENTS (targets)); + g_return_val_if_fail (tl, NULL); + if (item->type == OUTPUT_ITEM_TABLE || item->type == OUTPUT_ITEM_CHART) + gtk_target_list_add_image_targets (tl, SELECT_FMT_IMG, TRUE); + return tl; +} + static void on_copy (struct psppire_output_view *view) { GtkWidget *widget = GTK_WIDGET (view->overview); GtkClipboard *cb = gtk_widget_get_clipboard (widget, GDK_SELECTION_CLIPBOARD); - if (!gtk_clipboard_set_with_data (cb, targets, G_N_ELEMENTS (targets), + struct output_view_item *ov_item = find_selected_item (view); + if (ov_item == NULL) + return; + view->selected_item = ov_item; + GtkTargetList *tl = build_target_list (ov_item->item); + g_return_if_fail (tl); + gint no_of_targets = 0; + GtkTargetEntry *ta = gtk_target_table_new_from_list (tl, &no_of_targets); + g_return_if_fail (ta); + if (!gtk_clipboard_set_with_data (cb, ta, no_of_targets, clipboard_get_cb, clipboard_clear_cb, view)) clipboard_clear_cb (cb, view); -} -static void -on_selection_change (GtkTreeSelection *sel, GtkAction *copy_action) -{ - /* The Copy action is available only if there is something selected */ - gtk_action_set_sensitive (copy_action, gtk_tree_selection_count_selected_rows (sel) > 0); + gtk_target_list_unref (tl); + gtk_target_table_free (ta,no_of_targets); } static void -on_select_all (struct psppire_output_view *view) +on_size_allocate (GtkWidget *widget, + GdkRectangle *allocation, + struct psppire_output_view *view) { - GtkTreeSelection *sel = gtk_tree_view_get_selection (view->overview); - gtk_tree_view_expand_all (view->overview); - gtk_tree_selection_select_all (sel); + view->render_width = MAX (300, allocation->width); + rerender (view); } static void -on_size_allocate (GtkWidget *widget, - GdkRectangle *allocation, - struct psppire_output_view *view) +on_realize (GtkWidget *overview, GObject *view) { - int new_render_width = MAX (300, allocation->width); - if (view->render_width != new_render_width) - { - view->render_width = new_render_width; - rerender (view); - } + GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (overview)); + + GAction *copy_action = g_action_map_lookup_action (G_ACTION_MAP (toplevel), + "copy"); + + GAction *select_all_action = g_action_map_lookup_action (G_ACTION_MAP (toplevel), + "select-all"); + + g_object_set (copy_action, "enabled", FALSE, NULL); + g_object_set (select_all_action, "enabled", FALSE, NULL); + + g_signal_connect_swapped (copy_action, "activate", + G_CALLBACK (on_copy), view); + } struct psppire_output_view * -psppire_output_view_new (GtkLayout *output, GtkTreeView *overview, - GtkAction *copy_action, GtkAction *select_all_action) +psppire_output_view_new (GtkLayout *output, GtkTreeView *overview) { struct psppire_output_view *view; GtkTreeViewColumn *column; GtkCellRenderer *renderer; - GtkTreeSelection *sel; + GtkTreeModel *model; view = xmalloc (sizeof *view); - view->xr = NULL; - view->font_height = 0; - view->output = output; - view->render_width = 0; - view->max_width = 0; - view->y = 0; - string_map_init (&view->render_opts); - view->overview = overview; - memset (&view->cur_command, 0, sizeof view->cur_command); - view->in_command = false; - view->toplevel = gtk_widget_get_toplevel (GTK_WIDGET (output)); - view->items = NULL; - view->n_items = view->allocated_items = 0; - view->print_settings = NULL; - view->print_xrd = NULL; - view->print_item = 0; - view->print_n_pages = 0; - view->paginated = FALSE; + *view = (struct psppire_output_view) { + .object_spacing = 10, + .output = output, + .overview = overview, + .toplevel = gtk_widget_get_toplevel (GTK_WIDGET (output)), + }; - g_signal_connect (view->toplevel, "style-set", G_CALLBACK (on_style_set), view); + g_signal_connect (output, "draw", G_CALLBACK (layout_draw_callback), NULL); + + g_signal_connect (output, "style-updated", G_CALLBACK (on_style_updated), view); g_signal_connect (output, "size-allocate", G_CALLBACK (on_size_allocate), view); + gtk_widget_add_events (GTK_WIDGET (output), GDK_BUTTON_PRESS_MASK); + g_signal_connect (output, "button-press-event", + G_CALLBACK (off_item_button_press_event_cb), view); + + gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (output)), + GTK_STYLE_CLASS_VIEW); + if (overview) { + g_signal_connect (overview, "realize", G_CALLBACK (on_realize), view); + model = GTK_TREE_MODEL (gtk_tree_store_new ( N_COLS, - G_TYPE_STRING, /* COL_NAME */ + G_TYPE_STRING, /* COL_LABEL */ G_TYPE_POINTER, /* COL_ADDR */ G_TYPE_LONG)); /* COL_Y */ gtk_tree_view_set_model (overview, model); g_object_unref (model); - sel = gtk_tree_view_get_selection (overview); - gtk_tree_selection_set_mode (sel, GTK_SELECTION_MULTIPLE); - g_signal_connect (sel, "changed", G_CALLBACK (on_selection_change), - copy_action); - column = gtk_tree_view_column_new (); gtk_tree_view_append_column (GTK_TREE_VIEW (overview), column); renderer = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start (column, renderer, TRUE); - gtk_tree_view_column_add_attribute (column, renderer, "text", COL_NAME); + gtk_tree_view_column_add_attribute (column, renderer, "text", COL_LABEL); g_signal_connect (GTK_TREE_VIEW (overview), "row-activated", G_CALLBACK (on_row_activate), view); - - gtk_action_set_sensitive (copy_action, FALSE); - g_signal_connect_swapped (copy_action, "activate", - G_CALLBACK (on_copy), view); - g_signal_connect_swapped (select_all_action, "activate", - G_CALLBACK (on_select_all), view); } return view; @@ -681,10 +856,10 @@ psppire_output_view_destroy (struct psppire_output_view *view) if (!view) return; - g_signal_handlers_disconnect_by_func (view->toplevel, - G_CALLBACK (on_style_set), view); + g_signal_handlers_disconnect_by_func (view->output, + G_CALLBACK (on_style_updated), view); - string_map_destroy (&view->render_opts); + xr_fsm_style_unref (view->style); for (i = 0; i < view->n_items; i++) output_item_unref (view->items[i].item); @@ -695,7 +870,8 @@ psppire_output_view_destroy (struct psppire_output_view *view) if (view->print_settings != NULL) g_object_unref (view->print_settings); - xr_driver_destroy (view->xr); + if (view->cur_group) + gtk_tree_path_free (view->cur_group); free (view); } @@ -718,7 +894,7 @@ psppire_output_view_clear (struct psppire_output_view *view) view->items = NULL; view->n_items = view->allocated_items = 0; } - + /* Export. */ void @@ -744,55 +920,58 @@ static cairo_t * get_cairo_context_from_print_context (GtkPrintContext *context) { cairo_t *cr = gtk_print_context_get_cairo_context (context); - - /* - For all platforms except windows, gtk_print_context_get_dpi_[xy] returns 72. - Windows returns 600. - */ - double xres = gtk_print_context_get_dpi_x (context); - double yres = gtk_print_context_get_dpi_y (context); - - /* This means that the cairo context now has its dimensions in Points */ - cairo_scale (cr, xres / 72.0, yres / 72.0); - - return cr; + return cairo_reference (cr); } - static void create_xr_print_driver (GtkPrintContext *context, struct psppire_output_view *view) { - struct string_map options; - GtkPageSetup *page_setup; - double width, height; - double left_margin; - double right_margin; - double top_margin; - double bottom_margin; - - page_setup = gtk_print_context_get_page_setup (context); - width = gtk_page_setup_get_paper_width (page_setup, GTK_UNIT_MM); - height = gtk_page_setup_get_paper_height (page_setup, GTK_UNIT_MM); - left_margin = gtk_page_setup_get_left_margin (page_setup, GTK_UNIT_MM); - right_margin = gtk_page_setup_get_right_margin (page_setup, GTK_UNIT_MM); - top_margin = gtk_page_setup_get_top_margin (page_setup, GTK_UNIT_MM); - bottom_margin = gtk_page_setup_get_bottom_margin (page_setup, GTK_UNIT_MM); + GtkPageSetup *ps = gtk_print_context_get_page_setup (context); - string_map_init (&options); - string_map_insert_nocopy (&options, xstrdup ("paper-size"), - c_xasprintf("%.2fx%.2fmm", width, height)); - string_map_insert_nocopy (&options, xstrdup ("left-margin"), - c_xasprintf ("%.2fmm", left_margin)); - string_map_insert_nocopy (&options, xstrdup ("right-margin"), - c_xasprintf ("%.2fmm", right_margin)); - string_map_insert_nocopy (&options, xstrdup ("top-margin"), - c_xasprintf ("%.2fmm", top_margin)); - string_map_insert_nocopy (&options, xstrdup ("bottom-margin"), - c_xasprintf ("%.2fmm", bottom_margin)); + enum { H = TABLE_HORZ, V = TABLE_VERT }; + int paper[TABLE_N_AXES] = { + [H] = gtk_page_setup_get_paper_width (ps, GTK_UNIT_POINTS) * XR_POINT, + [V] = gtk_page_setup_get_paper_height (ps, GTK_UNIT_POINTS) * XR_POINT, + }; + + /* These are all 1/2 inch. The "margins" that GTK+ gives us are useless: + they are the printer's imagable area. */ + int margins[TABLE_N_AXES][2] = { + [H][0] = XR_POINT * 36, + [H][1] = XR_POINT * 36, + [V][0] = XR_POINT * 36, + [V][1] = XR_POINT * 36, + }; - view->print_xrd = xr_driver_create (get_cairo_context_from_print_context (context), &options); + double size[TABLE_N_AXES]; + for (int a = 0; a < TABLE_N_AXES; a++) + size[a] = paper[a] - margins[a][0] - margins[a][1]; + + view->page_style = xmalloc (sizeof *view->page_style); + *view->page_style = (struct xr_page_style) { + .ref_cnt = 1, + + .margins = { + [H] = { margins[H][0], margins[H][1] }, + [V] = { margins[V][0], margins[V][1] }, + }, + .initial_page_number = 1, + }; - string_map_destroy (&options); + view->fsm_style = xmalloc (sizeof *view->fsm_style); + *view->fsm_style = (struct xr_fsm_style) { + .ref_cnt = 1, + + .size = { [H] = size[H], [V] = size[V] }, + .min_break = { [H] = size[H] / 2, [V] = size[V] / 2 }, + .font = pango_font_description_from_string ("Sans Serif 10"), + .fg = CELL_COLOR_BLACK, + .use_system_colors = false, + .object_spacing = 12 * XR_POINT, + .font_resolution = 72.0 + }; + + view->pager = xr_pager_create (view->page_style, view->fsm_style); } static gboolean @@ -806,24 +985,24 @@ paginate (GtkPrintOperation *operation, complete. Don't let that screw up printing. */ return TRUE; } - else if ( view->print_item < view->n_items ) + else if (view->print_item < view->n_items) { - xr_driver_output_item (view->print_xrd, - view->items[view->print_item++].item); - while (xr_driver_need_new_page (view->print_xrd)) + xr_pager_add_item (view->pager, view->items[view->print_item++].item); + while (xr_pager_needs_new_page (view->pager)) { - xr_driver_next_page (view->print_xrd, NULL); + xr_pager_add_page (view->pager, + get_cairo_context_from_print_context (context)); view->print_n_pages ++; } return FALSE; } else { - gtk_print_operation_set_n_pages (operation, view->print_n_pages); + gtk_print_operation_set_n_pages (operation, MAX (1, view->print_n_pages)); /* Re-create the driver to do the real printing. */ - xr_driver_destroy (view->print_xrd); - create_xr_print_driver (context, view); + xr_pager_destroy (view->pager); + view->pager = xr_pager_create (view->page_style, view->fsm_style); view->print_item = 0; view->paginated = TRUE; @@ -839,7 +1018,7 @@ begin_print (GtkPrintOperation *operation, create_xr_print_driver (context, view); view->print_item = 0; - view->print_n_pages = 1; + view->print_n_pages = 0; view->paginated = FALSE; } @@ -848,7 +1027,8 @@ end_print (GtkPrintOperation *operation, GtkPrintContext *context, struct psppire_output_view *view) { - xr_driver_destroy (view->print_xrd); + xr_pager_destroy (view->pager); + view->pager = NULL; } @@ -858,10 +1038,11 @@ draw_page (GtkPrintOperation *operation, gint page_number, struct psppire_output_view *view) { - xr_driver_next_page (view->print_xrd, get_cairo_context_from_print_context (context)); - while (!xr_driver_need_new_page (view->print_xrd) + xr_pager_add_page (view->pager, + get_cairo_context_from_print_context (context)); + while (!xr_pager_needs_new_page (view->pager) && view->print_item < view->n_items) - xr_driver_output_item (view->print_xrd, view->items [view->print_item++].item); + xr_pager_add_item (view->pager, view->items [view->print_item++].item); } @@ -873,9 +1054,12 @@ psppire_output_view_print (struct psppire_output_view *view, GtkPrintOperation *print = gtk_print_operation_new (); - if (view->print_settings != NULL) + if (view->print_settings != NULL) gtk_print_operation_set_print_settings (print, view->print_settings); + gtk_print_operation_set_use_full_page (print, TRUE); + gtk_print_operation_set_unit (print, GTK_UNIT_POINTS); + g_signal_connect (print, "begin_print", G_CALLBACK (begin_print), view); g_signal_connect (print, "end_print", G_CALLBACK (end_print), view); g_signal_connect (print, "paginate", G_CALLBACK (paginate), view); @@ -912,7 +1096,7 @@ psppire_output_view_submit (struct output_driver *this, { struct psppire_output_view_driver *povd = psppire_output_view_driver_cast (this); - if (is_table_item (item)) + if (item->type == OUTPUT_ITEM_TABLE) psppire_output_view_put (povd->view, item); }