Minor fix.
[nedit.git] / source / textDrag.c
blobf6a982621f690ffb192bbeb94edc331d6c4e690c
1 static const char CVSID[] = "$Id: textDrag.c,v 1.11 2005/02/02 09:15:31 edg Exp $";
2 /*******************************************************************************
3 * *
4 * textDrag.c - Text Dragging routines for NEdit text widget *
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. In addition, you may distribute version of this program linked to *
12 * Motif or Open Motif. See README for details. *
13 * *
14 * This software is distributed in the hope that it will be useful, but WITHOUT *
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
17 * for more details. *
18 * *
19 * You should have received a copy of the GNU General Public License along with *
20 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
21 * Place, Suite 330, Boston, MA 02111-1307 USA *
22 * *
23 * Nirvana Text Editor *
24 * Dec. 15, 1995 *
25 * *
26 * Written by Mark Edel *
27 * *
28 *******************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 #include "../config.h"
32 #endif
34 #include "textDrag.h"
35 #include "textBuf.h"
36 #include "textDisp.h"
37 #include "textP.h"
39 #include <limits.h>
41 #include <X11/Intrinsic.h>
42 #include <X11/IntrinsicP.h>
43 #include <Xm/Xm.h>
44 #include <Xm/XmP.h>
45 #if XmVersion >= 1002
46 #include <Xm/PrimitiveP.h>
47 #endif
49 #ifdef HAVE_DEBUG_H
50 #include "../debug.h"
51 #endif
53 static void trackModifyRange(int *rangeStart, int *modRangeEnd,
54 int *unmodRangeEnd, int modPos, int nInserted, int nDeleted);
55 static void findTextMargins(textBuffer *buf, int start, int end, int *leftMargin,
56 int *rightMargin);
57 static int findRelativeLineStart(textBuffer *buf, int referencePos,
58 int referenceLineNum, int newLineNum);
59 static int min3(int i1, int i2, int i3);
60 static int max3(int i1, int i2, int i3);
61 static int max(int i1, int i2);
64 ** Start the process of dragging the current primary-selected text across
65 ** the window (move by dragging, as opposed to dragging to create the
66 ** selection)
68 void BeginBlockDrag(TextWidget tw)
70 textDisp *textD = tw->text.textD;
71 textBuffer *buf = textD->buffer;
72 int fontHeight = textD->fontStruct->ascent + textD->fontStruct->descent;
73 int fontWidth = textD->fontStruct->max_bounds.width;
74 selection *sel = &buf->primary;
75 int nLines, mousePos, lineStart;
76 int x, y, lineEnd;
78 char *text;
80 /* Save a copy of the whole text buffer as a backup, and for
81 deriving changes */
82 tw->text.dragOrigBuf = BufCreate();
83 BufSetTabDistance(tw->text.dragOrigBuf, buf->tabDist);
84 tw->text.dragOrigBuf->useTabs = buf->useTabs;
85 text = BufGetAll(buf);
86 BufSetAll(tw->text.dragOrigBuf, text);
87 XtFree(text);
88 if (sel->rectangular)
89 BufRectSelect(tw->text.dragOrigBuf, sel->start, sel->end, sel->rectStart,
90 sel->rectEnd);
91 else
92 BufSelect(tw->text.dragOrigBuf, sel->start, sel->end);
94 /* Record the mouse pointer offsets from the top left corner of the
95 selection (the position where text will actually be inserted In dragging
96 non-rectangular selections) */
97 if (sel->rectangular) {
98 tw->text.dragXOffset = tw->text.btnDownX + textD->horizOffset -
99 textD->left - sel->rectStart * fontWidth;
100 } else {
101 if (!TextDPositionToXY(textD, sel->start, &x, &y))
102 x = BufCountDispChars(buf, TextDStartOfLine(textD, sel->start),
103 sel->start) * fontWidth + textD->left -
104 textD->horizOffset;
105 tw->text.dragXOffset = tw->text.btnDownX - x;
107 mousePos = TextDXYToPosition(textD, tw->text.btnDownX, tw->text.btnDownY);
108 nLines = BufCountLines(buf, sel->start, mousePos);
109 tw->text.dragYOffset = nLines * fontHeight + (((tw->text.btnDownY -
110 tw->text.marginHeight) % fontHeight) - fontHeight/2);
111 tw->text.dragNLines = BufCountLines(buf, sel->start, sel->end);
113 /* Record the current drag insert position and the information for
114 undoing the fictional insert of the selection in its new position */
115 tw->text.dragInsertPos = sel->start;
116 tw->text.dragInserted = sel->end - sel->start;
117 if (sel->rectangular) {
118 textBuffer *testBuf = BufCreate();
119 char *testText = BufGetRange(buf, sel->start, sel->end);
120 BufSetTabDistance(testBuf, buf->tabDist);
121 testBuf->useTabs = buf->useTabs;
122 BufSetAll(testBuf, testText);
123 XtFree(testText);
124 BufRemoveRect(testBuf, 0, sel->end - sel->start, sel->rectStart,
125 sel->rectEnd);
126 tw->text.dragDeleted = testBuf->length;
127 BufFree(testBuf);
128 tw->text.dragRectStart = sel->rectStart;
129 } else {
130 tw->text.dragDeleted = 0;
131 tw->text.dragRectStart = 0;
133 tw->text.dragType = DRAG_MOVE;
134 tw->text.dragSourceDeletePos = sel->start;
135 tw->text.dragSourceInserted = tw->text.dragDeleted;
136 tw->text.dragSourceDeleted = tw->text.dragInserted;
138 /* For non-rectangular selections, fill in the rectangular information in
139 the selection for overlay mode drags which are done rectangularly */
140 if (!sel->rectangular) {
141 lineStart = BufStartOfLine(buf, sel->start);
142 if (tw->text.dragNLines == 0) {
143 tw->text.dragOrigBuf->primary.rectStart =
144 BufCountDispChars(buf, lineStart, sel->start);
145 tw->text.dragOrigBuf->primary.rectEnd =
146 BufCountDispChars(buf, lineStart, sel->end);
147 } else {
148 lineEnd = BufGetCharacter(buf, sel->end - 1) == '\n' ?
149 sel->end - 1 : sel->end;
150 findTextMargins(buf, lineStart, lineEnd,
151 &tw->text.dragOrigBuf->primary.rectStart,
152 &tw->text.dragOrigBuf->primary.rectEnd);
156 /* Set the drag state to announce an ongoing block-drag */
157 tw->text.dragState = PRIMARY_BLOCK_DRAG;
159 /* Call the callback announcing the start of a block drag */
160 XtCallCallbacks((Widget)tw, textNdragStartCallback, (XtPointer)NULL);
164 ** Reposition the primary-selected text that is being dragged as a block
165 ** for a new mouse position of (x, y)
167 void BlockDragSelection(TextWidget tw, int x, int y, int dragType)
169 textDisp *textD = tw->text.textD;
170 textBuffer *buf = textD->buffer;
171 int fontHeight = textD->fontStruct->ascent + textD->fontStruct->descent;
172 int fontWidth = textD->fontStruct->max_bounds.width;
173 textBuffer *origBuf = tw->text.dragOrigBuf;
174 int dragXOffset = tw->text.dragXOffset;
175 textBuffer *tempBuf;
176 selection *origSel = &origBuf->primary;
177 int rectangular = origSel->rectangular;
178 int overlay, oldDragType = tw->text.dragType;
179 int nLines = tw->text.dragNLines;
180 int insLineNum, insLineStart, insRectStart, insRectEnd, insStart;
181 char *repText, *text, *insText;
182 int modRangeStart = -1, tempModRangeEnd = -1, bufModRangeEnd = -1;
183 int referenceLine, referencePos, tempStart, tempEnd, origSelLen;
184 int insertInserted, insertDeleted, row, column;
185 int origSelLineStart, origSelLineEnd;
186 int sourceInserted, sourceDeleted, sourceDeletePos;
188 if (tw->text.dragState != PRIMARY_BLOCK_DRAG)
189 return;
191 /* The operation of block dragging is simple in theory, but not so simple
192 in practice. There is a backup buffer (tw->text.dragOrigBuf) which
193 holds a copy of the buffer as it existed before the drag. When the
194 user drags the mouse to a new location, this routine is called, and
195 a temporary buffer is created and loaded with the local part of the
196 buffer (from the backup) which might be changed by the drag. The
197 changes are all made to this temporary buffer, and the parts of this
198 buffer which then differ from the real (displayed) buffer are used to
199 replace those parts, thus one replace operation serves as both undo
200 and modify. This double-buffering of the operation prevents excessive
201 redrawing (though there is still plenty of needless redrawing due to
202 re-selection and rectangular operations).
204 The hard part is keeping track of the changes such that a single replace
205 operation will do everyting. This is done using a routine called
206 trackModifyRange which tracks expanding ranges of changes in the two
207 buffers in modRangeStart, tempModRangeEnd, and bufModRangeEnd. */
209 /* Create a temporary buffer for accumulating changes which will
210 eventually be replaced in the real buffer. Load the buffer with the
211 range of characters which might be modified in this drag step
212 (this could be tighter, but hopefully it's not too slow) */
213 tempBuf = BufCreate();
214 tempBuf->tabDist = buf->tabDist;
215 tempBuf->useTabs = buf->useTabs;
216 tempStart = min3(tw->text.dragInsertPos, origSel->start,
217 BufCountBackwardNLines(buf, textD->firstChar, nLines+2));
218 tempEnd = BufCountForwardNLines(buf, max3(tw->text.dragInsertPos,
219 origSel->start, textD->lastChar), nLines+2) +
220 origSel->end - origSel->start;
221 text = BufGetRange(origBuf, tempStart, tempEnd);
222 BufSetAll(tempBuf, text);
223 XtFree(text);
225 /* If the drag type is USE_LAST, use the last dragType applied */
226 if (dragType == USE_LAST)
227 dragType = tw->text.dragType;
228 overlay = dragType == DRAG_OVERLAY_MOVE || dragType == DRAG_OVERLAY_COPY;
230 /* Overlay mode uses rectangular selections whether or not the original
231 was rectangular. To use a plain selection as if it were rectangular,
232 the start and end positions need to be moved to the line boundaries
233 and trailing newlines must be excluded */
234 origSelLineStart = BufStartOfLine(origBuf, origSel->start);
235 if (!rectangular && BufGetCharacter(origBuf, origSel->end - 1) == '\n')
236 origSelLineEnd = origSel->end - 1;
237 else
238 origSelLineEnd = BufEndOfLine(origBuf, origSel->end);
239 if (!rectangular && overlay && nLines != 0)
240 dragXOffset -= fontWidth * (origSel->rectStart -
241 (origSel->start - origSelLineStart));
243 /* If the drag operation is of a different type than the last one, and the
244 operation is a move, expand the modified-range to include undoing the
245 text-removal at the site from which the text was dragged. */
246 if (dragType != oldDragType && tw->text.dragSourceDeleted != 0)
247 trackModifyRange(&modRangeStart, &bufModRangeEnd, &tempModRangeEnd,
248 tw->text.dragSourceDeletePos, tw->text.dragSourceInserted,
249 tw->text.dragSourceDeleted);
251 /* Do, or re-do the original text removal at the site where a move began.
252 If this part has not changed from the last call, do it silently to
253 bring the temporary buffer in sync with the real (displayed)
254 buffer. If it's being re-done, track the changes to complete the
255 redo operation begun above */
256 if (dragType == DRAG_MOVE || dragType == DRAG_OVERLAY_MOVE) {
257 if (rectangular || overlay) {
258 int prevLen = tempBuf->length;
259 origSelLen = origSelLineEnd - origSelLineStart;
260 if (overlay)
261 BufClearRect(tempBuf, origSelLineStart-tempStart,
262 origSelLineEnd-tempStart, origSel->rectStart,
263 origSel->rectEnd);
264 else
265 BufRemoveRect(tempBuf, origSelLineStart-tempStart,
266 origSelLineEnd-tempStart, origSel->rectStart,
267 origSel->rectEnd);
268 sourceDeletePos = origSelLineStart;
269 sourceInserted = origSelLen - prevLen + tempBuf->length;
270 sourceDeleted = origSelLen;
271 } else {
272 BufRemove(tempBuf, origSel->start - tempStart,
273 origSel->end - tempStart);
274 sourceDeletePos = origSel->start;
275 sourceInserted = 0;
276 sourceDeleted = origSel->end - origSel->start;
278 if (dragType != oldDragType)
279 trackModifyRange(&modRangeStart, &tempModRangeEnd, &bufModRangeEnd,
280 sourceDeletePos, sourceInserted, sourceDeleted);
281 } else {
282 sourceDeletePos = 0;
283 sourceInserted = 0;
284 sourceDeleted = 0;
287 /* Expand the modified-range to include undoing the insert from the last
288 call. */
289 trackModifyRange(&modRangeStart, &bufModRangeEnd, &tempModRangeEnd,
290 tw->text.dragInsertPos, tw->text.dragInserted, tw->text.dragDeleted);
292 /* Find the line number and column of the insert position. Note that in
293 continuous wrap mode, these must be calculated as if the text were
294 not wrapped */
295 TextDXYToUnconstrainedPosition(textD, max(0, x - dragXOffset),
296 max(0, y - (tw->text.dragYOffset % fontHeight)), &row, &column);
297 column = TextDOffsetWrappedColumn(textD, row, column);
298 row = TextDOffsetWrappedRow(textD, row);
299 insLineNum = row + textD->topLineNum - tw->text.dragYOffset / fontHeight;
301 /* find a common point of reference between the two buffers, from which
302 the insert position line number can be translated to a position */
303 if (textD->firstChar > modRangeStart) {
304 referenceLine = textD->topLineNum -
305 BufCountLines(buf, modRangeStart, textD->firstChar);
306 referencePos = modRangeStart;
307 } else {
308 referencePos = textD->firstChar;
309 referenceLine = textD->topLineNum;
312 /* find the position associated with the start of the new line in the
313 temporary buffer */
314 insLineStart = findRelativeLineStart(tempBuf, referencePos - tempStart,
315 referenceLine, insLineNum) + tempStart;
316 if (insLineStart - tempStart == tempBuf->length)
317 insLineStart = BufStartOfLine(tempBuf, insLineStart - tempStart) +
318 tempStart;
320 /* Find the actual insert position */
321 if (rectangular || overlay) {
322 insStart = insLineStart;
323 insRectStart = column;
324 } else { /* note, this will fail with proportional fonts */
325 insStart = BufCountForwardDispChars(tempBuf, insLineStart - tempStart,
326 column) + tempStart;
327 insRectStart = 0;
330 /* If the position is the same as last time, don't bother drawing (it
331 would be nice if this decision could be made earlier) */
332 if (insStart == tw->text.dragInsertPos &&
333 insRectStart == tw->text.dragRectStart && dragType == oldDragType) {
334 BufFree(tempBuf);
335 return;
338 /* Do the insert in the temporary buffer */
339 if (rectangular || overlay) {
340 insText = BufGetTextInRect(origBuf, origSelLineStart, origSelLineEnd,
341 origSel->rectStart, origSel->rectEnd);
342 if (overlay)
343 BufOverlayRect(tempBuf, insStart - tempStart, insRectStart,
344 insRectStart + origSel->rectEnd - origSel->rectStart,
345 insText, &insertInserted, &insertDeleted);
346 else
347 BufInsertCol(tempBuf, insRectStart, insStart - tempStart, insText,
348 &insertInserted, &insertDeleted);
349 trackModifyRange(&modRangeStart, &tempModRangeEnd, &bufModRangeEnd,
350 insStart, insertInserted, insertDeleted);
351 XtFree(insText);
352 } else {
353 insText = BufGetSelectionText(origBuf);
354 BufInsert(tempBuf, insStart - tempStart, insText);
355 trackModifyRange(&modRangeStart, &tempModRangeEnd, &bufModRangeEnd,
356 insStart, origSel->end - origSel->start, 0);
357 insertInserted = origSel->end - origSel->start;
358 insertDeleted = 0;
359 XtFree(insText);
362 /* Make the changes in the real buffer */
363 repText = BufGetRange(tempBuf, modRangeStart - tempStart,
364 tempModRangeEnd - tempStart);
365 BufFree(tempBuf);
366 TextDBlankCursor(textD);
367 BufReplace(buf, modRangeStart, bufModRangeEnd, repText);
368 XtFree(repText);
370 /* Store the necessary information for undoing this step */
371 tw->text.dragInsertPos = insStart;
372 tw->text.dragRectStart = insRectStart;
373 tw->text.dragInserted = insertInserted;
374 tw->text.dragDeleted = insertDeleted;
375 tw->text.dragSourceDeletePos = sourceDeletePos;
376 tw->text.dragSourceInserted = sourceInserted;
377 tw->text.dragSourceDeleted = sourceDeleted;
378 tw->text.dragType = dragType;
380 /* Reset the selection and cursor position */
381 if (rectangular || overlay) {
382 insRectEnd = insRectStart + origSel->rectEnd - origSel->rectStart;
383 BufRectSelect(buf, insStart, insStart + insertInserted, insRectStart,
384 insRectEnd);
385 TextDSetInsertPosition(textD, BufCountForwardDispChars(buf,
386 BufCountForwardNLines(buf, insStart, tw->text.dragNLines),
387 insRectEnd));
388 } else {
389 BufSelect(buf, insStart, insStart + origSel->end - origSel->start);
390 TextDSetInsertPosition(textD, insStart + origSel->end - origSel->start);
392 TextDUnblankCursor(textD);
393 XtCallCallbacks((Widget)tw, textNcursorMovementCallback, (XtPointer)NULL);
394 tw->text.emTabsBeforeCursor = 0;
398 ** Complete a block text drag operation
400 void FinishBlockDrag(TextWidget tw)
402 dragEndCBStruct endStruct;
403 int modRangeStart = -1, origModRangeEnd, bufModRangeEnd;
404 char *deletedText;
406 /* Find the changed region of the buffer, covering both the deletion
407 of the selected text at the drag start position, and insertion at
408 the drag destination */
409 trackModifyRange(&modRangeStart, &bufModRangeEnd, &origModRangeEnd,
410 tw->text.dragSourceDeletePos, tw->text.dragSourceInserted,
411 tw->text.dragSourceDeleted);
412 trackModifyRange(&modRangeStart, &bufModRangeEnd, &origModRangeEnd,
413 tw->text.dragInsertPos, tw->text.dragInserted,
414 tw->text.dragDeleted);
416 /* Get the original (pre-modified) range of text from saved backup buffer */
417 deletedText = BufGetRange(tw->text.dragOrigBuf, modRangeStart,
418 origModRangeEnd);
420 /* Free the backup buffer */
421 BufFree(tw->text.dragOrigBuf);
423 /* Return to normal drag state */
424 tw->text.dragState = NOT_CLICKED;
426 /* Call finish-drag calback */
427 endStruct.startPos = modRangeStart;
428 endStruct.nCharsDeleted = origModRangeEnd - modRangeStart;
429 endStruct.nCharsInserted = bufModRangeEnd - modRangeStart;
430 endStruct.deletedText = deletedText;
431 XtCallCallbacks((Widget)tw, textNdragEndCallback, (XtPointer)&endStruct);
432 XtFree(deletedText);
436 ** Cancel a block drag operation
438 void CancelBlockDrag(TextWidget tw)
440 textBuffer *buf = tw->text.textD->buffer;
441 textBuffer *origBuf = tw->text.dragOrigBuf;
442 selection *origSel = &origBuf->primary;
443 int modRangeStart = -1, origModRangeEnd, bufModRangeEnd;
444 char *repText;
445 dragEndCBStruct endStruct;
447 /* If the operation was a move, make the modify range reflect the
448 removal of the text from the starting position */
449 if (tw->text.dragSourceDeleted != 0)
450 trackModifyRange(&modRangeStart, &bufModRangeEnd, &origModRangeEnd,
451 tw->text.dragSourceDeletePos, tw->text.dragSourceInserted,
452 tw->text.dragSourceDeleted);
454 /* Include the insert being undone from the last step in the modified
455 range. */
456 trackModifyRange(&modRangeStart, &bufModRangeEnd, &origModRangeEnd,
457 tw->text.dragInsertPos, tw->text.dragInserted, tw->text.dragDeleted);
459 /* Make the changes in the buffer */
460 repText = BufGetRange(origBuf, modRangeStart, origModRangeEnd);
461 BufReplace(buf, modRangeStart, bufModRangeEnd, repText);
462 XtFree(repText);
464 /* Reset the selection and cursor position */
465 if (origSel->rectangular)
466 BufRectSelect(buf, origSel->start, origSel->end, origSel->rectStart,
467 origSel->rectEnd);
468 else
469 BufSelect(buf, origSel->start, origSel->end);
470 TextDSetInsertPosition(tw->text.textD, buf->cursorPosHint);
471 XtCallCallbacks((Widget)tw, textNcursorMovementCallback, NULL);
472 tw->text.emTabsBeforeCursor = 0;
474 /* Free the backup buffer */
475 BufFree(origBuf);
477 /* Indicate end of drag */
478 tw->text.dragState = DRAG_CANCELED;
480 /* Call finish-drag calback */
481 endStruct.startPos = 0;
482 endStruct.nCharsDeleted = 0;
483 endStruct.nCharsInserted = 0;
484 endStruct.deletedText = NULL;
485 XtCallCallbacks((Widget)tw, textNdragEndCallback, (XtPointer)&endStruct);
489 ** Maintain boundaries of changed region between two buffers which
490 ** start out with identical contents, but diverge through insertion,
491 ** deletion, and replacement, such that the buffers can be reconciled
492 ** by replacing the changed region of either buffer with the changed
493 ** region of the other.
495 ** rangeStart is the beginning of the modification region in the shared
496 ** coordinates of both buffers (which are identical up to rangeStart).
497 ** modRangeEnd is the end of the changed region for the buffer being
498 ** modified, unmodRangeEnd is the end of the region for the buffer NOT
499 ** being modified. A value of -1 in rangeStart indicates that there
500 ** have been no modifications so far.
502 static void trackModifyRange(int *rangeStart, int *modRangeEnd,
503 int *unmodRangeEnd, int modPos, int nInserted, int nDeleted)
505 if (*rangeStart == -1) {
506 *rangeStart = modPos;
507 *modRangeEnd = modPos + nInserted;
508 *unmodRangeEnd = modPos + nDeleted;
509 } else {
510 if (modPos < *rangeStart)
511 *rangeStart = modPos;
512 if (modPos + nDeleted > *modRangeEnd) {
513 *unmodRangeEnd += modPos + nDeleted - *modRangeEnd;
514 *modRangeEnd = modPos + nInserted;
515 } else
516 *modRangeEnd += nInserted - nDeleted;
521 ** Find the left and right margins of text between "start" and "end" in
522 ** buffer "buf". Note that "start is assumed to be at the start of a line.
524 static void findTextMargins(textBuffer *buf, int start, int end, int *leftMargin,
525 int *rightMargin)
527 char c;
528 int pos, width = 0, maxWidth = 0, minWhite = INT_MAX, inWhite = True;
530 for (pos=start; pos<end; pos++) {
531 c = BufGetCharacter(buf, pos);
532 if (inWhite && c != ' ' && c != '\t') {
533 inWhite = False;
534 if (width < minWhite)
535 minWhite = width;
537 if (c == '\n') {
538 if (width > maxWidth)
539 maxWidth = width;
540 width = 0;
541 inWhite = True;
542 } else
543 width += BufCharWidth(c, width, buf->tabDist, buf->nullSubsChar);
545 if (width > maxWidth)
546 maxWidth = width;
547 *leftMargin = minWhite == INT_MAX ? 0 : minWhite;
548 *rightMargin = maxWidth;
552 ** Find a text position in buffer "buf" by counting forward or backward
553 ** from a reference position with known line number
555 static int findRelativeLineStart(textBuffer *buf, int referencePos,
556 int referenceLineNum, int newLineNum)
558 if (newLineNum < referenceLineNum)
559 return BufCountBackwardNLines(buf, referencePos,
560 referenceLineNum - newLineNum);
561 else if (newLineNum > referenceLineNum)
562 return BufCountForwardNLines(buf, referencePos,
563 newLineNum - referenceLineNum);
564 return BufStartOfLine(buf, referencePos);
567 static int min3(int i1, int i2, int i3)
569 if (i1 <= i2 && i1 <= i3)
570 return i1;
571 return i2 <= i3 ? i2 : i3;
574 static int max3(int i1, int i2, int i3)
576 if (i1 >= i2 && i1 >= i3)
577 return i1;
578 return i2 >= i3 ? i2 : i3;
581 static int max(int i1, int i2)
583 return i1 >= i2 ? i1 : i2;