1 /*
2  *  gretl -- Gnu Regression, Econometrics and Time-series Library
3  *  Copyright (C) 2001 Allin Cottrell and Riccardo "Jack" Lucchetti
4  *
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 #include "gretl.h"
21 #include "dlgutils.h"
22 #include "varprint.h"
23 #include "textutil.h"
24 #include "textbuf.h"
25 #include "guiprint.h"
26 #include "model_table.h"
27 #include "clipboard.h"
28 #include "fileselect.h"
29 #include "texprint.h"
30 #include "system.h"
31 #include "winstack.h"
32 
33 #if GTKSOURCEVIEW_VERSION == 2
34 # include <gtksourceview/gtksourceiter.h>
35 #endif
36 
37 struct search_replace {
38     GtkWidget *w;          /* the dialog box */
39     GtkWidget *f_entry;    /* Find string entry */
40     GtkWidget *r_entry;    /* Replace string entry */
41     GtkWidget *r_button;   /* Replace button */
42     gchar *find;           /* the Find string */
43     gchar *replace;        /* the Replace string */
44     GtkTextBuffer *buf;
45     GtkTextView *view;
46     GtkTextMark *mark;
47     GtkTextIter iter;
48 };
49 
destroy_replacer(GtkWidget * widget,struct search_replace * s)50 static gboolean destroy_replacer (GtkWidget *widget,
51 				  struct search_replace *s)
52 {
53     GtkTextMark *mark;
54 
55     mark = gtk_text_buffer_get_mark(s->buf, "srmark");
56     if (mark != NULL) {
57 	gtk_text_buffer_delete_mark(s->buf, mark);
58     }
59 
60     g_free(s->find);
61     g_free(s->replace);
62 
63     gtk_main_quit();
64     return FALSE;
65 }
66 
67 /* here we simply find (or not) the given string */
68 
replace_find_callback(GtkWidget * widget,struct search_replace * s)69 static void replace_find_callback (GtkWidget *widget,
70 				   struct search_replace *s)
71 {
72     GtkTextIter f_start, f_end;
73     gboolean found;
74 
75     g_free(s->find);
76     s->find = gtk_editable_get_chars(GTK_EDITABLE(s->f_entry), 0, -1);
77 
78     if (s->find == NULL || *s->find == '\0') {
79 	return;
80     }
81 
82     gtk_text_buffer_get_iter_at_mark(s->buf, &s->iter, s->mark);
83 
84 #if GTKSOURCEVIEW_VERSION == 2
85     found = gtk_source_iter_forward_search(&s->iter,
86 					   s->find,
87 					   0,
88 					   &f_start,
89 					   &f_end,
90 					   NULL);
91 #else /* GTK 3 */
92     found = gtk_text_iter_forward_search(&s->iter,
93 					 s->find,
94 					 0,
95 					 &f_start,
96 					 &f_end,
97 					 NULL);
98 #endif
99 
100     if (found) {
101 	gtk_text_buffer_select_range(s->buf, &f_start, &f_end);
102 	gtk_text_buffer_move_mark(s->buf, s->mark, &f_end);
103 	if (gtk_text_iter_forward_line(&f_end)) {
104 	    /* go one line further on, if possible */
105 	    gtk_text_buffer_move_mark(s->buf, s->mark, &f_end);
106 	}
107 	gtk_text_view_scroll_mark_onscreen(s->view, s->mark);
108     } else {
109 	notify_string_not_found(s->f_entry);
110     }
111 
112     gtk_widget_set_sensitive(s->r_button, found);
113 }
114 
update_search_strings(struct search_replace * s)115 static void update_search_strings (struct search_replace *s)
116 {
117     g_free(s->find);
118     g_free(s->replace);
119 
120     s->find = gtk_editable_get_chars(GTK_EDITABLE(s->f_entry), 0, -1);
121     s->replace = gtk_editable_get_chars(GTK_EDITABLE(s->r_entry), 0, -1);
122 }
123 
124 /* replace an occurrence of the Find string that has just been
125    found, and selected */
126 
replace_single_callback(GtkWidget * button,struct search_replace * s)127 static void replace_single_callback (GtkWidget *button,
128 				     struct search_replace *s)
129 {
130     GtkTextIter r_start, r_end;
131     gchar *text;
132 
133     update_search_strings(s);
134 
135     if (s->find == NULL || s->replace == NULL || *s->find == '\0') {
136 	return;
137     }
138 
139     if (!gtk_text_buffer_get_selection_bounds(s->buf, &r_start, &r_end)) {
140 	return;
141     }
142 
143     text = gtk_text_buffer_get_text(s->buf, &r_start, &r_end, FALSE);
144 
145     if (strcmp(text, s->find) == 0) {
146 	gtk_text_buffer_begin_user_action(s->buf);
147 	gtk_text_buffer_delete(s->buf, &r_start, &r_end);
148 	gtk_text_buffer_insert(s->buf, &r_start, s->replace, -1);
149 	gtk_text_buffer_move_mark(s->buf, s->mark, &r_start);
150 	gtk_text_buffer_end_user_action(s->buf);
151     }
152 
153     gtk_widget_set_sensitive(button, FALSE);
154     g_free(text);
155 }
156 
157 /* replace all occurrences of the Find string in the text
158    buffer, or in the current selection if there is one
159 */
160 
replace_all_callback(GtkWidget * button,struct search_replace * s)161 static void replace_all_callback (GtkWidget *button,
162 				  struct search_replace *s)
163 {
164 #if GTKSOURCEVIEW_VERSION == 2
165     GtkSourceSearchFlags search_flags;
166 #else
167     GtkTextSearchFlags search_flags;
168 #endif
169     GtkTextIter start, selstart, end;
170     GtkTextIter r_start, r_end;
171     GtkTextMark *mark;
172     gint init_pos[2];
173     gint replace_len;
174     gboolean selected = FALSE;
175     gboolean found = TRUE;
176     gboolean do_brackets;
177     int count = 0;
178 
179     update_search_strings(s);
180 
181     if (s->find == NULL || s->replace == NULL || *s->find == '\0') {
182 	return;
183     }
184 
185     /* record the initial cursor position */
186     mark = gtk_text_buffer_get_insert(s->buf);
187     gtk_text_buffer_get_iter_at_mark(s->buf, &s->iter, mark);
188     init_pos[0] = gtk_text_iter_get_line(&s->iter);
189     init_pos[1] = gtk_text_iter_get_line_index(&s->iter);
190 
191     /* if there's a selection in place, respect it, otherwise work
192        on the whole buffer */
193     if (gtk_text_buffer_get_selection_bounds(s->buf, &start, &end)) {
194 	selected = TRUE;
195     } else {
196 	gtk_text_buffer_get_start_iter(s->buf, &start);
197 	gtk_text_buffer_get_end_iter(s->buf, &end);
198     }
199 
200 #if GTKSOURCEVIEW_VERSION == 2
201     search_flags = GTK_SOURCE_SEARCH_VISIBLE_ONLY | GTK_SOURCE_SEARCH_TEXT_ONLY;
202 #else
203     search_flags = GTK_TEXT_SEARCH_VISIBLE_ONLY | GTK_TEXT_SEARCH_TEXT_ONLY;
204 #endif
205     replace_len = strlen(s->replace);
206 
207     /* avoid spending time matching brackets */
208     do_brackets = gtk_source_buffer_get_highlight_matching_brackets(GTK_SOURCE_BUFFER(s->buf));
209     gtk_source_buffer_set_highlight_matching_brackets(GTK_SOURCE_BUFFER(s->buf), FALSE);
210 
211     gtk_text_buffer_begin_user_action(s->buf);
212 
213     do {
214 #if GTKSOURCEVIEW_VERSION == 2
215 	found = gtk_source_iter_forward_search(&start,
216 					       s->find,
217 					       search_flags,
218 					       &r_start,
219 					       &r_end,
220 					       selected ? &end : NULL);
221 #else /* GTK 3 */
222 	found = gtk_text_iter_forward_search(&start,
223 					     s->find,
224 					     search_flags,
225 					     &r_start,
226 					     &r_end,
227 					     selected ? &end : NULL);
228 #endif
229 	if (found) {
230 	    count++;
231 	    gtk_text_buffer_delete(s->buf, &r_start, &r_end);
232 	    gtk_text_buffer_insert(s->buf, &r_start,
233 				   s->replace,
234 				   replace_len);
235 	    start = r_start;
236 	    if (selected) {
237 		gtk_text_buffer_get_selection_bounds(s->buf,
238 						     &selstart,
239 						     &end);
240 	    }
241 	}
242     } while (found);
243 
244 #if 0
245     fprintf(stderr, "replaced %d occurrences\n", count);
246 #endif
247 
248     gtk_text_buffer_end_user_action(s->buf);
249 
250     gtk_source_buffer_set_highlight_matching_brackets(GTK_SOURCE_BUFFER(s->buf),
251 						      do_brackets);
252 
253     /* put the cursor back where we found it */
254     gtk_text_buffer_get_start_iter(s->buf, &s->iter);
255     gtk_text_iter_set_line(&s->iter, init_pos[0]);
256     gtk_text_iter_set_line_index(&s->iter, init_pos[1]);
257     gtk_text_buffer_place_cursor(s->buf, &s->iter);
258     gtk_text_buffer_move_mark(s->buf, s->mark, &s->iter);
259 }
260 
replace_string_dialog(windata_t * vwin)261 static void replace_string_dialog (windata_t *vwin)
262 {
263     GtkWidget *label, *button;
264     GtkWidget *vbox, *abox;
265     GtkWidget *table;
266     struct search_replace sr_t;
267     struct search_replace *s = &sr_t;
268 
269     s->find = s->replace = NULL;
270 
271     s->view = GTK_TEXT_VIEW(vwin->text);
272     s->buf = gtk_text_view_get_buffer(s->view);
273     gtk_text_buffer_get_start_iter(s->buf, &s->iter);
274     s->mark = gtk_text_buffer_create_mark(s->buf, "srmark",
275 					  &s->iter, FALSE);
276 
277     s->w = gretl_gtk_dialog();
278     gretl_dialog_set_destruction(s->w, vwin_toplevel(vwin));
279     g_signal_connect(G_OBJECT(s->w), "destroy",
280 		     G_CALLBACK(destroy_replacer), s);
281 
282     gtk_window_set_title(GTK_WINDOW(s->w), _("gretl: replace"));
283     gtk_container_set_border_width(GTK_CONTAINER(s->w), 5);
284 
285     table = gtk_table_new(2, 2, FALSE);
286 
287     /* 'Find' label and entry */
288     label = gtk_label_new(_("Find:"));
289     s->f_entry = gtk_entry_new();
290     gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, 0, 0,
291 		     5, 5);
292     gtk_table_attach(GTK_TABLE(table), s->f_entry, 1, 2, 0, 1,
293 		     GTK_EXPAND | GTK_FILL, 0, 5, 5);
294 
295     /* 'Replace' label and entry */
296     label = gtk_label_new(_("Replace with:"));
297     s->r_entry = gtk_entry_new();
298     gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, 0, 0,
299 		     5, 5);
300     gtk_table_attach(GTK_TABLE(table), s->r_entry, 1, 2, 1, 2,
301 		     GTK_EXPAND | GTK_FILL, 0, 5, 5);
302 
303     vbox = gtk_dialog_get_content_area(GTK_DIALOG(s->w));
304     gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 5);
305 
306     abox = gtk_dialog_get_action_area(GTK_DIALOG(s->w));
307 
308     gtk_box_set_spacing(GTK_BOX(abox), 15);
309     gtk_box_set_homogeneous(GTK_BOX(abox), TRUE);
310     gtk_window_set_position(GTK_WINDOW(s->w), GTK_WIN_POS_MOUSE);
311 
312     /* Close button */
313     button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
314     gtk_widget_set_can_default(button, TRUE);
315     gtk_box_pack_start(GTK_BOX(abox), button, TRUE, TRUE, 0);
316     g_signal_connect(G_OBJECT(button), "clicked",
317 		     G_CALLBACK(delete_widget), s->w);
318 
319     /* Replace All button */
320     button = gtk_button_new_with_mnemonic(_("Replace _All"));
321     gtk_widget_set_can_default(button, TRUE);
322     gtk_box_pack_start(GTK_BOX(abox), button, TRUE, TRUE, 0);
323     g_signal_connect(G_OBJECT(button), "clicked",
324 		     G_CALLBACK(replace_all_callback), s);
325 
326     /* Replace button */
327     button = gtk_button_new_with_mnemonic(_("_Replace"));
328     gtk_widget_set_can_default(button, TRUE);
329     gtk_box_pack_start(GTK_BOX(abox), button, TRUE, TRUE, 0);
330     g_signal_connect(G_OBJECT(button), "clicked",
331 		     G_CALLBACK(replace_single_callback), s);
332     gtk_widget_set_sensitive(button, FALSE);
333     s->r_button = button;
334 
335     /* Find button -- make this the default */
336     button = gtk_button_new_from_stock(GTK_STOCK_FIND);
337     gtk_widget_set_can_default(button, TRUE);
338     gtk_box_pack_start(GTK_BOX(abox), button, TRUE, TRUE, 0);
339     g_signal_connect(G_OBJECT(button), "clicked",
340 		     G_CALLBACK(replace_find_callback), s);
341     gtk_widget_grab_default(button);
342 
343     gtk_widget_grab_focus(s->f_entry);
344     gtk_widget_show_all(s->w);
345 
346     /* we need to block so that the search/replace
347        struct (above) doesn't drop out of scope */
348     gtk_main();
349 }
350 
text_replace(GtkWidget * w,windata_t * vwin)351 void text_replace (GtkWidget *w, windata_t *vwin)
352 {
353     replace_string_dialog(vwin);
354 }
355 
prep_prn_for_file_save(PRN * prn,int fmt)356 static int prep_prn_for_file_save (PRN *prn, int fmt)
357 {
358     const char *orig = gretl_print_get_buffer(prn);
359     char *modbuf;
360     int err;
361 
362     err = maybe_post_process_buffer(orig, fmt, W_SAVE, &modbuf);
363 
364     if (!err && modbuf != NULL) {
365 	gretl_print_replace_buffer(prn, modbuf);
366     }
367 
368     return err;
369 }
370 
special_text_handler(windata_t * vwin,guint fmt,int what)371 static int special_text_handler (windata_t *vwin, guint fmt, int what)
372 {
373     int cmd = vwin->role;
374     PRN *prn = NULL;
375     int err = 0;
376 
377     if (bufopen(&prn)) {
378 	return 1;
379     }
380 
381     gretl_print_set_format(prn, fmt);
382 
383     if (cmd == SUMMARY) {
384 	Summary *summ = (Summary *) vwin->data;
385 
386 	special_print_summary(summ, dataset, prn);
387     } else if (cmd == CORR || cmd == COVAR) {
388 	VMatrix *corr = (VMatrix *) vwin->data;
389 
390 	special_print_vmatrix(corr, dataset, prn);
391     } else if (cmd == AFR) {
392 	FITRESID *fr = (FITRESID *) vwin->data;
393 
394 	special_print_fit_resid(fr, dataset, prn);
395     } else if (cmd == FCAST) {
396 	FITRESID *fr = (FITRESID *) vwin->data;
397 
398 	special_print_forecast(fr, dataset, prn);
399     } else if (cmd == COEFFINT) {
400 	CoeffIntervals *cf = (CoeffIntervals *) vwin->data;
401 
402 	special_print_confints(cf, prn);
403     } else if (cmd == VIEW_MODEL) {
404 	MODEL *pmod = (MODEL *) vwin->data;
405 
406 	if (pmod->errcode) {
407 	    err = pmod->errcode;
408 	} else {
409 	    int wdigits = widget_get_int(vwin->text, "digits");
410 	    int dsave = get_gretl_digits();
411 
412 	    if (wdigits > 0 && wdigits != dsave) {
413 		set_gretl_digits(wdigits);
414 	    }
415 	    if (tex_format(prn)) {
416 		err = tex_print_model(pmod, dataset,
417 				      get_tex_eqn_opt(),
418 				      prn);
419 	    } else {
420 		/* RTF or CSV */
421 		err = printmodel(pmod, dataset, OPT_NONE, prn);
422 	    }
423 	    set_gretl_digits(dsave);
424 	}
425     } else if (cmd == VAR || cmd == VECM) {
426 	GRETL_VAR *var = (GRETL_VAR *) vwin->data;
427 
428 	err = gretl_VAR_print(var, dataset, OPT_NONE, prn);
429     } else if (cmd == VAR_IRF || cmd == VAR_DECOMP) {
430 	windata_t *parent = vwin->gretl_parent;
431 
432 	if (parent == NULL) {
433 	    err = E_DATA;
434 	} else {
435 	    GRETL_VAR *var = (GRETL_VAR *) parent->data;
436 	    /* here active_var records preferred horizon */
437 	    int h = vwin->active_var;
438 
439 	    if (cmd == VAR_IRF) {
440 		gretl_VAR_print_all_impulse_responses(var, dataset, h, prn);
441 	    } else {
442 		gretl_VAR_print_all_fcast_decomps(var, dataset, h, prn);
443 	    }
444 	}
445     } else if (cmd == SYSTEM) {
446 	equation_system *sys = (equation_system *) vwin->data;
447 
448 	err = gretl_system_print(sys, dataset, OPT_NONE, prn);
449     } else if (cmd == VIEW_MODELTABLE) {
450 	err = special_print_model_table(prn);
451     }
452 
453     if (!err && what == W_SAVE) {
454 	err = prep_prn_for_file_save(prn, fmt);
455     }
456 
457     if (err) {
458 	gui_errmsg(err);
459     } else {
460 	if (what == W_PREVIEW) {
461 	    /* TeX only: there's no RTF preview option */
462 	    view_latex(prn);
463 	} else if (what == W_COPY) {
464 	    prn_to_clipboard(prn, fmt);
465 	} else if (what == W_SAVE) {
466 	    int action;
467 
468 	    if (fmt & GRETL_FORMAT_TEX) {
469 		action = SAVE_TEX;
470 	    } else if (fmt & GRETL_FORMAT_CSV) {
471 		action = EXPORT_CSV;
472 	    } else {
473 		action = SAVE_RTF;
474 	    }
475 
476 	    file_selector_with_parent(action, FSEL_DATA_PRN,
477 				      prn, vwin_toplevel(vwin));
478 	}
479     }
480 
481     gretl_print_destroy(prn);
482 
483     return err;
484 }
485 
window_tex_callback(GtkWidget * w,windata_t * vwin)486 void window_tex_callback (GtkWidget *w, windata_t *vwin)
487 {
488     const char *opts[] = {
489 	N_("View"),
490 	N_("Copy"),
491 	N_("Save")
492     };
493     int opt;
494 
495     if (vwin->role == VAR_IRF || vwin->role == VAR_DECOMP) {
496 	if (vwin->gretl_parent == NULL) {
497 	    warnbox(_("Not available"));
498 	    gtk_widget_set_sensitive(w, FALSE);
499 	    return;
500 	}
501     }
502 
503     opt = radio_dialog("gretl: LaTeX", NULL, opts, 3, 0, 0,
504 		       vwin_toplevel(vwin));
505 
506     if (opt >= 0) {
507 	int fmt = GRETL_FORMAT_TEX;
508 
509 	if (vwin->role == VIEW_MODELTABLE) {
510 	    fmt |= GRETL_FORMAT_MODELTAB;
511 
512 	    if (model_table_landscape()) {
513 		fmt |= GRETL_FORMAT_LANDSCAPE;
514 	    }
515 	}
516 
517 	special_text_handler(vwin, fmt, opt);
518     }
519 }
520 
tex_format_code(GtkAction * action)521 static int tex_format_code (GtkAction *action)
522 {
523     const gchar *s = gtk_action_get_name(action);
524     int fmt = GRETL_FORMAT_TEX;
525 
526     if (strstr(s, "Eqn")) {
527 	fmt |= GRETL_FORMAT_EQN;
528     }
529 
530     return fmt;
531 }
532 
model_tex_view(GtkAction * action,gpointer data)533 void model_tex_view (GtkAction *action, gpointer data)
534 {
535     windata_t *vwin = (windata_t *) data;
536     int fmt = tex_format_code(action);
537 
538     special_text_handler(vwin, fmt, W_PREVIEW);
539 }
540 
model_tex_save(GtkAction * action,gpointer data)541 void model_tex_save (GtkAction *action, gpointer data)
542 {
543     windata_t *vwin = (windata_t *) data;
544     int fmt = tex_format_code(action);
545 
546     special_text_handler(vwin, fmt, W_SAVE);
547 }
548 
model_tex_copy(GtkAction * action,gpointer data)549 void model_tex_copy (GtkAction *action, gpointer data)
550 {
551     windata_t *vwin = (windata_t *) data;
552     int fmt = tex_format_code(action);
553 
554     special_text_handler(vwin, fmt, W_COPY);
555 }
556 
text_window_get_copy_buf(windata_t * vwin,int select)557 static gchar *text_window_get_copy_buf (windata_t *vwin, int select)
558 {
559     gchar *cpy = NULL;
560 
561     if (select) {
562 	GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text));
563 	GtkTextIter start, end;
564 
565 	if (gtk_text_buffer_get_selection_bounds(buf, &start, &end)) {
566 	    cpy = gtk_text_buffer_get_text(buf, &start, &end, FALSE);
567 	}
568     } else {
569 	cpy = textview_get_text(vwin->text);
570     }
571 
572     return cpy;
573 }
574 
multiple_formats_ok(windata_t * vwin)575 int multiple_formats_ok (windata_t *vwin)
576 {
577     int r = vwin->role;
578 
579     if (r == SUMMARY || r == VAR_SUMMARY ||
580 	r == ALL_SUMMARY || r == AFR ||
581 	r == CORR || r == ALL_CORR ||
582 	r == FCAST || r == COEFFINT ||
583 	r == COVAR || r == VIEW_MODELTABLE ||
584 	r == VAR || r == VECM ||
585 	r == VAR_IRF || r == VAR_DECOMP) {
586 	return 1;
587     } else if (r == VIEW_MODEL) {
588 	MODEL *pmod = (MODEL *) vwin->data;
589 
590 	return !RQ_SPECIAL_MODEL(pmod);
591     } else {
592 	return 0;
593     }
594 }
595 
make_prn_for_buf(gchar * buf,int fmt,int action,int * err)596 static PRN *make_prn_for_buf (gchar *buf, int fmt, int action,
597 			      int *err)
598 {
599     char *modbuf = NULL;
600     PRN *prn = NULL;
601 
602     if (action == W_SAVE) {
603 	*err = maybe_post_process_buffer(buf, fmt, action, &modbuf);
604     }
605 
606     if (*err) {
607 	g_free(buf);
608     } else {
609 	if (modbuf != NULL) {
610 	    prn = gretl_print_new_with_buffer(modbuf);
611 	    g_free(buf);
612 	} else {
613 	    prn = gretl_print_new_with_buffer(buf);
614 	}
615 	if (prn == NULL) {
616 	    *err = E_ALLOC;
617 	}
618     }
619 
620     return prn;
621 }
622 
623 /* copying text from gretl windows */
624 
625 #define SPECIAL_FORMAT(f) ((f & GRETL_FORMAT_TEX) || \
626                            (f & GRETL_FORMAT_RTF))
627 
window_copy_or_save(windata_t * vwin,guint fmt,int action)628 static void window_copy_or_save (windata_t *vwin, guint fmt, int action)
629 {
630     gchar *buf = NULL;
631 
632     if (vwin->role == VIEW_MODEL && fmt == GRETL_FORMAT_CSV) {
633 	special_text_handler(vwin, fmt, action);
634     } else if (multiple_formats_ok(vwin) && SPECIAL_FORMAT(fmt)) {
635 	special_text_handler(vwin, fmt, action);
636     } else if (fmt == GRETL_FORMAT_CSV || fmt == GRETL_FORMAT_TAB ||
637 	       fmt == GRETL_FORMAT_RTF) {
638 	copy_vars_formatted(vwin, fmt, action);
639     } else if (fmt == GRETL_FORMAT_TXT || fmt == GRETL_FORMAT_RTF_TXT) {
640 	buf = text_window_get_copy_buf(vwin, 0);
641     } else if (fmt == GRETL_FORMAT_SELECTION) {
642 	buf = text_window_get_copy_buf(vwin, 1);
643 	fmt = GRETL_FORMAT_TXT;
644     }
645 
646     if (buf != NULL) {
647 	/* handle the last two cases above */
648 	PRN *prn;
649 	int err = 0;
650 
651 	prn = make_prn_for_buf(buf, fmt, action, &err);
652 
653 	if (!err) {
654 	    if (action == W_COPY) {
655 		prn_to_clipboard(prn, fmt);
656 	    } else {
657 		/* saving to file */
658 		int fcode = (fmt == GRETL_FORMAT_RTF_TXT)?
659 		    SAVE_RTF : SAVE_OUTPUT;
660 
661 		file_selector_with_parent(fcode, FSEL_DATA_PRN, prn,
662 					  vwin_toplevel(vwin));
663 	    }
664 	    gretl_print_destroy(prn);
665 	}
666     }
667 }
668 
window_copy(windata_t * vwin,guint fmt)669 void window_copy (windata_t *vwin, guint fmt)
670 {
671     window_copy_or_save(vwin, fmt, W_COPY);
672 }
673 
window_save(windata_t * vwin,guint fmt)674 void window_save (windata_t *vwin, guint fmt)
675 {
676     window_copy_or_save(vwin, fmt, W_SAVE);
677 }
678 
679 /* "native" printing from gretl windows */
680 
window_print(GtkAction * action,windata_t * vwin)681 void window_print (GtkAction *action, windata_t *vwin)
682 {
683     gchar *buf, *selbuf = NULL;
684     const char *filename = NULL;
685     GtkTextBuffer *tbuf;
686     GtkTextIter start, end;
687 
688     tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text));
689     buf = textview_get_text(vwin->text);
690 
691     if (gtk_text_buffer_get_selection_bounds(tbuf, &start, &end)) {
692 	selbuf = gtk_text_buffer_get_text(tbuf, &start, &end, FALSE);
693     }
694 
695     if (vwin->role == EDIT_HANSL ||
696 	vwin->role == VIEW_SCRIPT) {
697 	const char *p = path_last_slash_const(vwin->fname);
698 
699 	if (p != NULL) {
700 	    filename = p + 1;
701 	} else {
702 	    filename = vwin->fname;
703 	}
704     }
705 
706     print_window_content(buf, selbuf, filename, vwin);
707 
708     g_free(buf);
709     g_free(selbuf);
710 }
711 
712 #define U_MINUS(u,i) (u[i] == 0xE2 && u[i+1] == 0x88 && u[i+2] == 0x92)
713 
714 /* check @s for Unicode minus signs (U+2212), and if any are found do an
715    in-place replacement with ASCII dashes
716 */
717 
strip_unicode_minus(char * s)718 char *strip_unicode_minus (char *s)
719 {
720     unsigned char *u = (unsigned char *) s;
721     int i, n = strlen(s);
722     int got_minus = 0;
723 
724     for (i=0; i<n-3; i++) {
725 	if (U_MINUS(u, i)) {
726 	    got_minus = 1;
727 	    break;
728 	}
729     }
730 
731     if (got_minus) {
732 	char *tmp = calloc(n, 1);
733 
734 	if (tmp != NULL) {
735 	    int j = 0;
736 
737 	    for (i=0; i<n; i++) {
738 		if (i < n - 3 && U_MINUS(u, i)) {
739 		    tmp[j++] = '-';
740 		    i += 2;
741 		} else {
742 		    tmp[j++] = u[i];
743 		}
744 	    }
745 	    strcpy(s, tmp);
746 	    free(tmp);
747 	}
748     }
749 
750     return s;
751 }
752 
has_unicode_minus(const unsigned char * s)753 int has_unicode_minus (const unsigned char *s)
754 {
755     int i, n = strlen((const char *) s);
756     int has_minus = 0;
757 
758     for (i=0; i<n-3; i++) {
759 	if (U_MINUS(s, i)) {
760 	    has_minus = 1;
761 	    break;
762 	}
763     }
764 
765     return has_minus;
766 }
767 
768 /* print buf to file, trying to ensure it's not messed up */
769 
system_print_buf(const gchar * buf,FILE * fp)770 void system_print_buf (const gchar *buf, FILE *fp)
771 {
772     const char *p = buf;
773     int cbak = 0;
774 
775     while (*p) {
776 	if (*p == '\r') {
777 	    if (*(p+1) != '\n') {
778 		fputc('\n', fp);
779 	    }
780 	} else {
781 	    fputc(*p, fp);
782 	}
783 	cbak = *p;
784 	p++;
785     }
786 
787     /* ensure file ends with newline */
788     if (cbak != '\n') {
789 	fputc('\n', fp);
790     }
791 }
792 
793 /* Convert a buffer to DOS/Windows text format, optionally
794    adding minimal RTF formatting. This function does not
795    take charge of text re-encoding, it just handles CR + LF
796    endings and (optional) RTF monospaced font spec.
797 */
798 
dosify_buffer(const char * buf,int format)799 static char *dosify_buffer (const char *buf, int format)
800 {
801 #ifdef G_OS_WIN32 /* alt font not working with lowriter */
802     const char *rtf_preamble = "{\\rtf1\r\n"
803 	"{\\fonttbl{\\f0\\fnil\\fprq1\\fcharset1 Consolas{\\*\\falt Courier New};}}\r\n"
804 	"\\f0\\fs18\r\n";
805 #else
806     const char *rtf_preamble = "{\\rtf1\r\n"
807 	"{\\fonttbl{\\f0\\fnil\\fprq1\\fcharset1 Courier New;}}\r\n"
808 	"\\f0\\fs18\r\n";
809 #endif
810     int extra = 0, nlines = 0;
811     int add_rtf = 0;
812     char *targ, *q;
813     const char *p;
814 
815     if (buf == NULL || *buf == '\0') {
816 	return NULL;
817     }
818 
819     if (format == GRETL_FORMAT_RTF_TXT) {
820 	add_rtf = 1;
821     }
822 
823     p = buf;
824     while (*p) {
825 	if (*p++ == '\n') nlines++;
826     }
827     extra = nlines + 1;
828 
829     if (add_rtf) {
830 	extra *= 5;
831 	extra += strlen(rtf_preamble) + 5;
832     }
833 
834     targ = malloc(strlen(buf) + extra);
835     if (targ == NULL) {
836 	return NULL;
837     }
838 
839     if (add_rtf) {
840 	strcpy(targ, rtf_preamble);
841 	q = targ + strlen(targ);
842     } else {
843 	q = targ;
844     }
845 
846     p = buf;
847 
848     while (*p) {
849 	int pplus = 1;
850 	int nl = 0;
851 
852 	if (*p == '\r' && *(p+1) == '\n') {
853 	    nl = 1; pplus = 2;
854 	} else if (*p == '\n') {
855 	    nl = 1;
856 	}
857 
858 	if (nl) {
859 	    if (add_rtf) {
860 		*q++ = '\\';
861 		*q++ = 'p';
862 		*q++ = 'a';
863 		*q++ = 'r';
864 	    }
865 	    *q++ = '\r';
866 	    *q++ = '\n';
867 	} else {
868 	    *q++ = *p;
869 	}
870 
871 	p += pplus;
872     }
873 
874     *q = '\0';
875 
876     if (add_rtf) {
877 	strcat(q, "}\r\n");
878     }
879 
880     return targ;
881 }
882 
883 #ifdef G_OS_WIN32
884 
885 #define plain_text(f) (f & (GRETL_FORMAT_TXT | GRETL_FORMAT_CSV | GRETL_FORMAT_TAB))
886 
want_bom(int fmt,int action)887 static int want_bom (int fmt, int action)
888 {
889     /* Note sure about this, but for now if we're saving
890        "plain text" to file in UTF-8, we'll leave it in
891        UTF-8 and prepend the UTF-8 BOM.
892     */
893     if (action == W_SAVE && fmt == GRETL_FORMAT_TXT) {
894 	return 1;
895     } else {
896 	return 0;
897     }
898 }
899 
prepend_bom(const char * orig)900 static char *prepend_bom (const char *orig)
901 {
902     char *buf = malloc(strlen(orig) + 4);
903 
904     if (buf != NULL) {
905 	buf[0] = 0xEF;
906 	buf[1] = 0xBB;
907 	buf[2] = 0xBF;
908 	buf[3] = 0;
909 	strcat(buf, orig);
910     }
911 
912     return buf;
913 }
914 
915 #endif
916 
maybe_post_process_buffer(const char * buf,int fmt,int action,char ** modbuf)917 int maybe_post_process_buffer (const char *buf, int fmt,
918 			       int action, char **modbuf)
919 {
920     int rtf_output = 0;
921     int utf8_coded = 0;
922     char *trbuf = NULL;
923     char *final = NULL;
924     int err = 0;
925 
926     if (fmt & (GRETL_FORMAT_RTF | GRETL_FORMAT_RTF_TXT)) {
927 	rtf_output = 1;
928     }
929 
930     if (utf8_encoded(buf)) {
931 	utf8_coded = 1;
932     }
933 
934     if (rtf_output) {
935 	/* When writing RTF, recode if required and ensure
936 	   CR + LF.
937 	*/
938 	if (utf8_coded) {
939 	    trbuf = utf8_to_rtf(buf);
940 	    if (trbuf == NULL) {
941 		err = E_ALLOC;
942 	    }
943 	}
944 	if (!err) {
945 	    if (trbuf != NULL) {
946 		final = dosify_buffer(trbuf, fmt);
947 	    } else {
948 		final = dosify_buffer(buf, fmt);
949 	    }
950 	    if (final == NULL) {
951 		err = E_ALLOC;
952 	    }
953 	}
954 	goto finish;
955     }
956 
957 #ifdef G_OS_WIN32
958     if (plain_text(fmt)) {
959 	if (utf8_coded && want_bom(fmt, action)) {
960 	    trbuf = prepend_bom(buf);
961 	    if (trbuf == NULL) {
962 		err = E_ALLOC;
963 	    }
964 	}
965 	if (!err && action == W_COPY) {
966 	    if (trbuf != NULL) {
967 		final = dosify_buffer(trbuf, fmt);
968 	    } else {
969 		final = dosify_buffer(buf, fmt);
970 	    }
971 	    if (final == NULL) {
972 		err = E_ALLOC;
973 	    }
974 	} else {
975 	    final = trbuf;
976 	}
977     }
978 #endif
979 
980  finish:
981 
982     if (trbuf != NULL && trbuf != final) {
983 	free(trbuf);
984     }
985 
986     *modbuf = final;
987 
988     return err;
989 }
990