1 static const char CVSID
[] = "$Id: textDrag.c,v 1.9 2004/03/25 07:12:47 n8gray Exp $";
2 /*******************************************************************************
4 * textDrag.c - Text Dragging routines for NEdit text widget *
6 * Copyright (C) 1999 Mark Edel *
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 *
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 *
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 *
22 * Nirvana Text Editor *
25 * Written by Mark Edel *
27 *******************************************************************************/
30 #include "../config.h"
40 #include <X11/Intrinsic.h>
41 #include <X11/IntrinsicP.h>
45 #include <Xm/PrimitiveP.h>
52 static void trackModifyRange(int *rangeStart
, int *modRangeEnd
,
53 int *unmodRangeEnd
, int modPos
, int nInserted
, int nDeleted
);
54 static void findTextMargins(textBuffer
*buf
, int start
, int end
, int *leftMargin
,
56 static int findRelativeLineStart(textBuffer
*buf
, int referencePos
,
57 int referenceLineNum
, int newLineNum
);
58 static int min3(int i1
, int i2
, int i3
);
59 static int max3(int i1
, int i2
, int i3
);
60 static int max(int i1
, int i2
);
63 ** Start the process of dragging the current primary-selected text across
64 ** the window (move by dragging, as opposed to dragging to create the
67 void BeginBlockDrag(TextWidget tw
)
69 textDisp
*textD
= tw
->text
.textD
;
70 textBuffer
*buf
= textD
->buffer
;
71 int fontHeight
= textD
->fontStruct
->ascent
+ textD
->fontStruct
->descent
;
72 int fontWidth
= textD
->fontStruct
->max_bounds
.width
;
73 selection
*sel
= &buf
->primary
;
74 int nLines
, mousePos
, lineStart
;
79 /* Save a copy of the whole text buffer as a backup, and for
81 tw
->text
.dragOrigBuf
= BufCreate();
82 BufSetTabDistance(tw
->text
.dragOrigBuf
, buf
->tabDist
);
83 tw
->text
.dragOrigBuf
->useTabs
= buf
->useTabs
;
84 text
= BufGetAll(buf
);
85 BufSetAll(tw
->text
.dragOrigBuf
, text
);
88 BufRectSelect(tw
->text
.dragOrigBuf
, sel
->start
, sel
->end
, sel
->rectStart
,
91 BufSelect(tw
->text
.dragOrigBuf
, sel
->start
, sel
->end
);
93 /* Record the mouse pointer offsets from the top left corner of the
94 selection (the position where text will actually be inserted In dragging
95 non-rectangular selections) */
96 if (sel
->rectangular
) {
97 tw
->text
.dragXOffset
= tw
->text
.btnDownX
+ textD
->horizOffset
-
98 textD
->left
- sel
->rectStart
* fontWidth
;
100 if (!TextDPositionToXY(textD
, sel
->start
, &x
, &y
))
101 x
= BufCountDispChars(buf
, TextDStartOfLine(textD
, sel
->start
),
102 sel
->start
) * fontWidth
+ textD
->left
-
104 tw
->text
.dragXOffset
= tw
->text
.btnDownX
- x
;
106 mousePos
= TextDXYToPosition(textD
, tw
->text
.btnDownX
, tw
->text
.btnDownY
);
107 nLines
= BufCountLines(buf
, sel
->start
, mousePos
);
108 tw
->text
.dragYOffset
= nLines
* fontHeight
+ (((tw
->text
.btnDownY
-
109 tw
->text
.marginHeight
) % fontHeight
) - fontHeight
/2);
110 tw
->text
.dragNLines
= BufCountLines(buf
, sel
->start
, sel
->end
);
112 /* Record the current drag insert position and the information for
113 undoing the fictional insert of the selection in its new position */
114 tw
->text
.dragInsertPos
= sel
->start
;
115 tw
->text
.dragInserted
= sel
->end
- sel
->start
;
116 if (sel
->rectangular
) {
117 textBuffer
*testBuf
= BufCreate();
118 char *testText
= BufGetRange(buf
, sel
->start
, sel
->end
);
119 BufSetTabDistance(testBuf
, buf
->tabDist
);
120 testBuf
->useTabs
= buf
->useTabs
;
121 BufSetAll(testBuf
, testText
);
123 BufRemoveRect(testBuf
, 0, sel
->end
- sel
->start
, sel
->rectStart
,
125 tw
->text
.dragDeleted
= testBuf
->length
;
127 tw
->text
.dragRectStart
= sel
->rectStart
;
129 tw
->text
.dragDeleted
= 0;
130 tw
->text
.dragRectStart
= 0;
132 tw
->text
.dragType
= DRAG_MOVE
;
133 tw
->text
.dragSourceDeletePos
= sel
->start
;
134 tw
->text
.dragSourceInserted
= tw
->text
.dragDeleted
;
135 tw
->text
.dragSourceDeleted
= tw
->text
.dragInserted
;
137 /* For non-rectangular selections, fill in the rectangular information in
138 the selection for overlay mode drags which are done rectangularly */
139 if (!sel
->rectangular
) {
140 lineStart
= BufStartOfLine(buf
, sel
->start
);
141 if (tw
->text
.dragNLines
== 0) {
142 tw
->text
.dragOrigBuf
->primary
.rectStart
=
143 BufCountDispChars(buf
, lineStart
, sel
->start
);
144 tw
->text
.dragOrigBuf
->primary
.rectEnd
=
145 BufCountDispChars(buf
, lineStart
, sel
->end
);
147 lineEnd
= BufGetCharacter(buf
, sel
->end
- 1) == '\n' ?
148 sel
->end
- 1 : sel
->end
;
149 findTextMargins(buf
, lineStart
, lineEnd
,
150 &tw
->text
.dragOrigBuf
->primary
.rectStart
,
151 &tw
->text
.dragOrigBuf
->primary
.rectEnd
);
155 /* Set the drag state to announce an ongoing block-drag */
156 tw
->text
.dragState
= PRIMARY_BLOCK_DRAG
;
158 /* Call the callback announcing the start of a block drag */
159 XtCallCallbacks((Widget
)tw
, textNdragStartCallback
, (XtPointer
)NULL
);
163 ** Reposition the primary-selected text that is being dragged as a block
164 ** for a new mouse position of (x, y)
166 void BlockDragSelection(TextWidget tw
, int x
, int y
, int dragType
)
168 textDisp
*textD
= tw
->text
.textD
;
169 textBuffer
*buf
= textD
->buffer
;
170 int fontHeight
= textD
->fontStruct
->ascent
+ textD
->fontStruct
->descent
;
171 int fontWidth
= textD
->fontStruct
->max_bounds
.width
;
172 textBuffer
*origBuf
= tw
->text
.dragOrigBuf
;
173 int dragXOffset
= tw
->text
.dragXOffset
;
175 selection
*origSel
= &origBuf
->primary
;
176 int rectangular
= origSel
->rectangular
;
177 int overlay
, oldDragType
= tw
->text
.dragType
;
178 int nLines
= tw
->text
.dragNLines
;
179 int insLineNum
, insLineStart
, insRectStart
, insRectEnd
, insStart
;
180 char *repText
, *text
, *insText
;
181 int modRangeStart
= -1, tempModRangeEnd
, bufModRangeEnd
;
182 int referenceLine
, referencePos
, tempStart
, tempEnd
, origSelLen
;
183 int insertInserted
, insertDeleted
, row
, column
;
184 int origSelLineStart
, origSelLineEnd
;
185 int sourceInserted
, sourceDeleted
, sourceDeletePos
;
187 if (tw
->text
.dragState
!= PRIMARY_BLOCK_DRAG
)
190 /* The operation of block dragging is simple in theory, but not so simple
191 in practice. There is a backup buffer (tw->text.dragOrigBuf) which
192 holds a copy of the buffer as it existed before the drag. When the
193 user drags the mouse to a new location, this routine is called, and
194 a temporary buffer is created and loaded with the local part of the
195 buffer (from the backup) which might be changed by the drag. The
196 changes are all made to this temporary buffer, and the parts of this
197 buffer which then differ from the real (displayed) buffer are used to
198 replace those parts, thus one replace operation serves as both undo
199 and modify. This double-buffering of the operation prevents excessive
200 redrawing (though there is still plenty of needless redrawing due to
201 re-selection and rectangular operations).
203 The hard part is keeping track of the changes such that a single replace
204 operation will do everyting. This is done using a routine called
205 trackModifyRange which tracks expanding ranges of changes in the two
206 buffers in modRangeStart, tempModRangeEnd, and bufModRangeEnd. */
208 /* Create a temporary buffer for accumulating changes which will
209 eventually be replaced in the real buffer. Load the buffer with the
210 range of characters which might be modified in this drag step
211 (this could be tighter, but hopefully it's not too slow) */
212 tempBuf
= BufCreate();
213 tempBuf
->tabDist
= buf
->tabDist
;
214 tempBuf
->useTabs
= buf
->useTabs
;
215 tempStart
= min3(tw
->text
.dragInsertPos
, origSel
->start
,
216 BufCountBackwardNLines(buf
, textD
->firstChar
, nLines
+2));
217 tempEnd
= BufCountForwardNLines(buf
, max3(tw
->text
.dragInsertPos
,
218 origSel
->start
, textD
->lastChar
), nLines
+2) +
219 origSel
->end
- origSel
->start
;
220 text
= BufGetRange(origBuf
, tempStart
, tempEnd
);
221 BufSetAll(tempBuf
, text
);
224 /* If the drag type is USE_LAST, use the last dragType applied */
225 if (dragType
== USE_LAST
)
226 dragType
= tw
->text
.dragType
;
227 overlay
= dragType
== DRAG_OVERLAY_MOVE
|| dragType
== DRAG_OVERLAY_COPY
;
229 /* Overlay mode uses rectangular selections whether or not the original
230 was rectangular. To use a plain selection as if it were rectangular,
231 the start and end positions need to be moved to the line boundaries
232 and trailing newlines must be excluded */
233 origSelLineStart
= BufStartOfLine(origBuf
, origSel
->start
);
234 if (!rectangular
&& BufGetCharacter(origBuf
, origSel
->end
- 1) == '\n')
235 origSelLineEnd
= origSel
->end
- 1;
237 origSelLineEnd
= BufEndOfLine(origBuf
, origSel
->end
);
238 if (!rectangular
&& overlay
&& nLines
!= 0)
239 dragXOffset
-= fontWidth
* (origSel
->rectStart
-
240 (origSel
->start
- origSelLineStart
));
242 /* If the drag operation is of a different type than the last one, and the
243 operation is a move, expand the modified-range to include undoing the
244 text-removal at the site from which the text was dragged. */
245 if (dragType
!= oldDragType
&& tw
->text
.dragSourceDeleted
!= 0)
246 trackModifyRange(&modRangeStart
, &bufModRangeEnd
, &tempModRangeEnd
,
247 tw
->text
.dragSourceDeletePos
, tw
->text
.dragSourceInserted
,
248 tw
->text
.dragSourceDeleted
);
250 /* Do, or re-do the original text removal at the site where a move began.
251 If this part has not changed from the last call, do it silently to
252 bring the temporary buffer in sync with the real (displayed)
253 buffer. If it's being re-done, track the changes to complete the
254 redo operation begun above */
255 if (dragType
== DRAG_MOVE
|| dragType
== DRAG_OVERLAY_MOVE
) {
256 if (rectangular
|| overlay
) {
257 int prevLen
= tempBuf
->length
;
258 origSelLen
= origSelLineEnd
- origSelLineStart
;
260 BufClearRect(tempBuf
, origSelLineStart
-tempStart
,
261 origSelLineEnd
-tempStart
, origSel
->rectStart
,
264 BufRemoveRect(tempBuf
, origSelLineStart
-tempStart
,
265 origSelLineEnd
-tempStart
, origSel
->rectStart
,
267 sourceDeletePos
= origSelLineStart
;
268 sourceInserted
= origSelLen
- prevLen
+ tempBuf
->length
;
269 sourceDeleted
= origSelLen
;
271 BufRemove(tempBuf
, origSel
->start
- tempStart
,
272 origSel
->end
- tempStart
);
273 sourceDeletePos
= origSel
->start
;
275 sourceDeleted
= origSel
->end
- origSel
->start
;
277 if (dragType
!= oldDragType
)
278 trackModifyRange(&modRangeStart
, &tempModRangeEnd
, &bufModRangeEnd
,
279 sourceDeletePos
, sourceInserted
, sourceDeleted
);
286 /* Expand the modified-range to include undoing the insert from the last
288 trackModifyRange(&modRangeStart
, &bufModRangeEnd
, &tempModRangeEnd
,
289 tw
->text
.dragInsertPos
, tw
->text
.dragInserted
, tw
->text
.dragDeleted
);
291 /* Find the line number and column of the insert position. Note that in
292 continuous wrap mode, these must be calculated as if the text were
294 TextDXYToUnconstrainedPosition(textD
, max(0, x
- dragXOffset
),
295 max(0, y
- (tw
->text
.dragYOffset
% fontHeight
)), &row
, &column
);
296 column
= TextDOffsetWrappedColumn(textD
, row
, column
);
297 row
= TextDOffsetWrappedRow(textD
, row
);
298 insLineNum
= row
+ textD
->topLineNum
- tw
->text
.dragYOffset
/ fontHeight
;
300 /* find a common point of reference between the two buffers, from which
301 the insert position line number can be translated to a position */
302 if (textD
->firstChar
> modRangeStart
) {
303 referenceLine
= textD
->topLineNum
-
304 BufCountLines(buf
, modRangeStart
, textD
->firstChar
);
305 referencePos
= modRangeStart
;
307 referencePos
= textD
->firstChar
;
308 referenceLine
= textD
->topLineNum
;
311 /* find the position associated with the start of the new line in the
313 insLineStart
= findRelativeLineStart(tempBuf
, referencePos
- tempStart
,
314 referenceLine
, insLineNum
) + tempStart
;
315 if (insLineStart
- tempStart
== tempBuf
->length
)
316 insLineStart
= BufStartOfLine(tempBuf
, insLineStart
- tempStart
) +
319 /* Find the actual insert position */
320 if (rectangular
|| overlay
) {
321 insStart
= insLineStart
;
322 insRectStart
= column
;
323 } else { /* note, this will fail with proportional fonts */
324 insStart
= BufCountForwardDispChars(tempBuf
, insLineStart
- tempStart
,
329 /* If the position is the same as last time, don't bother drawing (it
330 would be nice if this decision could be made earlier) */
331 if (insStart
== tw
->text
.dragInsertPos
&&
332 insRectStart
== tw
->text
.dragRectStart
&& dragType
== oldDragType
) {
337 /* Do the insert in the temporary buffer */
338 if (rectangular
|| overlay
) {
339 insText
= BufGetTextInRect(origBuf
, origSelLineStart
, origSelLineEnd
,
340 origSel
->rectStart
, origSel
->rectEnd
);
342 BufOverlayRect(tempBuf
, insStart
- tempStart
, insRectStart
,
343 insRectStart
+ origSel
->rectEnd
- origSel
->rectStart
,
344 insText
, &insertInserted
, &insertDeleted
);
346 BufInsertCol(tempBuf
, insRectStart
, insStart
- tempStart
, insText
,
347 &insertInserted
, &insertDeleted
);
348 trackModifyRange(&modRangeStart
, &tempModRangeEnd
, &bufModRangeEnd
,
349 insStart
, insertInserted
, insertDeleted
);
352 insText
= BufGetSelectionText(origBuf
);
353 BufInsert(tempBuf
, insStart
- tempStart
, insText
);
354 trackModifyRange(&modRangeStart
, &tempModRangeEnd
, &bufModRangeEnd
,
355 insStart
, origSel
->end
- origSel
->start
, 0);
356 insertInserted
= origSel
->end
- origSel
->start
;
361 /* Make the changes in the real buffer */
362 repText
= BufGetRange(tempBuf
, modRangeStart
- tempStart
,
363 tempModRangeEnd
- tempStart
);
365 TextDBlankCursor(textD
);
366 BufReplace(buf
, modRangeStart
, bufModRangeEnd
, repText
);
369 /* Store the necessary information for undoing this step */
370 tw
->text
.dragInsertPos
= insStart
;
371 tw
->text
.dragRectStart
= insRectStart
;
372 tw
->text
.dragInserted
= insertInserted
;
373 tw
->text
.dragDeleted
= insertDeleted
;
374 tw
->text
.dragSourceDeletePos
= sourceDeletePos
;
375 tw
->text
.dragSourceInserted
= sourceInserted
;
376 tw
->text
.dragSourceDeleted
= sourceDeleted
;
377 tw
->text
.dragType
= dragType
;
379 /* Reset the selection and cursor position */
380 if (rectangular
|| overlay
) {
381 insRectEnd
= insRectStart
+ origSel
->rectEnd
- origSel
->rectStart
;
382 BufRectSelect(buf
, insStart
, insStart
+ insertInserted
, insRectStart
,
384 TextDSetInsertPosition(textD
, BufCountForwardDispChars(buf
,
385 BufCountForwardNLines(buf
, insStart
, tw
->text
.dragNLines
),
388 BufSelect(buf
, insStart
, insStart
+ origSel
->end
- origSel
->start
);
389 TextDSetInsertPosition(textD
, insStart
+ origSel
->end
- origSel
->start
);
391 TextDUnblankCursor(textD
);
392 XtCallCallbacks((Widget
)tw
, textNcursorMovementCallback
, (XtPointer
)NULL
);
393 tw
->text
.emTabsBeforeCursor
= 0;
397 ** Complete a block text drag operation
399 void FinishBlockDrag(TextWidget tw
)
401 dragEndCBStruct endStruct
;
402 int modRangeStart
= -1, origModRangeEnd
, bufModRangeEnd
;
405 /* Find the changed region of the buffer, covering both the deletion
406 of the selected text at the drag start position, and insertion at
407 the drag destination */
408 trackModifyRange(&modRangeStart
, &bufModRangeEnd
, &origModRangeEnd
,
409 tw
->text
.dragSourceDeletePos
, tw
->text
.dragSourceInserted
,
410 tw
->text
.dragSourceDeleted
);
411 trackModifyRange(&modRangeStart
, &bufModRangeEnd
, &origModRangeEnd
,
412 tw
->text
.dragInsertPos
, tw
->text
.dragInserted
,
413 tw
->text
.dragDeleted
);
415 /* Get the original (pre-modified) range of text from saved backup buffer */
416 deletedText
= BufGetRange(tw
->text
.dragOrigBuf
, modRangeStart
,
419 /* Free the backup buffer */
420 BufFree(tw
->text
.dragOrigBuf
);
422 /* Return to normal drag state */
423 tw
->text
.dragState
= NOT_CLICKED
;
425 /* Call finish-drag calback */
426 endStruct
.startPos
= modRangeStart
;
427 endStruct
.nCharsDeleted
= origModRangeEnd
- modRangeStart
;
428 endStruct
.nCharsInserted
= bufModRangeEnd
- modRangeStart
;
429 endStruct
.deletedText
= deletedText
;
430 XtCallCallbacks((Widget
)tw
, textNdragEndCallback
, (XtPointer
)&endStruct
);
435 ** Cancel a block drag operation
437 void CancelBlockDrag(TextWidget tw
)
439 textBuffer
*buf
= tw
->text
.textD
->buffer
;
440 textBuffer
*origBuf
= tw
->text
.dragOrigBuf
;
441 selection
*origSel
= &origBuf
->primary
;
442 int modRangeStart
= -1, origModRangeEnd
, bufModRangeEnd
;
444 dragEndCBStruct endStruct
;
446 /* If the operation was a move, make the modify range reflect the
447 removal of the text from the starting position */
448 if (tw
->text
.dragSourceDeleted
!= 0)
449 trackModifyRange(&modRangeStart
, &bufModRangeEnd
, &origModRangeEnd
,
450 tw
->text
.dragSourceDeletePos
, tw
->text
.dragSourceInserted
,
451 tw
->text
.dragSourceDeleted
);
453 /* Include the insert being undone from the last step in the modified
455 trackModifyRange(&modRangeStart
, &bufModRangeEnd
, &origModRangeEnd
,
456 tw
->text
.dragInsertPos
, tw
->text
.dragInserted
, tw
->text
.dragDeleted
);
458 /* Make the changes in the buffer */
459 repText
= BufGetRange(origBuf
, modRangeStart
, origModRangeEnd
);
460 BufReplace(buf
, modRangeStart
, bufModRangeEnd
, repText
);
463 /* Reset the selection and cursor position */
464 if (origSel
->rectangular
)
465 BufRectSelect(buf
, origSel
->start
, origSel
->end
, origSel
->rectStart
,
468 BufSelect(buf
, origSel
->start
, origSel
->end
);
469 TextDSetInsertPosition(tw
->text
.textD
, buf
->cursorPosHint
);
470 XtCallCallbacks((Widget
)tw
, textNcursorMovementCallback
, NULL
);
471 tw
->text
.emTabsBeforeCursor
= 0;
473 /* Free the backup buffer */
476 /* Indicate end of drag */
477 tw
->text
.dragState
= DRAG_CANCELED
;
479 /* Call finish-drag calback */
480 endStruct
.startPos
= 0;
481 endStruct
.nCharsDeleted
= 0;
482 endStruct
.nCharsInserted
= 0;
483 endStruct
.deletedText
= NULL
;
484 XtCallCallbacks((Widget
)tw
, textNdragEndCallback
, (XtPointer
)&endStruct
);
488 ** Maintain boundaries of changed region between two buffers which
489 ** start out with identical contents, but diverge through insertion,
490 ** deletion, and replacement, such that the buffers can be reconciled
491 ** by replacing the changed region of either buffer with the changed
492 ** region of the other.
494 ** rangeStart is the beginning of the modification region in the shared
495 ** coordinates of both buffers (which are identical up to rangeStart).
496 ** modRangeEnd is the end of the changed region for the buffer being
497 ** modified, unmodRangeEnd is the end of the region for the buffer NOT
498 ** being modified. A value of -1 in rangeStart indicates that there
499 ** have been no modifications so far.
501 static void trackModifyRange(int *rangeStart
, int *modRangeEnd
,
502 int *unmodRangeEnd
, int modPos
, int nInserted
, int nDeleted
)
504 if (*rangeStart
== -1) {
505 *rangeStart
= modPos
;
506 *modRangeEnd
= modPos
+ nInserted
;
507 *unmodRangeEnd
= modPos
+ nDeleted
;
509 if (modPos
< *rangeStart
)
510 *rangeStart
= modPos
;
511 if (modPos
+ nDeleted
> *modRangeEnd
) {
512 *unmodRangeEnd
+= modPos
+ nDeleted
- *modRangeEnd
;
513 *modRangeEnd
= modPos
+ nInserted
;
515 *modRangeEnd
+= nInserted
- nDeleted
;
520 ** Find the left and right margins of text between "start" and "end" in
521 ** buffer "buf". Note that "start is assumed to be at the start of a line.
523 static void findTextMargins(textBuffer
*buf
, int start
, int end
, int *leftMargin
,
527 int pos
, width
= 0, maxWidth
= 0, minWhite
= INT_MAX
, inWhite
= True
;
529 for (pos
=start
; pos
<end
; pos
++) {
530 c
= BufGetCharacter(buf
, pos
);
531 if (inWhite
&& c
!= ' ' && c
!= '\t') {
533 if (width
< minWhite
)
537 if (width
> maxWidth
)
542 width
+= BufCharWidth(c
, width
, buf
->tabDist
, buf
->nullSubsChar
);
544 if (width
> maxWidth
)
546 *leftMargin
= minWhite
== INT_MAX
? 0 : minWhite
;
547 *rightMargin
= maxWidth
;
551 ** Find a text position in buffer "buf" by counting forward or backward
552 ** from a reference position with known line number
554 static int findRelativeLineStart(textBuffer
*buf
, int referencePos
,
555 int referenceLineNum
, int newLineNum
)
557 if (newLineNum
< referenceLineNum
)
558 return BufCountBackwardNLines(buf
, referencePos
,
559 referenceLineNum
- newLineNum
);
560 else if (newLineNum
> referenceLineNum
)
561 return BufCountForwardNLines(buf
, referencePos
,
562 newLineNum
- referenceLineNum
);
563 return BufStartOfLine(buf
, referencePos
);
566 static int min3(int i1
, int i2
, int i3
)
568 if (i1
<= i2
&& i1
<= i3
)
570 return i2
<= i3
? i2
: i3
;
573 static int max3(int i1
, int i2
, int i3
)
575 if (i1
>= i2
&& i1
>= i3
)
577 return i2
>= i3
? i2
: i3
;
580 static int max(int i1
, int i2
)
582 return i1
>= i2
? i1
: i2
;