1 /*
2  *  R : A Computer Language for Statistical Data Analysis
3  *  file pager.c
4  *  Copyright (C) 1998--2002  Guido Masarotto and Brian Ripley
5  *  Copyright (C) 2004--8     The R Foundation
6  *  Copyright (C) 2004--2020  The R Core Team
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program; if not, a copy is available at
20  *  https://www.R-project.org/Licenses/
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 
27 #include "win-nls.h"
28 
29 #ifdef Win32
30 #define USE_MDI 1
31 #endif
32 
33 #define WIN32_LEAN_AND_MEAN 1
34 #include <windows.h>
35 #include "graphapp/ga.h"
36 #ifdef USE_MDI
37 #include "graphapp/stdimg.h"
38 #endif
39 #include "console.h"
40 #include "consolestructs.h"
41 #include "rui.h"
42 #include <Startup.h> /* for CharacterMode */
43 
44 #define CE_UTF8 1
45 extern size_t Rf_utf8towcs(wchar_t *wc, const char *s, size_t n);
46 
47 #define PAGERMAXKEPT 12
48 #define PAGERMAXTITLE 128
49 
50 static int pagerActualKept = 0, pagerActualShown;
51 static pager pagerInstance = NULL;
52 static menubar pagerBar = NULL;
53 static xbuf pagerXbuf[PAGERMAXKEPT];
54 static char pagerTitles[PAGERMAXKEPT][PAGERMAXTITLE+8];
55 static menuitem pagerMenus[PAGERMAXKEPT];
56 static int pagerRow[PAGERMAXKEPT];
57 static void pagerupdateview(void);
58 
59 void menueditoropen(control m);
60 void menueditornew(control m);
61 
62 /* from console.c */
63 extern int pagerMultiple, haveusedapager;
64 
65 
66 /*
67    To be fixed: during creation, memory is allocated two times
68    (faster for small files but a big waste otherwise)
69 */
file2xbuf(const char * name,int enc,int del)70 static xbuf file2xbuf(const char *name, int enc, int del)
71 {
72     HANDLE f;
73     DWORD rr, vv;
74     char *p;
75     xlong dim, cnt;
76     xint  ms;
77     xbuf  xb;
78     wchar_t *wp, *q;
79 
80     if (enc == CE_UTF8) {
81 	wchar_t wfn[MAX_PATH+1];
82 	Rf_utf8towcs(wfn, name, MAX_PATH+1);
83 	f = CreateFileW(wfn, GENERIC_READ, FILE_SHARE_READ,
84 			NULL, OPEN_EXISTING, 0, NULL);
85     } else
86 	f = CreateFile(name, GENERIC_READ, FILE_SHARE_READ,
87 		       NULL, OPEN_EXISTING, 0, NULL);
88     if (f == INVALID_HANDLE_VALUE) {
89 	R_ShowMessage(G_("Error opening file"));
90 	return NULL;
91     }
92     vv = GetFileSize(f, NULL);
93     p = (char *) malloc((size_t) vv + 1);
94     if (!p) {
95 	CloseHandle(f);
96 	R_ShowMessage(G_("Insufficient memory to display file in internal pager"));
97 	return NULL;
98     }
99     ReadFile(f, p, vv, &rr, NULL);
100     CloseHandle(f);
101     if (del) DeleteFile(name);
102     p[rr] = '\0';
103     cnt = mbstowcs(NULL, p, 0);
104     wp = (wchar_t *) malloc((cnt+1) * sizeof(wchar_t));
105     mbstowcs(wp, p, cnt+1);
106     for (q = wp, ms = 1, dim = cnt; *q; q++) {
107 	if (*q == '\t')
108 	    dim += TABSIZE;
109 	else if (*q == '\n') {
110 	    dim++;
111 	    ms++;
112 	}
113     }
114     free(p);
115     if ((xb = newxbuf(dim + 1, ms + 1, 1)))
116 	for (q = wp, ms = 0; *q; q++) {
117 	    if (*q == L'\r') continue;
118 	    if (*q == L'\n') {
119 		ms++;
120 		xbufaddxc(xb, *q);
121 		/* next line interprets underlining in help files */
122 		if (q[1] ==  L'_' && q[2] == L'\b') xb->user[ms] = -2;
123 	    } else xbufaddxc(xb, *q);
124 	}
125     free(wp);
126     return xb;
127 }
128 
delpager(control m)129 static void delpager(control m)
130 {
131     int i;
132 
133     ConsoleData p = getdata(m);
134     if (!pagerMultiple) {
135 	for (i = 0; i < pagerActualKept; i++) xbufdel(pagerXbuf[i]);
136 	pagerActualKept = 0;
137     }
138     else
139 	xbufdel(p->lbuf);
140     freeConsoleData(getdata(m));
141 }
142 
pagerbclose(control m)143 void pagerbclose(control m)
144 {
145     show(RConsole);
146     if (!pagerMultiple) {
147 	hide(pagerInstance);
148 	del(pagerInstance);
149 	pagerInstance = pagerBar = NULL;
150     }
151     else {
152 	hide(m);
153 	del(m);
154     }
155 }
156 
pagerclose(control m)157 static void pagerclose(control m)
158 {
159     pagerbclose(getdata(m));
160 }
161 
pagerprint(control m)162 static void pagerprint(control m)
163 {
164     consoleprint(getdata(m));
165 }
166 
pagersavefile(control m)167 static void pagersavefile(control m)
168 {
169     consolesavefile(getdata(m), 1);
170 }
171 
pagercopy(control m)172 static void pagercopy(control m)
173 {
174     control c = getdata(m);
175 
176     if (consolecancopy(c)) consolecopy(c);
177     else R_ShowMessage(G_("No selection"));
178 }
179 
pagerpaste(control m)180 static void pagerpaste(control m)
181 {
182     control c = getdata(m);
183 
184     if (CharacterMode != RGui) {
185 	R_ShowMessage(G_("No RGui console to paste to"));
186 	return;
187     }
188     if (!consolecancopy(c)) {
189 	R_ShowMessage(G_("No selection"));
190 	return;
191     } else {
192 	consolecopy(c);
193     }
194     if (consolecanpaste(RConsole)) {
195 	consolepaste(RConsole);
196 	show(RConsole);
197     }
198 }
199 
pagerpastecmds(control m)200 static void pagerpastecmds(control m)
201 {
202     control c = getdata(m);
203 
204     if (CharacterMode != RGui) {
205 	R_ShowMessage(G_("No RGui console to paste to"));
206 	return;
207     }
208     if (!consolecancopy(c)) {
209 	R_ShowMessage(G_("No selection"));
210 	return;
211     } else {
212 	consolecopy(c);
213     }
214     if (consolecanpaste(RConsole)) {
215 	consolepastecmds(RConsole);
216 	show(RConsole);
217     }
218 }
219 
pagerselectall(control m)220 static void pagerselectall(control m)
221 {
222     control c = getdata(m);
223 
224     consoleselectall(c);
225 }
226 
pagerstayontop(control m)227 static void pagerstayontop(control m)
228 {
229     control c = getdata(m);
230 
231     BringToTop(c, 2);
232 }
233 
pagerconsole(control m)234 static void pagerconsole(control m)
235 {
236     show(RConsole);
237 }
238 
pagerchangeview(control m)239 static void pagerchangeview(control m)
240 {
241     ConsoleData p = getdata(pagerInstance);
242     int i = getvalue(m);
243 
244     if (i >= pagerActualKept) return;
245     uncheck(pagerMenus[pagerActualShown]);
246     /* save position of middle line of pager display */
247     pagerRow[pagerActualShown] = FV + ROWS/2;
248     pagerActualShown = i;
249     check(pagerMenus[i]);
250     pagerupdateview();
251 }
252 
pagerupdateview(void)253 static void pagerupdateview(void)
254 {
255     control c = pagerInstance;
256     ConsoleData p = getdata(c);
257 
258     settext(pagerInstance, &pagerTitles[pagerActualShown][4]);
259     p->lbuf = pagerXbuf[pagerActualShown];
260     setfirstvisible(c, pagerRow[pagerActualShown] - ROWS/2);
261     setfirstcol(c, 0);
262     show(c);
263 }
264 
pageraddfile(const char * wtitle,const char * filename,int enc,int deleteonexit)265 static int pageraddfile(const char *wtitle,
266 			const char *filename, int enc,
267 			int deleteonexit)
268 {
269     ConsoleData p = getdata(pagerInstance);
270     int i;
271     xbuf nxbuf = file2xbuf(filename, enc, deleteonexit);
272 
273     if (!nxbuf) {
274 	/* R_ShowMessage("File not found or memory insufficient"); */
275 	return 0;
276     }
277     if (pagerActualKept == PAGERMAXKEPT) {
278 	pagerActualKept -= 1;
279 	xbufdel(pagerXbuf[pagerActualKept]);
280     }
281     if(pagerActualKept > 0)
282 	pagerRow[0] = FV;
283     for (i = pagerActualKept; i > 0; i--) {
284 	pagerXbuf[i] = pagerXbuf[i - 1];
285 	pagerRow[i] = pagerRow[i - 1];
286 	strcpy(&pagerTitles[i][4], &pagerTitles[i - 1][4]);
287     }
288     pagerXbuf[0] = nxbuf;
289     pagerRow[0] = 0;
290     strcpy(&pagerTitles[0][4], wtitle);
291     pagerActualKept += 1;
292     for (i = 0; i < pagerActualKept; i++) {
293 	enable(pagerMenus[i]);
294 	settext(pagerMenus[i], pagerTitles[i]);
295     }
296     for (i = pagerActualKept; i < PAGERMAXKEPT; i++)
297 	disable(pagerMenus[i]);
298     uncheck(pagerMenus[pagerActualShown]);
299     pagerActualShown = 0;
300     check(pagerMenus[pagerActualShown]);
301     return 1;
302 }
303 
304 static MenuItem PagerPopup[] = {		   /* Numbers used below */
305     {GN_("Copy"), pagercopy, 'C', 0},			   /* 0 */
306     {GN_("Paste to console"), pagerpaste, 'V', 0},	   /* 1 */
307     {GN_("Paste commands to console"), pagerpastecmds, 0, 0},   /* 2 */
308     {GN_("Select all"), pagerselectall, 'A', 0},		   /* 3 */
309     {"-", 0, 0, 0},
310     {GN_("Stay on top"), pagerstayontop, 0, 0},		   /* 5 */
311     {"-", 0, 0, 0},
312     {GN_("Close"), pagerclose, 0, 0},			   /* 7 */
313     LASTMENUITEM
314 };
315 
pagermenuact(control m)316 static void pagermenuact(control m)
317 {
318     control c = getdata(m);
319     ConsoleData p = getdata(c);
320     if (consolecancopy(c)) {
321 	enable(p->mcopy);
322 	enable(p->mpopcopy);
323 	if (CharacterMode == RGui) {
324 	    enable(p->mpaste);
325 	    enable(p->mpastecmds);
326 	    enable(p->mpoppaste);
327 	    enable(p->mpoppastecmds);
328 	}
329     } else {
330 	disable(p->mcopy);
331 	disable(p->mpopcopy);
332 	disable(p->mpaste);
333 	disable(p->mpastecmds);
334 	disable(p->mpoppaste);
335 	disable(p->mpoppastecmds);
336     }
337     if (ismdi())
338 	disable(PagerPopup[5].m);
339     else {
340 	enable(PagerPopup[5].m);
341 	if (isTopmost(c))
342 	    check(PagerPopup[5].m);
343 	else
344 	    uncheck(PagerPopup[5].m);
345     }
346 }
347 
348 
349 #define MCHECK(a) if (!(a)) {freeConsoleData(p);del(c);return NULL;}
350 RECT *RgetMDIsize(void); /* in rui.c */
351 
pagercreate(void)352 static pager pagercreate(void)
353 {
354     ConsoleData p;
355     int w, h, i, x, y, w0, h0;
356     pager c;
357     menuitem m;
358 
359     p = newconsoledata((consolefn) ? consolefn : FixedFont,
360 		       pagerrow, pagercol, 0, 0,
361 		       guiColors,
362 		       PAGER, 0, 0);
363     if (!p) return NULL;
364 
365 /*    if (ismdi()) {
366       x = y = w = h = 0;
367       }
368       else {
369       w = WIDTH ;
370       h = HEIGHT;
371       x = (devicewidth(NULL) - w) / 2;
372       y = (deviceheight(NULL) - h) / 2 ;
373       } */
374     w = WIDTH ;
375     h = HEIGHT;
376     /* centre a single pager, randomly place each of multiple pagers */
377 #ifdef USE_MDI
378     if(ismdi()) {
379 	RECT *pR = RgetMDIsize();
380 	w0 = pR->right;
381 	h0 = pR->bottom;
382     } else {
383 #endif
384 	w0 = devicewidth(NULL);
385 	h0 = deviceheight(NULL);
386 #ifdef USE_MDI
387     }
388 #endif
389     x = (w0 - w) / 2; x = x > 20 ? x:20;
390     y = (h0 - h) / 2; y = y > 20 ? y:20;
391     if(pagerMultiple) {
392 #ifdef Win32
393 	DWORD rand = GetTickCount();
394 #else
395 	int rand = 0;
396 #endif
397 	int w0 = 0.4*x, h0 = 0.4*y;
398 	w0 = w0 > 20 ? w0 : 20;
399 	h0 = h0 > 20 ? h0 : 20;
400 	x += (rand % w0) - w0/2;
401 	y += ((rand/w0) % h0) - h0/2;
402     }
403     c = (pager) newwindow("PAGER", rect(x, y, w, h),
404 			  Document | StandardWindow | Menubar |
405 			  VScrollbar | HScrollbar | TrackMouse);
406     if (!c) {
407 	freeConsoleData(p);
408 	return NULL;
409     }
410     setdata(c, p);
411     if(h == 0) HEIGHT = getheight(c);
412     if(w == 0) WIDTH  = getwidth(c);
413     COLS = WIDTH / FW - 1;
414     ROWS = HEIGHT / FH - 1;
415     BORDERX = (WIDTH - COLS*FW) / 2;
416     BORDERY = (HEIGHT - ROWS*FH) / 2;
417     gsetcursor(c, ArrowCursor);
418     gchangescrollbar(c, VWINSB, 0, 0, ROWS, 0);
419     gchangescrollbar(c, HWINSB, 0, COLS-1, COLS, 1);
420     setbackground(c, guiColors[pagerbg]);
421 #ifdef USE_MDI
422     if (ismdi()) {
423 	int btsize = 24;
424 	rect r = rect(2, 2, btsize, btsize);
425 	control tb, bt;
426 	addto(c);
427 	MCHECK(tb = newtoolbar(btsize + 4));
428 	gsetcursor(tb, ArrowCursor);
429 	addto(tb);
430 	MCHECK(bt = newtoolbutton(open_image, r, menueditoropen));
431 	MCHECK(addtooltip(bt, G_("Open script")));
432 	gsetcursor(bt, ArrowCursor);
433 	/* wants NULL as data, not the pager */
434 	r.x += (btsize + 6) ;
435 	MCHECK(bt = newtoolbutton(copy1_image, r, pagerpaste));
436 	MCHECK(addtooltip(bt, G_("Paste to console")));
437 	gsetcursor(bt, ArrowCursor);
438 	setdata(bt, (void *) c);
439 	r.x += (btsize + 6) ;
440 	MCHECK(bt = newtoolbutton(copy1_image, r, pagerpastecmds));
441 	MCHECK(addtooltip(bt, G_("Paste commands to console")));
442 	gsetcursor(bt, ArrowCursor);
443 	setdata(bt, (void *) c);
444 	r.x += (btsize + 6) ;
445 	MCHECK(bt = newtoolbutton(print_image, r, pagerprint));
446 	MCHECK(addtooltip(bt, G_("Print")));
447 	gsetcursor(bt, ArrowCursor);
448 	setdata(bt, (void *) c);
449 	r.x += (btsize + 6) ;
450 	MCHECK(bt = newtoolbutton(console_image, r, pagerconsole));
451 	MCHECK(addtooltip(bt, G_("Return focus to Console")));
452 	gsetcursor(bt, ArrowCursor);
453 	setdata(bt, (void *) c);
454     }
455 #endif
456     addto(c);
457     MCHECK(m = gpopup(pagermenuact, PagerPopup));
458     setdata(m, c);
459     setdata(p->mpopcopy = PagerPopup[0].m, c);
460     setdata(p->mpoppaste = PagerPopup[1].m, c);
461     setdata(p->mpoppastecmds = PagerPopup[2].m, c);
462     setdata(PagerPopup[3].m, c);
463     setdata(PagerPopup[5].m, c);
464     setdata(PagerPopup[7].m, c);
465     MCHECK(m = newmenubar(pagermenuact));
466     setdata(m, c);
467     MCHECK(newmenu(G_("File")));
468     MCHECK(m = newmenuitem(G_("New script"), 'N', menueditornew));
469     MCHECK(m = newmenuitem(G_("Open script..."), 'O', menueditoropen));
470     MCHECK(m = newmenuitem(G_("Print..."), 'P', pagerprint));
471     setdata(m, c);
472     MCHECK(m = newmenuitem(G_("Save to File..."), 'S', pagersavefile));
473     setdata(m, c);
474     MCHECK(m = newmenuitem("-", 0, NULL));
475     MCHECK(m = newmenuitem(G_("Close"), 0, pagerclose));
476     setdata(m, c);
477     MCHECK(newmenu(G_("Edit")));
478     MCHECK(p->mcopy = newmenuitem(G_("Copy"), 'C', pagercopy));
479     setdata(p->mcopy, c);
480     MCHECK(p->mpaste = newmenuitem(G_("Paste to console"), 'V', pagerpaste));
481     setdata(p->mpaste, c);
482     MCHECK(p->mpastecmds = newmenuitem(G_("Paste commands to console"), 0, pagerpastecmds));
483     setdata(p->mpastecmds, c);
484     MCHECK(m = newmenuitem(G_("Select all"), 'A', pagerselectall));
485     setdata(m, c);
486     if (!pagerMultiple) {
487 	MCHECK(newmenu(G_("View")));
488 	for (i = 0; i < PAGERMAXKEPT; i++) {
489 	    snprintf(pagerTitles[i], PAGERMAXTITLE+8, "&%c.  ", 'A' + i);
490 	    MCHECK(pagerMenus[i] = newmenuitem(&pagerTitles[i][1], 0,
491 					       pagerchangeview));
492 	    setvalue(pagerMenus[i], i);
493 	}
494     }
495 #ifdef USE_MDI
496     if (ismdi()) newmdimenu();
497     if (ismdi() && !(RguiMDI & RW_TOOLBAR)) toolbar_hide();
498 #endif
499     MCHECK(BM = newbitmap(WIDTH, HEIGHT, 2));
500     setdata(c, p);
501     sethit(c, console_sbf);
502     setresize(c, consoleresize);
503     setredraw(c, drawconsole);
504     setdel(c, delpager);
505     setclose(c, pagerbclose);
506     setkeyaction(c, console_ctrlkeyin);
507     setkeydown(c, console_normalkeyin);
508     setmousedrag(c, console_mousedrag);
509     setmouserepeat(c, console_mouserep);
510     setmousedown(c, console_mousedown);
511     return(c);
512 }
513 
newpager1win(const char * wtitle,const char * filename,int enc,int deleteonexit)514 static pager newpager1win(const char *wtitle,
515 			  const char *filename, int enc,
516 			  int deleteonexit)
517 {
518     if (!pagerInstance && !(pagerInstance = pagercreate())) {
519 	R_ShowMessage(G_("Unable to create pager window"));
520 	return NULL;
521     }
522     if (!pageraddfile(wtitle, filename, enc, deleteonexit)) return NULL;
523     pagerupdateview();
524     return pagerInstance;
525 }
526 
newpagerNwin(const char * wtitle,const char * filename,int enc,int deleteonexit)527 static pager newpagerNwin(const char *wtitle,
528 			  const char *filename, int enc,
529 			  int deleteonexit)
530 {
531     pager c = pagercreate();
532     ConsoleData p;
533 
534     if (!c) return NULL;
535     settext(c, wtitle);
536     p = getdata(c);
537     if (!(p->lbuf = file2xbuf(filename, enc, deleteonexit))) {
538 	del(c);
539 	return NULL;
540     }
541     if (c) {
542 	gchangescrollbar(c, VWINSB, 0, NUMLINES - 1 , ROWS, 0);
543 	show(c);
544     }
545     return c;
546 }
547 
newpager(const char * title,const char * filename,int enc,const char * header,int deleteonexit)548 pager newpager(const char *title,
549 	       const char *filename, int enc,
550 	       const char *header, int deleteonexit)
551 {
552     char wtitle[PAGERMAXTITLE+1];
553     pager c;
554 
555     /*    if (ismdi()) pagerMultiple = 1;*/
556     strncpy(wtitle, title, PAGERMAXTITLE);
557     wtitle[PAGERMAXTITLE] = '\0';
558     if(strlen(header) &&
559        ((strlen(header) + strlen(wtitle) + 4) < PAGERMAXTITLE)) {
560 	if(strlen(wtitle)) strcat(wtitle, " - ");
561 	strcat(wtitle, header);
562     }
563     if (!pagerMultiple)
564 	c = newpager1win(wtitle, filename, enc, deleteonexit);
565     else
566 	c = newpagerNwin(wtitle, filename, enc, deleteonexit);
567     if (c) {
568 	haveusedapager++;
569 	BringToTop(c, 0);
570     }
571     return c;
572 }
573