Rev 29051 | Rev 29219 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
/** R : A Computer Language for Statistical Data Analysis* Copyright (C) 1999--2004 The R Development 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, write to the Free Software* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA*/#ifdef HAVE_CONFIG_H#include <config.h>#endif#ifdef Win32#define USE_MDI 1#endif#include "Defn.h"#include <stdio.h>#include <Startup.h>extern UImode CharacterMode;#include "graphapp/ga.h"#include "graphapp/graphapp.h"#include "graphapp/stdimg.h"#include "console.h"#include "consolestructs.h"#include <windows.h>#include "rui.h"#include "editor.h"#define MCHECK(a) if (!(a)) {del(c); return NULL;}RECT *RgetMDIsize();/* Pointers to currently open editors */static editor REditors[MAXNEDITORS];static int neditors = 0;static Rboolean fix_editor_up = FALSE;static EditorData neweditordata (int file, char *filename){EditorData p;p = (EditorData) malloc(sizeof(struct structEditorData));p->file = file;p->filename = (char *) malloc(_MAX_PATH*sizeof(char));if (filename)strncpy(p->filename, filename, _MAX_PATH);p->title = (char *) malloc((EDITORMAXTITLE + 1)*sizeof(char));p->title[EDITORMAXTITLE] = p->title[0] = '\0';return p;}void deleditordata(EditorData p){if (p->stealconsole)fix_editor_up = FALSE;free(p->filename);free(p->title);free(p);}static void editor_set_title(editor c, char *title) {char wtitle[EDITORMAXTITLE+1];textbox t = getdata(c);EditorData p = getdata(t);strncpy(wtitle, title, EDITORMAXTITLE);wtitle[EDITORMAXTITLE] = '\0';strcpy(p->title, wtitle);strncat(wtitle, " - R Editor", EDITORMAXTITLE);settext(c, wtitle);}/*** FILE MANAGEMENT FUNCTIONS ***/static void editor_load_file(editor c, char *name){textbox t = getdata(c);EditorData p = getdata(t);FILE *f;char *buffer=NULL, tmp[_MAX_PATH+30];long num=1, bufsize;f = fopen(name, "r");if (f == NULL)return;p->file = 1;strncpy(p->filename, name, _MAX_PATH);bufsize = 0;while (num > 0) {buffer = realloc(buffer, bufsize+3000+1);num = fread(buffer+bufsize, 1, 3000-1, f);if (num >= 0) {bufsize += num;buffer[bufsize] = '\0';}else {snprintf(tmp, _MAX_PATH+30, "Couldn't read from file %s", name);askok(tmp);}}setlimittext(t, 2 * strlen(buffer));settext(t, buffer);gsetmodified(t, 0);free(buffer);fclose(f);}static void editor_save_file(editor c, char *name){textbox t = getdata(c);FILE *f;char buf[_MAX_PATH+30];if (name == NULL)return;else {f = fopen(name, "w");if (f == NULL) {snprintf(buf, _MAX_PATH+30, "Couldn't save file %s", name);askok(buf);return;}fprintf(f, "%s", gettext(t));fclose(f);}}static void editorsaveas(editor c) {textbox t = getdata(c);EditorData p = getdata(t);char *current_name = (p->file ? p->filename : "");char *name = askfilesave("Save script as", current_name);if (name == NULL)return;else {editor_save_file(c, name);p->file = 1;strncpy(p->filename, name, _MAX_PATH);gsetmodified(t, 0);editor_set_title(c, name);show(c);}}static void menueditorsaveas(control m){editor c = getdata(m);editorsaveas(c);show(c);}static void editorsave(editor c){textbox t = getdata(c);EditorData p = getdata(t);if (p->file) { /* save existing file without prompt */char *current_name = p->filename;editor_save_file(c, current_name);gsetmodified(t, 0);}/* if new file then prompt for name to save as */else editorsaveas(c);}static void menueditorsave(control m){editor c = getdata(m);editorsave(c);show(c); /* steal focus back from tool button */}/* global console configuration variables */char fontname[LF_FACESIZE+1];int fontsty, pointsize;static void editorprint(control m){printer lpr;font f;textbox t = getdata(m);char *contents = gettext(t);char msg[LF_FACESIZE + 128];char *linebuf = NULL;int cc, rr, fh, page, linep, i, j, istartline;int top, left;if (!(lpr = newprinter(0.0, 0.0, ""))) return;f = gnewfont(lpr, strcmp(fontname, "FixedFont") ? fontname : "Courier New",fontsty, pointsize, 0.0);top = devicepixelsy(lpr) / 5;left = devicepixelsx(lpr) / 5;fh = fontheight(f);rr = getheight(lpr) - top;cc = getwidth(lpr) - 2*left;linep = rr; /* line in printer page */page = 1;i = 0;while (i < strlen(contents)) {if ( linep + fh >= rr ) { /* new page */if (page > 1) nextpage(lpr);sprintf(msg, "Page %d", page++);gdrawstr(lpr, f, Black, pt(cc - gstrwidth(lpr, f, msg) - 1, top), msg);linep = top + 2*fh;}j = 0;istartline = i;while (contents[i] != '\n' && contents[i] != '\0') {++i; ++j;}linebuf = realloc(linebuf, (j+1)*sizeof(char));strncpy(linebuf, &contents[istartline], j);linebuf[j] = '\0';gdrawstr(lpr, f, Black, pt(left, linep), linebuf);linep += fh;++i;}free(linebuf);if (f != FixedFont) del(f);del(lpr);}static void editorconsole(editor c){show(RConsole);}/* Remove global pointer to editor when closing an editor. Fill the gap in the array with the last editor in the list */static void editorupdateglobals(editor c) {int i=0;if (neditors > 1) {while (REditors[i] != c) ++i;if (i < neditors - 1)REditors[i] = REditors[neditors-1];}--neditors;}/* Hooks called when editor window is destroyed */static void editordel(editor c) {editorupdateglobals(c);}static void textboxdel(textbox t) {EditorData p = getdata(t);deleditordata(p);}int editorchecksave(editor c) {textbox t = getdata(c);EditorData p = getdata(t);int save;char buf[EDITORMAXTITLE + 100];if (ggetmodified(t)) {snprintf(buf, EDITORMAXTITLE + 100, "\"%s\" has been modified. Do you want to save the changes?",(p->title ? p->title : "Untitled"));save = askyesnocancel(buf);switch (save) {case YES:editorsave(c);break;case NO:break;case CANCEL:return 1; /* used in rui.c (closeconsole) to abort closing the whole of Rgui */}}return 0;}static void editorclose(editor c){if (!editorchecksave(c))del(c);}static void menueditorclose(control m){editor c = getdata(m);editorclose(c);}/* Called when exiting Rgui, check if any open editors need saving */void editorcleanall(){int i;for (i = neditors-1; i >= 0; --i) {if (editorchecksave(REditors[i]))jump_to_toplevel();del(REditors[i]);}}static void editornew(){Rgui_Edit("", "", 0);}void menueditornew(control m){editornew();}static void editoropen(char *default_name){char *name;int i; textbox t; EditorData p;setuserfilter("R files (*.R)\0*.R\0S files (*.q)\0*.q\0All files (*.*)\0*.*\0\0");name = askfilename("Open script", default_name); /* returns NULL if open dialog cancelled */if (name) {/* check if file is already open in an editor. If so, close and open again */for (i=0; i<neditors; ++i) {t = getdata(REditors[i]);p = getdata(t);if (!strcmp (name, p->filename)) {editorclose(REditors[i]);break;}}Rgui_Edit(name, name, 0);}}void menueditoropen(control m){editor c = getdata(m);char *default_name = "";if (c) {textbox t = getdata(c);EditorData p = getdata(t);if (p->file) default_name = p->filename; /* name of currently-open file, if there is one */}editoropen(default_name);}/*** EDITING FUNCTIONS ***/static void editorundo(control m){textbox t = getdata(m);undotext(t);}static void editorcut(control m){textbox t = getdata(m);cuttext(t);}static void editorcopy(control m){textbox t = getdata(m);copytext(t);}static void editorpaste(control m){textbox t = getdata(m);/* check whether the widget text limit needs to be increased before doing the paste */int pastelen = getpastelength();checklimittext(t, pastelen + 1);pastetext(t);}static void editordelete(control m){textbox t = getdata(m);cleartext(t);}static void editorselectall(control m){textbox t = getdata(m);selecttextex(t, 0, -1);}static void editorfind(control m){textbox t = getdata(m);finddialog(t); /* actual find/replace work is in graphapp/dialogs.c */}static void editorreplace(control m){textbox t = getdata(m);replacedialog(t);}/*** FUNCTIONS FOR RUNNING R CODE FROM THE EDITOR ***/static void editorrunline(textbox t){int length = getlinelength(t);char *line = malloc((length + 2) * sizeof(char)); /* Extra space for null and word length in getcurrentline */getcurrentline(t, line, length);consolecmd(RConsole, line);free(line);scrollcaret(t, 1);show(t);}/* For the moment, send the selected text to the console via theclipboard. Wasteful, but currently consolecmd only allows a maximumof 8K of text */static void editorrunselection(textbox t, long start, long end){copytext(t);if (consolecanpaste(RConsole)) {consolepaste(RConsole);if (gettext(t)[end-1] != '\n')consolenewline(RConsole);}}static Rboolean busy_running = FALSE;static void editorrun(textbox t){if (!busy_running) {long start=0, end=0;if (CharacterMode != RGui) {R_ShowMessage("No RGui console to paste to");return;}busy_running = TRUE;textselectionex(t, &start, &end);if (start >= end)editorrunline(t);else {editorrunselection(t, start, end);selecttextex(t, end, end); /* move insertion point to end of selection after running */}busy_running = FALSE;}}static void menueditorrun(control m){textbox t = getdata(m);editorrun(t);}static void editorrunall(control m){textbox t = getdata(m);long start=0, end=0;textselectionex(t, &start, &end); /* save current selection state */selecttextex(t, 0, -1);editorrun(t);selecttextex(t, start, end); /* return to original selection state */}/* Grey out menu buttons as appropriate */static void editormenuact(control m){long start, end;textbox t = getdata(m);EditorData p = getdata(t);textselectionex(t, &start, &end);if (start < end) {enable(p->mcut);enable(p->mcopy);enable(p->mdelete);enable(p->mpopcut);enable(p->mpopcopy);enable(p->mpopdelete);}else {disable(p->mcut);disable(p->mcopy);disable(p->mdelete);disable(p->mpopcut);disable(p->mpopcopy);disable(p->mpopdelete);}if (modeless_active()){disable(p->mfind);disable(p->mreplace);}else {enable(p->mfind);enable(p->mreplace);}}static void editorresize(editor c, rect r){resize(getdata(c), r);resize(c, r);}static void editorcontrolkeydown(textbox t, int key){switch (key) {case F5:editorrun(t);break;}}static void editorasciikeydown(textbox t, int key){/* check whether the text limit is about to be exceeded andincrease it if necessary. Ugh - this callback is called afterthe text is inserted, so we make space for two characters */checklimittext(t, 2);}/* Set keyboard focus to the text editing area when the editor window receives focus */static void editorfocus(editor c){textbox t = getdata(c);show(t);}static void editorhelp(){char s[4096];strcpy(s,"R EDITOR\n");strcat(s,"\n");strcat(s,"A standard text editor for editing and running R code.\n");strcat(s,"\n");strcat(s,"RUNNING COMMANDS\n");strcat(s,"To run a line or section of R code, select the code and either\n");strcat(s," Press Ctrl-R\n");strcat(s," Select \"Run line or selection\" from the \"Edit\" menu\n");strcat(s," Press the \"Run line or selection\" icon on the toolbar\n");strcat(s,"This will copy the selected commands to the console and evaluate them.\n");strcat(s,"If there is no selection, this will just run the current line and advance\n");strcat(s,"the cursor by one line.\n");askok(s);}static void menueditorhelp(control m){editorhelp();}static MenuItem EditorPopup[] = { /* Numbers used below */{"Run line or selection", menueditorrun, 'R', 0}, /* 0 */{"-", 0, 0, 0},{"Undo", editorundo, 'Z', 0}, /* 2 */{"-", 0, 0, 0},{"Cut", editorcut, 'X', 0}, /* 4 */{"Copy", editorcopy, 'C', 0}, /* 5 */{"Paste", editorpaste, 'V', 0}, /* 6 */{"Delete", editordelete, 0, 0}, /* 7 */{"-", 0, 0, 0},{"Select all", editorselectall, 'A', 0}, /* 9 */LASTMENUITEM};static editor neweditor(){int x, y, w, h, w0, h0;editor c;menuitem m;textbox t;long flags;font editorfn = (consolefn ? consolefn : FixedFont);EditorData p = neweditordata(0, NULL);DWORD rand;w = (pagercol + 1)*fontwidth(editorfn);h = (pagerrow + 1)*fontheight(editorfn) + 1;#ifdef USE_MDIif(ismdi()) {RECT *pR = RgetMDIsize();w0 = pR->right;h0 = pR->bottom;} else {#endifw0 = devicewidth(NULL);h0 = deviceheight(NULL);#ifdef USE_MDI}#endifx = (w0 - w) / 2; x = x > 20 ? x : 20;y = (h0 - h) / 2; y = y > 20 ? y : 20;rand = GetTickCount();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;flags = StandardWindow | Menubar;#ifdef USE_MDIif (ismdi()) flags |= Document;#endifc = (editor) newwindow("", rect(x, y, w, h), flags);t = newrichtextarea(NULL, rect(0, 0, w, h));setdata(c, t);setdata(t, p);gsetcursor(c, ArrowCursor);setforeground(c, consolefg);setbackground(c, consolebg);#ifdef USE_MDIif (ismdi() && (RguiMDI & RW_TOOLBAR)) {int btsize = 24;rect r = rect(2, 2, btsize, btsize);control tb, bt;addto(c);MCHECK(tb = newtoolbar(btsize + 4));addto(tb);MCHECK(bt = newtoolbutton(open_image, r, menueditoropen));MCHECK(addtooltip(bt, "Open script"));setdata(bt, c);r.x += (btsize + 1) ;MCHECK(bt = newtoolbutton(save_image, r, menueditorsave));MCHECK(addtooltip(bt, "Save script"));setdata(bt, c);r.x += (btsize + 6);MCHECK(bt = newtoolbutton(copy1_image, r, menueditorrun));MCHECK(addtooltip(bt, "Run line or selection"));setdata(bt, t);r.x += (btsize + 6);MCHECK(bt = newtoolbutton(console_image, r, editorconsole));MCHECK(addtooltip(bt, "Return focus to Console"));r.x += (btsize + 6);MCHECK(bt = newtoolbutton(print_image, r, editorprint));MCHECK(addtooltip(bt, "Print script"));setdata(bt, t);MCHECK(addtooltip(bt, "Print"));}#endifaddto(c);/* Right-click context menu */MCHECK(m = gpopup(editormenuact, EditorPopup));setdata(m, t);setdata(EditorPopup[0].m, t);setdata(EditorPopup[2].m, t);setdata(p->mpopcut = EditorPopup[4].m, t);setdata(p->mpopcopy = EditorPopup[5].m, t);setdata(EditorPopup[6].m, t);setdata(p->mpopdelete = EditorPopup[7].m, t);setdata(EditorPopup[9].m, t);addto(c);MCHECK(m = newmenubar(editormenuact));setdata(m, t);MCHECK(newmenu("File"));MCHECK(m = newmenuitem("New script", 'N', menueditornew));setdata(m, c);MCHECK(m = newmenuitem("Open script...", 'O', menueditoropen));setdata(m, c);MCHECK(m = newmenuitem("Save", 'S', menueditorsave));setdata(m, c);MCHECK(m = newmenuitem("Save as...", 0, menueditorsaveas));setdata(m, c);MCHECK(m = newmenuitem("-", 0, NULL));MCHECK(m = newmenuitem("Print...", 0, editorprint));setdata(m, t);MCHECK(m = newmenuitem("-", 0, NULL));MCHECK(m = newmenuitem("Close script", 0, menueditorclose));setdata(m, c);MCHECK(m = newmenuitem("-", 0, NULL));MCHECK(m = newmenuitem("Exit", 0, closeconsole));setdata(m, c);MCHECK(newmenu("Edit"));MCHECK(m = newmenuitem("Undo", 'Z', editorundo));setdata(m, t);MCHECK(m = newmenuitem("-", 0, NULL));MCHECK(p->mcut = newmenuitem("Cut", 'X', editorcut));setdata(p->mcut, t);MCHECK(p->mcopy = newmenuitem("Copy", 'C', editorcopy));setdata(p->mcopy, t);MCHECK(m = newmenuitem("Paste", 'V', editorpaste));setdata(m, t);MCHECK(p->mdelete = newmenuitem("Delete", 0, editordelete));setdata(p->mdelete, t);MCHECK(m = newmenuitem("Select all", 'A', editorselectall));setdata(m, t);MCHECK(newmenuitem("Clear console", 'L', menuclear));MCHECK(m = newmenuitem("-", 0, NULL));MCHECK(m = newmenuitem("Run line or selection", 'R', menueditorrun));setdata(m, t);MCHECK(m = newmenuitem("Run all", 0, editorrunall));setdata(m, t);MCHECK(m = newmenuitem("-", 0, NULL));MCHECK(p->mfind = newmenuitem("Find...", 'F', editorfind));setdata(p->mfind, t);MCHECK(p->mreplace = newmenuitem("Replace...", 'H', editorreplace));setdata(p->mreplace, t);MCHECK(m = newmenuitem("-", 0, NULL));MCHECK(newmenuitem("GUI preferences...", 0, menuconfig));/* Packages menu should go here */RguiPackageMenu();#ifdef USE_MDInewmdimenu(); /* Create and fill the 'Window' menu */#endifMCHECK(m = newmenu("Help"));MCHECK(newmenuitem("Editor", 0, menueditorhelp));MCHECK(newmenuitem("-", 0, NULL));RguiCommonHelp(m);settextfont(t, editorfn);setresize(c, editorresize);setclose(c, editorclose);setdel(c, editordel);setdel(t, textboxdel);setonfocus(c, editorfocus);setkeyaction(t, editorcontrolkeydown);setkeydown(t, editorasciikeydown);/* Store pointer to new editor in global array */REditors[neditors] = c;++neditors;return c;}/* Change the font used in all running editors */void editorsetfont(font f){int i, ismod;textbox t;setfont(f);for (i = 0; i<neditors; ++i) {t = getdata(REditors[i]);ismod = ggetmodified(t); /* Don't change the modification flag when changing font */settextfont(t, f);gsetmodified(t, ismod);show(t);}}static void eventloop(editor c){while (fix_editor_up) {/* avoid consuming 100% CPU time here */Sleep(10);R_ProcessEvents();}}/* Open existing file for editing or open blank editor for a newfile. If calling from fix() or edit(), then don't send events tothe console until editor is closed. */int Rgui_Edit(char *filename, char *title, int stealconsole){WIN32_FIND_DATA fd;editor c;EditorData p;if (neditors == MAXNEDITORS) {R_ShowMessage("Maximum number of editors reached");return 1;}c = neweditor();if (!c) {R_ShowMessage("Unable to create editor window");return 1;}if (strlen(filename) > 0) {if (FindFirstFile(filename, &fd) != INVALID_HANDLE_VALUE)editor_load_file(c, filename);editor_set_title(c, title);}else {editor_set_title(c, "Untitled");}show(c);if (stealconsole) {p = getdata(getdata(c));p->stealconsole = TRUE;fix_editor_up = TRUE;eventloop(c);}return 0;}