Warning fix (Andrew Stanaski).
[nedit.git] / source / textDrag.c
blobb33c1943aad9a4f24211d9ef1e158e7e0338b9b8
1 static const char CVSID[] = "$Id: textDrag.c,v 1.9 2004/03/25 07:12:47 n8gray 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. *
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 * Dec. 15, 1995 *
24 * *
25 * Written by Mark Edel *
26 * *
27 *******************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
33 #include "textDrag.h"
34 #include "textBuf.h"
35 #include "textDisp.h"
36 #include "textP.h"
38 #include <limits.h>
40 #include <X11/Intrinsic.h>
41 #include <X11/IntrinsicP.h>
42 #include <Xm/Xm.h>
43 #include <Xm/XmP.h>
44 #if XmVersion >= 1002
45 #include <Xm/PrimitiveP.h>
46 #endif
48 #ifdef HAVE_DEBUG_H
49 #include "../debug.h"
50 #endif
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,
55 int *rightMargin);
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
65 ** selection)
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;
75 int x, y, lineEnd;
77 char *text;
79 /* Save a copy of the whole text buffer as a backup, and for
80 deriving changes */
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);
86 XtFree(text);
87 if (sel->rectangular)
88 BufRectSelect(tw->text.dragOrigBuf, sel->start, sel->end, sel->rectStart,
89 sel->rectEnd);
90 else
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;
99 } else {
100 if (!TextDPositionToXY(textD, sel->start, &x, &y))
101 x = BufCountDispChars(buf, TextDStartOfLine(textD, sel->start),
102 sel->start) * fontWidth + textD->left -
103 textD->horizOffset;
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);
122 XtFree(testText);
123 BufRemoveRect(testBuf, 0, sel->end - sel->start, sel->rectStart,
124 sel->rectEnd);
125 tw->text.dragDeleted = testBuf->length;
126 BufFree(testBuf);
127 tw->text.dragRectStart = sel->rectStart;
128 } else {
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);
146 } else {
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;
174 textBuffer *tempBuf;
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)
188 return;
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);
222 XtFree(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;
236 else
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;
259 if (overlay)
260 BufClearRect(tempBuf, origSelLineStart-tempStart,
261 origSelLineEnd-tempStart, origSel->rectStart,
262 origSel->rectEnd);
263 else
264 BufRemoveRect(tempBuf, origSelLineStart-tempStart,
265 origSelLineEnd-tempStart, origSel->rectStart,
266 origSel->rectEnd);
267 sourceDeletePos = origSelLineStart;
268 sourceInserted = origSelLen - prevLen + tempBuf->length;
269 sourceDeleted = origSelLen;
270 } else {
271 BufRemove(tempBuf, origSel->start - tempStart,
272 origSel->end - tempStart);
273 sourceDeletePos = origSel->start;
274 sourceInserted = 0;
275 sourceDeleted = origSel->end - origSel->start;
277 if (dragType != oldDragType)
278 trackModifyRange(&modRangeStart, &tempModRangeEnd, &bufModRangeEnd,
279 sourceDeletePos, sourceInserted, sourceDeleted);
280 } else {
281 sourceDeletePos = 0;
282 sourceInserted = 0;
283 sourceDeleted = 0;
286 /* Expand the modified-range to include undoing the insert from the last
287 call. */
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
293 not wrapped */
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;
306 } else {
307 referencePos = textD->firstChar;
308 referenceLine = textD->topLineNum;
311 /* find the position associated with the start of the new line in the
312 temporary buffer */
313 insLineStart = findRelativeLineStart(tempBuf, referencePos - tempStart,
314 referenceLine, insLineNum) + tempStart;
315 if (insLineStart - tempStart == tempBuf->length)
316 insLineStart = BufStartOfLine(tempBuf, insLineStart - tempStart) +
317 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,
325 column) + tempStart;
326 insRectStart = 0;
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) {
333 BufFree(tempBuf);
334 return;
337 /* Do the insert in the temporary buffer */
338 if (rectangular || overlay) {
339 insText = BufGetTextInRect(origBuf, origSelLineStart, origSelLineEnd,
340 origSel->rectStart, origSel->rectEnd);
341 if (overlay)
342 BufOverlayRect(tempBuf, insStart - tempStart, insRectStart,
343 insRectStart + origSel->rectEnd - origSel->rectStart,
344 insText, &insertInserted, &insertDeleted);
345 else
346 BufInsertCol(tempBuf, insRectStart, insStart - tempStart, insText,
347 &insertInserted, &insertDeleted);
348 trackModifyRange(&modRangeStart, &tempModRangeEnd, &bufModRangeEnd,
349 insStart, insertInserted, insertDeleted);
350 XtFree(insText);
351 } else {
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;
357 insertDeleted = 0;
358 XtFree(insText);
361 /* Make the changes in the real buffer */
362 repText = BufGetRange(tempBuf, modRangeStart - tempStart,
363 tempModRangeEnd - tempStart);
364 BufFree(tempBuf);
365 TextDBlankCursor(textD);
366 BufReplace(buf, modRangeStart, bufModRangeEnd, repText);
367 XtFree(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,
383 insRectEnd);
384 TextDSetInsertPosition(textD, BufCountForwardDispChars(buf,
385 BufCountForwardNLines(buf, insStart, tw->text.dragNLines),
386 insRectEnd));
387 } else {
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;
403 char *deletedText;
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,
417 origModRangeEnd);
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);
431 XtFree(deletedText);
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;
443 char *repText;
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
454 range. */
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);
461 XtFree(repText);
463 /* Reset the selection and cursor position */
464 if (origSel->rectangular)
465 BufRectSelect(buf, origSel->start, origSel->end, origSel->rectStart,
466 origSel->rectEnd);
467 else
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 */
474 BufFree(origBuf);
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;
508 } else {
509 if (modPos < *rangeStart)
510 *rangeStart = modPos;
511 if (modPos + nDeleted > *modRangeEnd) {
512 *unmodRangeEnd += modPos + nDeleted - *modRangeEnd;
513 *modRangeEnd = modPos + nInserted;
514 } else
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,
524 int *rightMargin)
526 char c;
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') {
532 inWhite = False;
533 if (width < minWhite)
534 minWhite = width;
536 if (c == '\n') {
537 if (width > maxWidth)
538 maxWidth = width;
539 width = 0;
540 inWhite = True;
541 } else
542 width += BufCharWidth(c, width, buf->tabDist, buf->nullSubsChar);
544 if (width > maxWidth)
545 maxWidth = width;
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)
569 return i1;
570 return i2 <= i3 ? i2 : i3;
573 static int max3(int i1, int i2, int i3)
575 if (i1 >= i2 && i1 >= i3)
576 return i1;
577 return i2 >= i3 ? i2 : i3;
580 static int max(int i1, int i2)
582 return i1 >= i2 ? i1 : i2;