Support for Lynx, MacOS; better x86 reporting
[nedit.git] / source / window.c
blobb91a0861a02197a5890347a72bdaffb26c2039f4
1 static const char CVSID[] = "$Id: window.c,v 1.60 2002/08/09 13:11:03 tringali Exp $";
2 /*******************************************************************************
3 * *
4 * window.c -- Nirvana Editor window creation/deletion *
5 * *
6 * Copyright (C) 1999 Mark Edel *
7 * *
8 * This is free software; you can redistribute it and/or modify it under the *
9 * terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
11 * version. *
12 * *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
16 * for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License along with *
19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
20 * Place, Suite 330, Boston, MA 02111-1307 USA *
21 * *
22 * Nirvana Text Editor *
23 * May 10, 1991 *
24 * *
25 * Written by Mark Edel *
26 * *
27 *******************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
33 #include "window.h"
34 #include "textBuf.h"
35 #include "textSel.h"
36 #include "text.h"
37 #include "textDisp.h"
38 #include "textP.h"
39 #include "nedit.h"
40 #include "menu.h"
41 #include "file.h"
42 #include "search.h"
43 #include "undo.h"
44 #include "preferences.h"
45 #include "selection.h"
46 #include "shell.h"
47 #include "macro.h"
48 #include "highlight.h"
49 #include "smartIndent.h"
50 #include "userCmds.h"
51 #include "nedit.bm"
52 #include "n.bm"
53 #include "windowTitle.h"
54 #include "../util/clearcase.h"
55 #include "../util/misc.h"
56 #include "../util/utils.h"
58 #include <stdio.h>
59 #include <stdlib.h>
60 #include <string.h>
61 #ifdef VMS
62 #include "../util/VMSparam.h"
63 #else
64 #ifndef __MVS__
65 #include <sys/param.h>
66 #endif
67 #include "../util/clearcase.h"
68 #endif /*VMS*/
69 #include <limits.h>
70 #include <math.h>
71 #include <ctype.h>
73 #include <X11/Intrinsic.h>
74 #include <X11/Shell.h>
75 #include <X11/Xatom.h>
76 #include <Xm/Xm.h>
77 #include <Xm/MainW.h>
78 #include <Xm/PanedW.h>
79 #include <Xm/PanedWP.h>
80 #include <Xm/RowColumnP.h>
81 #include <Xm/Text.h>
82 #include <Xm/ToggleB.h>
83 #include <Xm/Form.h>
84 #include <Xm/Label.h>
85 #include <Xm/Protocols.h>
86 #include <Xm/ScrolledW.h>
87 #include <Xm/ScrollBar.h>
88 #include <Xm/PrimitiveP.h>
89 #include <Xm/Frame.h>
90 #ifdef EDITRES
91 #include <X11/Xmu/Editres.h>
92 /* extern void _XEditResCheckMessages(); */
93 #endif /* EDITRES */
96 #ifdef HAVE_DEBUG_H
97 #include "../debug.h"
98 #endif
101 /* Initial minimum height of a pane. Just a fallback in case setPaneMinHeight
102 (which may break in a future release) is not available */
103 #define PANE_MIN_HEIGHT 39
105 /* Thickness of 3D border around statistics and/or incremental search areas
106 below the main menu bar */
107 #define STAT_SHADOW_THICKNESS 1
109 static Widget createTextArea(Widget parent, WindowInfo *window, int rows,
110 int cols, int emTabDist, char *delimiters, int wrapMargin,
111 int lineNumCols);
112 static void showStats(WindowInfo *window, int state);
113 static void showISearch(WindowInfo *window, int state);
114 static void showStatsForm(WindowInfo *window, int state);
115 static void addToWindowList(WindowInfo *window);
116 static void removeFromWindowList(WindowInfo *window);
117 static void focusCB(Widget w, WindowInfo *window, XtPointer callData);
118 static void modifiedCB(int pos, int nInserted, int nDeleted, int nRestyled,
119 char *deletedText, void *cbArg);
120 static void movedCB(Widget w, WindowInfo *window, XtPointer callData);
121 static void dragStartCB(Widget w, WindowInfo *window, XtPointer callData);
122 static void dragEndCB(Widget w, WindowInfo *window, dragEndCBStruct *callData);
123 static void closeCB(Widget w, WindowInfo *window, XtPointer callData);
124 static void saveYourselfCB(Widget w, WindowInfo *window, XtPointer callData);
125 static void focusToISearchTextCB(Widget w, WindowInfo *window,
126 XtPointer callData);
127 static int isIconic(WindowInfo *window);
128 static void setPaneDesiredHeight(Widget w, int height);
129 static void setPaneMinHeight(Widget w, int min);
130 static void addWindowIcon(Widget shell);
131 static void wmSizeUpdateProc(XtPointer clientData, XtIntervalId *id);
132 static void getGeometryString(WindowInfo *window, char *geomString);
133 #ifdef ROWCOLPATCH
134 static void patchRowCol(Widget w);
135 static void patchedRemoveChild(Widget child);
136 #endif
137 static unsigned char* sanitizeVirtualKeyBindings();
138 static int sortAlphabetical(const void* k1, const void* k2);
139 static int virtKeyBindingsAreInvalid(const unsigned char* bindings);
140 static void restoreInsaneVirtualKeyBindings(unsigned char* bindings);
143 ** Create a new editor window
145 WindowInfo *CreateWindow(const char *name, char *geometry, int iconic)
147 Widget appShell, mainWin, menuBar, pane, text, stats, statsAreaForm;
148 Widget iSearchLabel;
149 WindowInfo *window;
150 Pixel bgpix, fgpix;
151 Arg al[20];
152 int ac;
153 XmString s1;
154 Dimension ht, ht_lc;
155 #ifdef SGI_CUSTOM
156 char sgi_title[MAXPATHLEN + 14 + SGI_WINDOW_TITLE_LEN] = SGI_WINDOW_TITLE;
157 #endif
158 char newGeometry[MAX_GEOM_STRING_LEN];
159 unsigned int rows, cols;
160 int x, y, bitmask;
161 static Atom wmpAtom, syAtom = 0;
162 static int firstTime = True;
163 unsigned char* invalidBindings = NULL;
165 if (firstTime)
167 invalidBindings = sanitizeVirtualKeyBindings();
168 firstTime = False;
171 /* Allocate some memory for the new window data structure */
172 window = (WindowInfo *)XtMalloc(sizeof(WindowInfo));
174 /* initialize window structure */
175 /* + Schwarzenberg: should a
176 memset(window, 0, sizeof(WindowInfo));
177 be added here ?
179 window->replaceDlog = NULL;
180 window->replaceText = NULL;
181 window->replaceWithText = NULL;
182 window->replaceWordToggle = NULL;
183 window->replaceCaseToggle = NULL;
184 window->replaceRegexToggle = NULL;
185 window->findDlog = NULL;
186 window->findText = NULL;
187 window->findWordToggle = NULL;
188 window->findCaseToggle = NULL;
189 window->findRegexToggle = NULL;
190 window->replaceMultiFileDlog = NULL;
191 window->replaceMultiFilePathBtn = NULL;
192 window->replaceMultiFileList = NULL;
193 window->multiFileReplSelected = FALSE;
194 window->multiFileBusy = FALSE;
195 window->writableWindows = NULL;
196 window->nWritableWindows = 0;
197 window->fileChanged = FALSE;
198 window->fileMode = 0;
199 window->filenameSet = FALSE;
200 window->fileFormat = UNIX_FILE_FORMAT;
201 window->lastModTime = 0;
202 strcpy(window->filename, name);
203 window->undo = NULL;
204 window->redo = NULL;
205 window->nPanes = 0;
206 window->autoSaveCharCount = 0;
207 window->autoSaveOpCount = 0;
208 window->undoOpCount = 0;
209 window->undoMemUsed = 0;
210 CLEAR_ALL_LOCKS(window->lockReasons);
211 window->indentStyle = GetPrefAutoIndent(PLAIN_LANGUAGE_MODE);
212 window->autoSave = GetPrefAutoSave();
213 window->saveOldVersion = GetPrefSaveOldVersion();
214 window->wrapMode = GetPrefWrap(PLAIN_LANGUAGE_MODE);
215 window->overstrike = False;
216 window->showMatchingStyle = GetPrefShowMatching();
217 window->matchSyntaxBased = GetPrefMatchSyntaxBased();
218 window->showStats = GetPrefStatsLine();
219 window->showISearchLine = GetPrefISearchLine();
220 window->showLineNumbers = GetPrefLineNums();
221 window->showPathInWindowsMenu = GetPrefShowPathInWindowsMenu();
222 window->highlightSyntax = GetPrefHighlightSyntax();
223 window->modeMessageDisplayed = FALSE;
224 window->ignoreModify = FALSE;
225 window->windowMenuValid = FALSE;
226 window->prevOpenMenuValid = FALSE;
227 window->flashTimeoutID = 0;
228 window->wasSelected = FALSE;
229 strcpy(window->fontName, GetPrefFontName());
230 strcpy(window->italicFontName, GetPrefItalicFontName());
231 strcpy(window->boldFontName, GetPrefBoldFontName());
232 strcpy(window->boldItalicFontName, GetPrefBoldItalicFontName());
233 window->fontList = GetPrefFontList();
234 window->italicFontStruct = GetPrefItalicFont();
235 window->boldFontStruct = GetPrefBoldFont();
236 window->boldItalicFontStruct = GetPrefBoldItalicFont();
237 window->fontDialog = NULL;
238 window->nMarks = 0;
239 window->markTimeoutID = 0;
240 window->highlightData = NULL;
241 window->shellCmdData = NULL;
242 window->macroCmdData = NULL;
243 window->smartIndentData = NULL;
244 window->languageMode = PLAIN_LANGUAGE_MODE;
245 window->iSearchHistIndex = 0;
246 window->iSearchStartPos = -1;
247 window->replaceLastRegexCase = TRUE;
248 window->replaceLastLiteralCase = FALSE;
249 window->iSearchLastRegexCase = TRUE;
250 window->iSearchLastLiteralCase = FALSE;
251 window->findLastRegexCase = TRUE;
252 window->findLastLiteralCase = FALSE;
254 /* If window geometry was specified, split it apart into a window position
255 component and a window size component. Create a new geometry string
256 containing the position component only. Rows and cols are stripped off
257 because we can't easily calculate the size in pixels from them until the
258 whole window is put together. Note that the preference resource is only
259 for clueless users who decide to specify the standard X geometry
260 application resource, which is pretty useless because width and height
261 are the same as the rows and cols preferences, and specifying a window
262 location will force all the windows to pile on top of one another */
263 if (geometry == NULL || geometry[0] == '\0')
264 geometry = GetPrefGeometry();
265 if (geometry == NULL || geometry[0] == '\0') {
266 rows = GetPrefRows();
267 cols = GetPrefCols();
268 newGeometry[0] = '\0';
269 } else {
270 bitmask = XParseGeometry(geometry, &x, &y, &cols, &rows);
271 if (bitmask == 0)
272 fprintf(stderr, "Bad window geometry specified: %s\n", geometry);
273 else {
274 if (!(bitmask & WidthValue))
275 cols = GetPrefCols();
276 if (!(bitmask & HeightValue))
277 rows = GetPrefRows();
279 CreateGeometryString(newGeometry, x, y, 0, 0,
280 bitmask & ~(WidthValue | HeightValue));
283 /* Create a new toplevel shell to hold the window */
284 ac = 0;
285 #ifdef SGI_CUSTOM
286 strcat(sgi_title, name);
287 XtSetArg(al[ac], XmNtitle, sgi_title); ac++;
288 XtSetArg(al[ac], XmNdeleteResponse, XmDO_NOTHING); ac++;
289 if (strncmp(name, "Untitled", 8) == 0) {
290 XtSetArg(al[ac], XmNiconName, APP_NAME); ac++;
291 } else {
292 XtSetArg(al[ac], XmNiconName, name); ac++;
294 #else
295 XtSetArg(al[ac], XmNtitle, name); ac++;
296 XtSetArg(al[ac], XmNdeleteResponse, XmDO_NOTHING); ac++;
297 XtSetArg(al[ac], XmNiconName, name); ac++;
298 #endif
299 XtSetArg(al[ac], XmNgeometry, newGeometry[0]=='\0'?NULL:newGeometry); ac++;
300 XtSetArg(al[ac], XmNinitialState,
301 iconic ? IconicState : NormalState); ac++;
302 appShell = CreateShellWithBestVis(APP_NAME, APP_CLASS,
303 applicationShellWidgetClass, TheDisplay, al, ac);
304 window->shell = appShell;
306 #ifdef EDITRES
307 XtAddEventHandler (appShell, (EventMask)0, True,
308 (XtEventHandler)_XEditResCheckMessages, NULL);
309 #endif /* EDITRES */
311 #ifndef SGI_CUSTOM
312 addWindowIcon(appShell);
313 #endif
315 /* Create a MainWindow to manage the menubar and text area, set the
316 userData resource to be used by WidgetToWindow to recover the
317 window pointer from the widget id of any of the window's widgets */
318 XtSetArg(al[ac], XmNuserData, window); ac++;
319 mainWin = XmCreateMainWindow(appShell, "main", al, ac);
320 XtManageChild(mainWin);
322 /* The statsAreaForm holds the stats line and the I-Search line. */
323 statsAreaForm = XtVaCreateWidget("statsAreaForm",
324 xmFormWidgetClass, mainWin,
325 XmNshadowThickness, STAT_SHADOW_THICKNESS,
326 XmNmarginWidth, STAT_SHADOW_THICKNESS,
327 XmNmarginHeight, STAT_SHADOW_THICKNESS,
328 /* XmNautoUnmanage, False, */
329 NULL);
330 if(window->showISearchLine || window->showStats)
331 XtManageChild(statsAreaForm);
333 /* NOTE: due to a bug in openmotif 2.1.30, NEdit used to crash when
334 the i-search bar was active, and the i-search text widget was focussed,
335 and the window's width was resized to nearly zero.
336 In theory, it is possible to avoid this by imposing a minimum
337 width constraint on the nedit windows, but that width would have to
338 be at least 30 characters, which is probably unacceptable.
339 Amazingly, adding a top offset of 1 pixel to the toggle buttons of
340 the i-search bar, while keeping the the top offset of the text widget
341 to 0 seems to avoid avoid the crash. */
343 window->iSearchForm = XtVaCreateWidget("iSearchForm",
344 xmFormWidgetClass, statsAreaForm,
345 XmNleftAttachment, XmATTACH_FORM,
346 XmNtopAttachment, XmATTACH_FORM,
347 XmNrightAttachment, XmATTACH_FORM,
348 XmNbottomAttachment, window->showStats ?
349 XmATTACH_NONE : XmATTACH_FORM,
350 NULL);
351 if(window->showISearchLine)
352 XtManageChild(window->iSearchForm);
353 iSearchLabel = XtVaCreateManagedWidget("iSearchLabel",
354 xmLabelWidgetClass, window->iSearchForm,
355 XmNlabelString, s1=XmStringCreateSimple("Find:"),
356 XmNmarginHeight, 0,
357 XmNleftAttachment, XmATTACH_FORM,
358 XmNleftOffset, 5,
359 XmNtopAttachment, XmATTACH_FORM,
360 XmNtopOffset, 1, /* see openmotif note above, for aligment
361 with toggle buttons below */
362 XmNbottomAttachment, XmATTACH_FORM, NULL);
363 XmStringFree(s1);
365 window->iSearchCaseToggle = XtVaCreateManagedWidget("iSearchCaseToggle",
366 xmToggleButtonWidgetClass, window->iSearchForm,
367 XmNlabelString, s1=XmStringCreateSimple("Case"),
368 XmNset, GetPrefSearch() == SEARCH_CASE_SENSE
369 || GetPrefSearch() == SEARCH_REGEX
370 || GetPrefSearch() == SEARCH_CASE_SENSE_WORD,
371 XmNtopAttachment, XmATTACH_FORM,
372 XmNbottomAttachment, XmATTACH_FORM,
373 XmNtopOffset, 1, /* see openmotif note above */
374 XmNrightAttachment, XmATTACH_FORM,
375 XmNmarginHeight, 0, NULL);
376 XmStringFree(s1);
378 XtAddCallback(window->iSearchCaseToggle, XmNvalueChangedCallback,
379 (XtCallbackProc)focusToISearchTextCB, window);
380 window->iSearchRegexToggle = XtVaCreateManagedWidget("iSearchREToggle",
381 xmToggleButtonWidgetClass, window->iSearchForm,
382 XmNlabelString, s1=XmStringCreateSimple("RegExp"),
383 XmNset, GetPrefSearch() == SEARCH_REGEX_NOCASE
384 || GetPrefSearch() == SEARCH_REGEX,
385 XmNtopAttachment, XmATTACH_FORM,
386 XmNbottomAttachment, XmATTACH_FORM,
387 XmNtopOffset, 1, /* see openmotif note above */
388 XmNrightAttachment, XmATTACH_WIDGET,
389 XmNrightWidget, window->iSearchCaseToggle,
390 XmNmarginHeight, 0, NULL);
391 XmStringFree(s1);
392 XtAddCallback(window->iSearchRegexToggle, XmNvalueChangedCallback,
393 (XtCallbackProc)focusToISearchTextCB, window);
395 window->iSearchRevToggle = XtVaCreateManagedWidget("iSearchRevToggle",
396 xmToggleButtonWidgetClass, window->iSearchForm,
397 XmNlabelString, s1=XmStringCreateSimple("Rev"),
398 XmNset, False,
399 XmNtopAttachment, XmATTACH_FORM,
400 XmNbottomAttachment, XmATTACH_FORM,
401 XmNtopOffset, 1, /* see openmotif note above */
402 XmNrightAttachment, XmATTACH_WIDGET,
403 XmNrightWidget, window->iSearchRegexToggle,
404 XmNmarginHeight, 0, NULL);
405 XmStringFree(s1);
406 XtAddCallback(window->iSearchRevToggle, XmNvalueChangedCallback,
407 (XtCallbackProc)focusToISearchTextCB, window);
409 window->iSearchText = XtVaCreateManagedWidget("iSearchText",
410 xmTextWidgetClass, window->iSearchForm,
411 XmNmarginHeight, 1,
412 XmNnavigationType, XmEXCLUSIVE_TAB_GROUP,
413 XmNleftAttachment, XmATTACH_WIDGET,
414 XmNleftWidget, iSearchLabel,
415 XmNrightAttachment, XmATTACH_WIDGET,
416 XmNrightWidget, window->iSearchRevToggle,
417 XmNrightOffset, 5,
418 XmNtopAttachment, XmATTACH_FORM,
419 XmNtopOffset, 0, /* see openmotif note above */
420 XmNbottomAttachment, XmATTACH_FORM,
421 XmNbottomOffset, 0, NULL);
422 RemapDeleteKey(window->iSearchText);
424 SetISearchTextCallbacks(window);
426 /* A form to hold the stats line text and line/col widgets */
427 window->statsLineForm = XtVaCreateWidget("statsLineForm",
428 xmFormWidgetClass, statsAreaForm,
429 XmNshadowThickness, 0,
430 XmNtopAttachment, window->showISearchLine ?
431 XmATTACH_WIDGET : XmATTACH_FORM,
432 XmNtopWidget, window->iSearchForm,
433 XmNrightAttachment, XmATTACH_FORM,
434 XmNleftAttachment, XmATTACH_FORM,
435 XmNbottomAttachment, XmATTACH_FORM,
436 NULL);
438 /* A separate display of the line/column number */
439 window->statsLineColNo = XtVaCreateManagedWidget("statsLineColNo",
440 xmLabelWidgetClass, window->statsLineForm,
441 XmNlabelString, s1=XmStringCreateSimple("L: --- C: ---"),
442 XmNshadowThickness, 0,
443 XmNmarginHeight, 0,
444 XmNmarginTop, 1, /* Help align with statsLine */
445 XmNtraversalOn, False,
446 XmNtopAttachment, XmATTACH_FORM,
447 XmNrightAttachment, XmATTACH_FORM,
448 NULL);
449 XmStringFree(s1);
451 /* Create file statistics display area. Using a text widget rather than
452 a label solves a layout problem with the main window, which messes up
453 if the label is too long (we would need a resize callback to control
454 the length when the window changed size), and allows users to select
455 file names and line numbers. Colors are copied from parent
456 widget, because many users and some system defaults color text
457 backgrounds differently from other widgets. */
459 XtVaGetValues(statsAreaForm, XmNbackground, &bgpix, NULL);
460 XtVaGetValues(statsAreaForm, XmNforeground, &fgpix, NULL);
461 stats = XtVaCreateManagedWidget("statsLine",
462 xmTextWidgetClass, window->statsLineForm,
463 XmNbackground, bgpix,
464 XmNforeground, fgpix,
465 XmNshadowThickness, 0,
466 XmNhighlightColor, bgpix,
467 XmNhighlightThickness, 1, /* Setting this to zero causes mysterious
468 problems with lesstif! */
469 XmNmarginHeight, 0,
470 XmNscrollHorizontal, False,
471 XmNeditMode, XmSINGLE_LINE_EDIT,
472 XmNeditable, False,
473 XmNtraversalOn, False,
474 XmNcursorPositionVisible, False,
475 XmNtopAttachment, XmATTACH_FORM,
476 XmNleftAttachment, XmATTACH_FORM,
477 XmNrightAttachment, XmATTACH_WIDGET,
478 XmNrightWidget, window->statsLineColNo,
479 XmNrightOffset, 3,
480 NULL);
481 window->statsLine = stats;
483 /* Manage the statsLineForm */
484 if(window->showStats)
485 XtManageChild(window->statsLineForm);
487 /* If the fontList was NULL, use the magical default provided by Motif,
488 since it must have worked if we've gotten this far */
489 if (window->fontList == NULL)
490 XtVaGetValues(stats, XmNfontList, &window->fontList, NULL);
492 /* Create the menu bar */
493 menuBar = CreateMenuBar(mainWin, window);
494 window->menuBar = menuBar;
495 XtManageChild(menuBar);
497 /* Create paned window to manage split window behavior */
498 pane = XtVaCreateManagedWidget("pane", xmPanedWindowWidgetClass, mainWin,
499 XmNmarginWidth, 0, XmNmarginHeight, 0, XmNseparatorOn, False,
500 XmNspacing, 3, XmNsashIndent, -2, NULL);
501 window->splitPane = pane;
502 XmMainWindowSetAreas(mainWin, menuBar, statsAreaForm, NULL, NULL, pane);
504 /* Patch around Motif's most idiotic "feature", that its menu accelerators
505 recognize Caps Lock and Num Lock as modifiers, and don't trigger if
506 they are engaged */
507 AccelLockBugPatch(pane, menuBar);
509 /* Create the first, and most permanent text area (other panes may
510 be added & removed, but this one will never be removed */
511 text = createTextArea(pane, window, rows,cols,
512 GetPrefEmTabDist(PLAIN_LANGUAGE_MODE), GetPrefDelimiters(),
513 GetPrefWrapMargin(), window->showLineNumbers?MIN_LINE_NUM_COLS:0);
514 XtManageChild(text);
515 window->textArea = text;
516 window->lastFocus = text;
518 /* Create the right button popup menu (note: order is important here,
519 since the translation for popping up this menu was probably already
520 added in createTextArea, but CreateBGMenu requires window->textArea
521 to be set so it can attach the menu to it (because menu shells are
522 finicky about the kinds of widgets they are attached to)) */
523 window->bgMenuPane = CreateBGMenu(window);
525 /* Create the text buffer rather than using the one created automatically
526 with the text area widget. This is done so the syntax highlighting
527 modify callback can be called to synchronize the style buffer BEFORE
528 the text display's callback is called upon to display a modification */
529 window->buffer = BufCreate();
530 BufAddModifyCB(window->buffer, SyntaxHighlightModifyCB, window);
532 /* Attach the buffer to the text widget, and add callbacks for modify */
533 TextSetBuffer(text, window->buffer);
534 BufAddModifyCB(window->buffer, modifiedCB, window);
536 /* Designate the permanent text area as the owner for selections */
537 HandleXSelections(text);
539 /* Set the requested hardware tab distance and useTabs in the text buffer */
540 BufSetTabDistance(window->buffer, GetPrefTabDist(PLAIN_LANGUAGE_MODE));
541 window->buffer->useTabs = GetPrefInsertTabs();
543 /* add the window to the global window list, update the Windows menus */
544 addToWindowList(window);
545 InvalidateWindowMenus();
547 /* realize all of the widgets in the new window */
548 RealizeWithoutForcingPosition(appShell);
549 XmProcessTraversal(text, XmTRAVERSE_CURRENT);
551 /* Make close command in window menu gracefully prompt for close */
552 AddMotifCloseCallback(appShell, (XtCallbackProc)closeCB, window);
554 #ifndef NO_SESSION_RESTART
555 /* Add wm protocol callback for making nedit restartable by session
556 managers. Doesn't yet handle multiple-desktops or iconifying right. */
557 if (syAtom == 0) {
558 wmpAtom = XmInternAtom(TheDisplay, "WM_PROTOCOLS", FALSE);
559 syAtom = XmInternAtom(TheDisplay, "WM_SAVE_YOURSELF", FALSE);
561 XmAddProtocolCallback(appShell, wmpAtom, syAtom,
562 (XtCallbackProc)saveYourselfCB, (XtPointer)window);
563 #endif
565 /* Make window resizing work in nice character heights */
566 UpdateWMSizeHints(window);
568 /* Set the minimum pane height for the initial text pane */
569 UpdateMinPaneHeights(window);
571 restoreInsaneVirtualKeyBindings(invalidBindings);
573 return window;
577 ** Close an editor window
579 void CloseWindow(WindowInfo *window)
581 int keepWindow;
582 char name[MAXPATHLEN];
584 /* Free smart indent macro programs */
585 EndSmartIndent(window);
587 /* Clean up macro references to the doomed window. If a macro is
588 executing, stop it. If macro is calling this (closing its own
589 window), leave the window alive until the macro completes */
590 keepWindow = !MacroWindowCloseActions(window);
592 #ifndef VMS
593 /* Kill shell sub-process and free related memory */
594 AbortShellCommand(window);
595 #endif /*VMS*/
597 /* Set lang. mode to PLAIN so that default calltips file will be unloaded
598 if necessary */
599 SetLanguageMode(window, PLAIN_LANGUAGE_MODE, True);
601 /* If a window is closed while it is on the multi-file replace dialog
602 list of any other window (or even the same one), we must update those
603 lists or we end up with dangling references. Normally, there can
604 be only one of those dialogs at the same time (application modal),
605 but LessTif doesn't even (always) honor application modalness, so
606 there can be more than one dialog. */
607 RemoveFromMultiReplaceDialog(window);
609 /* if this is the last window, or must be kept alive temporarily because
610 it's running the macro calling us, don't close it, make it Untitled */
611 if (keepWindow || (WindowList == window && window->next == NULL)) {
612 window->filename[0] = '\0';
613 UniqueUntitledName(name);
614 CLEAR_ALL_LOCKS(window->lockReasons);
615 window->fileMode = 0;
616 strcpy(window->filename, name);
617 strcpy(window->path, "");
618 window->ignoreModify = TRUE;
619 BufSetAll(window->buffer, "");
620 window->ignoreModify = FALSE;
621 window->filenameSet = FALSE;
622 window->fileChanged = FALSE;
623 window->fileFormat = UNIX_FILE_FORMAT;
624 window->lastModTime = 0;
625 StopHighlighting(window);
626 EndSmartIndent(window);
627 UpdateWindowTitle(window);
628 UpdateWindowReadOnly(window);
629 XtSetSensitive(window->closeItem, FALSE);
630 XtSetSensitive(window->readOnlyItem, TRUE);
631 XmToggleButtonSetState(window->readOnlyItem, FALSE, FALSE);
632 ClearUndoList(window);
633 ClearRedoList(window);
634 XmTextSetString(window->statsLine, ""); /* resets scroll pos of stats
635 line from long file names */
636 UpdateStatsLine(window);
637 DetermineLanguageMode(window, True);
638 return;
641 /* Free syntax highlighting patterns, if any. w/o redisplaying */
642 FreeHighlightingData(window);
644 /* remove the buffer modification callbacks so the buffer will be
645 deallocated when the last text widget is destroyed */
646 BufRemoveModifyCB(window->buffer, modifiedCB, window);
647 BufRemoveModifyCB(window->buffer, SyntaxHighlightModifyCB, window);
649 #ifdef ROWCOLPATCH
650 patchRowCol(window->menuBar);
651 #endif
653 /* free the undo and redo lists */
654 ClearUndoList(window);
655 ClearRedoList(window);
657 /* remove and deallocate all of the widgets associated with window */
658 XtDestroyWidget(window->shell);
660 /* remove the window from the global window list, update window menus */
661 removeFromWindowList(window);
662 InvalidateWindowMenus();
664 /* deallocate the window data structure */
665 XtFree((char *)window);
669 ** Check if there is already a window open for a given file
671 WindowInfo *FindWindowWithFile(const char *name, const char *path)
673 WindowInfo *w;
675 for (w=WindowList; w!=NULL; w=w->next) {
676 if (!strcmp(w->filename, name) && !strcmp(w->path, path)) {
677 return w;
680 return NULL;
684 ** Add another independently scrollable window pane to the current window,
685 ** splitting the pane which currently has keyboard focus.
687 void SplitWindow(WindowInfo *window)
689 short paneHeights[MAX_PANES+1];
690 int insertPositions[MAX_PANES+1], topLines[MAX_PANES+1];
691 int horizOffsets[MAX_PANES+1];
692 int i, focusPane, emTabDist, wrapMargin, lineNumCols, totalHeight=0;
693 char *delimiters;
694 Widget text;
696 /* Don't create new panes if we're already at the limit */
697 if (window->nPanes >= MAX_PANES)
698 return;
700 /* Record the current heights, scroll positions, and insert positions
701 of the existing panes, keyboard focus */
702 focusPane = 0;
703 for (i=0; i<=window->nPanes; i++) {
704 text = i==0 ? window->textArea : window->textPanes[i-1];
705 insertPositions[i] = TextGetCursorPos(text);
706 XtVaGetValues(XtParent(text), XmNheight, &paneHeights[i], NULL);
707 totalHeight += paneHeights[i];
708 TextGetScroll(text, &topLines[i], &horizOffsets[i]);
709 if (text == window->lastFocus)
710 focusPane = i;
713 /* Unmanage & remanage the panedWindow so it recalculates pane heights */
714 XtUnmanageChild(window->splitPane);
716 /* Create a text widget to add to the pane and set its buffer and
717 highlight data to be the same as the other panes in the window */
718 XtVaGetValues(window->textArea, textNemulateTabs, &emTabDist,
719 textNwordDelimiters, &delimiters, textNwrapMargin, &wrapMargin,
720 textNlineNumCols, &lineNumCols, NULL);
721 text = createTextArea(window->splitPane, window, 1, 1, emTabDist,
722 delimiters, wrapMargin, lineNumCols);
723 TextSetBuffer(text, window->buffer);
724 if (window->highlightData != NULL)
725 AttachHighlightToWidget(text, window);
726 XtManageChild(text);
727 window->textPanes[window->nPanes++] = text;
729 /* Set the minimum pane height in the new pane */
730 UpdateMinPaneHeights(window);
732 /* adjust the heights, scroll positions, etc., to split the focus pane */
733 for (i=window->nPanes; i>focusPane; i--) {
734 insertPositions[i] = insertPositions[i-1];
735 paneHeights[i] = paneHeights[i-1];
736 topLines[i] = topLines[i-1];
737 horizOffsets[i] = horizOffsets[i-1];
739 paneHeights[focusPane] = paneHeights[focusPane]/2;
740 paneHeights[focusPane+1] = paneHeights[focusPane];
742 for (i=0; i<=window->nPanes; i++) {
743 text = i==0 ? window->textArea : window->textPanes[i-1];
744 setPaneDesiredHeight(XtParent(text), paneHeights[i]);
747 /* Re-manage panedWindow to recalculate pane heights & reset selection */
748 XtManageChild(window->splitPane);
750 /* Reset all of the heights, scroll positions, etc. */
751 for (i=0; i<=window->nPanes; i++) {
752 text = i==0 ? window->textArea : window->textPanes[i-1];
753 TextSetCursorPos(text, insertPositions[i]);
754 TextSetScroll(text, topLines[i], horizOffsets[i]);
755 setPaneDesiredHeight(XtParent(text), totalHeight/(window->nPanes+1));
757 XmProcessTraversal(window->lastFocus, XmTRAVERSE_CURRENT);
759 /* Update the window manager size hints after the sizes of the panes have
760 been set (the widget heights are not yet readable here, but they will
761 be by the time the event loop gets around to running this timer proc) */
762 XtAppAddTimeOut(XtWidgetToApplicationContext(window->shell), 0,
763 wmSizeUpdateProc, window);
766 Widget GetPaneByIndex(WindowInfo *window, int paneIndex)
768 Widget text = NULL;
769 if (paneIndex >= 0 && paneIndex <= window->nPanes) {
770 text = (paneIndex == 0) ? window->textArea : window->textPanes[paneIndex - 1];
772 return(text);
775 int WidgetToPaneIndex(WindowInfo *window, Widget w)
777 int i;
778 Widget text;
779 int paneIndex = 0;
781 for (i = 0; i <= window->nPanes; ++i) {
782 text = (i == 0) ? window->textArea : window->textPanes[i - 1];
783 if (text == w) {
784 paneIndex = i;
787 return(paneIndex);
791 ** Close the window pane that last had the keyboard focus. (Actually, close
792 ** the bottom pane and make it look like pane which had focus was closed)
794 void ClosePane(WindowInfo *window)
796 short paneHeights[MAX_PANES+1];
797 int insertPositions[MAX_PANES+1], topLines[MAX_PANES+1];
798 int horizOffsets[MAX_PANES+1];
799 int i, focusPane,totalHeight=0;
800 Widget text;
802 /* Don't delete the last pane */
803 if (window->nPanes <= 0)
804 return;
806 /* Record the current heights, scroll positions, and insert positions
807 of the existing panes, and the keyboard focus */
808 focusPane = 0;
809 for (i=0; i<=window->nPanes; i++) {
810 text = i==0 ? window->textArea : window->textPanes[i-1];
811 insertPositions[i] = TextGetCursorPos(text);
812 XtVaGetValues(XtParent(text), XmNheight, &paneHeights[i], NULL);
813 totalHeight += paneHeights[i];
814 TextGetScroll(text, &topLines[i], &horizOffsets[i]);
815 if (text == window->lastFocus)
816 focusPane = i;
819 /* Unmanage & remanage the panedWindow so it recalculates pane heights */
820 XtUnmanageChild(window->splitPane);
822 /* Destroy last pane, and make sure lastFocus points to an existing pane */
823 XtDestroyWidget(XtParent(window->textPanes[--window->nPanes]));
824 if (window->nPanes == 0)
825 window->lastFocus = window->textArea;
826 else if (focusPane > window->nPanes)
827 window->lastFocus = window->textPanes[window->nPanes-1];
829 /* adjust the heights, scroll positions, etc., to make it look
830 like the pane with the input focus was closed */
831 for (i=window->nPanes; i>=focusPane; i--) {
832 insertPositions[i] = insertPositions[i+1];
833 paneHeights[i] = paneHeights[i+1];
834 topLines[i] = topLines[i+1];
835 horizOffsets[i] = horizOffsets[i+1];
838 /* set the desired heights and re-manage the paned window so it will
839 recalculate pane heights */
840 for (i=0; i<=window->nPanes; i++) {
841 text = i==0 ? window->textArea : window->textPanes[i-1];
842 setPaneDesiredHeight(XtParent(text), paneHeights[i]);
844 XtManageChild(window->splitPane);
846 /* Reset all of the scroll positions, insert positions, etc. */
847 for (i=0; i<=window->nPanes; i++) {
848 text = i==0 ? window->textArea : window->textPanes[i-1];
849 TextSetCursorPos(text, insertPositions[i]);
850 TextSetScroll(text, topLines[i], horizOffsets[i]);
851 setPaneDesiredHeight(XtParent(text), totalHeight/(window->nPanes+1));
853 XmProcessTraversal(window->lastFocus, XmTRAVERSE_CURRENT);
855 /* Update the window manager size hints after the sizes of the panes have
856 been set (the widget heights are not yet readable here, but they will
857 be by the time the event loop gets around to running this timer proc) */
858 XtAppAddTimeOut(XtWidgetToApplicationContext(window->shell), 0,
859 wmSizeUpdateProc, window);
863 ** Turn on and off the display of line numbers
865 void ShowLineNumbers(WindowInfo *window, int state)
867 Widget text;
868 int i, marginWidth;
869 Dimension windowWidth;
870 textDisp *textD = ((TextWidget)window->textArea)->text.textD;
872 if (window->showLineNumbers == state)
873 return;
874 window->showLineNumbers = state;
876 /* Just setting window->showLineNumbers is sufficient to tell
877 UpdateLineNumDisp to expand the line number areas and the window
878 size for the number of lines required. To hide the line number
879 display, set the width to zero, and contract the window width. */
880 if (state) {
881 UpdateLineNumDisp(window);
882 } else {
883 XtVaGetValues(window->shell, XmNwidth, &windowWidth, NULL);
884 XtVaGetValues(window->textArea, textNmarginWidth, &marginWidth, NULL);
885 XtVaSetValues(window->shell, XmNwidth,
886 windowWidth - textD->left + marginWidth, NULL);
887 for (i=0; i<=window->nPanes; i++) {
888 text = i==0 ? window->textArea : window->textPanes[i-1];
889 XtVaSetValues(text, textNlineNumCols, 0, NULL);
893 /* Tell WM that the non-expandable part of the window has changed size */
894 UpdateWMSizeHints(window);
897 void SetTabDist(WindowInfo *window, int tabDist)
899 if (window->buffer->tabDist != tabDist) {
900 int saveCursorPositions[MAX_PANES + 1];
901 int saveVScrollPositions[MAX_PANES + 1];
902 int saveHScrollPositions[MAX_PANES + 1];
903 int paneIndex;
905 window->ignoreModify = True;
907 for (paneIndex = 0; paneIndex <= window->nPanes; ++paneIndex) {
908 Widget w = GetPaneByIndex(window, paneIndex);
909 textDisp *textD = ((TextWidget)w)->text.textD;
911 TextGetScroll(w, &saveVScrollPositions[paneIndex], &saveHScrollPositions[paneIndex]);
912 saveCursorPositions[paneIndex] = TextGetCursorPos(w);
913 textD->modifyingTabDist = 1;
916 BufSetTabDistance(window->buffer, tabDist);
918 for (paneIndex = 0; paneIndex <= window->nPanes; ++paneIndex) {
919 Widget w = GetPaneByIndex(window, paneIndex);
920 textDisp *textD = ((TextWidget)w)->text.textD;
922 textD->modifyingTabDist = 0;
923 TextSetCursorPos(w, saveCursorPositions[paneIndex]);
924 TextSetScroll(w, saveVScrollPositions[paneIndex], saveHScrollPositions[paneIndex]);
927 window->ignoreModify = False;
931 void SetEmTabDist(WindowInfo *window, int emTabDist)
933 int i;
935 XtVaSetValues(window->textArea, textNemulateTabs, emTabDist, NULL);
936 for (i = 0; i < window->nPanes; ++i) {
937 XtVaSetValues(window->textPanes[i], textNemulateTabs, emTabDist, NULL);
942 ** Turn on and off the display of the statistics line
944 void ShowStatsLine(WindowInfo *window, int state)
946 Widget text;
947 int i;
949 /* In continuous wrap mode, text widgets must be told to keep track of
950 the top line number in absolute (non-wrapped) lines, because it can
951 be a costly calculation, and is only needed for displaying line
952 numbers, either in the stats line, or along the left margin */
953 for (i=0; i<=window->nPanes; i++) {
954 text = i==0 ? window->textArea : window->textPanes[i-1];
955 TextDMaintainAbsLineNum(((TextWidget)text)->text.textD, state);
957 window->showStats = state;
958 showStats(window, state);
962 ** Displays and undisplays the statistics line (regardless of settings of
963 ** window->showStats or window->modeMessageDisplayed)
965 static void showStats(WindowInfo *window, int state)
967 if (state) {
968 XtVaSetValues(window->iSearchForm,
969 XmNbottomAttachment, XmATTACH_NONE, NULL);
970 XtManageChild(window->statsLineForm);
971 showStatsForm(window, True);
972 } else {
973 XtUnmanageChild(window->statsLineForm);
974 XtVaSetValues(window->iSearchForm,
975 XmNbottomAttachment, XmATTACH_FORM, NULL);
976 showStatsForm(window, window->showISearchLine);
979 /* Tell WM that the non-expandable part of the window has changed size */
980 /* Already done in showStatsForm */
981 /* UpdateWMSizeHints(window); */
985 ** Turn on and off the continuing display of the incremental search line
986 ** (when off, it is popped up and down as needed via TempShowISearch)
988 void ShowISearchLine(WindowInfo *window, int state)
990 if (window->showISearchLine == state)
991 return;
992 window->showISearchLine = state;
993 showISearch(window, state);
997 ** Temporarily show and hide the incremental search line if the line is not
998 ** already up.
1000 void TempShowISearch(WindowInfo *window, int state)
1002 if (window->showISearchLine)
1003 return;
1004 if (XtIsManaged(window->iSearchForm) != state)
1005 showISearch(window, state);
1009 ** Put up or pop-down the incremental search line regardless of settings
1010 ** of showISearchLine or TempShowISearch
1012 static void showISearch(WindowInfo *window, int state)
1014 if (state) {
1015 XtManageChild(window->iSearchForm);
1016 XtVaSetValues(window->statsLineForm, XmNtopAttachment,
1017 XmATTACH_WIDGET, XmNtopWidget, window->iSearchForm, NULL);
1018 showStatsForm(window, True);
1019 } else {
1020 XtUnmanageChild(window->iSearchForm);
1021 XtVaSetValues(window->statsLineForm, XmNtopAttachment,
1022 XmATTACH_FORM, NULL);
1023 showStatsForm(window, window->showStats ||
1024 window->modeMessageDisplayed);
1027 /* Tell WM that the non-expandable part of the window has changed size */
1028 /* This is already done in showStatsForm */
1029 /* UpdateWMSizeHints(window); */
1033 ** Show or hide the extra display area under the main menu bar which
1034 ** optionally contains the status line and the incremental search bar
1036 static void showStatsForm(WindowInfo *window, int state)
1038 Widget statsAreaForm = XtParent(window->statsLineForm);
1039 Widget mainW = XtParent(statsAreaForm);
1041 /* The very silly use of XmNcommandWindowLocation and XmNshowSeparator
1042 below are to kick the main window widget to position and remove the
1043 status line when it is managed and unmanaged. At some Motif version
1044 level, the showSeparator trick backfires and leaves the separator
1045 shown, but fortunately the dynamic behavior is fixed, too so the
1046 workaround is no longer necessary, either. (... the version where
1047 this occurs may be earlier than 2.1. If the stats line shows
1048 double thickness shadows in earlier Motif versions, the #if XmVersion
1049 directive should be moved back to that earlier version) */
1050 if (state) {
1051 XtUnmanageChild(statsAreaForm); /*... will this fix Solaris 7??? */
1052 XtVaSetValues(mainW, XmNcommandWindowLocation,
1053 XmCOMMAND_ABOVE_WORKSPACE, NULL);
1054 #if XmVersion < 2001
1055 XtVaSetValues(mainW, XmNshowSeparator, True, NULL);
1056 #endif
1057 XtManageChild(statsAreaForm);
1058 XtVaSetValues(mainW, XmNshowSeparator, False, NULL);
1059 UpdateStatsLine(window);
1060 } else {
1061 XtUnmanageChild(statsAreaForm);
1062 XtVaSetValues(mainW, XmNcommandWindowLocation,
1063 XmCOMMAND_BELOW_WORKSPACE, NULL);
1066 /* Tell WM that the non-expandable part of the window has changed size */
1067 UpdateWMSizeHints(window);
1071 ** Display a special message in the stats line (show the stats line if it
1072 ** is not currently shown).
1074 void SetModeMessage(WindowInfo *window, char *message)
1076 window->modeMessageDisplayed = True;
1077 XmTextSetString(window->statsLine, message);
1078 showStats(window, True);
1082 ** Clear special statistics line message set in SetModeMessage, returns
1083 ** the statistics line to its original state as set in window->showStats
1085 void ClearModeMessage(WindowInfo *window)
1087 window->modeMessageDisplayed = False;
1088 showStats(window, window->showStats);
1089 UpdateStatsLine(window);
1093 ** Count the windows
1095 int NWindows(void)
1097 WindowInfo *win;
1098 int n;
1100 for (win=WindowList, n=0; win!=NULL; win=win->next, n++);
1101 return n;
1105 ** Set autoindent state to one of NO_AUTO_INDENT, AUTO_INDENT, or SMART_INDENT.
1107 void SetAutoIndent(WindowInfo *window, int state)
1109 int autoIndent = state == AUTO_INDENT, smartIndent = state == SMART_INDENT;
1110 int i;
1112 if (window->indentStyle == SMART_INDENT && !smartIndent)
1113 EndSmartIndent(window);
1114 else if (smartIndent && window->indentStyle != SMART_INDENT)
1115 BeginSmartIndent(window, True);
1116 window->indentStyle = state;
1117 XtVaSetValues(window->textArea, textNautoIndent, autoIndent,
1118 textNsmartIndent, smartIndent, NULL);
1119 for (i=0; i<window->nPanes; i++)
1120 XtVaSetValues(window->textPanes[i], textNautoIndent, autoIndent,
1121 textNsmartIndent, smartIndent, NULL);
1122 XmToggleButtonSetState(window->smartIndentItem, smartIndent, False);
1123 XmToggleButtonSetState(window->autoIndentItem, autoIndent, False);
1124 XmToggleButtonSetState(window->autoIndentOffItem, state == NO_AUTO_INDENT,
1125 False);
1129 ** Set showMatching state to one of NO_FLASH, FLASH_DELIMIT or FLASH_RANGE.
1130 ** Update the menu to reflect the change of state.
1132 void SetShowMatching(WindowInfo *window, int state)
1134 window->showMatchingStyle = state;
1135 XmToggleButtonSetState(window->showMatchingOffItem,
1136 state == NO_FLASH, False);
1137 XmToggleButtonSetState(window->showMatchingDelimitItem,
1138 state == FLASH_DELIMIT, False);
1139 XmToggleButtonSetState(window->showMatchingRangeItem,
1140 state == FLASH_RANGE, False);
1144 ** Set the fonts for "window" from a font name, and updates the display.
1145 ** Also updates window->fontList which is used for statistics line.
1147 ** Note that this leaks memory and server resources. In previous NEdit
1148 ** versions, fontLists were carefully tracked and freed, but X and Motif
1149 ** have some kind of timing problem when widgets are distroyed, such that
1150 ** fonts may not be freed immediately after widget destruction with 100%
1151 ** safety. Rather than kludge around this with timerProcs, I have chosen
1152 ** to create new fontLists only when the user explicitly changes the font
1153 ** (which shouldn't happen much in normal NEdit operation), and skip the
1154 ** futile effort of freeing them.
1156 void SetFonts(WindowInfo *window, const char *fontName, const char *italicName,
1157 const char *boldName, const char *boldItalicName)
1159 XFontStruct *font, *oldFont;
1160 int i, oldFontWidth, oldFontHeight, fontWidth, fontHeight;
1161 int borderWidth, borderHeight, marginWidth, marginHeight;
1162 int primaryChanged, highlightChanged = False;
1163 Dimension oldWindowWidth, oldWindowHeight, oldTextWidth, oldTextHeight;
1164 Dimension textHeight, newWindowWidth, newWindowHeight;
1165 textDisp *textD = ((TextWidget)window->textArea)->text.textD;
1167 /* Check which fonts have changed */
1168 primaryChanged = strcmp(fontName, window->fontName);
1169 if (strcmp(italicName, window->italicFontName)) highlightChanged = True;
1170 if (strcmp(boldName, window->boldFontName)) highlightChanged = True;
1171 if (strcmp(boldItalicName, window->boldItalicFontName))
1172 highlightChanged = True;
1173 if (!primaryChanged && !highlightChanged)
1174 return;
1176 /* Get information about the current window sizing, to be used to
1177 determine the correct window size after the font is changed */
1178 XtVaGetValues(window->shell, XmNwidth, &oldWindowWidth, XmNheight,
1179 &oldWindowHeight, NULL);
1180 XtVaGetValues(window->textArea, XmNheight, &textHeight,
1181 textNmarginHeight, &marginHeight, textNmarginWidth,
1182 &marginWidth, textNfont, &oldFont, NULL);
1183 oldTextWidth = textD->width + textD->lineNumWidth;
1184 oldTextHeight = textHeight - 2*marginHeight;
1185 for (i=0; i<window->nPanes; i++) {
1186 XtVaGetValues(window->textPanes[i], XmNheight, &textHeight, NULL);
1187 oldTextHeight += textHeight - 2*marginHeight;
1189 borderWidth = oldWindowWidth - oldTextWidth;
1190 borderHeight = oldWindowHeight - oldTextHeight;
1191 oldFontWidth = oldFont->max_bounds.width;
1192 oldFontHeight = textD->ascent + textD->descent;
1195 /* Change the fonts in the window data structure. If the primary font
1196 didn't work, use Motif's fallback mechanism by stealing it from the
1197 statistics line. Highlight fonts are allowed to be NULL, which
1198 is interpreted as "use the primary font" */
1199 if (primaryChanged) {
1200 strcpy(window->fontName, fontName);
1201 font = XLoadQueryFont(TheDisplay, fontName);
1202 if (font == NULL)
1203 XtVaGetValues(window->statsLine, XmNfontList, &window->fontList,
1204 NULL);
1205 else
1206 window->fontList = XmFontListCreate(font, XmSTRING_DEFAULT_CHARSET);
1208 if (highlightChanged) {
1209 strcpy(window->italicFontName, italicName);
1210 window->italicFontStruct = XLoadQueryFont(TheDisplay, italicName);
1211 strcpy(window->boldFontName, boldName);
1212 window->boldFontStruct = XLoadQueryFont(TheDisplay, boldName);
1213 strcpy(window->boldItalicFontName, boldItalicName);
1214 window->boldItalicFontStruct = XLoadQueryFont(TheDisplay, boldItalicName);
1217 /* Change the primary font in all the widgets */
1218 if (primaryChanged) {
1219 font = GetDefaultFontStruct(window->fontList);
1220 XtVaSetValues(window->textArea, textNfont, font, NULL);
1221 for (i=0; i<window->nPanes; i++)
1222 XtVaSetValues(window->textPanes[i], textNfont, font, NULL);
1225 /* Change the highlight fonts, even if they didn't change, because
1226 primary font is read through the style table for syntax highlighting */
1227 if (window->highlightData != NULL)
1228 UpdateHighlightStyles(window);
1230 /* Change the window manager size hints.
1231 Note: this has to be done _before_ we set the new sizes. ICCCM2
1232 compliant window managers (such as fvwm2) would otherwise resize
1233 the window twice: once because of the new sizes requested, and once
1234 because of the new size increments, resulting in an overshoot. */
1235 UpdateWMSizeHints(window);
1237 /* Use the information from the old window to re-size the window to a
1238 size appropriate for the new font */
1239 fontWidth = GetDefaultFontStruct(window->fontList)->max_bounds.width;
1240 fontHeight = textD->ascent + textD->descent;
1241 newWindowWidth = (oldTextWidth*fontWidth) / oldFontWidth + borderWidth;
1242 newWindowHeight = (oldTextHeight*fontHeight) / oldFontHeight + borderHeight;
1243 XtVaSetValues(window->shell, XmNwidth, newWindowWidth, XmNheight,
1244 newWindowHeight, NULL);
1246 /* Change the minimum pane height */
1247 UpdateMinPaneHeights(window);
1251 ** Set insert/overstrike mode
1253 void SetOverstrike(WindowInfo *window, int overstrike)
1255 int i;
1257 XtVaSetValues(window->textArea, textNoverstrike, overstrike, NULL);
1258 for (i=0; i<window->nPanes; i++)
1259 XtVaSetValues(window->textPanes[i], textNoverstrike, overstrike, NULL);
1260 window->overstrike = overstrike;
1264 ** Select auto-wrap mode, one of NO_WRAP, NEWLINE_WRAP, or CONTINUOUS_WRAP
1266 void SetAutoWrap(WindowInfo *window, int state)
1268 int i;
1269 int autoWrap = state == NEWLINE_WRAP, contWrap = state == CONTINUOUS_WRAP;
1271 XtVaSetValues(window->textArea, textNautoWrap, autoWrap,
1272 textNcontinuousWrap, contWrap, NULL);
1273 for (i=0; i<window->nPanes; i++)
1274 XtVaSetValues(window->textPanes[i], textNautoWrap, autoWrap,
1275 textNcontinuousWrap, contWrap, NULL);
1276 window->wrapMode = state;
1278 XmToggleButtonSetState(window->newlineWrapItem, autoWrap, False);
1279 XmToggleButtonSetState(window->continuousWrapItem, contWrap, False);
1280 XmToggleButtonSetState(window->noWrapItem, state == NO_WRAP, False);
1284 ** Set the wrap margin (0 == wrap at right edge of window)
1286 void SetWrapMargin(WindowInfo *window, int margin)
1288 int i;
1290 XtVaSetValues(window->textArea, textNwrapMargin, margin, NULL);
1291 for (i=0; i<window->nPanes; i++)
1292 XtVaSetValues(window->textPanes[i], textNwrapMargin, margin, NULL);
1296 ** Recover the window pointer from any widget in the window, by searching
1297 ** up the widget hierarcy for the top level container widget where the window
1298 ** pointer is stored in the userData field.
1300 WindowInfo *WidgetToWindow(Widget w)
1302 WindowInfo *window;
1303 Widget parent;
1305 while (True) {
1306 parent = XtParent(w);
1307 if (parent == NULL)
1308 return NULL;
1309 if (XtClass(parent) == applicationShellWidgetClass)
1310 break;
1311 w = parent;
1313 XtVaGetValues(w, XmNuserData, &window, NULL);
1314 return window;
1318 ** Change the window appearance and the window data structure to show
1319 ** that the file it contains has been modified
1321 void SetWindowModified(WindowInfo *window, int modified)
1323 if (window->fileChanged == FALSE && modified == TRUE) {
1324 XtSetSensitive(window->closeItem, TRUE);
1325 window->fileChanged = TRUE;
1326 UpdateWindowTitle(window);
1327 } else if (window->fileChanged == TRUE && modified == FALSE) {
1328 window->fileChanged = FALSE;
1329 UpdateWindowTitle(window);
1334 ** Update the window title to reflect the filename, read-only, and modified
1335 ** status of the window data structure
1337 void UpdateWindowTitle(const WindowInfo *window)
1339 char *title = FormatWindowTitle(window->filename,
1340 window->path,
1341 #ifdef VMS
1342 NULL,
1343 #else
1344 GetClearCaseViewTag(),
1345 #endif /* VMS */
1346 GetPrefServerName(),
1347 IsServer,
1348 window->filenameSet,
1349 window->lockReasons,
1350 window->fileChanged,
1351 GetPrefTitleFormat());
1353 char *iconTitle = XtMalloc(strlen(window->filename) + 2); /* strlen("*")+1 */
1355 strcpy(iconTitle, window->filename);
1356 if (window->fileChanged)
1357 strcat(iconTitle, "*");
1358 XtVaSetValues(window->shell, XmNtitle, title, XmNiconName, iconTitle, NULL);
1360 /* If there's a find or replace dialog up in "Keep Up" mode, with a
1361 file name in the title, update it too */
1362 if (window->findDlog && XmToggleButtonGetState(window->findKeepBtn)) {
1363 sprintf(title, "Find (in %s)", window->filename);
1364 XtVaSetValues(XtParent(window->findDlog), XmNtitle, title, NULL);
1366 if (window->replaceDlog && XmToggleButtonGetState(window->replaceKeepBtn)) {
1367 sprintf(title, "Replace (in %s)", window->filename);
1368 XtVaSetValues(XtParent(window->replaceDlog), XmNtitle, title, NULL);
1370 XtFree(iconTitle);
1372 /* Update the Windows menus with the new name */
1373 InvalidateWindowMenus();
1377 ** Update the read-only state of the text area(s) in the window, and
1378 ** the ReadOnly toggle button in the File menu to agree with the state in
1379 ** the window data structure.
1381 void UpdateWindowReadOnly(WindowInfo *window)
1383 int i, state;
1385 state = IS_ANY_LOCKED(window->lockReasons);
1386 XtVaSetValues(window->textArea, textNreadOnly, state, NULL);
1387 for (i=0; i<window->nPanes; i++)
1388 XtVaSetValues(window->textPanes[i], textNreadOnly, state, NULL);
1389 XmToggleButtonSetState(window->readOnlyItem, state, FALSE);
1390 XtSetSensitive(window->readOnlyItem,
1391 !IS_ANY_LOCKED_IGNORING_USER(window->lockReasons));
1395 ** Get the start and end of the current selection. This routine is obsolete
1396 ** because it ignores rectangular selections, and reads from the widget
1397 ** instead of the buffer. Use BufGetSelectionPos.
1399 int GetSelection(Widget widget, int *left, int *right)
1401 return GetSimpleSelection(TextGetBuffer(widget), left, right);
1405 ** Find the start and end of a single line selection. Hides rectangular
1406 ** selection issues for older routines which use selections that won't
1407 ** span lines.
1409 int GetSimpleSelection(textBuffer *buf, int *left, int *right)
1411 int selStart, selEnd, isRect, rectStart, rectEnd, lineStart;
1413 /* get the character to match and its position from the selection, or
1414 the character before the insert point if nothing is selected.
1415 Give up if too many characters are selected */
1416 if (!BufGetSelectionPos(buf, &selStart, &selEnd, &isRect,
1417 &rectStart, &rectEnd))
1418 return False;
1419 if (isRect) {
1420 lineStart = BufStartOfLine(buf, selStart);
1421 selStart = BufCountForwardDispChars(buf, lineStart, rectStart);
1422 selEnd = BufCountForwardDispChars(buf, lineStart, rectEnd);
1424 *left = selStart;
1425 *right = selEnd;
1426 return True;
1430 ** Returns a range of text from a text widget (this routine is obsolete,
1431 ** get text from the buffer instead). Memory is allocated with
1432 ** XtMalloc and caller should free it.
1434 char *GetTextRange(Widget widget, int left, int right)
1436 return BufGetRange(TextGetBuffer(widget), left, right);
1440 ** If the selection (or cursor position if there's no selection) is not
1441 ** fully shown, scroll to bring it in to view. Note that as written,
1442 ** this won't work well with multi-line selections. Modest re-write
1443 ** of the horizontal scrolling part would be quite easy to make it work
1444 ** well with rectangular selections.
1446 void MakeSelectionVisible(WindowInfo *window, Widget textPane)
1448 int left, right, isRect, rectStart, rectEnd, horizOffset;
1449 int scrollOffset, leftX, rightX, y, rows, margin;
1450 int topLineNum, lastLineNum, rightLineNum, leftLineNum, linesToScroll;
1451 textBuffer *buf = window->buffer;
1452 int topChar = TextFirstVisiblePos(textPane);
1453 int lastChar = TextLastVisiblePos(textPane);
1454 Dimension width;
1456 /* find out where the selection is */
1457 if (!BufGetSelectionPos(window->buffer, &left, &right, &isRect,
1458 &rectStart, &rectEnd)) {
1459 left = right = TextGetCursorPos(textPane);
1460 isRect = False;
1463 /* Check vertical positioning unless the selection is already shown or
1464 already covers the display. If the end of the selection is below
1465 bottom, scroll it in to view until the end selection is scrollOffset
1466 lines from the bottom of the display or the start of the selection
1467 scrollOffset lines from the top. Calculate a pleasing distance from the
1468 top or bottom of the window, to scroll the selection to (if scrolling is
1469 necessary), around 1/3 of the height of the window */
1470 if (!((left >= topChar && right <= lastChar) ||
1471 (left < topChar && right > lastChar))) {
1472 XtVaGetValues(textPane, textNrows, &rows, NULL);
1473 scrollOffset = rows/3;
1474 TextGetScroll(textPane, &topLineNum, &horizOffset);
1475 lastLineNum = topLineNum + rows;
1476 if (right > lastChar) {
1477 if (left <= topChar)
1478 return;
1479 leftLineNum = topLineNum + BufCountLines(buf, topChar, left);
1480 if (leftLineNum < topLineNum + scrollOffset)
1481 return;
1482 linesToScroll = BufCountLines(buf, lastChar, right) + scrollOffset;
1483 if (leftLineNum - linesToScroll < topLineNum + scrollOffset)
1484 linesToScroll = leftLineNum - (topLineNum + scrollOffset);
1485 TextSetScroll(textPane, topLineNum+linesToScroll, horizOffset);
1486 } else if (left < topChar) {
1487 if (right >= lastChar)
1488 return;
1489 rightLineNum = lastLineNum - BufCountLines(buf, right, lastChar);
1490 if (rightLineNum > lastLineNum - scrollOffset)
1491 return;
1492 linesToScroll = BufCountLines(buf, left, topChar) + scrollOffset;
1493 if (rightLineNum + linesToScroll > lastLineNum - scrollOffset)
1494 linesToScroll = (lastLineNum - scrollOffset) - rightLineNum;
1495 TextSetScroll(textPane, topLineNum-linesToScroll, horizOffset);
1499 /* If either end of the selection off screen horizontally, try to bring it
1500 in view, by making sure both end-points are visible. Using only end
1501 points of a multi-line selection is not a great idea, and disaster for
1502 rectangular selections, so this part of the routine should be re-written
1503 if it is to be used much with either. Note also that this is a second
1504 scrolling operation, causing the display to jump twice. It's done after
1505 vertical scrolling to take advantage of TextPosToXY which requires it's
1506 reqested position to be vertically on screen) */
1507 if ( TextPosToXY(textPane, left, &leftX, &y) &&
1508 TextPosToXY(textPane, right, &rightX, &y) && leftX <= rightX) {
1509 textDisp *textD = ((TextWidget)textPane)->text.textD;
1510 TextGetScroll(textPane, &topLineNum, &horizOffset);
1511 XtVaGetValues(textPane, XmNwidth, &width, textNmarginWidth, &margin,
1512 NULL);
1513 if (leftX < margin + textD->lineNumLeft + textD->lineNumWidth)
1514 horizOffset -= margin + textD->lineNumLeft + textD->lineNumWidth - leftX;
1515 else if (rightX > width - margin)
1516 horizOffset += rightX - (width - margin);
1517 TextSetScroll(textPane, topLineNum, horizOffset);
1520 /* make sure that the statistics line is up to date */
1521 UpdateStatsLine(window);
1524 static Widget createTextArea(Widget parent, WindowInfo *window, int rows,
1525 int cols, int emTabDist, char *delimiters, int wrapMargin,
1526 int lineNumCols)
1528 Widget text, sw, hScrollBar, vScrollBar;
1529 Pixel troughColor;
1531 /* Create a text widget inside of a scrolled window widget */
1532 sw = XtVaCreateManagedWidget("scrolledW", xmScrolledWindowWidgetClass,
1533 parent, XmNspacing, 0, XmNpaneMaximum, SHRT_MAX,
1534 XmNpaneMinimum, PANE_MIN_HEIGHT, XmNhighlightThickness, 0, NULL);
1535 hScrollBar = XtVaCreateManagedWidget("textHorScrollBar",
1536 xmScrollBarWidgetClass, sw, XmNorientation, XmHORIZONTAL,
1537 XmNrepeatDelay, 10, NULL);
1538 vScrollBar = XtVaCreateManagedWidget("textVertScrollBar",
1539 xmScrollBarWidgetClass, sw, XmNorientation, XmVERTICAL,
1540 XmNrepeatDelay, 10, NULL);
1541 text = XtVaCreateManagedWidget("text", textWidgetClass, sw,
1542 textNrows, rows, textNcolumns, cols,
1543 textNlineNumCols, lineNumCols,
1544 textNemulateTabs, emTabDist,
1545 textNfont, GetDefaultFontStruct(window->fontList),
1546 textNhScrollBar, hScrollBar, textNvScrollBar, vScrollBar,
1547 textNreadOnly, IS_ANY_LOCKED(window->lockReasons),
1548 textNwordDelimiters, delimiters,
1549 textNwrapMargin, wrapMargin,
1550 textNautoIndent, window->indentStyle == AUTO_INDENT,
1551 textNsmartIndent, window->indentStyle == SMART_INDENT,
1552 textNautoWrap, window->wrapMode == NEWLINE_WRAP,
1553 textNcontinuousWrap, window->wrapMode == CONTINUOUS_WRAP,
1554 textNoverstrike, window->overstrike, NULL);
1555 XtVaSetValues(sw, XmNworkWindow, text, XmNhorizontalScrollBar, hScrollBar,
1556 XmNverticalScrollBar, vScrollBar, NULL);
1558 /* add focus, drag, cursor tracking, and smart indent callbacks */
1559 XtAddCallback(text, textNfocusCallback, (XtCallbackProc)focusCB, window);
1560 XtAddCallback(text, textNcursorMovementCallback, (XtCallbackProc)movedCB,
1561 window);
1562 XtAddCallback(text, textNdragStartCallback, (XtCallbackProc)dragStartCB,
1563 window);
1564 XtAddCallback(text, textNdragEndCallback, (XtCallbackProc)dragEndCB,
1565 window);
1566 XtAddCallback(text, textNsmartIndentCallback, SmartIndentCB, window);
1568 /* This makes sure the text area initially has a the insert point shown
1569 ... (check if still true with the nedit text widget, probably not) */
1570 XmAddTabGroup(XtParent(text));
1572 /* Set the little square in the corner between the scroll
1573 bars to be the same color as the scroll bar interiors */
1574 XtVaGetValues(vScrollBar, XmNtroughColor, &troughColor, NULL);
1575 XtVaSetValues(sw, XmNbackground, troughColor, NULL);
1577 /* compensate for Motif delete/backspace problem */
1578 RemapDeleteKey(text);
1580 /* Augment translation table for right button popup menu */
1581 AddBGMenuAction(text);
1583 /* If absolute line numbers will be needed for display in the statistics
1584 line, tell the widget to maintain them (otherwise, it's a costly
1585 operation and performance will be better without it) */
1586 TextDMaintainAbsLineNum(((TextWidget)text)->text.textD, window->showStats);
1588 return text;
1591 static void movedCB(Widget w, WindowInfo *window, XtPointer callData)
1593 if (window->ignoreModify)
1594 return;
1596 /* update line and column nubers in statistics line */
1597 UpdateStatsLine(window);
1599 /* Check the character before the cursor for matchable characters */
1600 FlashMatching(window, w);
1602 /* Check for changes to read-only status and/or file modifications */
1603 CheckForChangesToFile(window);
1606 static void modifiedCB(int pos, int nInserted, int nDeleted, int nRestyled,
1607 char *deletedText, void *cbArg)
1609 WindowInfo *window = (WindowInfo *)cbArg;
1610 int selected = window->buffer->primary.selected;
1612 /* update the table of bookmarks */
1613 UpdateMarkTable(window, pos, nInserted, nDeleted);
1615 /* Check and dim/undim selection related menu items */
1616 if ((window->wasSelected && !selected) ||
1617 (!window->wasSelected && selected)) {
1618 window->wasSelected = selected;
1619 XtSetSensitive(window->printSelItem, selected);
1620 XtSetSensitive(window->cutItem, selected);
1621 XtSetSensitive(window->copyItem, selected);
1622 XtSetSensitive(window->delItem, selected);
1623 /* Note we don't change the selection for items like
1624 "Open Selected" and "Find Selected". That's because
1625 it works on selections in external applications.
1626 Desensitizing it if there's no NEdit selection
1627 disables this feature. */
1628 #ifndef VMS
1629 XtSetSensitive(window->filterItem, selected);
1630 #endif
1632 DimSelectionDepUserMenuItems(window, selected);
1633 if (window->replaceDlog != NULL)
1635 UpdateReplaceActionButtons(window);
1639 /* Make sure line number display is sufficient for new data */
1640 UpdateLineNumDisp(window);
1642 /* When the program needs to make a change to a text area without without
1643 recording it for undo or marking file as changed it sets ignoreModify */
1644 if (window->ignoreModify || (nDeleted == 0 && nInserted == 0))
1645 return;
1647 /* Save information for undoing this operation (this call also counts
1648 characters and editing operations for triggering autosave */
1649 SaveUndoInformation(window, pos, nInserted, nDeleted, deletedText);
1651 /* Trigger automatic backup if operation or character limits reached */
1652 if (window->autoSave &&
1653 (window->autoSaveCharCount > AUTOSAVE_CHAR_LIMIT ||
1654 window->autoSaveOpCount > AUTOSAVE_OP_LIMIT)) {
1655 WriteBackupFile(window);
1656 window->autoSaveCharCount = 0;
1657 window->autoSaveOpCount = 0;
1660 /* Indicate that the window has now been modified */
1661 SetWindowModified(window, TRUE);
1663 /* Update # of bytes, and line and col statistics */
1664 UpdateStatsLine(window);
1666 /* Check if external changes have been made to file and warn user */
1667 CheckForChangesToFile(window);
1670 static void focusCB(Widget w, WindowInfo *window, XtPointer callData)
1672 /* record which window pane last had the keyboard focus */
1673 window->lastFocus = w;
1675 /* update line number statistic to reflect current focus pane */
1676 UpdateStatsLine(window);
1678 /* finish off the current incremental search */
1679 EndISearch(window);
1681 /* Check for changes to read-only status and/or file modifications */
1682 CheckForChangesToFile(window);
1685 static void dragStartCB(Widget w, WindowInfo *window, XtPointer callData)
1687 /* don't record all of the intermediate drag steps for undo */
1688 window->ignoreModify = True;
1691 static void dragEndCB(Widget w, WindowInfo *window, dragEndCBStruct *callData)
1693 /* restore recording of undo information */
1694 window->ignoreModify = False;
1696 /* Do nothing if drag operation was canceled */
1697 if (callData->nCharsInserted == 0)
1698 return;
1700 /* Save information for undoing this operation not saved while
1701 undo recording was off */
1702 modifiedCB(callData->startPos, callData->nCharsInserted,
1703 callData->nCharsDeleted, 0, callData->deletedText, window);
1706 static void closeCB(Widget w, WindowInfo *window, XtPointer callData)
1708 if (WindowList->next == NULL) {
1709 if (!CheckPrefsChangesSaved(window->shell))
1710 return;
1711 if (!WindowList->fileChanged)
1712 exit(EXIT_SUCCESS);
1713 if (CloseFileAndWindow(window, PROMPT_SBC_DIALOG_RESPONSE))
1714 exit(EXIT_SUCCESS);
1715 } else
1716 CloseFileAndWindow(window, PROMPT_SBC_DIALOG_RESPONSE);
1719 static void saveYourselfCB(Widget w, WindowInfo *window, XtPointer callData)
1721 WindowInfo *win, **revWindowList;
1722 char geometry[MAX_GEOM_STRING_LEN];
1723 int argc = 0, maxArgc, nWindows, i;
1724 char **argv;
1725 int wasIconic = False;
1727 /* Only post a restart command on the first window in the window list so
1728 session manager can restart the whole set of windows in one executable,
1729 rather than one nedit per file. Even if the restart command is not on
1730 this window, the protocol demands that we set the window's WM_COMMAND
1731 property in response to the "save yourself" message */
1732 if (window != WindowList) {
1733 XSetCommand(TheDisplay, XtWindow(window->shell), NULL, 0);
1734 return;
1737 /* Allocate memory for an argument list and for a reversed list of
1738 windows. The window list is reversed for IRIX 4DWM and any other
1739 window/session manager combination which uses window creation
1740 order for re-associating stored geometry information with
1741 new windows created by a restored application */
1742 maxArgc = 1;
1743 nWindows = 0;
1744 for (win=WindowList; win!=NULL; win=win->next) {
1745 maxArgc += 4;
1746 nWindows++;
1748 argv = (char **)XtMalloc(maxArgc*sizeof(char *));
1749 revWindowList = (WindowInfo **)XtMalloc(sizeof(WindowInfo *)*nWindows);
1750 for (win=WindowList, i=nWindows-1; win!=NULL; win=win->next, i--)
1751 revWindowList[i] = win;
1753 /* Create command line arguments for restoring each window in the list */
1754 argv[argc++] = XtNewString(ArgV0);
1755 if (IsServer) {
1756 argv[argc++] = XtNewString("-server");
1757 if (GetPrefServerName()[0] != '\0') {
1758 argv[argc++] = XtNewString("-svrname");
1759 argv[argc++] = XtNewString(GetPrefServerName());
1762 for (i=0; i<nWindows; i++) {
1763 win = revWindowList[i];
1764 getGeometryString(win, geometry);
1765 argv[argc++] = XtNewString("-geometry");
1766 argv[argc++] = XtNewString(geometry);
1767 if (isIconic(win)) {
1768 argv[argc++] = XtNewString("-iconic");
1769 wasIconic = True;
1770 } else if (wasIconic) {
1771 argv[argc++] = XtNewString("-noiconic");
1772 wasIconic = False;
1774 if (win->filenameSet) {
1775 argv[argc] = XtMalloc(strlen(win->path) +
1776 strlen(win->filename) + 1);
1777 sprintf(argv[argc++], "%s%s", win->path, win->filename);
1780 XtFree((char *)revWindowList);
1782 /* Set the window's WM_COMMAND property to the created command line */
1783 XSetCommand(TheDisplay, XtWindow(window->shell), argv, argc);
1784 for (i=0; i<argc; i++)
1785 XtFree(argv[i]);
1786 XtFree((char *)argv);
1790 ** Return the focus to the incremental search text field. This reduces the
1791 ** number of mouse presses the user needs to do to use the toggle buttons in
1792 ** the incremental search bar. Admittedly, if the user traverses through with
1793 ** the intent of setting both direction and search type via the keyboard, it
1794 ** actually increases the number of keystrokes, but on the whole it appears to
1795 ** be a big winner.
1797 static void focusToISearchTextCB(Widget w, WindowInfo *window,
1798 XtPointer callData)
1800 XmProcessTraversal(window->iSearchText, XmTRAVERSE_CURRENT);
1804 ** Returns true if window is iconic (as determined by the WM_STATE property
1805 ** on the shell window. I think this is the most reliable way to tell,
1806 ** but if someone has a better idea please send me a note).
1808 static int isIconic(WindowInfo *window)
1810 unsigned long *property = NULL;
1811 unsigned long nItems;
1812 unsigned long leftover;
1813 static Atom wmStateAtom = 0;
1814 Atom actualType;
1815 int actualFormat;
1816 int result;
1818 if (wmStateAtom == 0)
1819 wmStateAtom = XInternAtom (XtDisplay(window->shell), "WM_STATE", False);
1820 if (XGetWindowProperty(XtDisplay(window->shell), XtWindow(window->shell),
1821 wmStateAtom, 0L, 1L, False, wmStateAtom, &actualType, &actualFormat,
1822 &nItems, &leftover, (unsigned char **)&property) != Success ||
1823 nItems != 1 || property == NULL)
1824 return FALSE;
1825 result = *property == IconicState;
1826 XtFree((char *)property);
1827 return result;
1831 ** Add a window to the the window list.
1833 static void addToWindowList(WindowInfo *window)
1835 WindowInfo *temp;
1837 temp = WindowList;
1838 WindowList = window;
1839 window->next = temp;
1843 ** Remove a window from the list of windows
1845 static void removeFromWindowList(WindowInfo *window)
1847 WindowInfo *temp;
1849 if (WindowList == window)
1850 WindowList = window->next;
1851 else {
1852 for (temp = WindowList; temp != NULL; temp = temp->next) {
1853 if (temp->next == window) {
1854 temp->next = window->next;
1855 break;
1862 ** If necessary, enlarges the window and line number display area
1863 ** to make room for numbers.
1865 void UpdateLineNumDisp(WindowInfo *window)
1867 Dimension windowWidth;
1868 int i, fontWidth, reqCols, lineNumCols;
1869 int oldWidth, newWidth, marginWidth;
1870 Widget text;
1871 textDisp *textD = ((TextWidget)window->textArea)->text.textD;
1873 if (!window->showLineNumbers)
1874 return;
1876 /* Decide how wide the line number field has to be to display all
1877 possible line numbers */
1878 reqCols = textD->nBufferLines<1 ? 1 : log10((double)textD->nBufferLines)+1;
1879 if (reqCols < MIN_LINE_NUM_COLS)
1880 reqCols = MIN_LINE_NUM_COLS;
1882 /* Is the width of the line number area sufficient to display all the
1883 line numbers in the file? If not, expand line number field, and the
1884 window width */
1885 XtVaGetValues(window->textArea, textNlineNumCols, &lineNumCols,
1886 textNmarginWidth, &marginWidth, NULL);
1887 if (lineNumCols < reqCols) {
1888 fontWidth = textD->fontStruct->max_bounds.width;
1889 oldWidth = textD->left - marginWidth;
1890 newWidth = reqCols * fontWidth + marginWidth;
1891 XtVaGetValues(window->shell, XmNwidth, &windowWidth, NULL);
1892 XtVaSetValues(window->shell, XmNwidth,
1893 windowWidth + newWidth-oldWidth, NULL);
1894 UpdateWMSizeHints(window);
1895 for (i=0; i<=window->nPanes; i++) {
1896 text = i==0 ? window->textArea : window->textPanes[i-1];
1897 XtVaSetValues(text, textNlineNumCols, reqCols, NULL);
1903 ** Update the optional statistics line.
1905 void UpdateStatsLine(WindowInfo *window)
1907 int line, pos, colNum;
1908 char *string, *format, slinecol[32];
1909 Widget statW = window->statsLine, statform;
1910 XmString xmslinecol;
1911 Dimension ht;
1912 #ifdef SGI_CUSTOM
1913 char *sleft, *smid, *sright;
1914 #endif
1916 /* This routine is called for each character typed, so its performance
1917 affects overall editor perfomance. Only update if the line is on. */
1918 if (!window->showStats)
1919 return;
1921 /* Compose the string to display. If line # isn't available, leave it off */
1922 pos = TextGetCursorPos(window->lastFocus);
1923 string = XtMalloc(strlen(window->filename) + strlen(window->path) + 45);
1924 format = window->fileFormat == DOS_FILE_FORMAT ? " DOS" :
1925 (window->fileFormat == MAC_FILE_FORMAT ? " Mac" : "");
1926 if (!TextPosToLineAndCol(window->lastFocus, pos, &line, &colNum)) {
1927 sprintf(string, "%s%s%s %d bytes", window->path, window->filename,
1928 format, window->buffer->length);
1929 sprintf(slinecol, "L: --- C: ---");
1930 } else {
1931 sprintf(slinecol, "L: %d C: %d", line, colNum);
1932 if (window->showLineNumbers)
1933 sprintf(string, "%s%s%s byte %d of %d", window->path,
1934 window->filename, format, pos,
1935 window->buffer->length);
1936 else
1937 sprintf(string, "%s%s%s %d bytes", window->path,
1938 window->filename, format, window->buffer->length);
1941 /* Update the line/column number */
1942 xmslinecol = XmStringCreateSimple(slinecol);
1943 XtVaSetValues( window->statsLineColNo,
1944 XmNlabelString, xmslinecol, NULL );
1945 XmStringFree(xmslinecol);
1947 /* Workaround for an OpenMotif bug that keeps the statsAreaForm from being
1948 redrawn */
1949 statform = XtParent(window->statsLineForm);
1950 XtVaSetValues(statform, XmNshadowType, XmSHADOW_IN, NULL);
1951 XtVaSetValues(statform, XmNshadowType, XmSHADOW_OUT, NULL);
1952 /* Another solution would be to send an expose event, but I don't know
1953 how I would fabricate one of those. */
1954 /*XtDispatchEventToWidget(XtParent(window->statsLineForm), ???);*/
1956 /* No need to go on if there's a special message being displayed */
1957 if (window->modeMessageDisplayed) {
1958 /* Keep the height of the line/col display equal to the stats line */
1959 XtVaGetValues(window->statsLine, XmNheight, &ht, NULL);
1960 XtVaSetValues(window->statsLineColNo, XmNheight, ht, NULL);
1961 XtFree(string);
1962 return;
1964 /* Change the text in the stats line */
1965 #ifdef SGI_CUSTOM
1966 /* don't show full pathname, just dir and filename (+ byte info) */
1967 smid = strchr(string, '/');
1968 if ( smid != NULL ) {
1969 sleft = smid;
1970 sright = strrchr(string, '/');
1971 while (strcmp(smid, sright)) {
1972 sleft = smid;
1973 smid = strchr(sleft + 1, '/');
1975 XmTextReplace(statW, 0, XmTextGetLastPosition(statW), sleft + 1);
1976 } else
1977 XmTextReplace(statW, 0, XmTextGetLastPosition(statW), string);
1978 #else
1979 XmTextReplace(statW, 0, XmTextGetLastPosition(statW), string);
1980 #endif
1982 /* Keep the height of the line/col display equal to the stats line. This
1983 seems to be the only way to keep the line/col numbers horizontally
1984 aligned with left side of the statsline */
1985 XtVaGetValues(window->statsLine, XmNheight, &ht, NULL);
1986 XtVaSetValues(window->statsLineColNo, XmNheight, ht, NULL);
1988 XtFree(string);
1992 ** Paned windows are impossible to adjust after they are created, which makes
1993 ** them nearly useless for NEdit (or any application which needs to dynamically
1994 ** adjust the panes) unless you tweek some private data to overwrite the
1995 ** desired and minimum pane heights which were set at creation time. These
1996 ** will probably break in a future release of Motif because of dependence on
1997 ** private data.
1999 static void setPaneDesiredHeight(Widget w, int height)
2001 ((XmPanedWindowConstraintPtr)w->core.constraints)->panedw.dheight = height;
2003 static void setPaneMinHeight(Widget w, int min)
2005 ((XmPanedWindowConstraintPtr)w->core.constraints)->panedw.min = min;
2009 ** Update the window manager's size hints. These tell it the increments in
2010 ** which it is allowed to resize the window. While this isn't particularly
2011 ** important for NEdit (since it can tolerate any window size), setting these
2012 ** hints also makes the resize indicator show the window size in characters
2013 ** rather than pixels, which is very helpful to users.
2015 void UpdateWMSizeHints(WindowInfo *window)
2017 Dimension shellWidth, shellHeight, textHeight, hScrollBarHeight;
2018 int marginHeight, marginWidth, totalHeight;
2019 XFontStruct *fs;
2020 int i, baseWidth, baseHeight, fontHeight, fontWidth;
2021 Widget hScrollBar;
2022 textDisp *textD = ((TextWidget)window->textArea)->text.textD;
2024 /* Find the base (non-expandable) width and height of the editor window */
2025 XtVaGetValues(window->textArea, XmNheight, &textHeight,
2026 textNmarginHeight, &marginHeight, textNmarginWidth, &marginWidth,
2027 NULL);
2028 totalHeight = textHeight - 2*marginHeight;
2029 for (i=0; i<window->nPanes; i++) {
2030 XtVaGetValues(window->textPanes[i], XmNheight, &textHeight,
2031 textNhScrollBar, &hScrollBar, NULL);
2032 totalHeight += textHeight - 2*marginHeight;
2033 if (!XtIsManaged(hScrollBar)) {
2034 XtVaGetValues(hScrollBar, XmNheight, &hScrollBarHeight, NULL);
2035 totalHeight -= hScrollBarHeight;
2038 XtVaGetValues(window->shell, XmNwidth, &shellWidth,
2039 XmNheight, &shellHeight, NULL);
2040 baseWidth = shellWidth - textD->width;
2041 baseHeight = shellHeight - totalHeight;
2043 /* Find the dimensions of a single character of the text font */
2044 XtVaGetValues(window->textArea, textNfont, &fs, NULL);
2045 fontHeight = textD->ascent + textD->descent;
2046 fontWidth = fs->max_bounds.width;
2048 /* Set the size hints in the shell widget */
2049 XtVaSetValues(window->shell, XmNwidthInc, fs->max_bounds.width,
2050 XmNheightInc, fontHeight,
2051 XmNbaseWidth, baseWidth, XmNbaseHeight, baseHeight,
2052 XmNminWidth, baseWidth + fontWidth,
2053 XmNminHeight, baseHeight + (1+window->nPanes) * fontHeight, NULL);
2057 ** Update the minimum allowable height for a split window pane after a change
2058 ** to font or margin height.
2060 void UpdateMinPaneHeights(WindowInfo *window)
2062 textDisp *textD = ((TextWidget)window->textArea)->text.textD;
2063 Dimension hsbHeight, swMarginHeight;
2064 int i, marginHeight, minPaneHeight;
2065 Widget hScrollBar;
2067 /* find the minimum allowable size for a pane */
2068 XtVaGetValues(window->textArea, textNhScrollBar, &hScrollBar, NULL);
2069 XtVaGetValues(XtParent(window->textArea),
2070 XmNscrolledWindowMarginHeight, &swMarginHeight, NULL);
2071 XtVaGetValues(window->textArea, textNmarginHeight, &marginHeight, NULL);
2072 XtVaGetValues(hScrollBar, XmNheight, &hsbHeight, NULL);
2073 minPaneHeight = textD->ascent + textD->descent + marginHeight*2 +
2074 swMarginHeight*2 + hsbHeight;
2076 /* Set it in all of the widgets in the window */
2077 setPaneMinHeight(XtParent(window->textArea), minPaneHeight);
2078 for (i=0; i<window->nPanes; i++)
2079 setPaneMinHeight(XtParent(window->textPanes[i]), minPaneHeight);
2082 /* Add an icon to an applicaction shell widget. addWindowIcon adds a large
2083 ** (primary window) icon, AddSmallIcon adds a small (secondary window) icon.
2085 ** Note: I would prefer that these were not hardwired, but yhere is something
2086 ** weird about the XmNiconPixmap resource that prevents it from being set
2087 ** from the defaults in the application resource database.
2089 static void addWindowIcon(Widget shell)
2091 static Pixmap iconPixmap = 0, maskPixmap = 0;
2093 if (iconPixmap == 0) {
2094 iconPixmap = XCreateBitmapFromData(TheDisplay,
2095 RootWindowOfScreen(XtScreen(shell)), (char *)iconBits,
2096 iconBitmapWidth, iconBitmapHeight);
2097 maskPixmap = XCreateBitmapFromData(TheDisplay,
2098 RootWindowOfScreen(XtScreen(shell)), (char *)maskBits,
2099 iconBitmapWidth, iconBitmapHeight);
2101 XtVaSetValues(shell, XmNiconPixmap, iconPixmap, XmNiconMask, maskPixmap,
2102 NULL);
2104 void AddSmallIcon(Widget shell)
2106 static Pixmap iconPixmap = 0, maskPixmap = 0;
2108 if (iconPixmap == 0) {
2109 iconPixmap = XCreateBitmapFromData(TheDisplay,
2110 RootWindowOfScreen(XtScreen(shell)), (char *)n_bits,
2111 n_width, n_height);
2112 maskPixmap = XCreateBitmapFromData(TheDisplay,
2113 RootWindowOfScreen(XtScreen(shell)), (char *)n_mask,
2114 n_width, n_height);
2116 XtVaSetValues(shell, XmNiconPixmap, iconPixmap,
2117 XmNiconMask, maskPixmap, NULL);
2121 ** Save the position and size of a window as an X standard geometry string.
2122 ** A string of at least MAX_GEOMETRY_STRING_LEN characters should be
2123 ** provided in the argument "geomString" to receive the result.
2125 static void getGeometryString(WindowInfo *window, char *geomString)
2127 int x, y, fontWidth, fontHeight, baseWidth, baseHeight;
2128 unsigned int width, height, dummyW, dummyH, bw, depth, nChild;
2129 Window parent, root, *child, w = XtWindow(window->shell);
2130 Display *dpy = XtDisplay(window->shell);
2132 /* Find the width and height from the window of the shell */
2133 XGetGeometry(dpy, w, &root, &x, &y, &width, &height, &bw, &depth);
2135 /* Find the top left corner (x and y) of the window decorations. (This
2136 is what's required in the geometry string to restore the window to it's
2137 original position, since the window manager re-parents the window to
2138 add it's title bar and menus, and moves the requested window down and
2139 to the left.) The position is found by traversing the window hier-
2140 archy back to the window to the last parent before the root window */
2141 for(;;) {
2142 XQueryTree(dpy, w, &root, &parent, &child, &nChild);
2143 XFree((char*)child);
2144 if (parent == root)
2145 break;
2146 w = parent;
2148 XGetGeometry(dpy, w, &root, &x, &y, &dummyW, &dummyH, &bw, &depth);
2150 /* Use window manager size hints (set by UpdateWMSizeHints) to
2151 translate the width and height into characters, as opposed to pixels */
2152 XtVaGetValues(window->shell, XmNwidthInc, &fontWidth,
2153 XmNheightInc, &fontHeight, XmNbaseWidth, &baseWidth,
2154 XmNbaseHeight, &baseHeight, NULL);
2155 width = (width-baseWidth) / fontWidth;
2156 height = (height-baseHeight) / fontHeight;
2158 /* Write the string */
2159 CreateGeometryString(geomString, x, y, width, height,
2160 XValue | YValue | WidthValue | HeightValue);
2164 ** Xt timer procedure for updating size hints. The new sizes of objects in
2165 ** the window are not ready immediately after adding or removing panes. This
2166 ** is a timer routine to be invoked with a timeout of 0 to give the event
2167 ** loop a chance to finish processing the size changes before reading them
2168 ** out for setting the window manager size hints.
2170 static void wmSizeUpdateProc(XtPointer clientData, XtIntervalId *id)
2172 UpdateWMSizeHints((WindowInfo *)clientData);
2175 #ifdef ROWCOLPATCH
2177 ** There is a bad memory reference in the delete_child method of the
2178 ** RowColumn widget in some Motif versions (so far, just Solaris with Motif
2179 ** 1.2.3) which appears durring the phase 2 destroy of the widget. This
2180 ** patch replaces the method with a call to the Composite widget's
2181 ** delete_child method. The composite delete_child method handles part,
2182 ** but not all of what would have been done by the original method, meaning
2183 ** that this is dangerous and should be used sparingly. Note that
2184 ** patchRowCol is called only in CloseWindow, before the widget is about to
2185 ** be destroyed, and only on systems where the bug has been observed
2187 static void patchRowCol(Widget w)
2189 ((XmRowColumnClassRec *)XtClass(w))->composite_class.delete_child =
2190 patchedRemoveChild;
2192 static void patchedRemoveChild(Widget child)
2194 /* Call composite class method instead of broken row col delete_child
2195 method */
2196 (*((CompositeWidgetClass)compositeWidgetClass)->composite_class.
2197 delete_child) (child);
2199 #endif /* ROWCOLPATCH */
2201 static int sortAlphabetical(const void* k1, const void* k2)
2203 const char* key1 = *(const char**)k1;
2204 const char* key2 = *(const char**)k2;
2205 return strcmp(key1, key2);
2209 * Checks whether a given virtual key binding string is invalid.
2210 * A binding is considered invalid if there are duplicate key entries.
2212 static int virtKeyBindingsAreInvalid(const unsigned char* bindings)
2214 int maxCount = 1, i, count;
2215 const char *pos = (const char*)bindings;
2216 char *copy;
2217 char *pos2, *pos3;
2218 char **keys;
2219 /* First count the number of bindings; bindings are separated by \n
2220 strings. The number of bindings equals the number of \n + 1.
2221 Beware of leading and trailing \n; the number is actually an
2222 upper bound on the number of entries. */
2223 while ((pos = strstr(pos, "\n"))) { ++pos; ++maxCount; }
2225 if (maxCount == 1) return False; /* One binding is always ok */
2227 keys = (char**)malloc(maxCount*sizeof(char*));
2228 copy = XtNewString((const char*)bindings);
2229 i = 0;
2230 pos2 = copy;
2232 count = 0;
2233 while (i<maxCount && pos2 && *pos2)
2235 while (isspace((int) *pos2) || *pos2 == '\n') ++pos2;
2237 if (*pos2)
2239 keys[i++] = pos2;
2240 ++count;
2241 pos3 = strstr(pos2, ":");
2242 if (pos3)
2244 *pos3++ = 0; /* Cut the string and jump to the next entry */
2245 pos2 = pos3;
2247 pos2 = strstr(pos2, "\n");
2251 if (count <= 1)
2253 free(keys);
2254 XtFree(copy);
2255 return False; /* No conflict */
2258 /* Sort the keys and look for duplicates */
2259 qsort((void*)keys, count, sizeof(const char*), sortAlphabetical);
2260 for (i=1; i<count; ++i)
2262 if (!strcmp(keys[i-1], keys[i]))
2264 /* Duplicate detected */
2265 free(keys);
2266 XtFree(copy);
2267 return True;
2270 free(keys);
2271 XtFree(copy);
2272 return False;
2276 * Optionally sanitizes the Motif default virtual key bindings.
2277 * Some applications install invalid bindings (attached to the root window),
2278 * which cause certain keys to malfunction in NEdit.
2279 * Through an X-resource, users can choose whether they want
2280 * - to always keep the existing bindings
2281 * - to override the bindings only if they are invalid
2282 * - to always override the existing bindings.
2285 static Atom virtKeyAtom;
2287 static unsigned char* sanitizeVirtualKeyBindings()
2289 int overrideBindings = GetPrefOverrideVirtKeyBindings();
2290 Window rootWindow;
2291 const char *virtKeyPropName = "_MOTIF_DEFAULT_BINDINGS";
2292 Atom dummyAtom;
2293 int getFmt;
2294 unsigned long dummyULong, nItems;
2295 unsigned char *insaneVirtKeyBindings = NULL;
2297 if (overrideBindings == VIRT_KEY_OVERRIDE_NEVER) return NULL;
2299 virtKeyAtom = XInternAtom(TheDisplay, virtKeyPropName, False);
2300 rootWindow = RootWindow(TheDisplay, DefaultScreen(TheDisplay));
2302 /* Remove the property, if it exists; we'll restore it later again */
2303 if (XGetWindowProperty(TheDisplay, rootWindow, virtKeyAtom, 0, INT_MAX,
2304 True, XA_STRING, &dummyAtom, &getFmt, &nItems,
2305 &dummyULong, &insaneVirtKeyBindings) != Success
2306 || nItems == 0)
2308 return NULL; /* No binding yet; nothing to do */
2311 if (overrideBindings == VIRT_KEY_OVERRIDE_AUTO)
2313 if (!virtKeyBindingsAreInvalid(insaneVirtKeyBindings))
2315 /* Restore the property immediately; it seems valid */
2316 XChangeProperty(TheDisplay, rootWindow, virtKeyAtom, XA_STRING, 8,
2317 PropModeReplace, insaneVirtKeyBindings,
2318 strlen((const char*)insaneVirtKeyBindings));
2319 XFree((char*)insaneVirtKeyBindings);
2320 return NULL; /* Prevent restoration */
2323 return insaneVirtKeyBindings;
2327 * NEdit should not mess with the bindings installed by other apps, so we
2328 * just restore whatever was installed, if necessary
2330 static void restoreInsaneVirtualKeyBindings(unsigned char *insaneVirtKeyBindings)
2332 if (insaneVirtKeyBindings)
2334 Window rootWindow = RootWindow(TheDisplay, DefaultScreen(TheDisplay));
2335 /* Restore the root window atom, such that we don't affect
2336 other apps. */
2337 XChangeProperty(TheDisplay, rootWindow, virtKeyAtom, XA_STRING, 8,
2338 PropModeReplace, insaneVirtKeyBindings,
2339 strlen((const char*)insaneVirtKeyBindings));
2340 XFree((char*)insaneVirtKeyBindings);