1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2008-2014 Free Software Foundation.
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
19 #include "ui/gui/psppire-output-view.h"
24 #include "libpspp/assertion.h"
25 #include "libpspp/string-map.h"
26 #include "output/cairo.h"
27 #include "output/driver-provider.h"
28 #include "output/driver.h"
29 #include "output/chart-item.h"
30 #include "output/message-item.h"
31 #include "output/output-item.h"
32 #include "output/table-item.h"
33 #include "output/text-item.h"
35 #include "gl/c-xvasprintf.h"
36 #include "gl/minmax.h"
37 #include "gl/tmpdir.h"
38 #include "gl/xalloc.h"
41 #define _(msgid) gettext (msgid)
43 struct output_view_item
45 struct output_item *item;
46 GtkWidget *drawing_area;
49 struct psppire_output_view
59 struct string_map render_opts;
60 GtkTreeView *overview;
61 GtkTreeIter cur_command;
66 struct output_view_item *items;
67 size_t n_items, allocated_items;
69 /* Variables pertaining to printing */
70 GtkPrintSettings *print_settings;
71 struct xr_driver *print_xrd;
79 COL_NAME, /* Table name. */
80 COL_ADDR, /* Pointer to the table */
81 COL_Y, /* Y position of top of name. */
85 static void on_dwgarea_realize (GtkWidget *widget, gpointer data);
88 expose_event_callback (GtkWidget *widget, GdkEventExpose *event, gpointer data)
90 struct psppire_output_view *view = data;
91 struct xr_rendering *r = g_object_get_data (G_OBJECT (widget), "rendering");
92 cairo_t *cr = gdk_cairo_create (widget->window);
94 const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (view->output));
96 PangoFontDescription *font_desc;
100 gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (view->output))]);
102 string_map_replace (&view->render_opts, "foreground-color", fgc);
106 /* Use GTK+ default font as proportional font. */
107 font_name = pango_font_description_to_string (style->font_desc);
108 string_map_replace (&view->render_opts, "prop-font", font_name);
111 /* Derived emphasized font from proportional font. */
112 font_desc = pango_font_description_copy (style->font_desc);
113 pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC);
114 font_name = pango_font_description_to_string (font_desc);
115 string_map_replace (&view->render_opts, "emph-font", font_name);
117 pango_font_description_free (font_desc);
119 xr_rendering_apply_options (r, &view->render_opts);
121 xr_rendering_draw (r, cr, event->area.x, event->area.y,
122 event->area.width, event->area.height);
129 free_rendering (gpointer rendering_)
131 struct xr_rendering *rendering = rendering_;
132 xr_rendering_destroy (rendering);
136 create_xr (struct psppire_output_view *view)
138 const GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (view->output));
139 struct text_item *text_item;
140 PangoFontDescription *font_desc;
141 struct xr_rendering *r;
147 cr = gdk_cairo_create (GTK_WIDGET (view->output)->window);
149 /* Set the widget's text color as the foreground color for the output driver */
150 fgc = gdk_color_to_string (&style->text[gtk_widget_get_state (GTK_WIDGET (view->output))]);
152 string_map_insert (&view->render_opts, "foreground-color", fgc);
155 /* Use GTK+ default font as proportional font. */
156 font_name = pango_font_description_to_string (style->font_desc);
157 string_map_insert (&view->render_opts, "prop-font", font_name);
160 /* Derived emphasized font from proportional font. */
161 font_desc = pango_font_description_copy (style->font_desc);
162 pango_font_description_set_style (font_desc, PANGO_STYLE_ITALIC);
163 font_name = pango_font_description_to_string (font_desc);
164 string_map_insert (&view->render_opts, "emph-font", font_name);
166 pango_font_description_free (font_desc);
168 /* Pretend that the "page" has a reasonable width and a very big length,
169 so that most tables can be conveniently viewed on-screen with vertical
170 scrolling only. (The length should not be increased very much because
171 it is already close enough to INT_MAX when expressed as thousands of a
173 string_map_insert_nocopy (&view->render_opts, xstrdup ("paper-size"),
174 xasprintf ("%dx1000000pt", view->render_width));
175 string_map_insert (&view->render_opts, "left-margin", "0");
176 string_map_insert (&view->render_opts, "right-margin", "0");
177 string_map_insert (&view->render_opts, "top-margin", "0");
178 string_map_insert (&view->render_opts, "bottom-margin", "0");
180 view->xr = xr_driver_create (cr, &view->render_opts);
182 text_item = text_item_create (TEXT_ITEM_PARAGRAPH, "X");
183 r = xr_rendering_create (view->xr, text_item_super (text_item), cr);
184 xr_rendering_measure (r, &font_width, &view->font_height);
185 /* xr_rendering_destroy (r); */
186 text_item_unref (text_item);
192 rerender (struct psppire_output_view *view)
194 struct output_view_item *item;
200 string_map_clear (&view->render_opts);
201 xr_driver_destroy (view->xr);
204 cr = gdk_cairo_create (GTK_WIDGET (view->output)->window);
208 for (item = view->items; item < &view->items[view->n_items]; item++)
210 struct xr_rendering *r;
214 view->y += view->font_height / 2;
216 r = xr_rendering_create (view->xr, item->item, cr);
219 g_warn_if_reached ();
223 xr_rendering_measure (r, &tw, &th);
224 g_object_set_data_full (G_OBJECT (item->drawing_area),
225 "rendering", r, free_rendering);
226 gtk_widget_set_size_request (item->drawing_area, tw, th);
227 gtk_layout_move (view->output, item->drawing_area, 0, view->y);
229 if (view->max_width < tw)
230 view->max_width = tw;
234 gtk_layout_set_size (view->output, view->max_width, view->y);
239 psppire_output_view_put (struct psppire_output_view *view,
240 const struct output_item *item)
242 GtkWidget *drawing_area;
243 struct xr_rendering *r;
251 if (is_text_item (item))
253 const struct text_item *text_item = to_text_item (item);
254 enum text_item_type type = text_item_get_type (text_item);
255 const char *text = text_item_get_text (text_item);
257 if (type == TEXT_ITEM_COMMAND_CLOSE)
259 view->in_command = false;
262 else if (text[0] == '\0')
266 if (view->n_items >= view->allocated_items)
267 view->items = x2nrealloc (view->items, &view->allocated_items,
268 sizeof *view->items);
269 view->items[view->n_items].item = output_item_ref (item);
270 view->items[view->n_items].drawing_area = drawing_area = gtk_drawing_area_new ();
273 cr = gdk_cairo_create (GTK_WIDGET (view->output)->window);
274 if (view->xr == NULL)
278 view->y += view->font_height / 2;
280 r = xr_rendering_create (view->xr, item, cr);
284 xr_rendering_measure (r, &tw, &th);
286 g_object_set_data_full (G_OBJECT (drawing_area), "rendering", r, free_rendering);
287 g_signal_connect (drawing_area, "realize",
288 G_CALLBACK (on_dwgarea_realize), view);
289 g_signal_connect (drawing_area, "expose_event",
290 G_CALLBACK (expose_event_callback), view);
292 gtk_widget_set_size_request (drawing_area, tw, th);
293 gtk_layout_put (view->output, drawing_area, 0, view->y);
295 gtk_widget_show (drawing_area);
298 && (!is_text_item (item)
299 || text_item_get_type (to_text_item (item)) != TEXT_ITEM_SYNTAX
300 || !view->in_command))
302 store = GTK_TREE_STORE (gtk_tree_view_get_model (view->overview));
304 ds_init_empty (&name);
305 if (is_text_item (item)
306 && text_item_get_type (to_text_item (item)) == TEXT_ITEM_COMMAND_OPEN)
308 gtk_tree_store_append (store, &iter, NULL);
309 view->cur_command = iter; /* XXX shouldn't save a GtkTreeIter */
310 view->in_command = true;
314 GtkTreeIter *p = view->in_command ? &view->cur_command : NULL;
315 gtk_tree_store_append (store, &iter, p);
319 if (is_text_item (item))
320 ds_put_cstr (&name, text_item_get_text (to_text_item (item)));
321 else if (is_message_item (item))
323 const struct message_item *msg_item = to_message_item (item);
324 const struct msg *msg = message_item_get_msg (msg_item);
325 ds_put_format (&name, "%s: %s", _("Message"),
326 msg_severity_to_string (msg->severity));
328 else if (is_table_item (item))
330 const char *title = table_item_get_title (to_table_item (item));
332 ds_put_format (&name, "Table: %s", title);
334 ds_put_cstr (&name, "Table");
336 else if (is_chart_item (item))
338 const char *s = chart_item_get_title (to_chart_item (item));
340 ds_put_format (&name, "Chart: %s", s);
342 ds_put_cstr (&name, "Chart");
344 gtk_tree_store_set (store, &iter,
345 COL_NAME, ds_cstr (&name),
351 path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
352 gtk_tree_view_expand_row (view->overview, path, TRUE);
353 gtk_tree_path_free (path);
356 if (view->max_width < tw)
357 view->max_width = tw;
360 gtk_layout_set_size (view->output, view->max_width, view->y);
367 on_row_activate (GtkTreeView *overview,
369 GtkTreeViewColumn *column,
370 struct psppire_output_view *view)
378 model = gtk_tree_view_get_model (overview);
379 if (!gtk_tree_model_get_iter (model, &iter, path))
382 gtk_tree_model_get_value (model, &iter, COL_Y, &value);
383 y = g_value_get_long (&value);
384 g_value_unset (&value);
386 vadj = gtk_layout_get_vadjustment (view->output);
388 max = vadj->upper - vadj->page_size;
393 gtk_adjustment_set_value (vadj, y);
397 copy_base_to_bg (GtkWidget *dest, GtkWidget *src)
400 for (i = 0; i < 5; ++i)
402 gtk_widget_modify_bg (dest, i, >k_widget_get_style (src)->base[i]);
403 gtk_widget_modify_fg (dest, i, >k_widget_get_style (src)->text[i]);
407 /* Copy the base style from the parent widget to the container and all its
408 children. We do this because the container's primary purpose is to display
409 text. This way psppire appears to follow the chosen gnome theme. */
411 on_style_set (GtkWidget *toplevel, GtkStyle *prev,
412 struct psppire_output_view *view)
414 copy_base_to_bg (GTK_WIDGET (view->output), toplevel);
415 gtk_container_foreach (GTK_CONTAINER (view->output),
416 (GtkCallback) copy_base_to_bg, view->output);
420 on_dwgarea_realize (GtkWidget *dwg_area, gpointer data)
422 copy_base_to_bg (dwg_area, gtk_widget_get_toplevel (dwg_area));
433 /* GNU Hurd doesn't have PATH_MAX. Use a fallback.
434 Temporary directory names are usually not that long. */
436 # define PATH_MAX 1024
440 clipboard_get_cb (GtkClipboard *clipboard,
441 GtkSelectionData *selection_data,
445 struct psppire_output_view *view = data;
449 struct output_driver *driver = NULL;
450 char dirname[PATH_MAX], *filename;
451 struct string_map options;
453 GtkTreeSelection *sel = gtk_tree_view_get_selection (view->overview);
454 GtkTreeModel *model = gtk_tree_view_get_model (view->overview);
456 GList *rows = gtk_tree_selection_get_selected_rows (sel, &model);
462 if (path_search (dirname, sizeof dirname, NULL, NULL, true)
463 || mkdtemp (dirname) == NULL)
465 msg_error (errno, _("failed to create temporary directory during clipboard operation"));
468 filename = xasprintf ("%s/clip.tmp", dirname);
470 string_map_init (&options);
471 string_map_insert (&options, "output-file", filename);
475 case SELECT_FMT_UTF8:
476 string_map_insert (&options, "box", "unicode");
479 case SELECT_FMT_TEXT:
480 string_map_insert (&options, "format", "txt");
483 case SELECT_FMT_HTML:
484 string_map_insert (&options, "format", "html");
485 string_map_insert (&options, "borders", "false");
486 string_map_insert (&options, "css", "false");
490 string_map_insert (&options, "format", "odt");
494 g_warning ("unsupported clip target\n");
499 driver = output_driver_create (&options);
505 GtkTreePath *path = n->data ;
507 struct output_item *item ;
509 gtk_tree_model_get_iter (model, &iter, path);
510 gtk_tree_model_get (model, &iter, COL_ADDR, &item, -1);
512 driver->class->submit (driver, item);
517 if ( driver->class->flush)
518 driver->class->flush (driver);
521 /* Some drivers (eg: the odt one) don't write anything until they
523 output_driver_destroy (driver);
526 if ( g_file_get_contents (filename, &text, &length, NULL) )
528 gtk_selection_data_set (selection_data, selection_data->target,
530 (const guchar *) text, length);
536 output_driver_destroy (driver);
548 clipboard_clear_cb (GtkClipboard *clipboard,
553 static const GtkTargetEntry targets[] = {
555 { "STRING", 0, SELECT_FMT_TEXT },
556 { "TEXT", 0, SELECT_FMT_TEXT },
557 { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
558 { "text/plain", 0, SELECT_FMT_TEXT },
560 { "UTF8_STRING", 0, SELECT_FMT_UTF8 },
561 { "text/plain;charset=utf-8", 0, SELECT_FMT_UTF8 },
563 { "text/html", 0, SELECT_FMT_HTML },
565 { "application/vnd.oasis.opendocument.text", 0, SELECT_FMT_ODT }
569 on_copy (struct psppire_output_view *view)
571 GtkWidget *widget = GTK_WIDGET (view->overview);
572 GtkClipboard *cb = gtk_widget_get_clipboard (widget, GDK_SELECTION_CLIPBOARD);
574 if (!gtk_clipboard_set_with_data (cb, targets, G_N_ELEMENTS (targets),
575 clipboard_get_cb, clipboard_clear_cb,
577 clipboard_clear_cb (cb, view);
581 on_selection_change (GtkTreeSelection *sel, GtkAction *copy_action)
583 /* The Copy action is available only if there is something selected */
584 gtk_action_set_sensitive (copy_action, gtk_tree_selection_count_selected_rows (sel) > 0);
588 on_select_all (struct psppire_output_view *view)
590 GtkTreeSelection *sel = gtk_tree_view_get_selection (view->overview);
591 gtk_tree_view_expand_all (view->overview);
592 gtk_tree_selection_select_all (sel);
596 on_size_allocate (GtkWidget *widget,
597 GdkRectangle *allocation,
598 struct psppire_output_view *view)
600 int new_render_width = MAX (300, allocation->width);
601 if (view->render_width != new_render_width)
603 view->render_width = new_render_width;
608 struct psppire_output_view *
609 psppire_output_view_new (GtkLayout *output, GtkTreeView *overview,
610 GtkAction *copy_action, GtkAction *select_all_action)
612 struct psppire_output_view *view;
613 GtkTreeViewColumn *column;
614 GtkCellRenderer *renderer;
615 GtkTreeSelection *sel;
618 view = xmalloc (sizeof *view);
620 view->font_height = 0;
621 view->output = output;
622 view->render_width = 0;
625 string_map_init (&view->render_opts);
626 view->overview = overview;
627 memset (&view->cur_command, 0, sizeof view->cur_command);
628 view->in_command = false;
629 view->toplevel = gtk_widget_get_toplevel (GTK_WIDGET (output));
631 view->n_items = view->allocated_items = 0;
632 view->print_settings = NULL;
633 view->print_xrd = NULL;
634 view->print_item = 0;
635 view->print_n_pages = 0;
636 view->paginated = FALSE;
638 g_signal_connect (view->toplevel, "style-set", G_CALLBACK (on_style_set), view);
640 g_signal_connect (output, "size-allocate", G_CALLBACK (on_size_allocate), view);
644 model = GTK_TREE_MODEL (gtk_tree_store_new (
646 G_TYPE_STRING, /* COL_NAME */
647 G_TYPE_POINTER, /* COL_ADDR */
648 G_TYPE_LONG)); /* COL_Y */
649 gtk_tree_view_set_model (overview, model);
650 g_object_unref (model);
652 sel = gtk_tree_view_get_selection (overview);
653 gtk_tree_selection_set_mode (sel, GTK_SELECTION_MULTIPLE);
654 g_signal_connect (sel, "changed", G_CALLBACK (on_selection_change),
657 column = gtk_tree_view_column_new ();
658 gtk_tree_view_append_column (GTK_TREE_VIEW (overview), column);
659 renderer = gtk_cell_renderer_text_new ();
660 gtk_tree_view_column_pack_start (column, renderer, TRUE);
661 gtk_tree_view_column_add_attribute (column, renderer, "text", COL_NAME);
663 g_signal_connect (GTK_TREE_VIEW (overview),
664 "row-activated", G_CALLBACK (on_row_activate), view);
666 gtk_action_set_sensitive (copy_action, FALSE);
667 g_signal_connect_swapped (copy_action, "activate",
668 G_CALLBACK (on_copy), view);
669 g_signal_connect_swapped (select_all_action, "activate",
670 G_CALLBACK (on_select_all), view);
677 psppire_output_view_destroy (struct psppire_output_view *view)
684 g_signal_handlers_disconnect_by_func (view->toplevel,
685 G_CALLBACK (on_style_set), view);
687 string_map_destroy (&view->render_opts);
689 for (i = 0; i < view->n_items; i++)
690 output_item_unref (view->items[i].item);
693 view->n_items = view->allocated_items = 0;
695 if (view->print_settings != NULL)
696 g_object_unref (view->print_settings);
698 xr_driver_destroy (view->xr);
704 psppire_output_view_clear (struct psppire_output_view *view)
711 for (i = 0; i < view->n_items; i++)
713 gtk_container_remove (GTK_CONTAINER (view->output),
714 view->items[i].drawing_area);
715 output_item_unref (view->items[i].item);
719 view->n_items = view->allocated_items = 0;
725 psppire_output_view_export (struct psppire_output_view *view,
726 struct string_map *options)
728 struct output_driver *driver;
730 driver = output_driver_create (options);
735 for (i = 0; i < view->n_items; i++)
736 driver->class->submit (driver, view->items[i].item);
737 output_driver_destroy (driver);
744 get_cairo_context_from_print_context (GtkPrintContext *context)
746 cairo_t *cr = gtk_print_context_get_cairo_context (context);
749 For all platforms except windows, gtk_print_context_get_dpi_[xy] returns 72.
752 double xres = gtk_print_context_get_dpi_x (context);
753 double yres = gtk_print_context_get_dpi_y (context);
755 /* This means that the cairo context now has its dimensions in Points */
756 cairo_scale (cr, xres / 72.0, yres / 72.0);
763 create_xr_print_driver (GtkPrintContext *context, struct psppire_output_view *view)
765 struct string_map options;
766 GtkPageSetup *page_setup;
767 double width, height;
771 double bottom_margin;
773 page_setup = gtk_print_context_get_page_setup (context);
774 width = gtk_page_setup_get_paper_width (page_setup, GTK_UNIT_MM);
775 height = gtk_page_setup_get_paper_height (page_setup, GTK_UNIT_MM);
776 left_margin = gtk_page_setup_get_left_margin (page_setup, GTK_UNIT_MM);
777 right_margin = gtk_page_setup_get_right_margin (page_setup, GTK_UNIT_MM);
778 top_margin = gtk_page_setup_get_top_margin (page_setup, GTK_UNIT_MM);
779 bottom_margin = gtk_page_setup_get_bottom_margin (page_setup, GTK_UNIT_MM);
781 string_map_init (&options);
782 string_map_insert_nocopy (&options, xstrdup ("paper-size"),
783 c_xasprintf("%.2fx%.2fmm", width, height));
784 string_map_insert_nocopy (&options, xstrdup ("left-margin"),
785 c_xasprintf ("%.2fmm", left_margin));
786 string_map_insert_nocopy (&options, xstrdup ("right-margin"),
787 c_xasprintf ("%.2fmm", right_margin));
788 string_map_insert_nocopy (&options, xstrdup ("top-margin"),
789 c_xasprintf ("%.2fmm", top_margin));
790 string_map_insert_nocopy (&options, xstrdup ("bottom-margin"),
791 c_xasprintf ("%.2fmm", bottom_margin));
793 view->print_xrd = xr_driver_create (get_cairo_context_from_print_context (context), &options);
795 string_map_destroy (&options);
799 paginate (GtkPrintOperation *operation,
800 GtkPrintContext *context,
801 struct psppire_output_view *view)
805 /* Sometimes GTK+ emits this signal again even after pagination is
806 complete. Don't let that screw up printing. */
809 else if ( view->print_item < view->n_items )
811 xr_driver_output_item (view->print_xrd,
812 view->items[view->print_item++].item);
813 while (xr_driver_need_new_page (view->print_xrd))
815 xr_driver_next_page (view->print_xrd, NULL);
816 view->print_n_pages ++;
822 gtk_print_operation_set_n_pages (operation, view->print_n_pages);
824 /* Re-create the driver to do the real printing. */
825 xr_driver_destroy (view->print_xrd);
826 create_xr_print_driver (context, view);
827 view->print_item = 0;
828 view->paginated = TRUE;
835 begin_print (GtkPrintOperation *operation,
836 GtkPrintContext *context,
837 struct psppire_output_view *view)
839 create_xr_print_driver (context, view);
841 view->print_item = 0;
842 view->print_n_pages = 1;
843 view->paginated = FALSE;
847 end_print (GtkPrintOperation *operation,
848 GtkPrintContext *context,
849 struct psppire_output_view *view)
851 xr_driver_destroy (view->print_xrd);
856 draw_page (GtkPrintOperation *operation,
857 GtkPrintContext *context,
859 struct psppire_output_view *view)
861 xr_driver_next_page (view->print_xrd, get_cairo_context_from_print_context (context));
862 while (!xr_driver_need_new_page (view->print_xrd)
863 && view->print_item < view->n_items)
864 xr_driver_output_item (view->print_xrd, view->items [view->print_item++].item);
869 psppire_output_view_print (struct psppire_output_view *view,
870 GtkWindow *parent_window)
872 GtkPrintOperationResult res;
874 GtkPrintOperation *print = gtk_print_operation_new ();
876 if (view->print_settings != NULL)
877 gtk_print_operation_set_print_settings (print, view->print_settings);
879 g_signal_connect (print, "begin_print", G_CALLBACK (begin_print), view);
880 g_signal_connect (print, "end_print", G_CALLBACK (end_print), view);
881 g_signal_connect (print, "paginate", G_CALLBACK (paginate), view);
882 g_signal_connect (print, "draw_page", G_CALLBACK (draw_page), view);
884 res = gtk_print_operation_run (print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
885 parent_window, NULL);
887 if (res == GTK_PRINT_OPERATION_RESULT_APPLY)
889 if (view->print_settings != NULL)
890 g_object_unref (view->print_settings);
891 view->print_settings = g_object_ref (gtk_print_operation_get_print_settings (print));
894 g_object_unref (print);
897 struct psppire_output_view_driver
899 struct output_driver driver;
900 struct psppire_output_view *view;
903 static struct psppire_output_view_driver *
904 psppire_output_view_driver_cast (struct output_driver *driver)
906 return UP_CAST (driver, struct psppire_output_view_driver, driver);
910 psppire_output_view_submit (struct output_driver *this,
911 const struct output_item *item)
913 struct psppire_output_view_driver *povd = psppire_output_view_driver_cast (this);
915 if (is_table_item (item))
916 psppire_output_view_put (povd->view, item);
919 static struct output_driver_class psppire_output_view_driver_class =
921 "PSPPIRE Output View", /* name */
923 psppire_output_view_submit, /* submit */
928 psppire_output_view_register_driver (struct psppire_output_view *view)
930 struct psppire_output_view_driver *povd;
931 struct output_driver *d;
933 povd = xzalloc (sizeof *povd);
936 output_driver_init (d, &psppire_output_view_driver_class, "PSPPIRE Output View",
937 SETTINGS_DEVICE_UNFILTERED);
938 output_driver_register (d);