/* * Copyright 1992, 1993 The University of Newcastle upon Tyne * * Permission to use, copy, modify and distribute this software and its * documentation for any purpose other than its commercial exploitation * is hereby granted without fee, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of The University of Newcastle upon Tyne not be used in * advertising or publicity pertaining to distribution of the software * without specific, written prior permission. The University of * Newcastle upon Tyne makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * THE UNIVERSITY OF NEWCASTLE UPON TYNE DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE UNIVERSITY OF * NEWCASTLE UPON TYNE BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. * * Author: Jim Wight (j.k.wight@newcastle.ac.uk) * Department of Computing Science * University of Newcastle upon Tyne, UK */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern int errno; #ifndef __BSD_4_4__ extern char *sys_errlist[]; #endif #include #ifdef EXTENSION #include "Language.h" #endif #include "AxeEditorP.h" extern char *getenv(); #include "regexp.h" #include "util.h" #include "version.h" #include "axeLogo.xbm" #include "fwdArrow.xbm" #include "bwdArrow.xbm" #include "vtCentre.xbm" #include "hzCentre.xbm" #include "tickMark.xbm" #include "modMark.xbm" static Boolean validAction; /* * The names in the first column must be the same * as those in the defaultMenuList that follows */ static MenuRecord chosenMenus[] = { "move", False, "search", False, "insert", False, "delete", False, "help", False, "misc", False, "font", False, "window", False, "buffer", False, "show", False, "file", False, "quit", False, 0, False, }; #define defaultMenuList "move search insert delete help misc font window \ buffer show file quit logo" #define defaultFontList "Small:6x13 Medium:8x13 Large:9x15 Huge:10x20" #define defaultMiniMenu "print:lpr spell nroff mail | \ exec:mini-commit abort:mini-abort | \ print:lpr\n spell:spell\n nroff:nroff\n" #define Offset(field) XtOffsetOf(AxeEditorRec, axeEditor.field) static XtResource resources[] = { {XtNfile, XtCFile, XtRString, sizeof(String), Offset(file), XtRString, NULL}, {XtNmenuList, XtCMenuList, XtRString, sizeof(String), Offset(menu_list), XtRString, defaultMenuList}, {XtNbuttons, XtCButtons, XtRBoolean, sizeof(Boolean), Offset(buttons), XtRImmediate, (XtPointer) False}, {XtNbuttonList, XtCButtonList, XtRString, sizeof(String), Offset(button_list), XtRString, NULL}, {XtNinfoTimeout, XtCInfoTimeout, XtRInt, sizeof(int), Offset(info_timeout), XtRImmediate, (XtPointer) 0}, {XtNfocusToText, XtCFocusToText, XtRBoolean, sizeof(Boolean), Offset(focus_to_text), XtRImmediate, (XtPointer) False}, {XtNsuppressFilename, XtCSuppressPane, XtRBoolean, sizeof(Boolean), Offset(suppressFilename), XtRImmediate, (XtPointer) False}, {XtNsuppressInfobar, XtCSuppressPane, XtRBoolean, sizeof(Boolean), Offset(suppressInfobar), XtRImmediate, (XtPointer) False}, {XtNsuppressMinibuffer, XtCSuppressPane, XtRBoolean, sizeof(Boolean), Offset(suppressMinibuffer), XtRImmediate, (XtPointer) False}, {XtNfontList, XtCFontList, XtRString, sizeof(String), Offset(font_list), XtRString, defaultFontList}, {XtNinternalBorderWidth, XtCBorderWidth, XtRDimension, sizeof(Dimension), Offset(internal_border_width), XtRImmediate, (XtPointer) 1}, {XtNfullPathnames, XtCFullPathnames, XtRBoolean, sizeof(Boolean), Offset(full_pathnames), XtRImmediate, (XtPointer) False}, {XtNchangeCallback, XtCCallback, XtRCallback, sizeof(XtPointer), Offset(change_callbacks), XtRCallback, (XtPointer) NULL}, }; #undef Offset #define INCREMENT 5 #define MAXSTRLEN 16 #define CHUNK 10 #define EDITORS axeEditorClassRec.axeEditor_class.editors #define NUMBEROFEDITORS axeEditorClassRec.axeEditor_class.numberOfEditors #define MAXEDITORS axeEditorClassRec.axeEditor_class.maxEditors #define CLASS(field) axeEditorClassRec.axeEditor_class.field #define PRIVATE(w,field) (((AxeEditorWidget) w)->axeEditor.field) static void ClassInitialize(), PseudoClassInitialize(), Initialize(); static void CalculateSize(), Resize(), Realize(); static void Destroy(); static XtGeometryResult GeometryManager(); static void InsertChild(), DeleteChild(); static XpTableLoc MakeButtonsLayout(); static void NewEditor(), DeckChange(), SetKeyboardFocus(); static Widget MakeMenu(); static void MakeOneMenu(); static void MakeFontMenu(), MakeShowMenu(), MakeLogoMenu(), MakeMiniMenu(); static void MakeButtons(); static void RePopulateShowMenu(), FreeFontList(); static FontListStruct *ParseFontList(); static void UpdateFileName(), UpdateTitles(); static Widget AxeEditorOf(), ShellOf(); static String HomeFile(); String AxeEditorExpandName(); static void Help(); static void NewWindow(), CloseWindow(), CloseAll(), IconifyAll(), Restack(); static void NewBuffer(), CloseBuffer(); static void SaveAndClose(), SaveAll(); static void miniSearch(), miniInsert(), miniSaveAs(), miniLoad(), miniPipe(); static void miniClear(), miniSelect(), miniAbort(), MiniLeave(), MiniCommit(); static void miniShell(), miniStuff(); #ifdef EXTENSION static int MiniInterp(); #endif static void MiniUninstallAccelerators(); static void startMacro(), endMacro(), execMacro(), repeatMacro(); static void UpdateInfo(), UpdateInfoBar(); static char commandButtonsLayout[] = "\ mbeg 0 1 wW; sbtext 1 1 wW; ifile 2 1 wW; \ mend 0 2 wW; sftext 1 2 wW; isel 2 2 wW; \ mup 0 3 wW; sline 1 3 wW; ipaste 2 3 wW; \ mdown 0 4 wW; scaret 1 4 wW; ictrl 2 4 wW; \ mtop 0 5 wW; sbsel 1 5 wW; \ mbot 0 6 wW; sfsel 1 6 wW; \ \ dword 3 1 wW; hgnrl 4 1 wW; mundo 5 1 wW; \ dline 3 2 wW; hbind 4 2 wW; mwhere 5 2 wW; \ dsel 3 3 wW; hcust 4 3 wW; mform 5 3 wW; \ dcut 3 4 wW; hpop 4 4 wW; mcentre 5 4 wW; \ hextn 4 5 wW; mhcentr 5 5 wW; \ hchng 4 6 wW; mpref 5 6 wW; \ \ wnew 7 1 wW; bempty 8 1 wW; fsvex 10 1 wW; \ wfull 7 2 wW; bfull 8 2 wW; fsvall 10 2 wW; \ wclose 7 3 wW; bclear 8 3 wW; fsave 10 3 wW; \ wclall 7 4 wW; bclose 8 4 wW; fsvas 10 4 wW; \ wicon 7 5 wW; fload 10 5 wW; \ wdeicon 7 6 wW; frvrt 10 6 wW; \ \ qquit 11 1 wW; \ qsvex 11 2 wW; \ qsvclw 11 3 wW; \ qsvclb 11 4 wW;"; static ButtonRecord buttonActions[] = { "mMove", NULL, "mbeg", "beginning-of-line", "mend", "end-of-line", "mup", "previous-page", "mdown", "next-page", "mtop", "beginning-of-file", "mbot", "end-of-file", "mSearch", NULL, "sbtext", "search(backward)", "sftext", "search(forward)", "sline", "goto-line", "scaret", "search-caret", "sbsel", "backward-search-selection", "sfsel", "forward-search-selection", "mInsert", NULL, "ifile", "include-file", "isel", "include-selection", "ipaste", "paste", "ictrl", "insert-control", "mDelete", NULL, "dword", "delete-word", "dline", "delete-line", "dsel", "delete-selection", "dcut", "kill-selection", "mHelp", NULL, "hgnrl", "help(Introduction)", "hbind", "help(Bindings)", "hcust", "help(Customise)", "hpop", "help(Popups)", #ifdef EXTENSION "hextn", "help(Language)", #endif "hchng", "help(Changes)", "mMisc", NULL, "mundo", "undo", "mwhere", "where", "mform", "form-paragraph", "mcentre", "redraw-display", "mhcentr", "centre-line", "mpref", "set-preferences", "mWindow", NULL, "wnew", "new-window", "wfull", "new-window(load)", "wclose", "close-window", "wclall", "close-all", "wicon", "iconify-all(I)", "wdeicon", "iconify-all(D)", "mBuffer", NULL, "bempty", "new-buffer", "bfull", "new-buffer(load)", "bclear", "clear-buffer", "bclose", "close-buffer", "mFile", NULL, "fsvex", "save-and-close", "fsvall", "save-all", "fsave", "save-file", "fsvas", "save-as", "fload", "load-file", "frvrt", "reload-file", "mQuit", NULL, "qquit", "close-all", "qsvex", "save-and-close", "qsvclw", "save-and-close(window)", "qsvclb", "save-and-close(buffer)", }; static char miniTranslationsTable[] = "\ : display-caret(on) update-info(\"search /)fwd ?)bwd; [0-9][0-9]*)goto; r)ead; w)rite; e)dit; |)pipe; !)shell\")\n\ : mini-leave() update-info(\" \")\n\ CtrlG: mini-abort() \n\ Escape: mini-abort() \n\ Return: mini-commit()"; static char miniAcceleratorsTable[] = "#override \n\ CtrlA: beginning-of-line() \n\ CtrlB: backward-character() \n\ CtrlD: delete-next-character() \n\ CtrlE: end-of-line() \n\ CtrlF: forward-character() \n\ CtrlG: mini-abort() \n\ CtrlK: kill-to-end-of-line() \n\ Delete: delete-previous-character() \n\ Escape: mini-abort() \n\ Return: mini-commit() \n\ : insert-char()"; static XtActionsRec actions[] = { "help", Help, "new-window", NewWindow, "close-window", CloseWindow, "close-all", CloseAll, "iconify-all", IconifyAll, "deiconify-all", IconifyAll, "restack", Restack, "new-buffer", NewBuffer, "close-buffer", CloseBuffer, "save-and-exit ", SaveAndClose, /* backwards compatability */ "save-and-close", SaveAndClose, "save-all", SaveAll, "mini-search", miniSearch, "mini-insert", miniInsert, "mini-saveas", miniSaveAs, "mini-load", miniLoad, "mini-pipe", miniPipe, "mini-clear", miniClear, "mini-select", miniSelect, "mini-abort", miniAbort, "mini-leave", MiniLeave, "mini-commit", MiniCommit, "mini-shell", miniShell, "mini-stuff", miniStuff, "start-macro", startMacro, "end-macro", endMacro, "exec-macro", execMacro, "repeat-macro", repeatMacro, "update-info", UpdateInfo, }; AxeEditorClassRec axeEditorClassRec = { /* Core class part */ { /* superclass */ (WidgetClass) &compositeClassRec, /* class_name */ "AxeEditor", /* widget_size */ sizeof(AxeEditorRec), /* class_initialize */ ClassInitialize, /* class_part_initialize */ NULL, /* class_inited */ FALSE, /* initialize */ Initialize, /* initialize_hook */ NULL, /* realize */ Realize, /* actions */ actions, /* num_actions */ XtNumber(actions), /* resources */ resources, /* num_resources */ XtNumber(resources), /* xrm_class */ NULLQUARK, /* compress_motion */ TRUE, /* compress_exposure */ XtExposeCompressMultiple, /* compress_enterleave */ TRUE, /* visible_interest */ FALSE, /* destroy */ Destroy, /* resize */ Resize, /* expose */ XtInheritExpose, /* set_values */ NULL, /* set_values_hook */ NULL, /* set_values_almost */ XtInheritSetValuesAlmost, /* get_values_hook */ NULL, /* accept_focus */ NULL, /* version */ XtVersion, /* callback offsets */ NULL, /* tm_table */ NULL, /* query_geometry */ XtInheritQueryGeometry, /* display_accelerator */ NULL, /* extension */ NULL, }, { /* composite class fields */ /* geometry_manager */ GeometryManager, /* change_managed */ XtInheritChangeManaged, /* insert_child */ InsertChild, /* delete_child */ DeleteChild, /* extension */ NULL }, /* AxeEditor class part */ { /* extension */ NULL } }; WidgetClass axeEditorWidgetClass = (WidgetClass) &axeEditorClassRec; static void ClassInitialize() { char *dir; #if defined(SYSV) || defined(SVR4) extern char *getcwd(); #define getwd(buf) getcwd(buf,MAXPATHLEN) #else extern char *getwd(); #endif extern char *getenv(); EDITORS = NULL; NUMBEROFEDITORS = 0; MAXEDITORS = 0; CLASS(show_menu) = (Widget) 0; CLASS(mini_translations) = XtParseTranslationTable(miniTranslationsTable); CLASS(mini_accelerators) = XtParseAcceleratorTable(miniAcceleratorsTable); if ( (dir = getenv("HOME")) ) { CLASS(home_dir) = XtNewString(dir); } else { CLASS(home_dir) = NULL; } CLASS(home_dir_len) = strlen(CLASS(home_dir)); CLASS(current_dir) = XtMalloc(MAXPATHLEN); if (!getwd(CLASS(current_dir))) { if (CLASS(home_dir)) { strcpy(CLASS(current_dir), CLASS(home_dir)); } else { CLASS(current_dir)[0] = '\0'; } } strcat(CLASS(current_dir), "/"); CLASS(current_dir_len) = strlen(CLASS(current_dir)); CLASS(show_menu_callbacks) = 0; CLASS(show_menu_callback_list) = 0; CLASS(default_mini_menu) = 0; CLASS(mini_menu) = 0; CLASS(macro_id) = (XtActionHookId) 0; CLASS(macro_size) = 0; CLASS(max_macro_size) = 0; CLASS(in_macro_string) = False; CLASS(macro_commands) = (MacroList) 0; CLASS(show_work_proc) = False; } static void PseudoClassInitialize(w) Widget w; { Display *display; Window rootWindow; Widget app; if (!CLASS(axeLogo)) { display = XtDisplay(w); rootWindow = XDefaultRootWindow(display); CLASS(axeLogo) = XCreateBitmapFromData(display, rootWindow, (char *) axeLogo_bits, axeLogo_width, axeLogo_height); CLASS(fwdArrow) = XCreateBitmapFromData(display, rootWindow, (char *) fwdArrow_bits, fwdArrow_width, fwdArrow_height); CLASS(bwdArrow) = XCreateBitmapFromData(display, rootWindow, (char *) bwdArrow_bits, bwdArrow_width, bwdArrow_height); CLASS(vtCentre) = XCreateBitmapFromData(display, rootWindow, (char *) vtCentre_bits, vtCentre_width, vtCentre_height); CLASS(hzCentre) = XCreateBitmapFromData(display, rootWindow, (char *) hzCentre_bits, hzCentre_width, hzCentre_height); CLASS(tickMark) = XCreateBitmapFromData(display, rootWindow, (char *) tickMark_bits, tickMark_width, tickMark_height); CLASS(modMark) = XCreateBitmapFromData(display, rootWindow, (char *) modMark_bits, modMark_width, modMark_height); while ((app = XtParent(w))) { w = app; } CLASS(menu_parent) = w; } } /* ARGSUSED */ static void Initialize(req, new, args, num_args) Widget req, new; ArgList args; Cardinal *num_args; { AxeEditorWidget aew = (AxeEditorWidget) new; FontListStruct *buttonList = NULL; XpTableLoc tableLoc; int menu; char menuName[32]; Widget fileName, infoBar, buttons, miniBuffer; Widget menuButton, logoButton, miniMenu; PseudoClassInitialize(aew); NewEditor(aew); fileName = PRIVATE(new,file_name) = XtVaCreateWidget("fileName", labelWidgetClass, new, NULL); if (!PRIVATE(new,suppressFilename)) { XtManageChild(fileName); } infoBar = PRIVATE(new,info_bar) = XtVaCreateWidget("infoBar", labelWidgetClass, new, XtNlabel, QVERSION, NULL); PRIVATE(new,info_timer) = (XtIntervalId) 0; if (!PRIVATE(new,suppressInfobar)) { XtManageChild(infoBar); } tableLoc = MakeButtonsLayout(new, &buttonList); buttons = PRIVATE(new,menu_bar) = XtVaCreateManagedWidget("buttons", xpTableWidgetClass, new, XtNlayout, tableLoc, NULL); XpTableLocFree(tableLoc); for (menu = 0; chosenMenus[menu].name; ++menu) { if (chosenMenus[menu].chosen) { sprintf(menuName, "%sMenu", chosenMenus[menu].name); menuButton = XtVaCreateManagedWidget(menuName, axeMenuButtonWidgetClass, buttons, NULL); if (strcmp(menuName, "showMenu") == 0 && !CLASS(show_menu)) { CLASS(show_menu) = MakeMenu(menuButton, CLASS(menu_parent), MakeShowMenu, False); } else if (strcmp(menuName, "fontMenu") == 0) { (void) MakeMenu(menuButton, new, MakeFontMenu, False); } else { (void) MakeMenu(menuButton, CLASS(menu_parent), MakeOneMenu, False); } } } PRIVATE(new,font_table) = (FontListStruct *) 0; logoButton = XtVaCreateManagedWidget("axeLogo", axeMenuButtonWidgetClass, buttons, XtNbitmap, CLASS(axeLogo), XtNhelp, QVERSION, NULL); (void) MakeMenu(logoButton, CLASS(menu_parent), MakeLogoMenu, False); if (PRIVATE(new,buttons) || PRIVATE(new,button_list)) { MakeButtons(buttons, buttonList); if (buttonList) { FreeFontList(XtDisplay(new), buttonList); } } PRIVATE(new,ed_deck) = XtVaCreateManagedWidget("edDeck", axeTextDeckWidgetClass, new, XtNfile, PRIVATE(new,file), NULL); XtAddCallback(PRIVATE(new,ed_deck), XtNchangeCallback, DeckChange, (XtPointer) new); XtVaGetValues(AxeTextDeckTop(PRIVATE(new,ed_deck)), XtNtranslations, &(PRIVATE(new,defaultTextTranslations)), NULL); miniBuffer = PRIVATE(new,mini_buffer) = XtVaCreateWidget("miniBuffer", asciiTextWidgetClass, new, XtNaccelerators, CLASS(mini_accelerators), NULL); XtOverrideTranslations(PRIVATE(new,mini_buffer), CLASS(mini_translations)); miniMenu = PRIVATE(new,mini_button) = XtVaCreateWidget("miniMenu", axeMenuButtonWidgetClass, new, NULL); (void) MakeMenu(miniMenu, CLASS(menu_parent), MakeMiniMenu, False); if (!PRIVATE(new,suppressMinibuffer)) { XtManageChild(miniBuffer); XtManageChild(miniMenu); } PRIVATE(new,accelerateMini) = False; PRIVATE(new,confirmer) = XtVaCreatePopupShell("confirmer", confirmerWidgetClass, new, NULL); SetKeyboardFocus(new, AxeTextDeckTop(PRIVATE(new,ed_deck))); /* The best we can do to keep Realize happy */ CalculateSize(aew, &aew->core.width, &aew->core.height); } static void CalculateSize(aew, width, height) AxeEditorWidget aew; Dimension *width, *height; { Cardinal child; Dimension wide, maxWidth, high, totalHeight = 0; for (child = 0; child < aew->composite.num_children; ++child) { if (XtIsManaged(aew->composite.children[child])) { XtVaGetValues(aew->composite.children[child], XtNwidth, &wide, XtNheight, &high, NULL); if (XtIsSubclass(aew->composite.children[child], axeTextDeckWidgetClass)) { maxWidth = wide; } if (XtIsSubclass(aew->composite.children[child], axeMenuButtonWidgetClass)) { continue; } totalHeight += high + PRIVATE(aew,internal_border_width); } } *width = maxWidth; *height = totalHeight - PRIVATE(aew,internal_border_width);; } static void Configure(aew, deltaHeight, initiator) AxeEditorWidget aew; Dimension deltaHeight; Widget initiator; { /* * This routine needs looking at; the addition of the * minibuffer MenuButton was done in a bit of a hurry. */ Cardinal child; Widget kid; Dimension width, height, border; Position x = -PRIVATE(aew,internal_border_width); Position y = -PRIVATE(aew,internal_border_width); Position atx, aty; for (child = 0; child < aew->composite.num_children; ++child) { kid = aew->composite.children[child]; if (XtIsManaged(kid)) { XtVaGetValues(kid, XtNheight, &height, NULL); if (XtIsSubclass(kid, axeTextDeckWidgetClass)) { height += deltaHeight; } if (kid == initiator) { kid->core.y = y; } else { aty = y; border = PRIVATE(aew,internal_border_width); if (XtIsSubclass(kid, asciiTextWidgetClass)) /* minibuffer */ { atx = PRIVATE(aew,mini_button)->core.width; width = aew->core.width - atx + 1; } else { atx = x; if (XtIsSubclass(kid, axeMenuButtonWidgetClass)) { width = kid->core.width; aty = y + PRIVATE(aew,internal_border_width); height = PRIVATE(aew,mini_buffer)->core.height; border = 0; } else { width = aew->core.width; } } XtConfigureWidget(kid, atx, aty, width, height, border); } if (!XtIsSubclass(kid, asciiTextWidgetClass)) /* order assumed */ { y += height + PRIVATE(aew,internal_border_width); } } } } static void Resize(aew) AxeEditorWidget aew; { Dimension oldWidth, oldHeight; CalculateSize(aew, &oldWidth, &oldHeight); Configure(aew, aew->core.height - oldHeight, (Widget) 0); } /* * This routine shouldn't be necessary, but it is because the size could * not be calculated in Initialize since the Table widget does not set its * core width and height fields in its Initialize procedure as it should. */ static void Realize(w, valueMask, attributes) Widget w; Mask *valueMask; XSetWindowAttributes *attributes; { AxeEditorWidget aew = (AxeEditorWidget) w; Cardinal child; Dimension width, height; (*((CompositeWidgetClass) (axeEditorWidgetClass->core_class.superclass))->core_class.realize) (w, valueMask, attributes); for (child = 0; child < aew->composite.num_children; ++child) { if (XtIsManaged(aew->composite.children[child])) { XtRealizeWidget(aew->composite.children[child]); } } CalculateSize(aew, &width, &height); XtVaSetValues(w, XtNwidth, width, XtNheight, height, NULL); /* * This is done here and not in Initialize for the benefit of AxeWindow. * UpdateTitles calls the change_callbacks list but AxeWindow can't * add to the list until after it has created its AxeEditor component, * hence if fileTitle were operative then the title bar would not be * correct until a change causing an update occurred. */ UpdateTitles(aew); } static void Destroy(w) Widget w; { int editor; for (editor = 0; editor < NUMBEROFEDITORS; ++editor) { if (w == EDITORS[editor]) { EDITORS[editor] = NULL; } } if (PRIVATE(w,info_timer)) { XtRemoveTimeOut(PRIVATE(w,info_timer)); } if (PRIVATE(w,font_table)) { FreeFontList(XtDisplay(w), PRIVATE(w,font_table)); } } static void NewEditor(aew) Widget aew; { int editor; Boolean spareSlot = False; WidgetList oldList; for (editor = 0; editor < NUMBEROFEDITORS; ++editor) { if (!EDITORS[editor]) { spareSlot = True; break; } } if (!spareSlot) { ++NUMBEROFEDITORS; if (NUMBEROFEDITORS > MAXEDITORS) { MAXEDITORS += INCREMENT; oldList = EDITORS; EDITORS = (WidgetList) XtRealloc((char *) oldList, MAXEDITORS * sizeof(Widget)); } editor = NUMBEROFEDITORS - 1; } EDITORS[editor] = aew; } /* ARGSUSED */ static XtGeometryResult GeometryManager(w, desired, allowed) Widget w; XtWidgetGeometry *desired, *allowed; { AxeEditorWidget aew = (AxeEditorWidget) XtParent(w); Dimension saveWidth, saveHeight, newWidth, newHeight; XtWidgetGeometry request; XtGeometryResult result; #define REQUESTS(flag) (desired->request_mode & flag) if (!XtIsSubclass(w, axeTextDeckWidgetClass)) { return XtGeometryNo; } if (REQUESTS(CWWidth)) { saveWidth = w->core.width; w->core.width = desired->width; } if (REQUESTS(CWHeight)) { saveHeight = w->core.height; w->core.height = desired->height; } CalculateSize(aew, &newWidth, &newHeight); request.request_mode = desired->request_mode; request.width = newWidth; request.height = newHeight; result = XtMakeGeometryRequest((Widget) aew, &request, (XtWidgetGeometry *) 0); if (REQUESTS(XtCWQueryOnly) || result != XtGeometryYes) { if(REQUESTS(CWWidth)) { w->core.width = saveWidth; } if (REQUESTS(CWHeight)) { w->core.height = saveHeight; } return result; } if (result == XtGeometryYes) { Configure(aew, 0, w); return XtGeometryYes; } return XtGeometryNo; #undef REQUESTS } static void InsertChild(w) Widget w; { CompositeWidget parent = (CompositeWidget) XtParent(w); /* Silently disallow unknown children; the 6 we know of are fileName */ /* infoBar, buttons, ed_deck, miniMenu, miniBuffer */ if (parent->composite.num_children < 6) { (*((CompositeWidgetClass) (axeEditorWidgetClass-> core_class.superclass))->composite_class.insert_child) (w); } } static void DeleteChild(w) Widget w; { Widget parent = XtParent(w); if (w == PRIVATE(parent,ed_deck)) { XtDestroyWidget(parent); } } static void SetKeyboardFocus(axeEditor, ed) Widget axeEditor, ed; { if (PRIVATE(axeEditor,focus_to_text)) { XtSetKeyboardFocus(PRIVATE(axeEditor,file_name), ed); XtSetKeyboardFocus(PRIVATE(axeEditor,info_bar), ed); XtSetKeyboardFocus(PRIVATE(axeEditor,menu_bar), ed); } } /* ARGSUSED */ static void DeckChange(w, client_data, call_data) Widget w; XtPointer client_data, call_data; { Widget axeEditor = (Widget) client_data; Widget ed = AxeTextDeckTop(PRIVATE(axeEditor,ed_deck)); SetKeyboardFocus(axeEditor, ed); if (PRIVATE(axeEditor,accelerateMini)) { MiniUninstallAccelerators(w); } UpdateTitles(axeEditor); RePopulateShowMenu(XtWidgetToApplicationContext(axeEditor)); } static Boolean CreatePopupLater(client_data) XtPointer client_data; { Widget menu = (Widget) client_data; XtCreatePopupChildProc create; XtVaGetValues(menu, XtNcreatePopupChildProc, &create, NULL); if (create) { (*create) (menu); } XtVaSetValues(menu, XtNcreatePopupChildProc, (XtCreatePopupChildProc) 0, NULL); return True; } static Widget MenuMade(menuName, menuParent) String menuName; Widget menuParent; { int n; for (n = 0; n < menuParent->core.num_popups; n++) { if (strcmp(XtName(menuParent->core.popup_list[n]), menuName) == 0) { return menuParent->core.popup_list[n]; } } return (Widget) 0; } static Widget MakeMenu(menuButton, menuParent, childProc, always) Widget menuButton, menuParent; XtCreatePopupChildProc childProc; Boolean always; { String menuName; Widget menu; XtVaGetValues(menuButton, XtNmenuName, &menuName, NULL); if (!(menu = MenuMade(menuName, menuParent))) { menu = XtVaCreatePopupShell(menuName, axeSimpleMenuWidgetClass, menuParent, NULL); XtVaSetValues(menu, XtNcreatePopupChildProc, childProc, NULL); AxeSimpleMenuStorePopperUpper(menu, AxeEditorParentOf(menuButton)); /* * No longer applies to any menu; retained just in case */ if (always) { XtAppAddWorkProc(XtWidgetToApplicationContext(menu), CreatePopupLater, (XtPointer) menu); } return menu; } return (Widget) 0; } static void MakeEvent(widget, event) Widget widget; XEvent *event; { Window root, child; unsigned int mask; XQueryPointer(XtDisplay(widget), XtWindow(widget), &root, &child, &event->xbutton.x_root, &event->xbutton.y_root, &event->xbutton.x, &event->xbutton.y, &mask); event->xbutton.type = ButtonRelease; } static void GenericAction(widget, client_data) Widget widget; XtPointer client_data; { XEvent event; String action = (String) client_data, params[1], p, q, proc; char routine[32], param[128]; Cardinal num_params; if ( (p = index(action, '(')) ) { strncpy(routine, action, p - action); routine[p - action] = '\0'; p += 1; q = index(p, ')'); strncpy(param, p, q - p); param[q - p] = '\0'; proc = routine; params[0] = param; num_params = strlen(param) == 0 ? 0 : 1; } else { params[0] = NULL; proc = action; num_params = 0; } MakeEvent(widget, &event); XtCallActionProc(widget, proc, &event, params, num_params); } /* ARGSUSED */ static void DoAction(widget, client_data, call_data) Widget widget; XtPointer client_data, call_data; { GenericAction(AxeTextDeckTop(PRIVATE(AxeSimpleMenuFetchPopperUpper(XtParent(widget)),ed_deck)), client_data); } static void AddBitmaps(widget, pixmap) Widget widget; Pixmap pixmap; { Pixmap lbitmap, rbitmap; XtVaGetValues(widget, XtNleftBitmap, &lbitmap, XtNrightBitmap, &rbitmap, NULL); if (!lbitmap) { XtVaSetValues(widget, XtNleftBitmap, pixmap, NULL); } if (!rbitmap) { XtVaSetValues(widget, XtNrightBitmap, pixmap, NULL); } } static void AddObjectBitmaps(obj, name) Widget obj; String name; { Pixmap bitmap; if (strcmp(name, "sbtext") == 0) { XtVaGetValues(obj, XtNleftBitmap, &bitmap, NULL); if (!bitmap) { XtVaSetValues(obj, XtNleftBitmap, CLASS(bwdArrow), NULL); } } else if (strcmp(name, "sftext") == 0) { XtVaGetValues(obj, XtNrightBitmap, &bitmap, NULL); if (!bitmap) { XtVaSetValues(obj, XtNrightBitmap, CLASS(fwdArrow), NULL); } } else if (strcmp(name, "sbsel") == 0) { XtVaGetValues(obj, XtNleftBitmap, &bitmap, NULL); if (!bitmap) { XtVaSetValues(obj, XtNleftBitmap, CLASS(bwdArrow), NULL); } } else if (strcmp(name, "sfsel") == 0) { XtVaGetValues(obj, XtNrightBitmap, &bitmap, NULL); if (!bitmap) { XtVaSetValues(obj, XtNrightBitmap, CLASS(fwdArrow), NULL); } } else if (strcmp(name, "mcentre") == 0) { AddBitmaps(obj, CLASS(vtCentre)); } else if (strcmp(name, "mhcentr") == 0) { AddBitmaps(obj, CLASS(hzCentre)); } } static void MakeOneDefaultMenu(menu, menuName) Widget menu; String menuName; { Widget obj; int button; for (button = 0; button < XtNumber(buttonActions); ++button) { if (!buttonActions[button].action && strcmp(menuName, buttonActions[button].name) == 0) { break; } } for (button += 1; button < XtNumber(buttonActions); ++button) { if (!buttonActions[button].action) { break; } obj = XtVaCreateManagedWidget(buttonActions[button].name, axeSmeBSBObjectClass, menu, NULL); XtAddCallback(obj, XtNcallback, DoAction, (XtPointer) buttonActions[button].action); AddObjectBitmaps(obj, buttonActions[button].name); } } static void MakeOneMenu(menu) Widget menu; { String menuName = XtName(menu); char resourceName[32]; String menuList; XtResource resource; Cardinal numChildren; /* * We don't know which button is popping up the menu, but we know the * menu name so we will generate something from that. Thus for menu * mMove we generate moveMenu and look for a resource moveMenuList. */ sprintf(resourceName, "%sMenu", menuName); resourceName[1] = tolower(resourceName[1]); resource.resource_name = &resourceName[1]; resource.resource_class = "Menu"; resource.resource_type = XtRString; resource.resource_size = sizeof(String); resource.resource_offset = 0; resource.default_type = XtRString; resource.default_addr = 0; XtVaGetApplicationResources(AxeSimpleMenuFetchPopperUpper(menu), (XtPointer) &menuList, &resource, 1, NULL); if (!menuList) { MakeOneDefaultMenu(menu, menuName); } else { FontListStruct *m, *userMenu = ParseFontList(menuList); int item; Widget obj; for (m = userMenu + 1; m->name; ++m) { if (*m->label == '+') { MakeOneDefaultMenu(menu, menuName); continue; } if (*m->label == '|') { XtVaCreateManagedWidget("line", smeLineObjectClass, menu, NULL); continue; } for (item = 0; item < XtNumber(buttonActions); ++item) { if (strcmp(buttonActions[item].name, m->name) == 0) { obj = XtVaCreateManagedWidget(buttonActions[item].name, axeSmeBSBObjectClass, menu, NULL); XtAddCallback(obj, XtNcallback, DoAction, (XtPointer) buttonActions[item].action); AddObjectBitmaps(obj, buttonActions[item].name); break; } } } XtVaGetValues(menu, XtNnumChildren, &numChildren, NULL); if (numChildren == 0) { char warning[64]; sprintf(warning, "The value of %s is invalid", &resourceName[1]); XtAppWarning(XtWidgetToApplicationContext(menu), warning); /* * Prevents permature exit if menu popped */ XtVaCreateManagedWidget("line", smeLineObjectClass, menu, NULL); } FreeFontList(XtDisplay(menu), userMenu); } } /* ARGSUSED */ static void Help(widget, event, params, num_params) Widget widget; XEvent *event; String *params; Cardinal *num_params; { if (*num_params) { AxeEditorHelpWindow(params[0], CLASS(menu_parent)); } else { AxeEditorHelpWindow((String) 0, CLASS(menu_parent)); } } /* ARGSUSED */ static void ChooseFont(widget, client_data, call_data) Widget widget; XtPointer client_data, call_data; { Pixmap leftBitmap; WidgetList children; Cardinal numChildren, child, choice; Widget axeEditor = AxeSimpleMenuFetchPopperUpper(XtParent(widget)); Widget lastFont; XFontStruct *fontStruct; XtVaGetValues(widget, XtNleftBitmap, &leftBitmap, NULL); if (leftBitmap) { return; } XtVaGetValues(XtParent(widget), XtNchildren, &children, XtNnumChildren, &numChildren, NULL); for (child = 0; child < numChildren; ++child) { XtVaGetValues(children[child], XtNleftBitmap, &leftBitmap, NULL); if (leftBitmap) { lastFont = children[child]; } if (widget == children[child]) { choice = child; } } if (!(fontStruct = PRIVATE(axeEditor,font_table)[choice].fontStruct)) { fontStruct = PRIVATE(axeEditor,font_table)[choice].fontStruct = XLoadQueryFont(XtDisplay(axeEditor), PRIVATE(axeEditor,font_table)[choice].name); } if (fontStruct) { XtVaSetValues(lastFont, XtNleftBitmap, (Pixmap) 0, NULL); XtVaSetValues(widget, XtNleftBitmap, CLASS(tickMark), NULL); XtVaSetValues(PRIVATE(axeEditor,ed_deck), XtNfont, fontStruct, NULL); } else { UpdateInfoBar(axeEditor, "Invalid font"); } } static void FreeFontList(display, list) Display *display; FontListStruct *list; { FontListStruct *l; for (l = list; l->name; ++l) { if (l->name != l->label) { XtFree(l->name); } XtFree(l->label); /* NB We didn't cause creation of the Default entry's XFontStruct */ if (l != list && l->fontStruct) { XFreeFont(display, l->fontStruct); } } XtFree((char *) list); } static FontListStruct* ParseFontList(fontList) String fontList; { int n, numberOfFonts, listLen = strlen(fontList); String i, fontString, parsed, p; Boolean skipping, quoting; char quoteChar; FontListStruct *fontTable; for (n = 0, i = fontList, skipping = True, quoting = False; i < fontList + strlen(fontList); ++i) { if (*i == '"' || *i == '\'') { if (quoting && (*i == quoteChar)) { quoting = False; } else if (!quoting) { quoting = True; quoteChar = *i; } } else if (*i == ' ' || *i == '\t') { if (!skipping) { ++n; skipping = !skipping; } } else if (skipping) { skipping = !skipping; } } numberOfFonts = n + 3; /* Default, no trailing space, NULL terminator */ fontTable = (FontListStruct *) XtMalloc(numberOfFonts * sizeof(FontListStruct)); fontString = (String) XtMalloc(listLen + 2); strcpy(fontString, fontList); strcat(fontString, " "); fontTable[0].label = XtNewString("Default"); fontTable[0].name = fontTable[0].label; parsed = (String) XtMalloc(listLen + 1); for (i = fontString, p = parsed, skipping = False, quoting = False, n = 0; i < fontString + strlen(fontList) + 1; ++i) { if (*i == '"' || *i == '\'') { if (quoting && (*i == quoteChar)) { quoting = False; } else if (!quoting) { quoting = True; quoteChar = *i; } } else if (!quoting && (*i == ' ' || *i == '\t')) { if (skipping) { *p = '\0'; if (!fontTable[n].label) { fontTable[n].label = parsed; } fontTable[n].name = parsed; fontTable[n].fontStruct = (XFontStruct *) 0; parsed = (String) XtMalloc(listLen + 1); p = parsed; } skipping = False; } else if (!quoting && *i == ':') { *p = '\0'; fontTable[n].label = parsed; parsed = (String) XtMalloc(listLen + 1); p = parsed; } else { *p++ = *i; if (!skipping) { skipping = !skipping; ++n; fontTable[n].label = 0; } } } fontTable[++n].name = (String) 0; XtFree(fontString); XtFree(parsed); return fontTable; } static void MakeFontMenu(menu) Widget menu; { Widget axeEditor = XtParent(menu); String fontList; Widget obj; FontListStruct *parseFontList, *p; XtVaGetValues(XtParent(menu), XtNfontList, &fontList, NULL); parseFontList = PRIVATE(axeEditor,font_table) = ParseFontList(fontList); XtVaGetValues(AxeTextDeckTop(PRIVATE(XtParent(menu),ed_deck)), XtNfont, &parseFontList[0].fontStruct, NULL); for (p = parseFontList; p->name; ++p) { obj = XtVaCreateManagedWidget(p->name, axeSmeBSBObjectClass, menu, XtNlabel, p->label, XtNleftBitmap, p == parseFontList ? CLASS(tickMark) : (Pixmap) 0, NULL); XtAddCallback(obj, XtNcallback, ChooseFont, (XtPointer) 0); } } /* ARGSUSED */ static void NewWindow(widget, event, params, num_params) Widget widget; XEvent *event; String *params; Cardinal *num_params; { Widget window = AxeEditorCreateWindow(widget, CLASS(menu_parent), (String) 0); XtPopup(window, XtGrabNone); if (*num_params > 0) { XtCallActionProc(AxeEditorEdWidget(AxeWindowAxeEditor(window)), "load-file", event, (String *) 0, (Cardinal) 0); } } /* ARGSUSED */ static void ForceClose(widget, client_data, call_data) Widget widget; XtPointer client_data, call_data; { XtDestroyWidget((Widget) client_data); RePopulateShowMenu(XtWidgetToApplicationContext(widget)); } static Boolean WindowClosed(w) Widget w; { Boolean closed; if (w && XtIsSubclass(XtParent(w), axeWindowWidgetClass)) { if ( (closed = AxeTextDeckIterate(PRIVATE(w,ed_deck), AxeSafeClose)) ) { XtDestroyWidget(w); } return closed; } else { return False; } } /* ARGSUSED */ static void CloseWindow(widget, event, params, num_params) Widget widget; XEvent *event; String *params; Cardinal *num_params; { Widget axeEditor = AxeEditorParentOf(widget); if (!WindowClosed(axeEditor)) { ConfirmerRequestConfirmation(PRIVATE(axeEditor,confirmer), "There are unsaved changes", "close unsaved", ForceClose, "unused", NULL, (XtPointer) axeEditor); } { RePopulateShowMenu(XtWidgetToApplicationContext(widget)); } } static Boolean AllWindowsClosed(w) Widget w; { Boolean result = False; if (w && XtIsSubclass(XtParent(w), axeWindowWidgetClass)) { result = AxeEditorIterate(AxeSafeClose); } return result; } /* ARGSUSED */ static void ForceCloseAll(widget, client_data, call_data) Widget widget; XtPointer client_data, call_data; { AxeEditorIterate(AxeForceClose); RePopulateShowMenu(XtWidgetToApplicationContext(widget)); } /* ARGSUSED */ static void CloseAll(widget, event, params, num_params) Widget widget; XEvent *event; String *params; Cardinal *num_params; { Widget axeEditor= AxeEditorParentOf(widget); int editor; if (!AllWindowsClosed(axeEditor)) { /* NB axeEditor may have been deleted, so find another. */ /* being_destroyed field is used since only Phase 1 */ /* of XtDestroyWidget for any windows destroyed will */ /* have been executed at this juncture. */ for (editor = 0; editor < NUMBEROFEDITORS; ++editor) { axeEditor = EDITORS[editor]; if (axeEditor && !axeEditor->core.being_destroyed) { ConfirmerRequestConfirmation(PRIVATE(axeEditor,confirmer), "There are unsaved changes", "close unsaved", ForceCloseAll, "unused", NULL, (XtPointer) axeEditor); break; } } } /* * This catches the case where the confirmer is necessary but * the user selects "cancel", but it also means that the menu * is generated twice if "close unsaved" is chosen - once from * this call and once from the ForceClassAll callback. */ RePopulateShowMenu(XtWidgetToApplicationContext(widget)); } /* ARGSUSED */ static void IconifyAll(widget, event, params, num_params) Widget widget; XEvent *event; String *params; Cardinal *num_params; { Boolean iconifying = (strcmp(params[0], "I") == 0); Display *display; Widget shell; Window window; int editor; for (editor = 0; editor < NUMBEROFEDITORS; ++editor) { if (EDITORS[editor]) { shell = ShellOf(EDITORS[editor]); display = XtDisplay(shell); window = XtWindow(shell); if (iconifying) { XIconifyWindow(display, window, XDefaultScreen(display)); } else { XMapWindow(display, window); } } } } /* ARGSUSED */ static void Restack(widget, event, params, num_params) Widget widget; XEvent *event; String *params; Cardinal *num_params; { printf("Restack not implemented\n"); } /* ARGSUSED */ static void NewBuffer(widget, event, params, num_params) Widget widget; XEvent *event; String *params; Cardinal *num_params; { Widget axeEditor = AxeEditorParentOf(widget), ed; XFontStruct *font; char *assocFile, assocDir[MAXPATHLEN]; XtVaGetValues(PRIVATE(axeEditor,ed_deck), XtNfont, &font, NULL); ed = XtVaCreateManagedWidget("ed", axeTextWidgetClass, PRIVATE(axeEditor,ed_deck), XtNfont, font, NULL); if ( (assocFile = AxeTextGetAssociatedFile(widget)) ) { strcpy(assocDir, assocFile); assocDir[rindex(assocDir, '/') - assocDir] = '\0'; XtVaSetValues(ed, XtNassociatedDirectory, assocDir, NULL); } if (*num_params > 0) { XtCallActionProc(ed, "load-file", event, (String *) 0, (Cardinal) 0); } } /* ARGSUSED */ static void ForceCloseTopBuffer(widget, client_data, call_data) Widget widget; XtPointer client_data, call_data; { Widget axeEditor = (Widget) client_data; AxeTextDeckCloseTop(PRIVATE(axeEditor,ed_deck)); RePopulateShowMenu(XtWidgetToApplicationContext(axeEditor)); } /* ARGSUSED */ static void CloseBufferSaved(widget, client_data, call_data) Widget widget; XtPointer client_data, call_data; { Widget axeEditor = (Widget) client_data; if (!AxeTextSaveFile(AxeTextDeckTop(PRIVATE(axeEditor,ed_deck)))) { ConfirmerRequestConfirmation(PRIVATE(axeEditor,confirmer), "There are unsavable changes", "acknowledge", ConfirmerPopdown, "unused", NULL, (XtPointer) axeEditor); } else { ForceCloseTopBuffer((XtPointer) 0, (XtPointer) axeEditor, (XtPointer) 0); } } /* ARGSUSED */ static void CloseBuffer(widget, event, params, num_params) Widget widget; XEvent *event; String *params; Cardinal *num_params; { Widget axeEditor = AxeEditorParentOf(widget); Widget deck = PRIVATE(axeEditor,ed_deck); Widget buffer = AxeTextDeckTop(deck); Cardinal numChildren; XtVaGetValues(deck, XtNnumChildren, &numChildren, NULL); if (numChildren == 1) { CloseWindow(widget, event, params, num_params); return; } if (AxeTextIsModified(buffer)) { if (AxeTextGetAssociatedFile(buffer)) { ConfirmerRequestConfirmation(PRIVATE(axeEditor,confirmer), "There are unsaved changes", "close unsaved", ForceCloseTopBuffer, "close saved", CloseBufferSaved, (XtPointer) axeEditor); } else { ConfirmerRequestConfirmation(PRIVATE(axeEditor,confirmer), "There are unsavable changes", "close unsaved", ForceCloseTopBuffer, "unused", NULL, (XtPointer) axeEditor); } } else { ForceCloseTopBuffer((XtPointer) 0, (XtPointer) axeEditor, (XtPointer) 0); } } static Widget ShellOf(axeEditor) Widget axeEditor; { Widget w; for (w = XtParent(axeEditor) ;; w = XtParent(w)) { if (XtIsShell(w)) { break; } } return w; } /* ARGSUSED */ static void RaiseWindow(widget, client_data, call_data) Widget widget; XtPointer client_data, call_data; { Widget axeEditor = AxeEditorOf((Widget) client_data); Widget shell = ShellOf(axeEditor); AxeTextDeckRaise(PRIVATE(axeEditor,ed_deck), (Widget) client_data); XMapRaised(XtDisplay(shell), XtWindow(shell)); } static void CopyShowMenuChildren(children, numChildren) WidgetList *children; Cardinal *numChildren; { WidgetList copyChildren; Cardinal child; XtVaGetValues(CLASS(show_menu), XtNchildren, ©Children, XtNnumChildren, numChildren, NULL); *children = (WidgetList) XtMalloc(*numChildren * sizeof(Widget)); for (child = 0; child < *numChildren; ++child) { (*children)[child] = copyChildren[child]; } } /* ARGSUSED */ static Boolean PopulateShowMenu(client_data) XtPointer client_data; { WidgetList copyChildren; Cardinal numChildren; int child, editor, windows, n; AxeTextDeckList list, lst; String homeFile; Pixmap leftBitmap; Widget obj, line = 0; AxeEditorWidgetClass wc = (AxeEditorWidgetClass) axeEditorWidgetClass; if (!CLASS(show_menu)) { return True; } CopyShowMenuChildren(©Children, &numChildren); /* * I'm not sure that I understand this, but if I deleted the old menu * objects here first, then app crashed with X_ConfigureWindow error, * although it was fine when this routine was a callback routine that * was invoked via the triggering of the MenuButton's notify action. */ for (windows = 0, editor = 0; editor < NUMBEROFEDITORS; ++editor) { if (EDITORS[editor]) { list = AxeTextDeckListOf(PRIVATE(EDITORS[editor],ed_deck)); for (lst = list; (*lst).file; ++lst) { leftBitmap = AxeTextIsModified((*lst).widget) ? CLASS(modMark) : (Pixmap) 0; homeFile = HomeFile((*lst).file); obj = XtVaCreateManagedWidget(homeFile, axeSmeBSBObjectClass, CLASS(show_menu), XtNleftBitmap, leftBitmap, NULL); XtAddCallback(obj, XtNcallback, RaiseWindow, (XtPointer) (*lst).widget); if (homeFile != (*lst).file) { XtFree(homeFile); } XtFree((char *) (*lst).file); } XtFree((char *) list); line = XtVaCreateManagedWidget("line", smeLineObjectClass, CLASS(show_menu), NULL); ++windows; } } if (line) { /* If the menu is non-empty then the last line was one too many */ XtDestroyWidget(line); } else { /* Can't have an empty menu so give it a dummy entry */ XtVaCreateManagedWidget("line", smeLineObjectClass, CLASS(show_menu), NULL); } /* See comment above */ for (child = 0; child < numChildren; ++child) { XtDestroyWidget(copyChildren[child]); } XtFree((char *) copyChildren); CLASS(show_work_proc) = False; for (n = 0; n < CLASS(show_menu_callbacks); ++n) { (*CLASS(show_menu_callback_list)[n].cbr.callback) (CLASS(show_menu_callback_list)[n].widget, CLASS(show_menu_callback_list)[n].cbr.closure, (XtPointer) windows); } return True; } static void RePopulateShowMenu(app) XtAppContext app; { if (!CLASS(show_work_proc)) { (void) XtAppAddWorkProc(app, PopulateShowMenu, (XtPointer) 0); CLASS(show_work_proc) = True; } } /* ARGSUSED */ static void OnShowPopup(widget, client_data, call_data) Widget widget; XtPointer client_data, call_data; { Widget axeEditor = AxeSimpleMenuFetchPopperUpper(widget); Widget ed; WidgetList children; Cardinal numChildren, child; XtCallbackList callback; if (axeEditor) { ed = AxeEditorEdWidget(axeEditor); } XtVaGetValues(CLASS(show_menu), XtNchildren, &children, XtNnumChildren, &numChildren, NULL); for (child = 0; child < numChildren; ++child) { if (!XtIsSubclass(children[child], axeSmeBSBObjectClass)) { continue; } XtVaGetValues(children[child], XtNcallback, &callback, NULL); XtVaSetValues(children[child], XtNrightBitmap, axeEditor ? (((Widget) callback[0].closure == ed) ? CLASS(tickMark) : (Pixmap) 0) : (Pixmap) 0, NULL); } } static void MakeShowMenu(menu) Widget menu; { Cardinal numChildren; XtAddCallback(menu, XtNpopupCallback, OnShowPopup, (XtPointer) 0); XtVaGetValues(menu, XtNnumChildren, &numChildren, NULL); if (numChildren == 0) { PopulateShowMenu((XtPointer) 0); } } static Boolean BuffersSaved(w) Widget w; { return AxeTextDeckIterate(PRIVATE(w,ed_deck), AxeSave); } /* ARGSUSED */ static void SaveAndClose(widget, event, params, num_params) Widget widget; XEvent *event; String *params; Cardinal *num_params; { Widget axeEditor = AxeEditorParentOf(widget); Widget axeText; int editor; Cardinal numChildren; XtVaGetValues(PRIVATE(axeEditor,ed_deck), XtNnumChildren, &numChildren, NULL); if (*num_params == 1) { if (strcmp(params[0], "window") == 0 || (strcmp(params[0], "buffer") == 0 && numChildren == 1)) { (void) BuffersSaved(axeEditor); if (!WindowClosed(axeEditor)) { ConfirmerRequestConfirmation(PRIVATE(axeEditor,confirmer), "There are unsavable buffers", "acknowledge", ConfirmerPopdown, "unused", NULL, (XtPointer) axeEditor); } } else if (strcmp(params[0], "buffer") == 0) { axeText = AxeEditorEdWidget(axeEditor); if (AxeTextSaveFile(axeText)) { XtDestroyWidget(axeText); } else { ConfirmerRequestConfirmation(PRIVATE(axeEditor,confirmer), "There are unsavable changes", "acknowledge", ConfirmerPopdown, "unused", NULL, (XtPointer) axeEditor); } } } else if (*num_params == 0 && !AxeEditorIterate(AxeSaveAndClose)) { (void) AxeEditorIterate(AxeSafeClose); /* NB axeEditor may have been deleted, so find another. */ /* being_destroyed field is used since only Phase1 */ /* of XtDestroyWidget for any windows destroyed will */ /* have been executed at this juncture. */ for (editor = 0; editor < NUMBEROFEDITORS; ++editor) { axeEditor = EDITORS[editor]; if (!axeEditor->core.being_destroyed) { ConfirmerRequestConfirmation(PRIVATE(axeEditor,confirmer), "There are unsavable buffers", "acknowledge", ConfirmerPopdown, "unused", NULL, (XtPointer) axeEditor); break; } } } RePopulateShowMenu(XtWidgetToApplicationContext(widget)); } /* ARGSUSED */ static void SaveAll(widget, event, params, num_params) Widget widget; XEvent *event; String *params; Cardinal *num_params; { Widget axeEditor = AxeEditorParentOf(widget);; if (!AxeEditorIterate(AxeSave)) { ConfirmerRequestConfirmation(PRIVATE(axeEditor,confirmer), "There are unsavable buffers", "acknowledge", ConfirmerPopdown, "unused", NULL, (XtPointer) axeEditor); } else { UpdateInfoBar(axeEditor, "All buffers saved"); } } static void MakeLogoMenu(menu) Widget menu; { XtVaCreateManagedWidget(QVERSION, axeSmeBSBObjectClass, menu, NULL); XtVaCreateManagedWidget("by", axeSmeBSBObjectClass, menu, NULL); XtVaCreateManagedWidget("Jim Wight", axeSmeBSBObjectClass, menu, NULL); XtVaCreateManagedWidget("Department of Computing Science", axeSmeBSBObjectClass, menu, NULL); XtVaCreateManagedWidget("University of Newcastle upon Tyne", axeSmeBSBObjectClass, menu, NULL); XtVaCreateManagedWidget("", axeSmeBSBObjectClass, menu, NULL); } static void MakeMiniSubMenu(menu, menuList) Widget menu; FontListStruct *menuList; { char action[80]; Widget obj; FontListStruct *p; for (p = menuList + 1; p->name; ++p) { if (*p->label == '+') { if (!CLASS(default_mini_menu)) { CLASS(default_mini_menu) = ParseFontList(defaultMiniMenu); } MakeMiniSubMenu(menu, CLASS(default_mini_menu)); continue; } if (*p->label == '|') { XtVaCreateManagedWidget("line", smeLineObjectClass, menu, NULL); continue; } obj = XtVaCreateManagedWidget(p->label, axeSmeBSBObjectClass, menu, NULL); if (strncmp(p->name, "mini-", 5) != 0) { sprintf(action, "mini-shell(%s)", p->name); if (p->name != p->label) { XtFree(p->name); } p->name = XtNewString(action); } XtAddCallback(obj, XtNcallback, DoAction, (XtPointer) p->name); } } static void MakeMiniMenu(menu) Widget menu; { String miniMenuList; FontListStruct *miniMenu; static XtResource resource = {XtNminiMenu, XtCMiniMenu, XtRString, sizeof(String), (Cardinal) 0, XtRString, defaultMiniMenu}; /* * The miniMenu resource is an application resource, not an AxeEditor * resource. We do it this way because we can't get a handle onto the * invoking AxeEditor widget. */ XtVaGetApplicationResources(menu, (XtPointer) &miniMenuList, &resource, 1, NULL); CLASS(mini_menu) = ParseFontList(miniMenuList); MakeMiniSubMenu(menu, CLASS(mini_menu)); } /* ARGSUSED */ static void ButtonAction(widget, client_data, call_data) Widget widget; XtPointer client_data, call_data; { GenericAction(AxeEditorEdWidget(AxeEditorParentOf(widget)), client_data); } static void MakeButtons(parent, buttonList) Widget parent; FontListStruct *buttonList; { FontListStruct *p; Widget butt; int len, button; if (PRIVATE(XtParent(parent),button_list)) { for (p = buttonList + 1; p->name; ++p) { if (*(p->name) == '-' || *(p->name) == ';') { continue; } else if (p->name[(len = strlen(p->name)) - 1] == ';') { len -= 1; } for (button = 0; button < XtNumber(buttonActions); ++button) { if (strncmp(buttonActions[button].name, p->name, len) == 0) { butt = XtVaCreateManagedWidget(buttonActions[button].name, axeCommandWidgetClass, parent, NULL); XtAddCallback(butt, XtNcallback, ButtonAction, (XtPointer) buttonActions[button].action); break; } } if (button >= XtNumber(buttonActions)) { printf("Invalid button name %s in buttonList\n", p->name); } } } else { for (button = 0; button < XtNumber(buttonActions); ++button) { if (buttonActions[button].action) { butt = XtVaCreateManagedWidget(buttonActions[button].name, axeCommandWidgetClass, parent, NULL); XtAddCallback(butt, XtNcallback, ButtonAction, (XtPointer) buttonActions[button].action); } } } } static String ButtonListLayout(widget, menuButtonsLayout, list) Widget widget; char *menuButtonsLayout; FontListStruct **list; { String buttonLayout = (String) XtMalloc(strlen(menuButtonsLayout) + strlen(commandButtonsLayout) + 1); FontListStruct *buttonList = ParseFontList(PRIVATE(widget,button_list)), *p; int col = 0, row = 1, len; Boolean newRow = False; char buf[16]; strcpy(buttonLayout, menuButtonsLayout); for (p = buttonList + 1; p->name; ++p) { if (*(p->name) == '-' ) { ++col; continue; } else if (*(p->name) == ';') { ++row; col = 0; continue; } else if (p->name[(len = strlen(p->name)) - 1] == ';') { strncat(buttonLayout, p->name, len - 1); newRow = True; } else { strcat(buttonLayout, p->name); } sprintf(buf, " %d %d wW;", col, row); strcat(buttonLayout, buf); if (newRow) { ++row; col = 0; newRow = False; } else { ++col; } } *list = buttonList; return buttonLayout; } static XpTableLoc MakeButtonsLayout(widget, buttonList) Widget widget; FontListStruct **buttonList; { String layoutString; XpTableLoc tableLoc; Boolean toFree = True; FontListStruct *menuList = ParseFontList(PRIVATE(widget,menu_list)), *p; char menuButtonsLayout[1024]; char *mbl; int n, m; Boolean axeLogoSeen = False, matched; menuButtonsLayout[0] = '\0'; mbl = menuButtonsLayout; for (n = 0, p = menuList + 1; p->name; ++p, ++n) { matched = False; for (m = 0; chosenMenus[m].name; ++m) { if (strcmp(p->name, chosenMenus[m].name) == 0) { chosenMenus[m].chosen = True; matched = True; sprintf(mbl, "%sMenu %d 0 wW; ", p->name, n); mbl += strlen(mbl); break; } } if (!matched && strcmp(p->name, "logo") == 0) { axeLogoSeen = True; sprintf(mbl, "axeLogo %d 0 1 8; ", n); mbl += strlen(mbl); } } if (!axeLogoSeen) { sprintf(mbl, "axeLogo %d 0 1 8; ", n); } if (PRIVATE(widget,button_list)) { layoutString = ButtonListLayout(widget, menuButtonsLayout, buttonList); } else if (PRIVATE(widget,buttons)) { layoutString = (String) XtMalloc(strlen(menuButtonsLayout) + strlen(commandButtonsLayout) + 1); strcpy(layoutString, menuButtonsLayout); strcat(layoutString, commandButtonsLayout); } else { layoutString = menuButtonsLayout; toFree = False; } tableLoc = XpTableLocParse(layoutString); if (toFree) { XtFree(layoutString); } FreeFontList(XtDisplay(widget), menuList); return tableLoc; } /************************************************************* * * utilities * *************************************************************/ static Widget AxeEditorOf(widget) Widget widget; { Widget w; for (w = widget; w; w = XtParent(w)) { if (XtIsSubclass(w, axeEditorWidgetClass)) { return w; } } return (Widget) 0; } static void UpdateFileName(aew, file) Widget aew; String file; { if (PRIVATE(aew,suppressFilename)) { return; } XtVaSetValues(PRIVATE(aew,file_name), XtNlabel, file, NULL); } static void UpdateTitles(aew) Widget aew; { Widget top = AxeTextDeckTop(PRIVATE(aew,ed_deck)); String file, fileName, homeFile; Boolean freeFile = False; if (!(file = AxeTextGetAssociatedFile(top))) { file = XtNewString(noname); freeFile = True; } homeFile = HomeFile(file); fileName = XtMalloc(strlen(homeFile) + 2 + 1); *fileName = '\0'; if (AxeTextIsModified(top)) { strcat(fileName, "* "); } strcat(fileName, homeFile); UpdateFileName(aew, fileName); XtCallCallbackList(aew, PRIVATE(aew,change_callbacks), (XtPointer) fileName); XtFree(fileName); if (homeFile != file) { XtFree(homeFile); } if (freeFile) { XtFree(file); } } /*ARGSUSED*/ static void InfoBarTimeout(client_data, id) XtPointer client_data; XtIntervalId *id; { Widget axeEditor = (Widget) client_data; PRIVATE(axeEditor,info_timer) = (XtIntervalId) 0; XtVaSetValues(PRIVATE(axeEditor,info_bar), XtNlabel, QVERSION, NULL); } /* ARGSUSED */ static void UpdateInfo(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { UpdateInfoBar(AxeEditorOf(w), params[0]); } static void UpdateInfoBar(w, info) Widget w; String info; { if (PRIVATE(w,suppressInfobar)) { return; } if (PRIVATE(w,info_timer)) { XtRemoveTimeOut(PRIVATE(w,info_timer)); } XtVaSetValues(PRIVATE(w,info_bar), XtNlabel, info ? info : QVERSION, NULL); if (PRIVATE(w,info_timeout)) { PRIVATE(w,info_timer) = XtAppAddTimeOut(XtWidgetToApplicationContext(w), (unsigned long) PRIVATE(w,info_timeout) * 1000, InfoBarTimeout, (XtPointer) w); } } static Atom AxeisUserAtom(display) Display *display; { char propName[1024], *user; strcpy(propName, "AXE_AXINFO"); if ( (user = getenv("USER")) ) { strcat(propName, "_"); strcat(propName, user); } return XInternAtom(display, propName, False); } /************************************************************* * * Minibuffer * *************************************************************/ /* Caller must free return value */ static String CurrentDir(w) Widget w; { Widget axeEditor = AxeEditorOf(w); String assocFile, dir, filePart; if (assocFile = AxeTextGetAssociatedFile(AxeTextDeckTop(PRIVATE(axeEditor,ed_deck)))) { dir = XtNewString(assocFile); filePart = rindex(dir, '/'); if (filePart) { *(filePart + 1) = '\0'; } return dir; } else { return XtNewString(CLASS(current_dir)); } } /* NB Caller must free return value if different from input */ static String HomeFile(file) String file; { String new; if (strlen(file) > CLASS(home_dir_len) && strncmp(file, CLASS(home_dir), CLASS(home_dir_len)) == 0) { new = XtMalloc(strlen(file) + 3); strcpy(new, "~"); strcat(new, &file[CLASS(home_dir_len)]); return new; } return file; } static void MiniDisplayCaret(w, On) Widget w; Boolean On; { XtVaSetValues(PRIVATE(AxeEditorOf(w),mini_buffer), XtNdisplayCaret, On, NULL); } static void MiniEnable(w, prompt) Widget w; String prompt; { Widget axeEditor = AxeEditorOf(w); if (!PRIVATE(axeEditor,suppressMinibuffer)) { Widget input = PRIVATE(axeEditor,mini_buffer); XtVaSetValues(input, XtNdisplayCaret, True, XtNstring, prompt, NULL); XawTextSetInsertionPoint(input, strlen(prompt)); } } static XawTextPosition MiniAppend(w, text) Widget w; String text; { XawTextBlock textBlock; XawTextPosition textPos, textRtn; Widget input = PRIVATE(AxeEditorOf(w),mini_buffer); textPos = XawTextGetInsertionPoint(input); textBlock.firstPos = 0; textBlock.length = strlen(text); textBlock.ptr = text; textBlock.format = FMT8BIT; if ((textRtn = XawTextReplace(input, textPos, textPos, &textBlock)) == XawEditDone) { XawTextSetInsertionPoint(input, textPos + textBlock.length); } return textRtn; } static void MiniInstallAccelerators(w) Widget w; { Widget axeEditor = AxeEditorOf(w); if (!PRIVATE(axeEditor,suppressMinibuffer)) { XtVaSetValues(w, XtNdisplayCaret, False, NULL); XtInstallAccelerators(w, PRIVATE(axeEditor,mini_buffer)); PRIVATE(axeEditor,accelerateMini) = True; } } static void MiniUninstallAccelerators(w) Widget w; { Widget axeEditor = AxeEditorOf(w); if (!PRIVATE(axeEditor,suppressMinibuffer)) { XtVaSetValues(AxeTextDeckTop(PRIVATE(axeEditor,ed_deck)), XtNtranslations, PRIVATE(axeEditor,defaultTextTranslations), XtNdisplayCaret, True, NULL); XtVaSetValues(PRIVATE(axeEditor,mini_buffer), XtNdisplayCaret, False, NULL); PRIVATE(axeEditor,accelerateMini) = False; } } static void MiniInitInput(w) Widget w; { String dir = CurrentDir(w); String home = HomeFile(dir); (void) MiniAppend(w, home); if (home != dir) { XtFree(home); } XtFree(dir); } static String MiniInput(w, skip) Widget w; Boolean skip; { String input, p; XtVaGetValues(PRIVATE(AxeEditorOf(w),mini_buffer), XtNstring, &input, NULL); for (p = input + strlen(input) - 1; p >= input; --p) { if (*p != ' ' && *p != '\t') { break; } else { *p = 0; } } if (skip) { for (p = &input[1]; *p == ' '; ++p) {} return p; } else { return input; } } /*ARGSUSED*/ static void miniSelect(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { if (!PRIVATE(AxeEditorOf(w),suppressMinibuffer)) { MiniDisplayCaret(w, True); MiniInstallAccelerators(w); } } /*ARGSUSED*/ static void miniClear(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { if (!PRIVATE(AxeEditorOf(w),suppressMinibuffer)) { MiniEnable(w, ""); MiniInstallAccelerators(w); } } /*ARGSUSED*/ static void miniSearch(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { if (!PRIVATE(AxeEditorOf(w),suppressMinibuffer)) { if (*num_params > 0 && (strcmp(params[0], "backward") == 0)) { MiniEnable(w, "?"); } else { MiniEnable(w, "/"); } MiniInstallAccelerators(w); } } static void MiniSearch(w, searchDirection) Widget w; XawTextScanDirection searchDirection; { Widget text = AxeTextDeckTop(PRIVATE(AxeEditorOf(w),ed_deck)); regexp *compexp; String nomatch, regexpr = MiniInput(w, False); XawTextPosition nobegin, noend; Boolean found; compexp = 0; if (compexp = regcomp(®expr[1])) { nomatch = 0; found = AxeiiTextReSearch(text, (searchDirection == XawsdRight), ®expr[1], compexp, &nomatch, &nobegin, &noend); if (!RegError && !found) { UpdateInfoBar(AxeEditorOf(w), "Search string not found"); } XtFree(nomatch); } if (RegError) { UpdateInfoBar(AxeEditorOf(w), RegError); XBell(XtDisplay(w), 100); RegError = 0; } XtFree((char *) compexp); } static void MiniGoto(w) Widget w; { AxeTextGotoLine(AxeTextDeckTop(PRIVATE(AxeEditorOf(w),ed_deck)), atoi(MiniInput(w, False))); } /*ARGSUSED*/ static void miniInsert(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { if (!PRIVATE(AxeEditorOf(w),suppressMinibuffer)) { MiniEnable(w, "r "); MiniInitInput(w); MiniInstallAccelerators(w); } } static void MiniInsert(w) Widget w; { Widget axeEditor = AxeEditorOf(w); Widget axe = AxeTextDeckTop(PRIVATE(axeEditor,ed_deck)); String insertFile = AxeEditorExpandName(MiniInput(w, True)); Boolean undo; void (*proc)(); XtVaGetValues(axe, XtNundo, &undo, NULL); if ( (proc = AxeiiTextUndoPreInsert(axe)) ) { (*proc)(axe); } if (!InsertFileNamed(axe, insertFile)) { UpdateInfoBar(axeEditor, "Error inserting file"); XBell(XtDisplay(w), 100); } XtFree(insertFile); } /*ARGSUSED*/ static void miniSaveAs(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { if (!PRIVATE(AxeEditorOf(w),suppressMinibuffer)) { MiniEnable(w, "w "); MiniInitInput(w); MiniInstallAccelerators(w); } } static void MiniSaveAs(w) Widget w; { Widget axeEditor = AxeEditorOf(w); String saveAs = AxeEditorExpandName(MiniInput(w, True)); FileNominatorStruct fnomStruct; AxeTextFileToNominatorStruct(saveAs, &fnomStruct); if (!fnomStruct.directoryPart) { UpdateInfoBar(axeEditor, "Invalid pathname"); XBell(XtDisplay(w), 100); } else if (!fnomStruct.filenamePart) { UpdateInfoBar(axeEditor, "Pathname is a directory"); XBell(XtDisplay(w), 100); } else { (void) AxeTextSaveAsFile(AxeEditorEdWidget(axeEditor), &fnomStruct); } XtFree(saveAs); AxeTextFreeNominatorStruct(&fnomStruct); } /*ARGSUSED*/ static void miniLoad(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { if (!PRIVATE(AxeEditorOf(w),suppressMinibuffer)) { MiniEnable(w, "e "); MiniInitInput(w); MiniInstallAccelerators(w); } } /* ARGSUSED */ static void MiniForceLoad(widget, client_data, call_data) Widget widget; XtPointer client_data, call_data; { Widget mini = (Widget) client_data; Widget axeEditor = AxeEditorOf(mini); String loadFile; FileNominatorStruct fnomStruct; loadFile = AxeEditorExpandName(MiniInput(mini, True)); AxeTextFileToNominatorStruct(loadFile, &fnomStruct); if (!fnomStruct.directoryPart) { UpdateInfoBar(axeEditor, "Invalid pathname"); XBell(XtDisplay(mini), 100); } else if (!fnomStruct.filenamePart) { UpdateInfoBar(axeEditor, "Pathname is a directory"); XBell(XtDisplay(mini), 100); } else { AxeTextLoadFile(AxeEditorEdWidget(axeEditor), &fnomStruct); } XtFree(loadFile); AxeTextFreeNominatorStruct(&fnomStruct); } static void MiniLoad(w) Widget w; { Widget axeEditor = AxeEditorOf(w); if (AxeTextIsModified(AxeTextDeckTop(PRIVATE(axeEditor,ed_deck)))) { ConfirmerRequestConfirmation(PRIVATE(axeEditor,confirmer), "There are unsaved changes", "Discard changes", MiniForceLoad, "unused", NULL, (XtPointer) w); } else { MiniForceLoad((XtPointer) 0, (XtPointer) w, (XtPointer) 0); } } static void MiniPipeOrShell(w, cmd, params, num_params) Widget w; String cmd, *params; Cardinal *num_params; { int i, len; char param[80]; Boolean commit = False; if (!PRIVATE(AxeEditorOf(w),suppressMinibuffer)) { MiniEnable(w, cmd); for (i = 0; i < *num_params; ++i) { if (i == (*num_params) - 1 && (len = strlen(params[i])) >= 1 && params[i][len - 1] == '\n') { strcpy(param, params[i]); param[len - 1] = '\0'; MiniAppend(w, param); commit = True; } else { MiniAppend(w, params[i]); MiniAppend(w, " "); } } MiniInstallAccelerators(w); if (commit) { MiniCommit(w, (XEvent *) 0, (String *) 0, (Cardinal *) 0); } } } /*ARGSUSED*/ static void miniPipe(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { MiniPipeOrShell(w, "| ", params, num_params); } static void ClosePipes(output, input, errput) int *output, *input, *errput; { if (output[0] != -1) close(output[0]); if (output[1] != -1) close(output[1]); if (input[0] != -1) close(input[0]); if (input[1] != -1) close(input[1]); if (errput[0] != -1) close(errput[0]); if (errput[1] != -1) close(errput[1]); } static Boolean ExecSubProcess(widget, command, output, input, errput) Widget widget; String command; int *output, *input, *errput; { output[0] = output[1] = input[0] = input[1] = errput[0] = errput[1] = -1; if (pipe(output) == -1 || pipe(input) == -1 || pipe(errput) == -1) { ClosePipes(output, input, errput); return False; } switch(fork()) { case -1: ClosePipes(output, input, errput); return False; case 0: /* Child */ (void) close(XConnectionNumber(XtDisplay(widget))); if (close(0) == 0 && dup(output[0]) == 0 && close(output[0]) == 0 && close(output[1]) == 0 && close(1) == 0 && dup(input[1]) == 1 && close(input[0]) == 0 && close(input[1]) == 0 && close(2) == 0 && dup(errput[1]) == 2 && close(errput[0]) == 0 && close(errput[1]) == 0) { char *assocFile, assocDir[MAXPATHLEN]; if ( (assocFile = AxeTextGetAssociatedFile(widget)) ) { strcpy(assocDir, assocFile); assocDir[rindex(assocDir, '/') - assocDir] = '\0'; (void) chdir(assocDir); } (void) execl("/bin/sh", "sh", "-c", command, (char *) 0); } ClosePipes(output, input, errput); exit(0); default: /* Parent */ if (close(output[0]) == -1 || close(input[1]) == -1 || close(errput[1]) == -1) { ClosePipes(output, input, errput); return False; } } return True; } static void ChildInput(client_data, source, id) XtPointer client_data; int *source; XtInputId *id; { struct childInfo *ci = (struct childInfo*) client_data; char buffer[1024]; int nread; XawTextBlock block; XawTextPosition pos; void (*proc)(); nread = read(*source, buffer, sizeof(buffer)); if (nread == -1) { UpdateInfoBar(AxeEditorOf(ci->ed), "Output from command is possibly incomplete"); XBell(XtDisplay(ci->ed), 100); } if (nread == 0 || nread == -1) { if (!ci->first && !ci->replace) { XtVaSetValues(ci->ed, XtNinsertPosition, 0, NULL); XtPopup(ci->window, XtGrabNone); } else if (ci->replace && (ci->selStart != ci->selEnd)) { XawTextSetSelection(ci->ed, ci->selStart, XawTextGetInsertionPoint(ci->ed)); } XtRemoveInput(*id); close(*source); XtFree((char *) ci); wait((int *) 0); } else { block.firstPos = 0; block.length = nread; block.ptr = buffer; block.format = FMT8BIT; if (ci->first) { char first[2]; String f = first, *ff = &f; ci->first = False; if (ci->replace) { XawTextBlock notext; notext.firstPos = 0; notext.length = 0; notext.ptr = 0; notext.format = FMT8BIT; XawTextSetInsertionPoint(ci->ed, ci->selStart); if (ci->selStart != ci->selEnd) { if ( (proc = AxeiiTextUndoPreReplace(ci->ed)) ) { (*proc)(ci->ed); } } else { if ( (proc = AxeiiTextUndoPreInsert(ci->ed)) ) { (*proc)(ci->ed); } } (void) XawTextReplace(ci->ed, ci->selStart, ci->selEnd, ¬ext); /* * This shouldn't be necessary, but for some reason the * insertion point is incorrect following the replace. */ XawTextSetInsertionPoint(ci->ed, ci->selStart); } else { ci->window = AxeEditorCreateWindow(ci->ed, CLASS(menu_parent), (String) 0); ci->ed = AxeEditorEdWidget(AxeWindowAxeEditor(ci->window)); XtRealizeWidget(ci->window); /* * Silly kludge to get round problem of caret behaving oddly * if text is inserted into empty widget using XawTextReplace */ first[0] = buffer[0]; first[1] = '\0'; XtCallActionProc(ci->ed, "insert-string", (XEvent *) 0, ff, (Cardinal) 1); block.firstPos = 1; block.length = nread - 1; } } pos = XawTextGetInsertionPoint(ci->ed); if (XawTextReplace(ci->ed, pos, pos, &block) != XawEditDone) { UpdateInfoBar(AxeEditorOf(ci->ed), "Text insertion failure"); } else { XawTextSetInsertionPoint(ci->ed, pos + block.length); } } } static void MiniPopen(w, replace) Widget w; Boolean replace; { Widget axeEditor = AxeEditorOf(w); Widget text = AxeTextDeckTop(PRIVATE(axeEditor,ed_deck)); Widget asciiSrc; XawTextPosition selStart, selEnd, from; XawTextBlock textBlock; Cursor busyCursor = 0, menuCursor, textCursor, miniCursor; int length, toread, piece; int tochild[2], fromchild[2], errchild[2]; struct childInfo *ci, *ce; /* * The selection or the entire buffer is fed to the command in the * minibuffer. The output from the command replaces the selection or * is inserted into the buffer if replace is True (| - mini-pipe), * otherwise it is inserted into a new window (! - mini-shell). Nothing * happens if there is no standard output from the command. Error output * is popped up in a separate standard aXe window. */ if (!ExecSubProcess(text, MiniInput(w, True), tochild, fromchild, errchild)) { UpdateInfoBar(axeEditor, "Unable to execute command"); XBell(XtDisplay(w), 100); return; } asciiSrc = XawTextGetSource(text); XtVaGetValues(asciiSrc, XtNpieceSize, &piece, NULL); XawTextGetSelectionPos(text, &selStart, &selEnd); if ((length = selEnd - selStart) == 0) { if (replace) { selStart = XawTextGetInsertionPoint(text); selEnd = selStart; } else { selStart = selEnd = 0; length = piece; } } XtVaGetValues(PRIVATE(axeEditor,menu_bar), XtNcursor, &menuCursor, NULL); XtVaGetValues(text, XtNcursor, &textCursor, NULL); XtVaGetValues(PRIVATE(axeEditor,mini_buffer), XtNcursor, &miniCursor, NULL); busyCursor = XCreateFontCursor(XtDisplay(axeEditor), XC_watch); XtVaSetValues(PRIVATE(axeEditor,menu_bar), XtNcursor, busyCursor, NULL); XtVaSetValues(text, XtNcursor, busyCursor, NULL); XtVaSetValues(PRIVATE(axeEditor,mini_buffer), XtNcursor, busyCursor, NULL); XFlush(XtDisplay(axeEditor)); ci = XtNew(struct childInfo); ci->first = True; ci->replace = replace; ci->ed = text; ci->window = 0; ci->selStart = selStart; ci->selEnd = selEnd; (void) XtAppAddInput(XtWidgetToApplicationContext(w), fromchild[0], (XtPointer) XtInputReadMask, ChildInput, (XtPointer) ci); ce = XtNew(struct childInfo); ce->first = True; ce->replace = False; ce->ed = text; ce->window = 0; ce->selStart = 0; /* Not used */ ce->selEnd = 0; /* Not used */ (void) XtAppAddInput(XtWidgetToApplicationContext(w), errchild[0], (XtPointer) XtInputReadMask, ChildInput, (XtPointer) ce); signal(SIGPIPE, SIG_IGN); textBlock.format = FMT8BIT; for(toread = length, from = selStart; toread > 0; toread = (selEnd != selStart ? toread - textBlock.length : piece), from = from + textBlock.length) { (void) XawTextSourceRead(asciiSrc, from, &textBlock, toread); if (textBlock.length == 0) { break; } if (write(tochild[1], textBlock.ptr, textBlock.length) == -1) { UpdateInfoBar(axeEditor, "Pipe output error"); XBell(XtDisplay(w), 100); break; } } close(tochild[1]); signal(SIGPIPE, SIG_DFL); if (busyCursor) { XtVaSetValues(PRIVATE(axeEditor,menu_bar), XtNcursor, menuCursor,NULL); XtVaSetValues(text, XtNcursor, textCursor, NULL); XtVaSetValues(PRIVATE(axeEditor,mini_buffer), XtNcursor, miniCursor, NULL); } } #ifdef EXTENSION /*ARGSUSED*/ static int MiniInterp(w) Widget w; { #if TCL_MAJOR_VERSION < 7 return Tcl_Eval(interpreter.interp, MiniInput(w, False), 0, NULL); #else return Tcl_Eval(interpreter.interp, MiniInput(w, False)); #endif } #endif /*ARGSUSED*/ static void miniShell(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { MiniPipeOrShell(w, "! ", params, num_params); } /*ARGSUSED*/ static void miniStuff(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { MiniPipeOrShell(w, "", params, num_params); } /*ARGSUSED*/ static void miniAbort(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { Widget axeEditor = AxeEditorOf(w); if (!PRIVATE(axeEditor,suppressMinibuffer)) { UpdateInfoBar(axeEditor, "Minibuffer abort"); XBell(XtDisplay(w), 100); if (PRIVATE(axeEditor,accelerateMini)) { MiniUninstallAccelerators(w); } } } /*ARGSUSED*/ static void TrapWarningMsgss(name, type, class, defaultp, params, num_params) String name, type, class, defaultp, *params; Cardinal *num_params; { validAction = False; } /*ARGSUSED*/ static void TrapWarnings(message) String message; { validAction = False; } /*ARGSUSED*/ static void MiniCommit(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { Widget axeEditor = AxeEditorOf(w); String input; char command; Boolean done; XtVaGetValues(PRIVATE(axeEditor,mini_buffer), XtNstring, &input, NULL); command = input[0]; switch (command) { case '/': MiniSearch(w, XawsdRight); break; case '?': MiniSearch(w, XawsdLeft); break; case '|': MiniPopen(w, True); break; case '!': MiniPopen(w, False); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': MiniGoto(w); break; case 'r': case 'R': case 'w': case 'W': case 'e': case 'E': done = False; if (input[1] == ' ') { switch (input[0]) { case 'r': case 'R': MiniInsert(w); done = True; break; case 'w': case 'W': MiniSaveAs(w); done = True; break; case 'e': case 'E': MiniLoad(w); done = True; break; default: break; } } if (done) { break; } else { /* Drop-though; not r|w|e; try action or Tcl command */ } default: { XtAppContext app = XtWidgetToApplicationContext(w); XtErrorMsgHandler oldWarningMsgHandler = XtAppSetWarningMsgHandler(app, TrapWarningMsgss); XtErrorHandler oldWarningHandler = XtAppSetWarningHandler(app, TrapWarnings); validAction = True; GenericAction(AxeEditorEdWidget(axeEditor), (XtPointer) MiniInput(w, False)); XtAppSetWarningMsgHandler(app, oldWarningMsgHandler); XtAppSetWarningHandler(app, oldWarningHandler); if (validAction) { break; } } #ifdef EXTENSION if (MiniInterp(w) == TCL_OK) { break; } else { ConfirmerRequestConfirmation(PRIVATE(axeEditor,confirmer), interpreter.interp->result, "acknowledge", ConfirmerPopdown, "unused", NULL, (XtPointer) axeEditor); #else if (True) { ConfirmerRequestConfirmation(PRIVATE(axeEditor,confirmer), "Invalid command", "acknowledge", ConfirmerPopdown, "unused", NULL, (XtPointer) axeEditor); #endif command = '$'; break; } } switch (command) { case '/': case '?': case '$': break; default: if (PRIVATE(axeEditor,accelerateMini)) { MiniUninstallAccelerators(w); } } } /*ARGSUSED*/ static void MiniLeave(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { if (!PRIVATE(AxeEditorOf(w),accelerateMini)) { MiniDisplayCaret(w, False); } } /************************************************************* * * Keyboard Macro * *************************************************************/ static void AddToMacro(w, client_data, action_name, event, params, num_params) Widget w; XtPointer client_data; String action_name; XEvent *event; String *params; Cardinal *num_params; { char buffer[MAXSTRLEN + 1]; int bytes; if (w != (Widget) client_data) { return; } if (strcmp(action_name, "insert-char") == 0) { bytes = XLookupString((XKeyEvent *) event, buffer, MAXSTRLEN, (KeySym *) 0, (XComposeStatus *) 0); if (bytes != 0) { buffer[bytes] = '\0'; if (!CLASS(in_macro_string)) { CLASS(macro_commands)[CLASS(macro_size)].name = XrmStringToQuark("insert-string"); CLASS(macro_commands)[CLASS(macro_size)].numArgs = 1; CLASS(macro_commands)[CLASS(macro_size)].arg = (String) XtMalloc(bytes + 1); strncpy(CLASS(macro_commands)[CLASS(macro_size)].arg, buffer, bytes + 1); CLASS(in_macro_string) = True; } else { CLASS(macro_commands)[CLASS(macro_size)].arg = (String) XtRealloc(CLASS(macro_commands)[CLASS(macro_size)].arg, strlen(CLASS(macro_commands)[CLASS(macro_size)].arg) + bytes + 1); strncat(CLASS(macro_commands)[CLASS(macro_size)].arg, buffer, bytes); } } } else { if (CLASS(in_macro_string)) { ++CLASS(macro_size); CLASS(in_macro_string) = False; } CLASS(macro_commands)[CLASS(macro_size)].name = XrmStringToQuark(action_name); CLASS(macro_commands)[CLASS(macro_size)].numArgs = (*num_params) ? 1 : 0; if (*num_params != 0) { CLASS(macro_commands)[CLASS(macro_size)].arg = XtNewString(params[0]); } else { CLASS(macro_commands)[CLASS(macro_size)].arg = NULL; } ++CLASS(macro_size); } if (CLASS(macro_size) == CLASS(max_macro_size)) { CLASS(max_macro_size) += CHUNK; CLASS(macro_commands) = (MacroList) XtRealloc((char *) CLASS(macro_commands), CLASS(max_macro_size) * sizeof(MacroCommand)); } } /*ARGSUSED*/ static void startMacro(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { Widget axeEditor = AxeEditorOf(w); int m; if (!CLASS(macro_id)) { CLASS(macro_id) = XtAppAddActionHook(XtWidgetToApplicationContext(w), AddToMacro, (XtPointer) w); UpdateInfoBar(axeEditor, "Defining keyboard macro"); if (CLASS(macro_size) == 0) { CLASS(macro_commands) = (MacroList) XtMalloc(CHUNK * sizeof(MacroCommand)); CLASS(max_macro_size) = CHUNK; } else { for (m = 0; m < CLASS(macro_size); ++m) { if (CLASS(macro_commands)[m].numArgs != 0) { XtFree(CLASS(macro_commands)[m].arg); } } CLASS(macro_size) = 0; } CLASS(in_macro_string) = False; } else { UpdateInfoBar(axeEditor, "Already defining keyboard macro"); XBell(XtDisplay(w), 100); } } /*ARGSUSED*/ static void endMacro(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { Widget axeEditor = AxeEditorOf(w); if (CLASS(macro_id)) { --CLASS(macro_size); /* removes end-macro itself */ XtRemoveActionHook(CLASS(macro_id)); CLASS(macro_id) = (XtActionHookId) 0; UpdateInfoBar(axeEditor, "Keyboard macro defined"); } else { UpdateInfoBar(axeEditor, "Not defining keyboard macro"); XBell(XtDisplay(w), 100); } } static void ExecMacro(w, event) Widget w; XEvent *event; { int c; for (c = 0; c < CLASS(macro_size); ++c) { XtCallActionProc(w, XrmQuarkToString(CLASS(macro_commands)[c].name), event, &CLASS(macro_commands)[c].arg, CLASS(macro_commands)[c].numArgs); } } /*ARGSUSED*/ static void execMacro(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { if (CLASS(macro_size) == 0) { UpdateInfoBar(AxeEditorOf(w), "No macro defined"); XBell(XtDisplay(w), 100); return; } ExecMacro(w, event); } /*ARGSUSED*/ static void repeatMacro(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { Widget axeEditor = AxeEditorOf(w); int repeat, repeats, times; if (CLASS(macro_size) == 0) { UpdateInfoBar(axeEditor, "No macro defined"); XBell(XtDisplay(w), 100); return; } if (*num_params > 0) { repeats = atoi(params[0]); } else { if (!PRIVATE(axeEditor,suppressMinibuffer)) { repeats = ((times = atoi(MiniInput(w, False))) == 0) ? 1 : times; } else { repeats = 1; } } for (repeat = 1; repeat <= repeats; ++repeat) { ExecMacro(w, event); } } /************************************************************* * * Semi-public functions * *************************************************************/ /* * This routine is made public for the benefit of AxeTextDeck, but it is * not advertised as such in AxeEditor.h. If there are multiple files it * is easier to do the expansions after splitting in AxeTextDeck. * * NB caller must free returned string */ String AxeEditorExpandName(name) String name; { String expandedName; if (name[0] == '~') { expandedName = XtMalloc(CLASS(home_dir_len) + strlen(name) + 1); strcpy(expandedName, CLASS(home_dir)); strcat(expandedName, "/"); strcat(expandedName, &name[2]); } #ifdef __apollo else if (strncmp (name, "`node_data", 10) == 0) { expandedName = XtNewString(name); } #endif else if (name[0] == '/') { expandedName = XtNewString(name); } else { expandedName = XtMalloc(CLASS(current_dir_len) + strlen(name) + 1); strcpy(expandedName, CLASS(current_dir)); strcat(expandedName, name); } return expandedName; } /************************************************************* * * Public functions * *************************************************************/ int AxeEditorNextEditor() { int editor; /* AxeWindows are named according to the slot number of the child AxeEditor. But the AxeWindow has to be created, and hence named, before the AxeEditor is created. This makes sure a correct slot number is returned in the first case */ XtInitializeWidgetClass(axeEditorWidgetClass); for (editor = 0; editor < NUMBEROFEDITORS; ++editor) { if (!EDITORS[editor]) { break; } } return editor; } Widget AxeEditorParentOf(widget) Widget widget; { Widget axeEditor; for (axeEditor = XtParent(widget); axeEditor; axeEditor = XtParent(axeEditor)) { if (XtIsSubclass(axeEditor, axeEditorWidgetClass)) { break; } } return axeEditor; } Widget AxeEditorFileNameWidget(w) Widget w; { return (XtIsSubclass(w, axeEditorWidgetClass)) ? PRIVATE(w,file_name) : (Widget) 0; } Widget AxeEditorInfoBarWidget(w) Widget w; { return (XtIsSubclass(w, axeEditorWidgetClass)) ? PRIVATE(w,info_bar) : (Widget) 0; } Widget AxeEditorMenuBarWidget(w) Widget w; { return (XtIsSubclass(w, axeEditorWidgetClass)) ? PRIVATE(w,menu_bar) : (Widget) 0; } Widget AxeEditorEdWidget(w) Widget w; { return (XtIsSubclass(w, axeEditorWidgetClass)) ? AxeTextDeckTop(PRIVATE(w,ed_deck)) : (Widget) 0; } Widget AxeEditorMiniBufferWidget(w) Widget w; { return (XtIsSubclass(w, axeEditorWidgetClass)) ? PRIVATE(w,mini_buffer) : (Widget) 0; } void AxeEditorUpdateInfoBar(w, info) Widget w; String info; { if (w && XtIsSubclass(w, axeEditorWidgetClass)) { UpdateInfoBar(w, info); } } Boolean AxeEditorIterate(iterator) AxeIterationType iterator; { Widget axeEditor; int editor; Boolean result = True; XtInitializeWidgetClass(axeEditorWidgetClass); for (editor = 0; editor < NUMBEROFEDITORS; ++editor) { axeEditor = EDITORS[editor]; if (!axeEditor) { continue; } switch (iterator) { case AxeSafeClose: result &= WindowClosed(axeEditor); break; case AxeForceClose: XtDestroyWidget(axeEditor); break; case AxeSave: result &= BuffersSaved(axeEditor); break; case AxeSaveAndClose: (void) BuffersSaved(axeEditor); result &= WindowClosed(axeEditor); break; default: ; } } return result; } Widget AxeEditorCreateWindow(widget, parent, file) Widget widget; Widget parent; String file; { Widget window, deck; char name[3], assocDir[MAXPATHLEN], *assocFile; RePopulateShowMenu(XtWidgetToApplicationContext(parent)); sprintf(name, "%02d", AxeEditorNextEditor()); window = XtVaCreatePopupShell(name, axeWindowWidgetClass, parent, XtNfile, file, NULL); if (XtIsSubclass(widget, axeTextWidgetClass)) { if ( (assocFile = AxeTextGetAssociatedFile(widget)) ) { strcpy(assocDir, assocFile); assocDir[rindex(assocDir, '/') - assocDir] = '\0'; } deck = PRIVATE(AxeWindowAxeEditor(window),ed_deck); AxeTextDeckSetAssociatedDirectory (deck, assocFile ? assocDir : assocFile); } return window; } static XtResource appResource[] = { { "display", "Display", XtRString, sizeof(String), (Cardinal) 0, XtRImmediate, (XtPointer) 0 } }; void AxeEditorHelpWindow(helpName, parent) String helpName; Widget parent; { Atom reqAtom = XA_WINDOW, gotAtom; int gotFormat; unsigned long gotItems, moreBytes; unsigned char *propValue; Display *display = XtDisplay(parent); Window propWindow; char command[1024], property; Boolean startAxinfo = True; String dpy; /* * Check if axinfo is already running. First look to * see if expected root window property is present. */ if (XGetWindowProperty(display, XDefaultRootWindow(display), AxeisUserAtom(display), 0, 1, False, reqAtom, &gotAtom, &gotFormat, &gotItems, &moreBytes, &propValue) == Success) { /* * The property does exist. The window that is the value of the * property should have the AXE_AXINFO_user property attached. */ if (gotAtom == reqAtom) { propWindow = *((Window *) propValue); XFree((char *) propValue); TrapErrors(display); if (XGetWindowProperty(display, propWindow, AxeisUserAtom(display), 0, 0, False, XA_STRING, &gotAtom, &gotFormat, &gotItems, &moreBytes, &propValue) == Success) { if (gotAtom == None) { xerror = BadAtom; } XFree((char *) propValue); } DontTrapErrors(display); /* * The window id from the root window property seems to be OK. * So go ahead and notify it by changing its AXE_AXINFO property * with the tag of the node that is to be displayed. If that * succeeds then the work is done. */ if (xerror == Success) { TrapErrors(display); XChangeProperty(display, propWindow, XInternAtom(display, "AXE_AXINFO", False), XA_STRING, 8, PropModeReplace, (unsigned char *) helpName, strlen(helpName)); if (xerror == Success) { startAxinfo = False; } DontTrapErrors(display); } } } if (startAxinfo) { XtVaGetApplicationResources(parent, (XtPointer) &dpy, appResource, (Cardinal) 1, NULL); if (dpy) { sprintf(command, "axinfo -node %s -display %s &", (helpName ? helpName : "Top"), dpy); } else { sprintf(command, "axinfo -node %s &",(helpName ? helpName : "Top")); } system(command); } } void AxeEditorAddShowMenuCallback(widget, proc, client_data) Widget widget; XtCallbackProc proc; XtPointer client_data; { AxeEditorWidgetClass wc; int n; XtInitializeWidgetClass(axeEditorWidgetClass); wc = (AxeEditorWidgetClass) axeEditorWidgetClass; n = ++CLASS(show_menu_callbacks); CLASS(show_menu_callback_list) = (ShowMenuList) XtRealloc((char *) CLASS(show_menu_callback_list), (Cardinal) (n * sizeof(ShowMenuRec))); CLASS(show_menu_callback_list)[n - 1].widget = widget; CLASS(show_menu_callback_list)[n - 1].cbr.callback = proc; CLASS(show_menu_callback_list)[n - 1].cbr.closure = client_data; } #undef PRIVATE #undef CLASS #undef MAXEDITORS #undef NUMBEROFEDITORS #undef EDITORS