blob: c6aee74a9bc8a078959ec766565959c16185a1dc [file] [log] [blame]
/*
* R : A Computer Language for Statistical Data Analysis
* file pager.c
* Copyright (C) 1998--2002 Guido Masarotto and Brian Ripley
* Copyright (C) 2004--8 The R Foundation
* Copyright (C) 2004--2020 The R Core Team
*
* 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
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, a copy is available at
* https://www.R-project.org/Licenses/
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "win-nls.h"
#ifdef Win32
#define USE_MDI 1
#endif
#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>
#include "graphapp/ga.h"
#ifdef USE_MDI
#include "graphapp/stdimg.h"
#endif
#include "console.h"
#include "consolestructs.h"
#include "rui.h"
#include <Startup.h> /* for CharacterMode */
#define CE_UTF8 1
extern size_t Rf_utf8towcs(wchar_t *wc, const char *s, size_t n);
#define PAGERMAXKEPT 12
#define PAGERMAXTITLE 128
static int pagerActualKept = 0, pagerActualShown;
static pager pagerInstance = NULL;
static menubar pagerBar = NULL;
static xbuf pagerXbuf[PAGERMAXKEPT];
static char pagerTitles[PAGERMAXKEPT][PAGERMAXTITLE+8];
static menuitem pagerMenus[PAGERMAXKEPT];
static int pagerRow[PAGERMAXKEPT];
static void pagerupdateview(void);
void menueditoropen(control m);
void menueditornew(control m);
/* from console.c */
extern int pagerMultiple, haveusedapager;
/*
To be fixed: during creation, memory is allocated two times
(faster for small files but a big waste otherwise)
*/
static xbuf file2xbuf(const char *name, int enc, int del)
{
HANDLE f;
DWORD rr, vv;
char *p;
xlong dim, cnt;
xint ms;
xbuf xb;
wchar_t *wp, *q;
if (enc == CE_UTF8) {
wchar_t wfn[MAX_PATH+1];
Rf_utf8towcs(wfn, name, MAX_PATH+1);
f = CreateFileW(wfn, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
} else
f = CreateFile(name, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
if (f == INVALID_HANDLE_VALUE) {
R_ShowMessage(G_("Error opening file"));
return NULL;
}
vv = GetFileSize(f, NULL);
p = (char *) malloc((size_t) vv + 1);
if (!p) {
CloseHandle(f);
R_ShowMessage(G_("Insufficient memory to display file in internal pager"));
return NULL;
}
ReadFile(f, p, vv, &rr, NULL);
CloseHandle(f);
if (del) DeleteFile(name);
p[rr] = '\0';
cnt = mbstowcs(NULL, p, 0);
wp = (wchar_t *) malloc((cnt+1) * sizeof(wchar_t));
mbstowcs(wp, p, cnt+1);
for (q = wp, ms = 1, dim = cnt; *q; q++) {
if (*q == '\t')
dim += TABSIZE;
else if (*q == '\n') {
dim++;
ms++;
}
}
free(p);
if ((xb = newxbuf(dim + 1, ms + 1, 1)))
for (q = wp, ms = 0; *q; q++) {
if (*q == L'\r') continue;
if (*q == L'\n') {
ms++;
xbufaddxc(xb, *q);
/* next line interprets underlining in help files */
if (q[1] == L'_' && q[2] == L'\b') xb->user[ms] = -2;
} else xbufaddxc(xb, *q);
}
free(wp);
return xb;
}
static void delpager(control m)
{
int i;
ConsoleData p = getdata(m);
if (!pagerMultiple) {
for (i = 0; i < pagerActualKept; i++) xbufdel(pagerXbuf[i]);
pagerActualKept = 0;
}
else
xbufdel(p->lbuf);
freeConsoleData(getdata(m));
}
void pagerbclose(control m)
{
show(RConsole);
if (!pagerMultiple) {
hide(pagerInstance);
del(pagerInstance);
pagerInstance = pagerBar = NULL;
}
else {
hide(m);
del(m);
}
}
static void pagerclose(control m)
{
pagerbclose(getdata(m));
}
static void pagerprint(control m)
{
consoleprint(getdata(m));
}
static void pagersavefile(control m)
{
consolesavefile(getdata(m), 1);
}
static void pagercopy(control m)
{
control c = getdata(m);
if (consolecancopy(c)) consolecopy(c);
else R_ShowMessage(G_("No selection"));
}
static void pagerpaste(control m)
{
control c = getdata(m);
if (CharacterMode != RGui) {
R_ShowMessage(G_("No RGui console to paste to"));
return;
}
if (!consolecancopy(c)) {
R_ShowMessage(G_("No selection"));
return;
} else {
consolecopy(c);
}
if (consolecanpaste(RConsole)) {
consolepaste(RConsole);
show(RConsole);
}
}
static void pagerpastecmds(control m)
{
control c = getdata(m);
if (CharacterMode != RGui) {
R_ShowMessage(G_("No RGui console to paste to"));
return;
}
if (!consolecancopy(c)) {
R_ShowMessage(G_("No selection"));
return;
} else {
consolecopy(c);
}
if (consolecanpaste(RConsole)) {
consolepastecmds(RConsole);
show(RConsole);
}
}
static void pagerselectall(control m)
{
control c = getdata(m);
consoleselectall(c);
}
static void pagerstayontop(control m)
{
control c = getdata(m);
BringToTop(c, 2);
}
static void pagerconsole(control m)
{
show(RConsole);
}
static void pagerchangeview(control m)
{
ConsoleData p = getdata(pagerInstance);
int i = getvalue(m);
if (i >= pagerActualKept) return;
uncheck(pagerMenus[pagerActualShown]);
/* save position of middle line of pager display */
pagerRow[pagerActualShown] = FV + ROWS/2;
pagerActualShown = i;
check(pagerMenus[i]);
pagerupdateview();
}
static void pagerupdateview(void)
{
control c = pagerInstance;
ConsoleData p = getdata(c);
settext(pagerInstance, &pagerTitles[pagerActualShown][4]);
p->lbuf = pagerXbuf[pagerActualShown];
setfirstvisible(c, pagerRow[pagerActualShown] - ROWS/2);
setfirstcol(c, 0);
show(c);
}
static int pageraddfile(const char *wtitle,
const char *filename, int enc,
int deleteonexit)
{
ConsoleData p = getdata(pagerInstance);
int i;
xbuf nxbuf = file2xbuf(filename, enc, deleteonexit);
if (!nxbuf) {
/* R_ShowMessage("File not found or memory insufficient"); */
return 0;
}
if (pagerActualKept == PAGERMAXKEPT) {
pagerActualKept -= 1;
xbufdel(pagerXbuf[pagerActualKept]);
}
if(pagerActualKept > 0)
pagerRow[0] = FV;
for (i = pagerActualKept; i > 0; i--) {
pagerXbuf[i] = pagerXbuf[i - 1];
pagerRow[i] = pagerRow[i - 1];
strcpy(&pagerTitles[i][4], &pagerTitles[i - 1][4]);
}
pagerXbuf[0] = nxbuf;
pagerRow[0] = 0;
strcpy(&pagerTitles[0][4], wtitle);
pagerActualKept += 1;
for (i = 0; i < pagerActualKept; i++) {
enable(pagerMenus[i]);
settext(pagerMenus[i], pagerTitles[i]);
}
for (i = pagerActualKept; i < PAGERMAXKEPT; i++)
disable(pagerMenus[i]);
uncheck(pagerMenus[pagerActualShown]);
pagerActualShown = 0;
check(pagerMenus[pagerActualShown]);
return 1;
}
static MenuItem PagerPopup[] = { /* Numbers used below */
{GN_("Copy"), pagercopy, 'C', 0}, /* 0 */
{GN_("Paste to console"), pagerpaste, 'V', 0}, /* 1 */
{GN_("Paste commands to console"), pagerpastecmds, 0, 0}, /* 2 */
{GN_("Select all"), pagerselectall, 'A', 0}, /* 3 */
{"-", 0, 0, 0},
{GN_("Stay on top"), pagerstayontop, 0, 0}, /* 5 */
{"-", 0, 0, 0},
{GN_("Close"), pagerclose, 0, 0}, /* 7 */
LASTMENUITEM
};
static void pagermenuact(control m)
{
control c = getdata(m);
ConsoleData p = getdata(c);
if (consolecancopy(c)) {
enable(p->mcopy);
enable(p->mpopcopy);
if (CharacterMode == RGui) {
enable(p->mpaste);
enable(p->mpastecmds);
enable(p->mpoppaste);
enable(p->mpoppastecmds);
}
} else {
disable(p->mcopy);
disable(p->mpopcopy);
disable(p->mpaste);
disable(p->mpastecmds);
disable(p->mpoppaste);
disable(p->mpoppastecmds);
}
if (ismdi())
disable(PagerPopup[5].m);
else {
enable(PagerPopup[5].m);
if (isTopmost(c))
check(PagerPopup[5].m);
else
uncheck(PagerPopup[5].m);
}
}
#define MCHECK(a) if (!(a)) {freeConsoleData(p);del(c);return NULL;}
RECT *RgetMDIsize(void); /* in rui.c */
static pager pagercreate(void)
{
ConsoleData p;
int w, h, i, x, y, w0, h0;
pager c;
menuitem m;
p = newconsoledata((consolefn) ? consolefn : FixedFont,
pagerrow, pagercol, 0, 0,
guiColors,
PAGER, 0, 0);
if (!p) return NULL;
/* if (ismdi()) {
x = y = w = h = 0;
}
else {
w = WIDTH ;
h = HEIGHT;
x = (devicewidth(NULL) - w) / 2;
y = (deviceheight(NULL) - h) / 2 ;
} */
w = WIDTH ;
h = HEIGHT;
/* centre a single pager, randomly place each of multiple pagers */
#ifdef USE_MDI
if(ismdi()) {
RECT *pR = RgetMDIsize();
w0 = pR->right;
h0 = pR->bottom;
} else {
#endif
w0 = devicewidth(NULL);
h0 = deviceheight(NULL);
#ifdef USE_MDI
}
#endif
x = (w0 - w) / 2; x = x > 20 ? x:20;
y = (h0 - h) / 2; y = y > 20 ? y:20;
if(pagerMultiple) {
#ifdef Win32
DWORD rand = GetTickCount();
#else
int rand = 0;
#endif
int w0 = 0.4*x, h0 = 0.4*y;
w0 = w0 > 20 ? w0 : 20;
h0 = h0 > 20 ? h0 : 20;
x += (rand % w0) - w0/2;
y += ((rand/w0) % h0) - h0/2;
}
c = (pager) newwindow("PAGER", rect(x, y, w, h),
Document | StandardWindow | Menubar |
VScrollbar | HScrollbar | TrackMouse);
if (!c) {
freeConsoleData(p);
return NULL;
}
setdata(c, p);
if(h == 0) HEIGHT = getheight(c);
if(w == 0) WIDTH = getwidth(c);
COLS = WIDTH / FW - 1;
ROWS = HEIGHT / FH - 1;
BORDERX = (WIDTH - COLS*FW) / 2;
BORDERY = (HEIGHT - ROWS*FH) / 2;
gsetcursor(c, ArrowCursor);
gchangescrollbar(c, VWINSB, 0, 0, ROWS, 0);
gchangescrollbar(c, HWINSB, 0, COLS-1, COLS, 1);
setbackground(c, guiColors[pagerbg]);
#ifdef USE_MDI
if (ismdi()) {
int btsize = 24;
rect r = rect(2, 2, btsize, btsize);
control tb, bt;
addto(c);
MCHECK(tb = newtoolbar(btsize + 4));
gsetcursor(tb, ArrowCursor);
addto(tb);
MCHECK(bt = newtoolbutton(open_image, r, menueditoropen));
MCHECK(addtooltip(bt, G_("Open script")));
gsetcursor(bt, ArrowCursor);
/* wants NULL as data, not the pager */
r.x += (btsize + 6) ;
MCHECK(bt = newtoolbutton(copy1_image, r, pagerpaste));
MCHECK(addtooltip(bt, G_("Paste to console")));
gsetcursor(bt, ArrowCursor);
setdata(bt, (void *) c);
r.x += (btsize + 6) ;
MCHECK(bt = newtoolbutton(copy1_image, r, pagerpastecmds));
MCHECK(addtooltip(bt, G_("Paste commands to console")));
gsetcursor(bt, ArrowCursor);
setdata(bt, (void *) c);
r.x += (btsize + 6) ;
MCHECK(bt = newtoolbutton(print_image, r, pagerprint));
MCHECK(addtooltip(bt, G_("Print")));
gsetcursor(bt, ArrowCursor);
setdata(bt, (void *) c);
r.x += (btsize + 6) ;
MCHECK(bt = newtoolbutton(console_image, r, pagerconsole));
MCHECK(addtooltip(bt, G_("Return focus to Console")));
gsetcursor(bt, ArrowCursor);
setdata(bt, (void *) c);
}
#endif
addto(c);
MCHECK(m = gpopup(pagermenuact, PagerPopup));
setdata(m, c);
setdata(p->mpopcopy = PagerPopup[0].m, c);
setdata(p->mpoppaste = PagerPopup[1].m, c);
setdata(p->mpoppastecmds = PagerPopup[2].m, c);
setdata(PagerPopup[3].m, c);
setdata(PagerPopup[5].m, c);
setdata(PagerPopup[7].m, c);
MCHECK(m = newmenubar(pagermenuact));
setdata(m, c);
MCHECK(newmenu(G_("File")));
MCHECK(m = newmenuitem(G_("New script"), 'N', menueditornew));
MCHECK(m = newmenuitem(G_("Open script..."), 'O', menueditoropen));
MCHECK(m = newmenuitem(G_("Print..."), 'P', pagerprint));
setdata(m, c);
MCHECK(m = newmenuitem(G_("Save to File..."), 'S', pagersavefile));
setdata(m, c);
MCHECK(m = newmenuitem("-", 0, NULL));
MCHECK(m = newmenuitem(G_("Close"), 0, pagerclose));
setdata(m, c);
MCHECK(newmenu(G_("Edit")));
MCHECK(p->mcopy = newmenuitem(G_("Copy"), 'C', pagercopy));
setdata(p->mcopy, c);
MCHECK(p->mpaste = newmenuitem(G_("Paste to console"), 'V', pagerpaste));
setdata(p->mpaste, c);
MCHECK(p->mpastecmds = newmenuitem(G_("Paste commands to console"), 0, pagerpastecmds));
setdata(p->mpastecmds, c);
MCHECK(m = newmenuitem(G_("Select all"), 'A', pagerselectall));
setdata(m, c);
if (!pagerMultiple) {
MCHECK(newmenu(G_("View")));
for (i = 0; i < PAGERMAXKEPT; i++) {
snprintf(pagerTitles[i], PAGERMAXTITLE+8, "&%c. ", 'A' + i);
MCHECK(pagerMenus[i] = newmenuitem(&pagerTitles[i][1], 0,
pagerchangeview));
setvalue(pagerMenus[i], i);
}
}
#ifdef USE_MDI
if (ismdi()) newmdimenu();
if (ismdi() && !(RguiMDI & RW_TOOLBAR)) toolbar_hide();
#endif
MCHECK(BM = newbitmap(WIDTH, HEIGHT, 2));
setdata(c, p);
sethit(c, console_sbf);
setresize(c, consoleresize);
setredraw(c, drawconsole);
setdel(c, delpager);
setclose(c, pagerbclose);
setkeyaction(c, console_ctrlkeyin);
setkeydown(c, console_normalkeyin);
setmousedrag(c, console_mousedrag);
setmouserepeat(c, console_mouserep);
setmousedown(c, console_mousedown);
return(c);
}
static pager newpager1win(const char *wtitle,
const char *filename, int enc,
int deleteonexit)
{
if (!pagerInstance && !(pagerInstance = pagercreate())) {
R_ShowMessage(G_("Unable to create pager window"));
return NULL;
}
if (!pageraddfile(wtitle, filename, enc, deleteonexit)) return NULL;
pagerupdateview();
return pagerInstance;
}
static pager newpagerNwin(const char *wtitle,
const char *filename, int enc,
int deleteonexit)
{
pager c = pagercreate();
ConsoleData p;
if (!c) return NULL;
settext(c, wtitle);
p = getdata(c);
if (!(p->lbuf = file2xbuf(filename, enc, deleteonexit))) {
del(c);
return NULL;
}
if (c) {
gchangescrollbar(c, VWINSB, 0, NUMLINES - 1 , ROWS, 0);
show(c);
}
return c;
}
pager newpager(const char *title,
const char *filename, int enc,
const char *header, int deleteonexit)
{
char wtitle[PAGERMAXTITLE+1];
pager c;
/* if (ismdi()) pagerMultiple = 1;*/
strncpy(wtitle, title, PAGERMAXTITLE);
wtitle[PAGERMAXTITLE] = '\0';
if(strlen(header) &&
((strlen(header) + strlen(wtitle) + 4) < PAGERMAXTITLE)) {
if(strlen(wtitle)) strcat(wtitle, " - ");
strcat(wtitle, header);
}
if (!pagerMultiple)
c = newpager1win(wtitle, filename, enc, deleteonexit);
else
c = newpagerNwin(wtitle, filename, enc, deleteonexit);
if (c) {
haveusedapager++;
BringToTop(c, 0);
}
return c;
}