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