themes: Workaround for bug where a background color of RGB 0,0,0 in Black color schem...
[ntk.git] / src / Fl_Text_Display.cxx
blob538392a86e8096cab5daac0bb2dbf3b2d91654be
1 //
2 // "$Id: Fl_Text_Display.cxx 8808 2011-06-16 13:31:25Z manolo $"
3 //
4 // Copyright 2001-2010 by Bill Spitzak and others.
5 // Original code Copyright Mark Edel. Permission to distribute under
6 // the LGPL for the FLTK library granted by Mark Edel.
7 //
8 // This library is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU Library General Public
10 // License as published by the Free Software Foundation; either
11 // version 2 of the License, or (at your option) any later version.
13 // This library is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // Library General Public License for more details.
18 // You should have received a copy of the GNU Library General Public
19 // License along with this library; if not, write to the Free Software
20 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21 // USA.
23 // Please report all bugs and problems on the following page:
25 // http://www.fltk.org/str.php
28 // TODO: rendering of the "optional hyphen"
29 // TODO: make line numbering work again
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <FL/fl_utf8.h>
34 #include "flstring.h"
35 #include <limits.h>
36 #include <ctype.h>
37 #include <FL/Fl.H>
38 #include <FL/Fl_Text_Buffer.H>
39 #include <FL/Fl_Text_Display.H>
40 #include <FL/Fl_Window.H>
41 #include <FL/Fl_Printer.H>
43 #undef min
44 #undef max
46 // Text area margins. Left & right margins should be at least 3 so that
47 // there is some room for the overhanging parts of the cursor!
48 #define TOP_MARGIN 1
49 #define BOTTOM_MARGIN 1
50 #define LEFT_MARGIN 3
51 #define RIGHT_MARGIN 3
53 #define NO_HINT -1
55 /* Masks for text drawing methods. These are or'd together to form an
56 integer which describes what drawing calls to use to draw a string */
57 #define FILL_MASK 0x0100
58 #define SECONDARY_MASK 0x0200
59 #define PRIMARY_MASK 0x0400
60 #define HIGHLIGHT_MASK 0x0800
61 #define BG_ONLY_MASK 0x1000
62 #define TEXT_ONLY_MASK 0x2000
63 #define STYLE_LOOKUP_MASK 0xff
65 /* Maximum displayable line length (how many characters will fit across the
66 widest window). This amount of memory is temporarily allocated from the
67 stack in the draw_vline() method for drawing strings */
68 #define MAX_DISP_LINE_LEN 1000
70 static int max( int i1, int i2 );
71 static int min( int i1, int i2 );
72 static int countlines( const char *string );
74 /* The variables below are used in a timer event to allow smooth
75 scrolling of the text area when the pointer has left the area. */
76 static int scroll_direction = 0;
77 static int scroll_amount = 0;
78 static int scroll_y = 0;
79 static int scroll_x = 0;
81 // CET - FIXME
82 #define TMPFONTWIDTH 6
86 /**
87 \brief Creates a new text display widget.
89 \param X, Y, W, H position and size of widget
90 \param l label text, defaults to none
92 Fl_Text_Display::Fl_Text_Display(int X, int Y, int W, int H, const char* l)
93 : Fl_Group(X, Y, W, H, l) {
94 int i;
96 mMaxsize = 0;
97 damage_range1_start = damage_range1_end = -1;
98 damage_range2_start = damage_range2_end = -1;
99 dragPos = dragging = 0;
100 dragType = DRAG_CHAR;
101 display_insert_position_hint = 0;
102 shortcut_ = 0;
104 color(FL_BACKGROUND2_COLOR, FL_SELECTION_COLOR);
105 box(FL_DOWN_FRAME);
106 textsize(FL_NORMAL_SIZE);
107 textcolor(FL_FOREGROUND_COLOR);
108 textfont(FL_HELVETICA);
109 set_flag(SHORTCUT_LABEL);
111 text_area.x = 0;
112 text_area.y = 0;
113 text_area.w = 0;
114 text_area.h = 0;
116 mVScrollBar = new Fl_Scrollbar(0,0,1,1);
117 mVScrollBar->callback((Fl_Callback*)v_scrollbar_cb, this);
118 mHScrollBar = new Fl_Scrollbar(0,0,1,1);
119 mHScrollBar->callback((Fl_Callback*)h_scrollbar_cb, this);
120 mHScrollBar->type(FL_HORIZONTAL);
122 end();
124 scrollbar_width(Fl::scrollbar_size());
125 scrollbar_align(FL_ALIGN_BOTTOM_RIGHT);
127 mCursorOn = 0;
128 mCursorPos = 0;
129 mCursorOldY = -100;
130 mCursorToHint = NO_HINT;
131 mCursorStyle = NORMAL_CURSOR;
132 mCursorPreferredXPos = -1;
133 mBuffer = 0;
134 mFirstChar = 0;
135 mLastChar = 0;
136 mNBufferLines = 0;
137 mTopLineNum = mTopLineNumHint = 1;
138 mAbsTopLineNum = 1;
139 mNeedAbsTopLineNum = 0;
140 mHorizOffset = mHorizOffsetHint = 0;
142 mCursor_color = FL_FOREGROUND_COLOR;
144 mStyleBuffer = 0;
145 mStyleTable = 0;
146 mNStyles = 0;
147 mNVisibleLines = 1;
148 mLineStarts = new int[mNVisibleLines];
149 mLineStarts[0] = 0;
150 for (i=1; i<mNVisibleLines; i++)
151 mLineStarts[i] = -1;
152 mSuppressResync = 0;
153 mNLinesDeleted = 0;
154 mModifyingTabDistance = 0;
156 mUnfinishedStyle = 0;
157 mUnfinishedHighlightCB = 0;
158 mHighlightCBArg = 0;
160 mLineNumLeft = mLineNumWidth = 0;
161 mContinuousWrap = 0;
162 mWrapMarginPix = 0;
163 mSuppressResync = mNLinesDeleted = mModifyingTabDistance = 0;
169 Free a text display and release its associated memory.
171 Note, the text BUFFER that the text display displays is a separate
172 entity and is not freed, nor are the style buffer or style table.
174 Fl_Text_Display::~Fl_Text_Display() {
175 if (scroll_direction) {
176 Fl::remove_timeout(scroll_timer_cb, this);
177 scroll_direction = 0;
179 if (mBuffer) {
180 mBuffer->remove_modify_callback(buffer_modified_cb, this);
181 mBuffer->remove_predelete_callback(buffer_predelete_cb, this);
183 if (mLineStarts) delete[] mLineStarts;
188 /**
189 Attach a text buffer to display, replacing the current buffer (if any)
190 \param buf attach this text buffer
192 void Fl_Text_Display::buffer( Fl_Text_Buffer *buf ) {
193 /* If the text display is already displaying a buffer, clear it off
194 of the display and remove our callback from it */
195 if ( buf == mBuffer) return;
196 if ( mBuffer != 0 ) {
197 // we must provide a copy of the buffer that we are deleting!
198 char *deletedText = mBuffer->text();
199 buffer_modified_cb( 0, 0, mBuffer->length(), 0, deletedText, this );
200 free(deletedText);
201 mNBufferLines = 0;
202 mBuffer->remove_modify_callback( buffer_modified_cb, this );
203 mBuffer->remove_predelete_callback( buffer_predelete_cb, this );
206 /* Add the buffer to the display, and attach a callback to the buffer for
207 receiving modification information when the buffer contents change */
208 mBuffer = buf;
209 if (mBuffer) {
210 mBuffer->add_modify_callback( buffer_modified_cb, this );
211 mBuffer->add_predelete_callback( buffer_predelete_cb, this );
213 /* Update the display */
214 buffer_modified_cb( 0, buf->length(), 0, 0, 0, this );
217 /* Resize the widget to update the screen... */
218 resize(x(), y(), w(), h());
223 /**
224 \brief Attach (or remove) highlight information in text display and redisplay.
226 Highlighting information consists of a style buffer which parallels the
227 normal text buffer, but codes font and color information for the display;
228 a style table which translates style buffer codes (indexed by buffer
229 character - 'A') into fonts and colors; and a callback mechanism for
230 as-needed highlighting, triggered by a style buffer entry of
231 "unfinishedStyle". Style buffer can trigger additional redisplay during
232 a normal buffer modification if the buffer contains a primary Fl_Text_Selection
233 (see extendRangeForStyleMods for more information on this protocol).
235 Style buffers, tables and their associated memory are managed by the caller.
237 Styles are ranged from 65 ('A') to 126.
239 \param styleBuffer this buffer works in parallel to the text buffer. For every
240 character in the text buffer, the stye buffer has a byte at the same offset
241 that contains an index into an array of possible styles.
242 \param styleTable a list of styles indexed by the style buffer
243 \param nStyles number of styles in the style table
244 \param unfinishedStyle if this style is found, the callback below is called
245 \param unfinishedHighlightCB if a character with an unfinished style is found,
246 this callback will be called
247 \param cbArg and optional argument for the callback above, usually a pointer
248 to the Text Display.
250 void Fl_Text_Display::highlight_data(Fl_Text_Buffer *styleBuffer,
251 const Style_Table_Entry *styleTable,
252 int nStyles, char unfinishedStyle,
253 Unfinished_Style_Cb unfinishedHighlightCB,
254 void *cbArg ) {
255 mStyleBuffer = styleBuffer;
256 mStyleTable = styleTable;
257 mNStyles = nStyles;
258 mUnfinishedStyle = unfinishedStyle;
259 mUnfinishedHighlightCB = unfinishedHighlightCB;
260 mHighlightCBArg = cbArg;
261 mColumnScale = 0;
263 mStyleBuffer->canUndo(0);
264 damage(FL_DAMAGE_EXPOSE);
270 \brief Find the longest line of all visible lines.
271 \return the width of the longest visible line in pixels
273 int Fl_Text_Display::longest_vline() const {
274 int longest = 0;
275 for (int i = 0; i < mNVisibleLines; i++)
276 longest = max(longest, measure_vline(i));
277 return longest;
283 \brief Change the size of the displayed text area.
284 Calling this function will trigger a recalculation of all lines visible and
285 of all scrollbar sizes.
286 \param X, Y, W, H new position and size of this widget
288 void Fl_Text_Display::resize(int X, int Y, int W, int H) {
289 #ifdef DEBUG
290 printf("Fl_Text_Display::resize(X=%d, Y=%d, W=%d, H=%d)\n", X, Y, W, H);
291 #endif // DEBUG
292 const int oldWidth = w();
293 #ifdef DEBUG
294 printf(" oldWidth=%d, mContinuousWrap=%d, mWrapMargin=%d\n", oldWidth, mContinuousWrap, mWrapMargin);
295 #endif // DEBUG
296 Fl_Widget::resize(X,Y,W,H);
297 if (!buffer()) return;
298 X += Fl::box_dx(box());
299 Y += Fl::box_dy(box());
300 W -= Fl::box_dw(box());
301 H -= Fl::box_dh(box());
303 text_area.x = X+LEFT_MARGIN;
304 text_area.y = Y+TOP_MARGIN;
305 text_area.w = W-LEFT_MARGIN-RIGHT_MARGIN;
306 text_area.h = H-TOP_MARGIN-BOTTOM_MARGIN;
307 int i;
309 /* Find the new maximum font height for this text display */
310 for (i = 0, mMaxsize = fl_height(textfont(), textsize()); i < mNStyles; i++)
311 mMaxsize = max(mMaxsize, fl_height(mStyleTable[i].font, mStyleTable[i].size));
313 // did we have scrollbars initially?
314 unsigned int hscrollbarvisible = mHScrollBar->visible();
315 unsigned int vscrollbarvisible = mVScrollBar->visible();
317 // try without scrollbars first
318 mVScrollBar->clear_visible();
319 mHScrollBar->clear_visible();
321 for (int again = 1; again;) {
322 again = 0;
323 /* In continuous wrap mode, a change in width affects the total number of
324 lines in the buffer, and can leave the top line number incorrect, and
325 the top character no longer pointing at a valid line start */
326 if (mContinuousWrap && !mWrapMarginPix && W!=oldWidth) {
327 int oldFirstChar = mFirstChar;
328 mNBufferLines = count_lines(0, buffer()->length(), true);
329 mFirstChar = line_start(mFirstChar);
330 mTopLineNum = count_lines(0, mFirstChar, true)+1;
331 absolute_top_line_number(oldFirstChar);
332 #ifdef DEBUG
333 printf(" mNBufferLines=%d\n", mNBufferLines);
334 #endif // DEBUG
337 /* reallocate and update the line starts array, which may have changed
338 size and / or contents. */
339 int nvlines = (text_area.h + mMaxsize - 1) / mMaxsize;
340 if (nvlines < 1) nvlines = 1;
341 if (mNVisibleLines != nvlines) {
342 mNVisibleLines = nvlines;
343 if (mLineStarts) delete[] mLineStarts;
344 mLineStarts = new int [mNVisibleLines];
347 calc_line_starts(0, mNVisibleLines);
348 calc_last_char();
350 // figure the scrollbars
351 if (scrollbar_width()) {
352 /* Decide if the vertical scrollbar needs to be visible */
353 if (scrollbar_align() & (FL_ALIGN_LEFT|FL_ALIGN_RIGHT) &&
354 mNBufferLines >= mNVisibleLines - 1)
356 mVScrollBar->set_visible();
357 if (scrollbar_align() & FL_ALIGN_LEFT) {
358 text_area.x = X+scrollbar_width()+LEFT_MARGIN;
359 text_area.w = W-scrollbar_width()-LEFT_MARGIN-RIGHT_MARGIN;
360 mVScrollBar->resize(X, text_area.y-TOP_MARGIN, scrollbar_width(),
361 text_area.h+TOP_MARGIN+BOTTOM_MARGIN);
362 } else {
363 text_area.x = X+LEFT_MARGIN;
364 text_area.w = W-scrollbar_width()-LEFT_MARGIN-RIGHT_MARGIN;
365 mVScrollBar->resize(X+W-scrollbar_width(), text_area.y-TOP_MARGIN,
366 scrollbar_width(), text_area.h+TOP_MARGIN+BOTTOM_MARGIN);
371 Decide if the horizontal scrollbar needs to be visible. If there
372 is a vertical scrollbar, a horizontal is always created too. This
373 is because the alternatives are unattractive:
374 * Dynamically creating a horizontal scrollbar based on the currently
375 visible lines is what the original nedit does, but it always wastes
376 space for the scrollbar even when it's not used. Since the FLTK
377 widget dynamically allocates the space for the scrollbar and
378 rearranges the widget to make room for it, this would create a very
379 visually displeasing "bounce" effect when the vertical scrollbar is
380 dragged. Trust me, I tried it and it looks really bad.
381 * The other alternative would be to keep track of what the longest
382 line in the entire buffer is and base the scrollbar on that. I
383 didn't do this because I didn't see any easy way to do that using
384 the nedit code and this could involve a lengthy calculation for
385 large buffers. If an efficient and non-costly way of doing this
386 can be found, this might be a way to go.
388 /* WAS: Suggestion: Try turning the horizontal scrollbar on when
389 you first see a line that is too wide in the window, but then
390 don't turn it off (ie mix both of your solutions). */
391 if (scrollbar_align() & (FL_ALIGN_TOP|FL_ALIGN_BOTTOM) &&
392 (longest_vline() > text_area.w))
394 if (!mHScrollBar->visible()) {
395 mHScrollBar->set_visible();
396 again = 1; // loop again to see if we now need vert. & recalc sizes
398 if (scrollbar_align() & FL_ALIGN_TOP) {
399 text_area.y = Y + scrollbar_width()+TOP_MARGIN;
400 text_area.h = H - scrollbar_width()-TOP_MARGIN-BOTTOM_MARGIN;
401 mHScrollBar->resize(text_area.x-LEFT_MARGIN, Y,
402 text_area.w+LEFT_MARGIN+RIGHT_MARGIN, scrollbar_width());
403 } else {
404 text_area.y = Y+TOP_MARGIN;
405 text_area.h = H - scrollbar_width()-TOP_MARGIN-BOTTOM_MARGIN;
406 mHScrollBar->resize(text_area.x-LEFT_MARGIN, Y+H-scrollbar_width(),
407 text_area.w+LEFT_MARGIN+RIGHT_MARGIN, scrollbar_width());
413 // user request to change viewport
414 if (mTopLineNumHint != mTopLineNum || mHorizOffsetHint != mHorizOffset)
415 scroll_(mTopLineNumHint, mHorizOffsetHint);
417 // everything will fit in the viewport
418 if (mNBufferLines < mNVisibleLines || mBuffer == NULL || mBuffer->length() == 0) {
419 scroll_(1, mHorizOffset);
420 /* if empty lines become visible, there may be an opportunity to
421 display more text by scrolling down */
422 } else {
423 while ( mNVisibleLines>=2
424 && (mLineStarts[mNVisibleLines-2]==-1)
425 && scroll_(mTopLineNum-1, mHorizOffset))
429 // user request to display insert position
430 if (display_insert_position_hint)
431 display_insert();
433 // in case horizontal offset is now greater than longest line
434 int maxhoffset = max(0, longest_vline()-text_area.w);
435 if (mHorizOffset > maxhoffset)
436 scroll_(mTopLineNumHint, maxhoffset);
438 mTopLineNumHint = mTopLineNum;
439 mHorizOffsetHint = mHorizOffset;
440 display_insert_position_hint = 0;
442 if (mContinuousWrap ||
443 hscrollbarvisible != mHScrollBar->visible() ||
444 vscrollbarvisible != mVScrollBar->visible())
445 redraw();
447 update_v_scrollbar();
448 update_h_scrollbar();
454 \brief Refresh a rectangle of the text display.
455 \param left, top are in coordinates of the text drawing window.
456 \param width, height size in pixels
458 void Fl_Text_Display::draw_text( int left, int top, int width, int height ) {
459 int fontHeight, firstLine, lastLine, line;
461 /* find the line number range of the display */
462 fontHeight = mMaxsize ? mMaxsize : textsize_;
463 firstLine = ( top - text_area.y - fontHeight + 1 ) / fontHeight;
464 lastLine = ( top + height - text_area.y ) / fontHeight + 1;
466 fl_push_clip( left, top, width, height );
468 /* draw the lines */
469 for ( line = firstLine; line <= lastLine; line++ )
470 draw_vline( line, left, left + width, 0, INT_MAX );
472 /* draw the line numbers if exposed area includes them */
473 if (mLineNumWidth != 0 && left <= mLineNumLeft + mLineNumWidth)
474 draw_line_numbers(false);
476 fl_pop_clip();
481 /**
482 \brief Marks text from start to end as needing a redraw.
483 This function will trigger a damage event and later a redraw of parts of
484 the widget.
485 \param startpos index of first character needing redraw
486 \param endpos index after last character needing redraw
488 void Fl_Text_Display::redisplay_range(int startpos, int endpos) {
489 IS_UTF8_ALIGNED2(buffer(), startpos)
490 IS_UTF8_ALIGNED2(buffer(), endpos)
492 if (damage_range1_start == -1 && damage_range1_end == -1) {
493 damage_range1_start = startpos;
494 damage_range1_end = endpos;
495 } else if ((startpos >= damage_range1_start && startpos <= damage_range1_end) ||
496 (endpos >= damage_range1_start && endpos <= damage_range1_end)) {
497 damage_range1_start = min(damage_range1_start, startpos);
498 damage_range1_end = max(damage_range1_end, endpos);
499 } else if (damage_range2_start == -1 && damage_range2_end == -1) {
500 damage_range2_start = startpos;
501 damage_range2_end = endpos;
502 } else {
503 damage_range2_start = min(damage_range2_start, startpos);
504 damage_range2_end = max(damage_range2_end, endpos);
506 damage(FL_DAMAGE_SCROLL);
512 \brief Draw a range of text.
514 Refresh all of the text between buffer positions \p startpos and
515 \p endpos not including the character at the position \p endpos.
517 If \p endpos points beyond the end of the buffer, refresh the whole display
518 after \p startpos, including blank lines which are not technically part of
519 any range of characters.
521 \param startpos index of first character to draw
522 \param endpos index after last character to draw
524 void Fl_Text_Display::draw_range(int startpos, int endpos) {
525 startpos = buffer()->utf8_align(startpos);
526 endpos = buffer()->utf8_align(endpos);
528 int i, startLine, lastLine, startIndex, endIndex;
530 /* If the range is outside of the displayed text, just return */
531 if ( endpos < mFirstChar || ( startpos > mLastChar && !empty_vlines() ) )
532 return;
534 /* Clean up the starting and ending values */
535 if ( startpos < 0 ) startpos = 0;
536 if ( startpos > mBuffer->length() ) startpos = mBuffer->length();
537 if ( endpos < 0 ) endpos = 0;
538 if ( endpos > mBuffer->length() ) endpos = mBuffer->length();
540 /* Get the starting and ending lines */
541 if ( startpos < mFirstChar )
542 startpos = mFirstChar;
543 if ( !position_to_line( startpos, &startLine ) )
544 startLine = mNVisibleLines - 1;
545 if ( endpos >= mLastChar ) {
546 lastLine = mNVisibleLines - 1;
547 } else {
548 if ( !position_to_line( endpos, &lastLine ) ) {
549 /* shouldn't happen */
550 lastLine = mNVisibleLines - 1;
554 /* Get the starting and ending positions within the lines */
555 startIndex = mLineStarts[ startLine ] == -1 ? 0 : startpos - mLineStarts[ startLine ];
556 if ( endpos >= mLastChar )
557 endIndex = INT_MAX;
558 else if ( mLineStarts[ lastLine ] == -1 )
559 endIndex = 0;
560 else
561 endIndex = endpos - mLineStarts[ lastLine ];
563 /* If the starting and ending lines are the same, redisplay the single
564 line between "start" and "end" */
565 if ( startLine == lastLine ) {
566 draw_vline( startLine, 0, INT_MAX, startIndex, endIndex );
567 return;
570 /* Redisplay the first line from "start" */
571 draw_vline( startLine, 0, INT_MAX, startIndex, INT_MAX );
573 /* Redisplay the lines in between at their full width */
574 for ( i = startLine + 1; i < lastLine; i++ )
575 draw_vline( i, 0, INT_MAX, 0, INT_MAX );
577 /* Redisplay the last line to "end" */
578 draw_vline( lastLine, 0, INT_MAX, 0, endIndex );
583 /**
584 \brief Sets the position of the text insertion cursor for text display.
585 Move the insertion cursor in front of the character at \p newPos.
586 This function may trigger a redraw.
587 \param newPos new caret position
589 void Fl_Text_Display::insert_position( int newPos ) {
590 IS_UTF8_ALIGNED2(buffer(), newPos)
592 /* make sure new position is ok, do nothing if it hasn't changed */
593 if ( newPos == mCursorPos ) return;
594 if ( newPos < 0 ) newPos = 0;
595 if ( newPos > mBuffer->length() ) newPos = mBuffer->length();
597 /* cursor movement cancels vertical cursor motion column */
598 mCursorPreferredXPos = -1;
600 /* erase the cursor at its previous position */
601 redisplay_range(buffer()->prev_char_clipped(mCursorPos), buffer()->next_char(mCursorPos));
603 mCursorPos = newPos;
605 /* draw cursor at its new position */
606 redisplay_range(buffer()->prev_char_clipped(mCursorPos), buffer()->next_char(mCursorPos));
611 /**
612 \brief Shows the text cursor.
613 This function may trigger a redraw.
614 \param b show(1) or hide(0) the text cursor (caret).
616 void Fl_Text_Display::show_cursor(int b) {
617 mCursorOn = b;
618 redisplay_range(buffer()->prev_char_clipped(mCursorPos), buffer()->next_char(mCursorPos));
624 \brief Sets the text cursor style.
625 Sets the text cursor style to one of the following:
627 \li Fl_Text_Display::NORMAL_CURSOR - Shows an I beam.
628 \li Fl_Text_Display::CARET_CURSOR - Shows a caret under the text.
629 \li Fl_Text_Display::DIM_CURSOR - Shows a dimmed I beam.
630 \li Fl_Text_Display::BLOCK_CURSOR - Shows an unfilled box around the current
631 character.
632 \li Fl_Text_Display::HEAVY_CURSOR - Shows a thick I beam.
634 This call also switches the cursor on and may trigger a redraw.
636 \param style new cursor style
638 void Fl_Text_Display::cursor_style(int style) {
639 mCursorStyle = style;
640 if (mCursorOn) show_cursor();
646 \brief Set the new text wrap mode.
648 If \p wrap mode is not zero, this call enables automatic word wrapping at column
649 \p wrapMargin. Word-wrapping does not change the text buffer itself, only the way
650 the text is displayed. Different Text Displays can have different wrap modes,
651 even if they share the same Text Buffer.
653 \param wrap new wrap mode is WRAP_NONE (don't wrap text at all), WRAP_AT_COLUMN
654 (wrap text at the given text column), WRAP_AT_PIXEL (wrap text at a pixel
655 position), or WRAP_AT_BOUNDS (wrap text so that it fits into the
656 widget width)
657 \param wrapMargin in WRAP_AT_COLUMN mode, text will wrap at the n'th character.
658 For variable width fonts, an average character width is calculated. The
659 column width is calculated using the current textfont or the first style
660 when this function is called. If the font size changes, this function
661 must be called again. In WRAP_AT_PIXEL mode, this is the pixel position.
662 \todo we need new wrap modes to wrap at the window edge and based on pixel width
663 or average character width.
665 void Fl_Text_Display::wrap_mode(int wrap, int wrapMargin) {
666 switch (wrap) {
667 case WRAP_NONE:
668 mWrapMarginPix = 0;
669 mContinuousWrap = 0;
670 break;
671 case WRAP_AT_COLUMN:
672 default:
673 mWrapMarginPix = int(col_to_x(wrapMargin));
674 mContinuousWrap = 1;
675 break;
676 case WRAP_AT_PIXEL:
677 mWrapMarginPix = wrapMargin;
678 mContinuousWrap = 1;
679 break;
680 case WRAP_AT_BOUNDS:
681 mWrapMarginPix = 0;
682 mContinuousWrap = 1;
683 break;
686 if (buffer()) {
687 /* wrapping can change the total number of lines, re-count */
688 mNBufferLines = count_lines(0, buffer()->length(), true);
690 /* changing wrap margins or changing from wrapped mode to non-wrapped
691 can leave the character at the top no longer at a line start, and/or
692 change the line number */
693 mFirstChar = line_start(mFirstChar);
694 mTopLineNum = count_lines(0, mFirstChar, true) + 1;
696 reset_absolute_top_line_number();
698 /* update the line starts array */
699 calc_line_starts(0, mNVisibleLines);
700 calc_last_char();
701 } else {
702 // No buffer, so just clear the state info for later...
703 mNBufferLines = 0;
704 mFirstChar = 0;
705 mTopLineNum = 1;
706 mAbsTopLineNum = 0;
709 resize(x(), y(), w(), h());
715 \brief Inserts "text" at the current cursor location.
717 This has the same effect as inserting the text into the buffer using BufInsert
718 and then moving the insert position after the newly inserted text, except
719 that it's optimized to do less redrawing.
721 \param text new text in UTF-8 encoding.
723 void Fl_Text_Display::insert(const char* text) {
724 IS_UTF8_ALIGNED2(buffer(), mCursorPos)
725 IS_UTF8_ALIGNED(text)
727 int pos = mCursorPos;
729 mCursorToHint = pos + strlen( text );
730 mBuffer->insert( pos, text );
731 mCursorToHint = NO_HINT;
736 /**
737 \brief Replaces text at the current insert position.
738 \param text new text in UTF-8 encoding
740 \todo Unicode? Find out exactly what we do here and simplify.
742 void Fl_Text_Display::overstrike(const char* text) {
743 IS_UTF8_ALIGNED2(buffer(), mCursorPos)
744 IS_UTF8_ALIGNED(text)
746 int startPos = mCursorPos;
747 Fl_Text_Buffer *buf = mBuffer;
748 int lineStart = buf->line_start( startPos );
749 int textLen = strlen( text );
750 int i, p, endPos, indent, startIndent, endIndent;
751 const char *c;
752 unsigned int ch;
753 char *paddedText = NULL;
755 /* determine how many displayed character positions are covered */
756 startIndent = mBuffer->count_displayed_characters( lineStart, startPos );
757 indent = startIndent;
758 for ( c = text; *c != '\0'; c += fl_utf8len1(*c) )
759 indent++;
760 endIndent = indent;
762 /* find which characters to remove, and if necessary generate additional
763 padding to make up for removed control characters at the end */
764 indent = startIndent;
765 for ( p = startPos; ; p=buffer()->next_char(p) ) {
766 if ( p == buf->length() )
767 break;
768 ch = buf->char_at( p );
769 if ( ch == '\n' )
770 break;
771 indent++;
772 if ( indent == endIndent ) {
773 p++;
774 break;
775 } else if ( indent > endIndent ) {
776 if ( ch != '\t' ) {
777 p++;
778 paddedText = new char [ textLen + FL_TEXT_MAX_EXP_CHAR_LEN + 1 ];
779 strcpy( paddedText, text );
780 for ( i = 0; i < indent - endIndent; i++ )
781 paddedText[ textLen + i ] = ' ';
782 paddedText[ textLen + i ] = '\0';
784 break;
787 endPos = p;
789 mCursorToHint = startPos + textLen;
790 buf->replace( startPos, endPos, paddedText == NULL ? text : paddedText );
791 mCursorToHint = NO_HINT;
792 if ( paddedText != NULL )
793 delete [] paddedText;
799 \brief Convert a character index into a pixel position.
801 Translate a buffer text position to the XY location where the top left of the
802 cursor would be positioned to point to that character. Returns 0 if the
803 position is not displayed because it is \e \b vertically out of view.
804 If the position is horizontally out of view, returns the X coordinate where
805 the position would be if it were visible.
807 \param pos character index
808 \param[out] X, Y pixel position of character on screen
809 \return 0 if character vertically out of view, X position otherwise
811 int Fl_Text_Display::position_to_xy( int pos, int* X, int* Y ) const {
812 IS_UTF8_ALIGNED2(buffer(), pos)
814 int lineStartPos, fontHeight, lineLen;
815 int visLineNum;
817 /* If position is not displayed, return false */
818 if (pos < mFirstChar || (pos > mLastChar && !empty_vlines())) {
819 return 0;
822 /* Calculate Y coordinate */
823 if (!position_to_line(pos, &visLineNum)) {
824 return 0;
826 if (visLineNum < 0 || visLineNum > mNBufferLines) {
827 return 0;
830 fontHeight = mMaxsize;
831 *Y = text_area.y + visLineNum * fontHeight;
833 /* Get the text, length, and buffer position of the line. If the position
834 is beyond the end of the buffer and should be at the first position on
835 the first empty line, don't try to get or scan the text */
836 lineStartPos = mLineStarts[visLineNum];
837 if ( lineStartPos == -1 ) {
838 *X = text_area.x - mHorizOffset;
839 return 1;
841 lineLen = vline_length( visLineNum );
842 *X = text_area.x + handle_vline(GET_WIDTH, lineStartPos, pos-lineStartPos, 0, 0, 0, 0, 0, 0) - mHorizOffset;
843 return 1;
849 \brief Find the line and column number of position \p pos.
851 This only works for displayed lines. If the line is not displayed, the
852 function returns 0 (without the mLineStarts array it could turn in to very long
853 calculation involving scanning large amounts of text in the buffer).
854 If continuous wrap mode is on, returns the absolute line number (as opposed
855 to the wrapped line number which is used for scrolling).
857 \param pos character index
858 \param[out] lineNum absolute (unwrapped) line number
859 \param[out] column character offset to the beginning of the line
860 \return 0 if \p pos is off screen, line number otherwise
861 \todo a column number makes little sense in the UTF-8/variable font width
862 environment. We will have to further define what exactly we want to return.
863 Please check the functions that call this particular function.
865 int Fl_Text_Display::position_to_linecol( int pos, int* lineNum, int* column ) const {
866 IS_UTF8_ALIGNED2(buffer(), pos)
868 int retVal;
870 /* In continuous wrap mode, the absolute (non-wrapped) line count is
871 maintained separately, as needed. Only return it if we're actually
872 keeping track of it and pos is in the displayed text */
873 if (mContinuousWrap) {
874 if (!maintaining_absolute_top_line_number() || pos < mFirstChar || pos > mLastChar)
875 return 0;
876 *lineNum = mAbsTopLineNum + buffer()->count_lines(mFirstChar, pos);
877 *column = buffer()->count_displayed_characters(buffer()->line_start(pos), pos);
878 return 1;
881 retVal = position_to_line( pos, lineNum );
882 if ( retVal ) {
883 *column = mBuffer->count_displayed_characters( mLineStarts[ *lineNum ], pos );
884 *lineNum += mTopLineNum;
886 return retVal;
892 \brief Check if a pixel position is within the primary selection.
893 \param X, Y pixel position to test
894 \return 1 if position (X, Y) is inside of the primary Fl_Text_Selection
896 int Fl_Text_Display::in_selection( int X, int Y ) const {
897 int pos = xy_to_position( X, Y, CHARACTER_POS );
898 IS_UTF8_ALIGNED2(buffer(), pos)
899 Fl_Text_Buffer *buf = mBuffer;
900 return buf->primary_selection()->includes(pos);
906 \brief Nobody knows what this function does.
908 Correct a column number based on an unconstrained position (as returned by
909 TextDXYToUnconstrainedPosition) to be relative to the last actual newline
910 in the buffer before the row and column position given, rather than the
911 last line start created by line wrapping. This is an adapter
912 for rectangular selections and code written before continuous wrap mode,
913 which thinks that the unconstrained column is the number of characters
914 from the last newline. Obviously this is time consuming, because it
915 invloves character re-counting.
917 \param row
918 \param column
919 \return something unknown
920 \todo What does this do and how is it useful? Column numbers mean little in
921 this context. Which functions depend on this one?
923 \todo Unicode?
925 int Fl_Text_Display::wrapped_column(int row, int column) const {
926 int lineStart, dispLineStart;
928 if (!mContinuousWrap || row < 0 || row > mNVisibleLines)
929 return column;
930 dispLineStart = mLineStarts[row];
931 if (dispLineStart == -1)
932 return column;
933 lineStart = buffer()->line_start(dispLineStart);
934 return column + buffer()->count_displayed_characters(lineStart, dispLineStart);
940 \brief Nobody knows what this function does.
942 Correct a row number from an unconstrained position (as returned by
943 TextDXYToUnconstrainedPosition) to a straight number of newlines from the
944 top line of the display. Because rectangular selections are based on
945 newlines, rather than display wrapping, and anywhere a rectangular selection
946 needs a row, it needs it in terms of un-wrapped lines.
948 \param row
949 \return something unknown
950 \todo What does this do and how is it useful? Column numbers mean little in
951 this context. Which functions depend on this one?
953 int Fl_Text_Display::wrapped_row(int row) const {
954 if (!mContinuousWrap || row < 0 || row > mNVisibleLines)
955 return row;
956 return buffer()->count_lines(mFirstChar, mLineStarts[row]);
962 \brief Scroll the display to bring insertion cursor into view.
964 Note: it would be nice to be able to do this without counting lines twice
965 (scroll_() counts them too) and/or to count from the most efficient
966 starting point, but the efficiency of this routine is not as important to
967 the overall performance of the text display.
969 \todo Unicode?
971 void Fl_Text_Display::display_insert() {
972 int hOffset, topLine, X, Y;
973 hOffset = mHorizOffset;
974 topLine = mTopLineNum;
976 if (insert_position() < mFirstChar) {
977 topLine -= count_lines(insert_position(), mFirstChar, false);
978 } else if (mNVisibleLines>=2 && mLineStarts[mNVisibleLines-2] != -1) {
979 int lastChar = line_end(mLineStarts[mNVisibleLines-2],true);
980 if (insert_position() >= lastChar)
981 topLine += count_lines(lastChar - (wrap_uses_character(mLastChar) ? 0 : 1),
982 insert_position(), false);
985 /* Find the new setting for horizontal offset (this is a bit ungraceful).
986 If the line is visible, just use PositionToXY to get the position
987 to scroll to, otherwise, do the vertical scrolling first, then the
988 horizontal */
989 if (!position_to_xy( mCursorPos, &X, &Y )) {
990 scroll_(topLine, hOffset);
991 if (!position_to_xy( mCursorPos, &X, &Y )) {
992 #ifdef DEBUG
993 printf ("*** display_insert/position_to_xy # GIVE UP !\n"); fflush(stdout);
994 #endif // DEBUG
995 return; /* Give up, it's not worth it (but why does it fail?) */
998 if (X > text_area.x + text_area.w)
999 hOffset += X-(text_area.x + text_area.w);
1000 else if (X < text_area.x)
1001 hOffset += X-text_area.x;
1003 /* Do the scroll */
1004 if (topLine != mTopLineNum || hOffset != mHorizOffset)
1005 scroll_(topLine, hOffset);
1009 /**
1010 \brief Scrolls the text buffer to show the current insert position.
1011 This function triggers a complete recalculation, ending in a call to
1012 Fl_Text_Display::display_insert()
1014 void Fl_Text_Display::show_insert_position() {
1015 display_insert_position_hint = 1;
1016 resize(x(), y(), w(), h());
1021 Cursor movement functions
1024 /**
1025 \brief Moves the current insert position right one character.
1026 \return 1 if the cursor moved, 0 if the end of the text was reached
1028 int Fl_Text_Display::move_right() {
1029 if ( mCursorPos >= mBuffer->length() )
1030 return 0;
1031 int p = insert_position();
1032 int q = buffer()->next_char(p);
1033 insert_position(q);
1034 return 1;
1039 /**
1040 \brief Moves the current insert position left one character.
1041 \return 1 if the cursor moved, 0 if the beginning of the text was reached
1043 int Fl_Text_Display::move_left() {
1044 if ( mCursorPos <= 0 )
1045 return 0;
1046 int p = insert_position();
1047 int q = buffer()->prev_char_clipped(p);
1048 insert_position(q);
1049 return 1;
1054 /**
1055 \brief Moves the current insert position up one line.
1056 \return 1 if the cursor moved, 0 if the beginning of the text was reached
1058 int Fl_Text_Display::move_up() {
1059 int lineStartPos, xPos, prevLineStartPos, newPos, visLineNum;
1061 /* Find the position of the start of the line. Use the line starts array
1062 if possible */
1063 if ( position_to_line( mCursorPos, &visLineNum ) )
1064 lineStartPos = mLineStarts[ visLineNum ];
1065 else {
1066 lineStartPos = line_start( mCursorPos );
1067 visLineNum = -1;
1069 if ( lineStartPos == 0 )
1070 return 0;
1072 /* Decide what column to move to, if there's a preferred column use that */
1073 if (mCursorPreferredXPos >= 0)
1074 xPos = mCursorPreferredXPos;
1075 else
1076 xPos = handle_vline(GET_WIDTH, lineStartPos, mCursorPos-lineStartPos,
1077 0, 0, 0, 0, 0, INT_MAX);
1079 /* count forward from the start of the previous line to reach the column */
1080 if ( visLineNum != -1 && visLineNum != 0 )
1081 prevLineStartPos = mLineStarts[ visLineNum - 1 ];
1082 else
1083 prevLineStartPos = rewind_lines( lineStartPos, 1 );
1085 int lineEnd = line_end(prevLineStartPos, true);
1086 newPos = handle_vline(FIND_INDEX_FROM_ZERO, prevLineStartPos, lineEnd-prevLineStartPos,
1087 0, 0, 0, 0, 0, xPos);
1089 /* move the cursor */
1090 insert_position( newPos );
1092 /* if a preferred column wasn't aleady established, establish it */
1093 mCursorPreferredXPos = xPos;
1094 return 1;
1099 /**
1100 \brief Moves the current insert position down one line.
1101 \return 1 if the cursor moved, 0 if the beginning of the text was reached
1103 int Fl_Text_Display::move_down() {
1104 int lineStartPos, xPos, newPos, visLineNum;
1106 if ( mCursorPos == mBuffer->length() )
1107 return 0;
1109 if ( position_to_line( mCursorPos, &visLineNum ) )
1110 lineStartPos = mLineStarts[ visLineNum ];
1111 else {
1112 lineStartPos = line_start( mCursorPos );
1113 visLineNum = -1;
1115 if (mCursorPreferredXPos >= 0) {
1116 xPos = mCursorPreferredXPos;
1117 } else {
1118 xPos = handle_vline(GET_WIDTH, lineStartPos, mCursorPos-lineStartPos,
1119 0, 0, 0, 0, 0, INT_MAX);
1122 int nextLineStartPos = skip_lines( lineStartPos, 1, true );
1123 int lineEnd = line_end(nextLineStartPos, true);
1124 newPos = handle_vline(FIND_INDEX_FROM_ZERO, nextLineStartPos, lineEnd-nextLineStartPos,
1125 0, 0, 0, 0, 0, xPos);
1127 insert_position( newPos );
1128 mCursorPreferredXPos = xPos;
1129 return 1;
1135 \brief Count the number of lines between two positions.
1137 Same as BufCountLines, but takes into account wrapping if wrapping is
1138 turned on. If the caller knows that \p startPos is at a line start, it
1139 can pass \p startPosIsLineStart as True to make the call more efficient
1140 by avoiding the additional step of scanning back to the last newline.
1142 \param startPos index to first character
1143 \param endPos index after last character
1144 \param startPosIsLineStart avoid scanning back to the line start
1145 \return number of lines
1147 int Fl_Text_Display::count_lines(int startPos, int endPos,
1148 bool startPosIsLineStart) const {
1149 IS_UTF8_ALIGNED2(buffer(), startPos)
1150 IS_UTF8_ALIGNED2(buffer(), endPos)
1152 int retLines, retPos, retLineStart, retLineEnd;
1154 #ifdef DEBUG
1155 printf("Fl_Text_Display::count_lines(startPos=%d, endPos=%d, startPosIsLineStart=%d\n",
1156 startPos, endPos, startPosIsLineStart);
1157 #endif // DEBUG
1159 /* If we're not wrapping use simple (and more efficient) BufCountLines */
1160 if (!mContinuousWrap)
1161 return buffer()->count_lines(startPos, endPos);
1163 wrapped_line_counter(buffer(), startPos, endPos, INT_MAX,
1164 startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1165 &retLineEnd);
1167 #ifdef DEBUG
1168 printf(" # after WLC: retPos=%d, retLines=%d, retLineStart=%d, retLineEnd=%d\n",
1169 retPos, retLines, retLineStart, retLineEnd);
1170 #endif // DEBUG
1172 return retLines;
1178 \brief Skip a number of lines forward.
1180 Same as BufCountForwardNLines, but takes into account line breaks when
1181 wrapping is turned on. If the caller knows that startPos is at a line start,
1182 it can pass "startPosIsLineStart" as True to make the call more efficient
1183 by avoiding the additional step of scanning back to the last newline.
1185 \param startPos index to starting character
1186 \param nLines number of lines to skip ahead
1187 \param startPosIsLineStart avoid scanning back to the line start
1188 \return new position as index
1190 int Fl_Text_Display::skip_lines(int startPos, int nLines,
1191 bool startPosIsLineStart) {
1192 IS_UTF8_ALIGNED2(buffer(), startPos)
1194 int retLines, retPos, retLineStart, retLineEnd;
1196 /* if we're not wrapping use more efficient BufCountForwardNLines */
1197 if (!mContinuousWrap)
1198 return buffer()->skip_lines(startPos, nLines);
1200 /* wrappedLineCounter can't handle the 0 lines case */
1201 if (nLines == 0)
1202 return startPos;
1204 /* use the common line counting routine to count forward */
1205 wrapped_line_counter(buffer(), startPos, buffer()->length(),
1206 nLines, startPosIsLineStart, 0,
1207 &retPos, &retLines, &retLineStart, &retLineEnd);
1208 IS_UTF8_ALIGNED2(buffer(), retPos)
1209 return retPos;
1215 \brief Returns the end of a line.
1217 Same as BufEndOfLine, but takes into account line breaks when wrapping
1218 is turned on. If the caller knows that \p startPos is at a line start, it
1219 can pass "startPosIsLineStart" as True to make the call more efficient
1220 by avoiding the additional step of scanning back to the last newline.
1222 Note that the definition of the end of a line is less clear when continuous
1223 wrap is on. With continuous wrap off, it's just a pointer to the newline
1224 that ends the line. When it's on, it's the character beyond the last
1225 \b displayable character on the line, where a whitespace character which has
1226 been "converted" to a newline for wrapping is not considered displayable.
1227 Also note that a line can be wrapped at a non-whitespace character if the
1228 line had no whitespace. In this case, this routine returns a pointer to
1229 the start of the next line. This is also consistent with the model used by
1230 visLineLength.
1232 \param startPos index to starting character
1233 \param startPosIsLineStart avoid scanning back to the line start
1234 \return new position as index
1236 int Fl_Text_Display::line_end(int startPos, bool startPosIsLineStart) const {
1237 IS_UTF8_ALIGNED2(buffer(), startPos)
1239 int retLines, retPos, retLineStart, retLineEnd;
1241 /* If we're not wrapping use more efficient BufEndOfLine */
1242 if (!mContinuousWrap)
1243 return buffer()->line_end(startPos);
1245 if (startPos == buffer()->length())
1246 return startPos;
1248 wrapped_line_counter(buffer(), startPos, buffer()->length(), 1,
1249 startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1250 &retLineEnd);
1252 IS_UTF8_ALIGNED2(buffer(), retLineEnd)
1253 return retLineEnd;
1259 \brief Return the beginning of a line.
1261 Same as BufStartOfLine, but returns the character after last wrap point
1262 rather than the last newline.
1264 \param pos index to starting character
1265 \return new position as index
1267 int Fl_Text_Display::line_start(int pos) const {
1268 IS_UTF8_ALIGNED2(buffer(), pos)
1270 int retLines, retPos, retLineStart, retLineEnd;
1272 /* If we're not wrapping, use the more efficient BufStartOfLine */
1273 if (!mContinuousWrap)
1274 return buffer()->line_start(pos);
1276 wrapped_line_counter(buffer(), buffer()->line_start(pos), pos, INT_MAX, true, 0,
1277 &retPos, &retLines, &retLineStart, &retLineEnd);
1279 IS_UTF8_ALIGNED2(buffer(), retLineStart)
1280 return retLineStart;
1286 \brief Skip a number of lines back.
1288 Same as BufCountBackwardNLines, but takes into account line breaks when
1289 wrapping is turned on.
1291 \param startPos index to starting character
1292 \param nLines number of lines to skip back
1293 \return new position as index
1295 int Fl_Text_Display::rewind_lines(int startPos, int nLines) {
1296 IS_UTF8_ALIGNED2(buffer(), startPos)
1298 Fl_Text_Buffer *buf = buffer();
1299 int pos, lineStart, retLines, retPos, retLineStart, retLineEnd;
1301 /* If we're not wrapping, use the more efficient BufCountBackwardNLines */
1302 if (!mContinuousWrap)
1303 return buf->rewind_lines(startPos, nLines);
1305 pos = startPos;
1306 for (;;) {
1307 lineStart = buf->line_start(pos);
1308 wrapped_line_counter(buf, lineStart, pos, INT_MAX, true, 0,
1309 &retPos, &retLines, &retLineStart, &retLineEnd, false);
1310 if (retLines > nLines)
1311 return skip_lines(lineStart, retLines-nLines, true);
1312 nLines -= retLines;
1313 pos = lineStart - 1;
1314 if (pos < 0)
1315 return 0;
1316 nLines -= 1;
1322 static inline int fl_isseparator(unsigned int c) {
1323 // FIXME: this does not take UCS-4 encoding into account
1324 return c != '$' && c != '_' && (isspace(c) || ispunct(c));
1329 /**
1330 \brief Moves the current insert position right one word.
1332 void Fl_Text_Display::next_word() {
1333 int pos = insert_position();
1335 while (pos < buffer()->length() && !fl_isseparator(buffer()->char_at(pos))) {
1336 pos = buffer()->next_char(pos);
1339 while (pos < buffer()->length() && fl_isseparator(buffer()->char_at(pos))) {
1340 pos = buffer()->next_char(pos);
1343 insert_position( pos );
1348 /**
1349 \brief Moves the current insert position left one word.
1351 void Fl_Text_Display::previous_word() {
1352 int pos = insert_position();
1353 if (pos==0) return;
1354 pos = buffer()->prev_char(pos);
1356 while (pos && fl_isseparator(buffer()->char_at(pos))) {
1357 pos = buffer()->prev_char(pos);
1360 while (pos && !fl_isseparator(buffer()->char_at(pos))) {
1361 pos = buffer()->prev_char(pos);
1364 if (fl_isseparator(buffer()->char_at(pos))) {
1365 pos = buffer()->next_char(pos);
1368 insert_position( pos );
1374 \brief This is called before any characters are deleted.
1376 Callback attached to the text buffer to receive delete information before
1377 the modifications are actually made.
1379 \param pos starting index of deletion
1380 \param nDeleted number of bytes we will delete (must be UTF-8 aligned!)
1381 \param cbArg "this" pointer for static callback function
1383 void Fl_Text_Display::buffer_predelete_cb(int pos, int nDeleted, void *cbArg) {
1384 Fl_Text_Display *textD = (Fl_Text_Display *)cbArg;
1385 if (textD->mContinuousWrap) {
1386 /* Note: we must perform this measurement, even if there is not a
1387 single character deleted; the number of "deleted" lines is the
1388 number of visual lines spanned by the real line in which the
1389 modification takes place.
1390 Also, a modification of the tab distance requires the same
1391 kind of calculations in advance, even if the font width is "fixed",
1392 because when the width of the tab characters changes, the layout
1393 of the text may be completely different. */
1394 IS_UTF8_ALIGNED2(textD->buffer(), pos)
1395 textD->measure_deleted_lines(pos, nDeleted);
1396 } else {
1397 textD->mSuppressResync = 0; /* Probably not needed, but just in case */
1404 \brief This is called whenever the buffer is modified.
1406 Callback attached to the text buffer to receive modification information
1408 \param pos starting index of modification
1409 \param nInserted number of bytes we inserted (must be UTF-8 aligned!)
1410 \param nDeleted number of bytes deleted (must be UTF-8 aligned!)
1411 \param nRestyled ??
1412 \param deletedText this is what was removed, must not be NULL if nDeleted is set
1413 \param cbArg "this" pointer for static callback function
1415 void Fl_Text_Display::buffer_modified_cb( int pos, int nInserted, int nDeleted,
1416 int nRestyled, const char *deletedText, void *cbArg ) {
1417 int linesInserted, linesDeleted, startDispPos, endDispPos;
1418 Fl_Text_Display *textD = ( Fl_Text_Display * ) cbArg;
1419 Fl_Text_Buffer *buf = textD->mBuffer;
1420 int oldFirstChar = textD->mFirstChar;
1421 int scrolled, origCursorPos = textD->mCursorPos;
1422 int wrapModStart = 0, wrapModEnd = 0;
1424 IS_UTF8_ALIGNED2(buf, pos)
1425 IS_UTF8_ALIGNED2(buf, oldFirstChar)
1427 /* buffer modification cancels vertical cursor motion column */
1428 if ( nInserted != 0 || nDeleted != 0 )
1429 textD->mCursorPreferredXPos = -1;
1431 /* Count the number of lines inserted and deleted, and in the case
1432 of continuous wrap mode, how much has changed */
1433 if (textD->mContinuousWrap) {
1434 textD->find_wrap_range(deletedText, pos, nInserted, nDeleted,
1435 &wrapModStart, &wrapModEnd, &linesInserted, &linesDeleted);
1436 } else {
1437 linesInserted = nInserted == 0 ? 0 : buf->count_lines( pos, pos + nInserted );
1438 linesDeleted = nDeleted == 0 ? 0 : countlines( deletedText );
1441 /* Update the line starts and mTopLineNum */
1442 if ( nInserted != 0 || nDeleted != 0 ) {
1443 if (textD->mContinuousWrap) {
1444 textD->update_line_starts( wrapModStart, wrapModEnd-wrapModStart,
1445 nDeleted + pos-wrapModStart + (wrapModEnd-(pos+nInserted)),
1446 linesInserted, linesDeleted, &scrolled );
1447 } else {
1448 textD->update_line_starts( pos, nInserted, nDeleted, linesInserted,
1449 linesDeleted, &scrolled );
1451 } else
1452 scrolled = 0;
1454 /* If we're counting non-wrapped lines as well, maintain the absolute
1455 (non-wrapped) line number of the text displayed */
1456 if (textD->maintaining_absolute_top_line_number() &&
1457 (nInserted != 0 || nDeleted != 0)) {
1458 if (deletedText && (pos + nDeleted < oldFirstChar))
1459 textD->mAbsTopLineNum += buf->count_lines(pos, pos + nInserted) -
1460 countlines(deletedText);
1461 else if (pos < oldFirstChar)
1462 textD->reset_absolute_top_line_number();
1465 /* Update the line count for the whole buffer */
1466 textD->mNBufferLines += linesInserted - linesDeleted;
1468 /* Update the cursor position */
1469 if ( textD->mCursorToHint != NO_HINT ) {
1470 textD->mCursorPos = textD->mCursorToHint;
1471 textD->mCursorToHint = NO_HINT;
1472 } else if ( textD->mCursorPos > pos ) {
1473 if ( textD->mCursorPos < pos + nDeleted )
1474 textD->mCursorPos = pos;
1475 else
1476 textD->mCursorPos += nInserted - nDeleted;
1479 // refigure scrollbars & stuff
1480 textD->resize(textD->x(), textD->y(), textD->w(), textD->h());
1482 // don't need to do anything else if not visible?
1483 if (!textD->visible_r()) return;
1485 /* If the changes caused scrolling, re-paint everything and we're done. */
1486 if ( scrolled ) {
1487 textD->damage(FL_DAMAGE_EXPOSE);
1488 if ( textD->mStyleBuffer ) /* See comments in extendRangeForStyleMods */
1489 textD->mStyleBuffer->primary_selection()->selected(0);
1490 return;
1493 /* If the changes didn't cause scrolling, decide the range of characters
1494 that need to be re-painted. Also if the cursor position moved, be
1495 sure that the redisplay range covers the old cursor position so the
1496 old cursor gets erased, and erase the bits of the cursor which extend
1497 beyond the left and right edges of the text. */
1498 startDispPos = textD->mContinuousWrap ? wrapModStart : pos;
1499 IS_UTF8_ALIGNED2(buf, startDispPos)
1501 if ( origCursorPos == startDispPos && textD->mCursorPos != startDispPos )
1502 startDispPos = min( startDispPos, buf->prev_char_clipped(origCursorPos) );
1503 IS_UTF8_ALIGNED2(buf, startDispPos)
1505 if ( linesInserted == linesDeleted ) {
1506 if ( nInserted == 0 && nDeleted == 0 )
1507 endDispPos = pos + nRestyled;
1508 else {
1509 if (textD->mContinuousWrap)
1510 endDispPos = wrapModEnd;
1511 else
1512 endDispPos = buf->next_char(buf->line_end( pos + nInserted ));
1514 // CET - FIXME if ( origCursorPos >= startDispPos &&
1515 // ( origCursorPos <= endDispPos || endDispPos == buf->length() ) )
1518 if (linesInserted > 1) textD->draw_line_numbers(false);
1519 } else {
1520 endDispPos = buf->next_char(textD->mLastChar);
1521 // CET - FIXME if ( origCursorPos >= pos )
1522 /* If more than one line is inserted/deleted, a line break may have
1523 been inserted or removed in between, and the line numbers may
1524 have changed. If only one line is altered, line numbers cannot
1525 be affected (the insertion or removal of a line break always
1526 results in at least two lines being redrawn). */
1527 textD->draw_line_numbers(false);
1529 IS_UTF8_ALIGNED2(buf, startDispPos)
1530 IS_UTF8_ALIGNED2(buf, endDispPos)
1532 /* If there is a style buffer, check if the modification caused additional
1533 changes that need to be redisplayed. (Redisplaying separately would
1534 cause double-redraw on almost every modification involving styled
1535 text). Extend the redraw range to incorporate style changes */
1536 if ( textD->mStyleBuffer )
1537 textD->extend_range_for_styles( &startDispPos, &endDispPos );
1538 IS_UTF8_ALIGNED2(buf, startDispPos)
1539 IS_UTF8_ALIGNED2(buf, endDispPos)
1541 /* Redisplay computed range */
1542 textD->redisplay_range( startDispPos, endDispPos );
1548 \brief Line numbering stuff, currently unused.
1550 In continuous wrap mode, internal line numbers are calculated after
1551 wrapping. A separate non-wrapped line count is maintained when line
1552 numbering is turned on. There is some performance cost to maintaining this
1553 line count, so normally absolute line numbers are not tracked if line
1554 numbering is off. This routine allows callers to specify that they still
1555 want this line count maintained (for use via TextDPosToLineAndCol).
1556 More specifically, this allows the line number reported in the statistics
1557 line to be calibrated in absolute lines, rather than post-wrapped lines.
1559 void Fl_Text_Display::maintain_absolute_top_line_number(int state) {
1560 mNeedAbsTopLineNum = state;
1561 reset_absolute_top_line_number();
1567 \brief Line numbering stuff, currently unused.
1569 Returns the absolute (non-wrapped) line number of the first line displayed.
1570 Returns 0 if the absolute top line number is not being maintained.
1572 int Fl_Text_Display::get_absolute_top_line_number() const {
1573 if (!mContinuousWrap)
1574 return mTopLineNum;
1575 if (maintaining_absolute_top_line_number())
1576 return mAbsTopLineNum;
1577 return 0;
1583 \brief Line numbering stuff, currently unused.
1585 Re-calculate absolute top line number for a change in scroll position.
1587 void Fl_Text_Display::absolute_top_line_number(int oldFirstChar) {
1588 if (maintaining_absolute_top_line_number()) {
1589 if (mFirstChar < oldFirstChar)
1590 mAbsTopLineNum -= buffer()->count_lines(mFirstChar, oldFirstChar);
1591 else
1592 mAbsTopLineNum += buffer()->count_lines(oldFirstChar, mFirstChar);
1599 \brief Line numbering stuff, currently unused.
1601 Return true if a separate absolute top line number is being maintained
1602 (for displaying line numbers or showing in the statistics line).
1604 int Fl_Text_Display::maintaining_absolute_top_line_number() const {
1605 return mContinuousWrap &&
1606 (mLineNumWidth != 0 || mNeedAbsTopLineNum);
1612 \brief Line numbering stuff, probably unused.
1614 Count lines from the beginning of the buffer to reestablish the
1615 absolute (non-wrapped) top line number. If mode is not continuous wrap,
1616 or the number is not being maintained, does nothing.
1618 void Fl_Text_Display::reset_absolute_top_line_number() {
1619 mAbsTopLineNum = 1;
1620 absolute_top_line_number(0);
1626 \brief Convert a position index into a line number offset.
1628 Find the line number of position \p pos relative to the first line of
1629 displayed text. Returns 0 if the line is not displayed.
1631 \param pos ??
1632 \param[out] lineNum ??
1633 \return ??
1634 \todo What does this do?
1636 int Fl_Text_Display::position_to_line( int pos, int *lineNum ) const {
1637 IS_UTF8_ALIGNED2(buffer(), pos)
1639 int i;
1641 *lineNum = 0;
1642 if ( pos < mFirstChar ) return 0;
1643 if ( pos > mLastChar ) {
1644 if ( empty_vlines() ) {
1645 if ( mLastChar < mBuffer->length() ) {
1646 if ( !position_to_line( mLastChar, lineNum ) ) {
1647 Fl::error("Fl_Text_Display::position_to_line(): Consistency check ptvl failed");
1648 return 0;
1650 return ++( *lineNum ) <= mNVisibleLines - 1;
1651 } else {
1652 position_to_line( buffer()->prev_char_clipped(mLastChar), lineNum );
1653 return 1;
1656 return 0;
1659 for ( i = mNVisibleLines - 1; i >= 0; i-- ) {
1660 if ( mLineStarts[ i ] != -1 && pos >= mLineStarts[ i ] ) {
1661 *lineNum = i;
1662 return 1;
1665 return 0; /* probably never be reached */
1670 Universal pixel machine.
1672 We use a single function that handles all line layout, measuring, and drawing
1673 \li draw a text range
1674 \li return the width of a text range in pixels
1675 \li return the index of a character that is at a pixel position
1677 \param[in] mode DRAW_LINE, GET_WIDTH, FIND_INDEX
1678 \param[in] lineStartPos index of first character
1679 \param[in] lineLen size of string in bytes
1680 \param[in] leftChar, rightChar
1681 \param[in] Y drawing position
1682 \param[in] bottomClip, leftClip, rightClip stop work when we reach the clipped
1683 area. rightClip is the X position that we search in FIND_INDEX.
1684 \retval DRAW_LINE index of last drawn character
1685 \retval GET_WIDTH width in pixels of text segment if we would draw it
1686 \retval FIND_INDEX index of character at given x position in window coordinates
1687 \retval FIND_INDEX_FROM_ZERO index of character at given x position without scrolling and widget offsets
1688 \todo we need to handle hidden hyphens and tabs here!
1689 \todo we handle all styles and selections
1690 \todo we must provide code to get pixel positions of the middle of a character as well
1692 int Fl_Text_Display::handle_vline(
1693 int mode,
1694 int lineStartPos, int lineLen, int leftChar, int rightChar,
1695 int Y, int bottomClip,
1696 int leftClip, int rightClip) const
1698 IS_UTF8_ALIGNED2(buffer(), lineStartPos)
1700 // FIXME: we need to allow two modes for FIND_INDEX: one on the edge of the
1701 // FIXME: character for selection, and one on the character center for cursors.
1702 int i, X, startX, startIndex, style, charStyle;
1703 char *lineStr;
1705 if ( lineStartPos == -1 ) {
1706 lineStr = NULL;
1707 } else {
1708 lineStr = mBuffer->text_range( lineStartPos, lineStartPos + lineLen );
1711 if (mode==GET_WIDTH) {
1712 X = 0;
1713 } else if (mode==FIND_INDEX_FROM_ZERO) {
1714 X = 0;
1715 mode = FIND_INDEX;
1716 } else {
1717 X = text_area.x - mHorizOffset;
1720 startX = X;
1721 startIndex = 0;
1722 if (!lineStr) {
1723 // just clear the background
1724 if (mode==DRAW_LINE) {
1725 style = position_style(lineStartPos, lineLen, -1);
1726 draw_string( style|BG_ONLY_MASK, text_area.x, Y, text_area.x+text_area.w, lineStr, lineLen );
1728 if (mode==FIND_INDEX) {
1729 IS_UTF8_ALIGNED2(buffer(), lineStartPos)
1730 return lineStartPos;
1732 return 0;
1735 char currChar = 0, prevChar = 0;
1736 // draw the line
1737 style = position_style(lineStartPos, lineLen, 0);
1738 for (i=0; i<lineLen; ) {
1739 currChar = lineStr[i]; // one byte is enough to handele tabs and other cases
1740 int len = fl_utf8len1(currChar);
1741 if (len<=0) len = 1; // OUCH!
1742 charStyle = position_style(lineStartPos, lineLen, i);
1743 if (charStyle!=style || currChar=='\t' || prevChar=='\t') {
1744 // draw a segment whenever the style changes or a Tab is found
1745 int w = 0;
1746 if (prevChar=='\t') {
1747 // draw a single Tab space
1748 int tab = (int)col_to_x(mBuffer->tab_distance());
1749 int xAbs = (mode==GET_WIDTH) ? startX : startX+mHorizOffset-text_area.x;
1750 w = (((xAbs/tab)+1)*tab) - xAbs;
1751 if (mode==DRAW_LINE)
1752 draw_string( style|BG_ONLY_MASK, startX, Y, startX+w, 0, 0 );
1753 if (mode==FIND_INDEX && startX+w>rightClip) {
1754 // find x pos inside block
1755 free(lineStr);
1756 return lineStartPos + startIndex;
1758 } else {
1759 // draw a text segment
1760 w = int( string_width( lineStr+startIndex, i-startIndex, style ) );
1761 if (mode==DRAW_LINE)
1762 draw_string( style, startX, Y, startX+w, lineStr+startIndex, i-startIndex );
1763 if (mode==FIND_INDEX && startX+w>rightClip) {
1764 // find x pos inside block
1765 int di = find_x(lineStr+startIndex, i-startIndex, style, rightClip-startX);
1766 free(lineStr);
1767 IS_UTF8_ALIGNED2(buffer(), (lineStartPos+startIndex+di))
1768 return lineStartPos + startIndex + di;
1771 style = charStyle;
1772 startX += w;
1773 startIndex = i;
1775 i += len;
1776 prevChar = currChar;
1778 int w = 0;
1779 if (currChar=='\t') {
1780 // draw a single Tab space
1781 int tab = (int)col_to_x(mBuffer->tab_distance());
1782 int xAbs = (mode==GET_WIDTH) ? startX : startX+mHorizOffset-text_area.x;
1783 w = (((xAbs/tab)+1)*tab) - xAbs;
1784 if (mode==DRAW_LINE)
1785 draw_string( style|BG_ONLY_MASK, startX, Y, startX+w, 0, 0 );
1786 if (mode==FIND_INDEX) {
1787 // find x pos inside block
1788 free(lineStr);
1789 return lineStartPos + startIndex + ( rightClip-startX>w ? 1 : 0 );
1791 } else {
1792 w = int( string_width( lineStr+startIndex, i-startIndex, style ) );
1793 if (mode==DRAW_LINE)
1794 draw_string( style, startX, Y, startX+w, lineStr+startIndex, i-startIndex );
1795 if (mode==FIND_INDEX) {
1796 // find x pos inside block
1797 int di = find_x(lineStr+startIndex, i-startIndex, style, rightClip-startX);
1798 free(lineStr);
1799 IS_UTF8_ALIGNED2(buffer(), (lineStartPos+startIndex+di))
1800 return lineStartPos + startIndex + di;
1803 if (mode==GET_WIDTH) {
1804 free(lineStr);
1805 return startX+w;
1808 // clear the rest of the line
1809 startX += w;
1810 style = position_style(lineStartPos, lineLen, i);
1811 if (mode==DRAW_LINE)
1812 draw_string( style|BG_ONLY_MASK, startX, Y, text_area.x+text_area.w, lineStr, lineLen );
1814 free(lineStr);
1815 IS_UTF8_ALIGNED2(buffer(), (lineStartPos+lineLen))
1816 return lineStartPos + lineLen;
1821 \brief Find the index of the character that lies at the given x position.
1822 \param s UTF-8 text string
1823 \param len length of string
1824 \param style index into style lookup table
1825 \param x position in pixels
1826 \return index into buffer
1828 int Fl_Text_Display::find_x(const char *s, int len, int style, int x) const {
1829 IS_UTF8_ALIGNED(s)
1831 // TODO: use binary search which may be quicker.
1832 int i = 0;
1833 while (i<len) {
1834 int cl = fl_utf8len1(s[i]);
1835 int w = int( string_width(s, i+cl, style) );
1836 if (w>x)
1837 return i;
1838 i += cl;
1840 return len;
1846 \brief Draw a single line of text.
1848 Draw the text on a single line represented by \p visLineNum (the
1849 number of lines down from the top of the display), limited by
1850 \p leftClip and \p rightClip window coordinates and \p leftCharIndex and
1851 \p rightCharIndex character positions (not including the character at
1852 position \p rightCharIndex).
1854 \param visLineNum index of line in the visible line number lookup
1855 \param leftClip, rightClip pixel position of clipped area
1856 \param leftCharIndex, rightCharIndex index into line of segment that we want to draw
1858 void Fl_Text_Display::draw_vline(int visLineNum, int leftClip, int rightClip,
1859 int leftCharIndex, int rightCharIndex) {
1860 int Y, lineStartPos, lineLen, fontHeight;
1862 // printf("draw_vline(visLineNum=%d, leftClip=%d, rightClip=%d, leftCharIndex=%d, rightCharIndex=%d)\n",
1863 // visLineNum, leftClip, rightClip, leftCharIndex, rightCharIndex);
1864 // printf("nNVisibleLines=%d\n", mNVisibleLines);
1866 /* If line is not displayed, skip it */
1867 if ( visLineNum < 0 || visLineNum >= mNVisibleLines )
1868 return;
1870 /* Calculate Y coordinate of the string to draw */
1871 fontHeight = mMaxsize;
1872 Y = text_area.y + visLineNum * fontHeight;
1874 /* Get the text, length, and buffer position of the line to display */
1875 lineStartPos = mLineStarts[ visLineNum ];
1876 if ( lineStartPos == -1 ) {
1877 lineLen = 0;
1878 } else {
1879 lineLen = vline_length( visLineNum );
1882 /* Shrink the clipping range to the active display area */
1883 leftClip = max( text_area.x, leftClip );
1884 rightClip = min( rightClip, text_area.x + text_area.w );
1886 handle_vline(DRAW_LINE,
1887 lineStartPos, lineLen, leftCharIndex, rightCharIndex,
1888 Y, Y+fontHeight, leftClip, rightClip);
1889 return;
1895 \brief Draw a text segment in a single style.
1897 Draw a string or blank area according to parameter \p style, using the
1898 appropriate colors and drawing method for that style, with top left
1899 corner at \p X, \p Y. If style says to draw text, use \p string as
1900 source of characters, and draw \p nChars, if style is FILL, erase
1901 rectangle where text would have drawn from \p X to \p toX and from
1902 \p Y to the maximum y extent of the current font(s).
1904 \param style index into style lookup table
1905 \param X, Y drawing origin
1906 \param toX rightmost position if this is a fill operation
1907 \param string text if this is a drawing operation
1908 \param nChars number of characters to draw
1910 void Fl_Text_Display::draw_string(int style,
1911 int X, int Y, int toX,
1912 const char *string, int nChars) const {
1913 IS_UTF8_ALIGNED(string)
1915 const Style_Table_Entry * styleRec;
1917 /* Draw blank area rather than text, if that was the request */
1918 if ( style & FILL_MASK ) {
1919 if (style & TEXT_ONLY_MASK) return;
1920 clear_rect( style, X, Y, toX - X, mMaxsize );
1921 return;
1923 /* Set font, color, and gc depending on style. For normal text, GCs
1924 for normal drawing, or drawing within a Fl_Text_Selection or highlight are
1925 pre-allocated and pre-configured. For syntax highlighting, GCs are
1926 configured here, on the fly. */
1928 Fl_Font font = textfont();
1929 int fsize = textsize();
1930 Fl_Color foreground;
1931 Fl_Color background;
1933 if ( style & STYLE_LOOKUP_MASK ) {
1934 int si = (style & STYLE_LOOKUP_MASK) - 'A';
1935 if (si < 0) si = 0;
1936 else if (si >= mNStyles) si = mNStyles - 1;
1938 styleRec = mStyleTable + si;
1939 font = styleRec->font;
1940 fsize = styleRec->size;
1942 if (style & PRIMARY_MASK) {
1943 if (Fl::focus() == (Fl_Widget*)this) background = selection_color();
1944 else background = fl_color_average(color(), selection_color(), 0.4f);
1945 } else if (style & HIGHLIGHT_MASK) {
1946 if (Fl::focus() == (Fl_Widget*)this) background = fl_color_average(color(), selection_color(), 0.5f);
1947 else background = fl_color_average(color(), selection_color(), 0.6f);
1948 } else background = color();
1949 foreground = fl_contrast(styleRec->color, background);
1950 } else if (style & PRIMARY_MASK) {
1951 if (Fl::focus() == (Fl_Widget*)this) background = selection_color();
1952 else background = fl_color_average(color(), selection_color(), 0.4f);
1953 foreground = fl_contrast(textcolor(), background);
1954 } else if (style & HIGHLIGHT_MASK) {
1955 if (Fl::focus() == (Fl_Widget*)this) background = fl_color_average(color(), selection_color(), 0.5f);
1956 else background = fl_color_average(color(), selection_color(), 0.6f);
1957 foreground = fl_contrast(textcolor(), background);
1958 } else {
1959 foreground = textcolor();
1960 background = color();
1963 if (!(style & TEXT_ONLY_MASK)) {
1964 fl_color( background );
1965 fl_rectf( X, Y, toX - X, mMaxsize );
1967 if (!(style & BG_ONLY_MASK)) {
1968 fl_color( foreground );
1969 fl_font( font, fsize );
1970 #if !(defined(__APPLE__) || defined(WIN32)) && USE_XFT
1971 // makes sure antialiased ÄÖÜ do not leak on line above
1972 fl_push_clip(X, Y, toX - X, mMaxsize);
1973 #endif
1974 fl_draw( string, nChars, X, Y + mMaxsize - fl_descent());
1975 #if !(defined(__APPLE__) || defined(WIN32)) && USE_XFT
1976 fl_pop_clip();
1977 #endif
1980 // CET - FIXME
1981 /* If any space around the character remains unfilled (due to use of
1982 different sized fonts for highlighting), fill in above or below
1983 to erase previously drawn characters */
1985 if (fs->ascent < mAscent)
1986 clear_rect( style, X, Y, toX - X, mAscent - fs->ascent);
1987 if (fs->descent < mDescent)
1988 clear_rect( style, X, Y + mAscent + fs->descent, toX - x,
1989 mDescent - fs->descent);
1991 /* Underline if style is secondary Fl_Text_Selection */
1994 if (style & SECONDARY_MASK)
1995 XDrawLine(XtDisplay(mW), XtWindow(mW), gc, x,
1996 y + mAscent, toX - 1, Y + fs->ascent);
2003 \brief Clear a rectangle with the appropriate background color for \p style.
2005 \param style index into style table
2006 \param X, Y, width, height size and position of background area
2008 void Fl_Text_Display::clear_rect(int style,
2009 int X, int Y,
2010 int width, int height) const {
2011 /* A width of zero means "clear to end of window" to XClearArea */
2012 if ( width == 0 )
2013 return;
2015 if (style & PRIMARY_MASK) {
2016 if (Fl::focus()==(Fl_Widget*)this) {
2017 fl_color(selection_color());
2018 } else {
2019 fl_color(fl_color_average(color(), selection_color(), 0.4f));
2021 } else if (style & HIGHLIGHT_MASK) {
2022 if (Fl::focus()==(Fl_Widget*)this) {
2023 fl_color(fl_color_average(color(), selection_color(), 0.5f));
2024 } else {
2025 fl_color(fl_color_average(color(), selection_color(), 0.6f));
2027 } else {
2028 fl_color( color() );
2030 fl_rectf( X, Y, width, height );
2036 \brief Draw a cursor with top center at \p X, \p Y.
2038 \param X, Y cursor position in pixels
2040 void Fl_Text_Display::draw_cursor( int X, int Y ) {
2042 typedef struct {
2043 int x1, y1, x2, y2;
2045 Segment;
2047 Segment segs[ 5 ];
2048 int left, right, cursorWidth, midY;
2049 // int fontWidth = mFontStruct->min_bounds.width, nSegs = 0;
2050 int fontWidth = TMPFONTWIDTH; // CET - FIXME
2051 int nSegs = 0;
2052 int fontHeight = mMaxsize;
2053 int bot = Y + fontHeight - 1;
2055 if ( X < text_area.x - 1 || X > text_area.x + text_area.w )
2056 return;
2058 /* For cursors other than the block, make them around 2/3 of a character
2059 width, rounded to an even number of pixels so that X will draw an
2060 odd number centered on the stem at x. */
2061 cursorWidth = 4; //(fontWidth/3) * 2;
2062 left = X - cursorWidth / 2;
2063 right = left + cursorWidth;
2065 /* Create segments and draw cursor */
2066 if ( mCursorStyle == CARET_CURSOR ) {
2067 midY = bot - fontHeight / 5;
2068 segs[ 0 ].x1 = left; segs[ 0 ].y1 = bot; segs[ 0 ].x2 = X; segs[ 0 ].y2 = midY;
2069 segs[ 1 ].x1 = X; segs[ 1 ].y1 = midY; segs[ 1 ].x2 = right; segs[ 1 ].y2 = bot;
2070 segs[ 2 ].x1 = left; segs[ 2 ].y1 = bot; segs[ 2 ].x2 = X; segs[ 2 ].y2 = midY - 1;
2071 segs[ 3 ].x1 = X; segs[ 3 ].y1 = midY - 1; segs[ 3 ].x2 = right; segs[ 3 ].y2 = bot;
2072 nSegs = 4;
2073 } else if ( mCursorStyle == NORMAL_CURSOR ) {
2074 segs[ 0 ].x1 = left; segs[ 0 ].y1 = Y; segs[ 0 ].x2 = right; segs[ 0 ].y2 = Y;
2075 segs[ 1 ].x1 = X; segs[ 1 ].y1 = Y; segs[ 1 ].x2 = X; segs[ 1 ].y2 = bot;
2076 segs[ 2 ].x1 = left; segs[ 2 ].y1 = bot; segs[ 2 ].x2 = right; segs[ 2 ].y2 = bot;
2077 nSegs = 3;
2078 } else if ( mCursorStyle == HEAVY_CURSOR ) {
2079 segs[ 0 ].x1 = X - 1; segs[ 0 ].y1 = Y; segs[ 0 ].x2 = X - 1; segs[ 0 ].y2 = bot;
2080 segs[ 1 ].x1 = X; segs[ 1 ].y1 = Y; segs[ 1 ].x2 = X; segs[ 1 ].y2 = bot;
2081 segs[ 2 ].x1 = X + 1; segs[ 2 ].y1 = Y; segs[ 2 ].x2 = X + 1; segs[ 2 ].y2 = bot;
2082 segs[ 3 ].x1 = left; segs[ 3 ].y1 = Y; segs[ 3 ].x2 = right; segs[ 3 ].y2 = Y;
2083 segs[ 4 ].x1 = left; segs[ 4 ].y1 = bot; segs[ 4 ].x2 = right; segs[ 4 ].y2 = bot;
2084 nSegs = 5;
2085 } else if ( mCursorStyle == DIM_CURSOR ) {
2086 midY = Y + fontHeight / 2;
2087 segs[ 0 ].x1 = X; segs[ 0 ].y1 = Y; segs[ 0 ].x2 = X; segs[ 0 ].y2 = Y;
2088 segs[ 1 ].x1 = X; segs[ 1 ].y1 = midY; segs[ 1 ].x2 = X; segs[ 1 ].y2 = midY;
2089 segs[ 2 ].x1 = X; segs[ 2 ].y1 = bot; segs[ 2 ].x2 = X; segs[ 2 ].y2 = bot;
2090 nSegs = 3;
2091 } else if ( mCursorStyle == BLOCK_CURSOR ) {
2092 right = X + fontWidth;
2093 segs[ 0 ].x1 = X; segs[ 0 ].y1 = Y; segs[ 0 ].x2 = right; segs[ 0 ].y2 = Y;
2094 segs[ 1 ].x1 = right; segs[ 1 ].y1 = Y; segs[ 1 ].x2 = right; segs[ 1 ].y2 = bot;
2095 segs[ 2 ].x1 = right; segs[ 2 ].y1 = bot; segs[ 2 ].x2 = X; segs[ 2 ].y2 = bot;
2096 segs[ 3 ].x1 = X; segs[ 3 ].y1 = bot; segs[ 3 ].x2 = X; segs[ 3 ].y2 = Y;
2097 nSegs = 4;
2099 fl_color( mCursor_color );
2101 for ( int k = 0; k < nSegs; k++ ) {
2102 fl_line( segs[ k ].x1, segs[ k ].y1, segs[ k ].x2, segs[ k ].y2 );
2109 \brief Find the correct style for a character.
2111 Determine the drawing method to use to draw a specific character from "buf".
2112 \p lineStartPos gives the character index where the line begins, \p lineIndex,
2113 the number of characters past the beginning of the line, and \p lineIndex
2114 the number of displayed characters past the beginning of the line. Passing
2115 \p lineStartPos of -1 returns the drawing style for "no text".
2117 Why not just: position_style(pos)? Because style applies to blank areas
2118 of the window beyond the text boundaries, and because this routine must also
2119 decide whether a position is inside of a rectangular Fl_Text_Selection, and do
2120 so efficiently, without re-counting character positions from the start of the
2121 line.
2123 Note that style is a somewhat incorrect name, drawing method would
2124 be more appropriate.
2126 \param lineStartPos beginning of this line
2127 \param lineLen number of bytes in line
2128 \param lineIndex position of character within line
2129 \return style for the given character
2131 int Fl_Text_Display::position_style( int lineStartPos, int lineLen, int lineIndex) const
2133 IS_UTF8_ALIGNED2(buffer(), lineStartPos)
2135 Fl_Text_Buffer * buf = mBuffer;
2136 Fl_Text_Buffer *styleBuf = mStyleBuffer;
2137 int pos, style = 0;
2139 if ( lineStartPos == -1 || buf == NULL )
2140 return FILL_MASK;
2142 pos = lineStartPos + min( lineIndex, lineLen );
2144 if ( lineIndex >= lineLen )
2145 style = FILL_MASK;
2146 else if ( styleBuf != NULL ) {
2147 style = ( unsigned char ) styleBuf->byte_at( pos );
2148 if (style == mUnfinishedStyle && mUnfinishedHighlightCB) {
2149 /* encountered "unfinished" style, trigger parsing */
2150 (mUnfinishedHighlightCB)( pos, mHighlightCBArg);
2151 style = (unsigned char) styleBuf->byte_at( pos);
2154 if (buf->primary_selection()->includes(pos))
2155 style |= PRIMARY_MASK;
2156 if (buf->highlight_selection()->includes(pos))
2157 style |= HIGHLIGHT_MASK;
2158 if (buf->secondary_selection()->includes(pos))
2159 style |= SECONDARY_MASK;
2160 return style;
2165 \brief Find the width of a string in the font of a particular style.
2167 \param string the text
2168 \param length number of bytes in string
2169 \param style index into style table
2170 \return width of text segment in pixels
2172 double Fl_Text_Display::string_width( const char *string, int length, int style ) const {
2173 IS_UTF8_ALIGNED(string)
2175 Fl_Font font;
2176 Fl_Fontsize fsize;
2178 if ( mNStyles && (style & STYLE_LOOKUP_MASK) ) {
2179 int si = (style & STYLE_LOOKUP_MASK) - 'A';
2180 if (si < 0) si = 0;
2181 else if (si >= mNStyles) si = mNStyles - 1;
2183 font = mStyleTable[si].font;
2184 fsize = mStyleTable[si].size;
2185 } else {
2186 font = textfont();
2187 fsize = textsize();
2189 fl_font( font, fsize );
2190 return fl_width( string, length );
2196 \brief Translate a pixel position into a character index.
2198 Translate window coordinates to the nearest (insert cursor or character
2199 cell) text position. The parameter \p posType specifies how to interpret the
2200 position: CURSOR_POS means translate the coordinates to the nearest cursor
2201 position, and CHARACTER_POS means return the position of the character
2202 closest to (\p X, \p Y).
2204 \param X, Y pixel position
2205 \param posType CURSOR_POS or CHARACTER_POS
2206 \return index into text buffer
2208 int Fl_Text_Display::xy_to_position( int X, int Y, int posType ) const {
2209 int lineStart, lineLen, fontHeight;
2210 int visLineNum;
2212 /* Find the visible line number corresponding to the Y coordinate */
2213 fontHeight = mMaxsize;
2214 visLineNum = ( Y - text_area.y ) / fontHeight;
2215 if ( visLineNum < 0 )
2216 return mFirstChar;
2217 if ( visLineNum >= mNVisibleLines )
2218 visLineNum = mNVisibleLines - 1;
2220 /* Find the position at the start of the line */
2221 lineStart = mLineStarts[ visLineNum ];
2223 /* If the line start was empty, return the last position in the buffer */
2224 if ( lineStart == -1 )
2225 return mBuffer->length();
2227 /* Get the line text and its length */
2228 lineLen = vline_length( visLineNum );
2230 return handle_vline(FIND_INDEX,
2231 lineStart, lineLen, 0, 0,
2232 0, 0,
2233 text_area.x, X);
2239 \brief Translate pixel coordinates into row and column.
2241 Translate window coordinates to the nearest row and column number for
2242 positioning the cursor. This, of course, makes no sense when the font is
2243 proportional, since there are no absolute columns. The parameter posType
2244 specifies how to interpret the position: CURSOR_POS means translate the
2245 coordinates to the nearest position between characters, and CHARACTER_POS
2246 means translate the position to the nearest character cell.
2248 \param X, Y pixel coordinates
2249 \param[out] row, column neares row and column
2250 \param posType CURSOR_POS or CHARACTER_POS
2252 void Fl_Text_Display::xy_to_rowcol( int X, int Y, int *row,
2253 int *column, int posType ) const {
2254 int fontHeight = mMaxsize;
2255 int fontWidth = TMPFONTWIDTH; //mFontStruct->max_bounds.width;
2257 /* Find the visible line number corresponding to the Y coordinate */
2258 *row = ( Y - text_area.y ) / fontHeight;
2259 if ( *row < 0 ) *row = 0;
2260 if ( *row >= mNVisibleLines ) *row = mNVisibleLines - 1;
2262 *column = ( ( X - text_area.x ) + mHorizOffset +
2263 ( posType == CURSOR_POS ? fontWidth / 2 : 0 ) ) / fontWidth;
2264 if ( *column < 0 ) * column = 0;
2270 \brief Offset line start counters for a new vertical scroll position.
2272 Offset the line starts array, mTopLineNum, mFirstChar and lastChar, for a new
2273 vertical scroll position given by newTopLineNum. If any currently displayed
2274 lines will still be visible, salvage the line starts values, otherwise,
2275 count lines from the nearest known line start (start or end of buffer, or
2276 the closest value in the mLineStarts array)
2278 \param newTopLineNum index into buffer
2280 void Fl_Text_Display::offset_line_starts( int newTopLineNum ) {
2281 int oldTopLineNum = mTopLineNum;
2282 int oldFirstChar = mFirstChar;
2283 int lineDelta = newTopLineNum - oldTopLineNum;
2284 int nVisLines = mNVisibleLines;
2285 int *lineStarts = mLineStarts;
2286 int i, lastLineNum;
2287 Fl_Text_Buffer *buf = mBuffer;
2289 /* If there was no offset, nothing needs to be changed */
2290 if ( lineDelta == 0 )
2291 return;
2293 /* Find the new value for mFirstChar by counting lines from the nearest
2294 known line start (start or end of buffer, or the closest value in the
2295 lineStarts array) */
2296 lastLineNum = oldTopLineNum + nVisLines - 1;
2297 if ( newTopLineNum < oldTopLineNum && newTopLineNum < -lineDelta ) {
2298 mFirstChar = skip_lines( 0, newTopLineNum - 1, true );
2299 } else if ( newTopLineNum < oldTopLineNum ) {
2300 mFirstChar = rewind_lines( mFirstChar, -lineDelta );
2301 } else if ( newTopLineNum < lastLineNum ) {
2302 mFirstChar = lineStarts[ newTopLineNum - oldTopLineNum ];
2303 } else if ( newTopLineNum - lastLineNum < mNBufferLines - newTopLineNum ) {
2304 mFirstChar = skip_lines( lineStarts[ nVisLines - 1 ],
2305 newTopLineNum - lastLineNum, true );
2306 } else {
2307 mFirstChar = rewind_lines( buf->length(), mNBufferLines - newTopLineNum + 1 );
2310 /* Fill in the line starts array */
2311 if ( lineDelta < 0 && -lineDelta < nVisLines ) {
2312 for ( i = nVisLines - 1; i >= -lineDelta; i-- )
2313 lineStarts[ i ] = lineStarts[ i + lineDelta ];
2314 calc_line_starts( 0, -lineDelta );
2315 } else if ( lineDelta > 0 && lineDelta < nVisLines ) {
2316 for ( i = 0; i < nVisLines - lineDelta; i++ )
2317 lineStarts[ i ] = lineStarts[ i + lineDelta ];
2318 calc_line_starts( nVisLines - lineDelta, nVisLines - 1 );
2319 } else
2320 calc_line_starts( 0, nVisLines );
2322 /* Set lastChar and mTopLineNum */
2323 calc_last_char();
2324 mTopLineNum = newTopLineNum;
2326 /* If we're numbering lines or being asked to maintain an absolute line
2327 number, re-calculate the absolute line number */
2328 absolute_top_line_number(oldFirstChar);
2334 \brief Update line start arrays and variables.
2336 Update the line starts array, mTopLineNum, mFirstChar and lastChar for this
2337 text display after a modification to the text buffer, given by the
2338 position \p pos where the change began, and the numbers of characters
2339 and lines inserted and deleted.
2341 \param pos index into buffer of recent changes
2342 \param charsInserted number of bytes(!) inserted
2343 \param charsDeleted number of bytes(!) deleted
2344 \param linesInserted number of lines
2345 \param linesDeleted number of lines
2346 \param[out] scrolled set to 1 if the text display needs to be scrolled
2348 void Fl_Text_Display::update_line_starts(int pos, int charsInserted,
2349 int charsDeleted, int linesInserted,
2350 int linesDeleted, int *scrolled ) {
2351 IS_UTF8_ALIGNED2(buffer(), pos)
2353 int *lineStarts = mLineStarts;
2354 int i, lineOfPos, lineOfEnd, nVisLines = mNVisibleLines;
2355 int charDelta = charsInserted - charsDeleted;
2356 int lineDelta = linesInserted - linesDeleted;
2358 /* If all of the changes were before the displayed text, the display
2359 doesn't change, just update the top line num and offset the line
2360 start entries and first and last characters */
2361 if ( pos + charsDeleted < mFirstChar ) {
2362 mTopLineNum += lineDelta;
2363 for ( i = 0; i < nVisLines && lineStarts[i] != -1; i++ )
2364 lineStarts[ i ] += charDelta;
2365 mFirstChar += charDelta;
2366 mLastChar += charDelta;
2367 *scrolled = 0;
2368 return;
2371 /* The change began before the beginning of the displayed text, but
2372 part or all of the displayed text was deleted */
2373 if ( pos < mFirstChar ) {
2374 /* If some text remains in the window, anchor on that */
2375 if ( position_to_line( pos + charsDeleted, &lineOfEnd ) &&
2376 ++lineOfEnd < nVisLines && lineStarts[ lineOfEnd ] != -1 ) {
2377 mTopLineNum = max( 1, mTopLineNum + lineDelta );
2378 mFirstChar = rewind_lines(lineStarts[ lineOfEnd ] + charDelta, lineOfEnd );
2379 /* Otherwise anchor on original line number and recount everything */
2380 } else {
2381 if ( mTopLineNum > mNBufferLines + lineDelta ) {
2382 mTopLineNum = 1;
2383 mFirstChar = 0;
2384 } else
2385 mFirstChar = skip_lines( 0, mTopLineNum - 1, true );
2387 calc_line_starts( 0, nVisLines - 1 );
2388 /* calculate lastChar by finding the end of the last displayed line */
2389 calc_last_char();
2390 *scrolled = 1;
2391 return;
2394 /* If the change was in the middle of the displayed text (it usually is),
2395 salvage as much of the line starts array as possible by moving and
2396 offsetting the entries after the changed area, and re-counting the
2397 added lines or the lines beyond the salvaged part of the line starts
2398 array */
2399 if ( pos <= mLastChar ) {
2400 /* find line on which the change began */
2401 position_to_line( pos, &lineOfPos );
2402 /* salvage line starts after the changed area */
2403 if ( lineDelta == 0 ) {
2404 for ( i = lineOfPos + 1; i < nVisLines && lineStarts[ i ] != -1; i++ )
2405 lineStarts[ i ] += charDelta;
2406 } else if ( lineDelta > 0 ) {
2407 for ( i = nVisLines - 1; i >= lineOfPos + lineDelta + 1; i-- )
2408 lineStarts[ i ] = lineStarts[ i - lineDelta ] +
2409 ( lineStarts[ i - lineDelta ] == -1 ? 0 : charDelta );
2410 } else /* (lineDelta < 0) */ {
2411 for ( i = max( 0, lineOfPos + 1 ); i < nVisLines + lineDelta; i++ )
2412 lineStarts[ i ] = lineStarts[ i - lineDelta ] +
2413 ( lineStarts[ i - lineDelta ] == -1 ? 0 : charDelta );
2415 /* fill in the missing line starts */
2416 if ( linesInserted >= 0 )
2417 calc_line_starts( lineOfPos + 1, lineOfPos + linesInserted );
2418 if ( lineDelta < 0 )
2419 calc_line_starts( nVisLines + lineDelta, nVisLines );
2420 /* calculate lastChar by finding the end of the last displayed line */
2421 calc_last_char();
2422 *scrolled = 0;
2423 return;
2426 /* Change was past the end of the displayed text, but displayable by virtue
2427 of being an insert at the end of the buffer into visible blank lines */
2428 if ( empty_vlines() ) {
2429 position_to_line( pos, &lineOfPos );
2430 calc_line_starts( lineOfPos, lineOfPos + linesInserted );
2431 calc_last_char();
2432 *scrolled = 0;
2433 return;
2436 /* Change was beyond the end of the buffer and not visible, do nothing */
2437 *scrolled = 0;
2443 \brief Update the line start arrays.
2445 Scan through the text in the "textD"'s buffer and recalculate the line
2446 starts array values beginning at index "startLine" and continuing through
2447 (including) "endLine". It assumes that the line starts entry preceding
2448 "startLine" (or mFirstChar if startLine is 0) is good, and re-counts
2449 newlines to fill in the requested entries. Out of range values for
2450 "startLine" and "endLine" are acceptable.
2452 \param startLine, endLine range of lines to scan as line numbers
2454 void Fl_Text_Display::calc_line_starts( int startLine, int endLine ) {
2455 int startPos, bufLen = mBuffer->length();
2456 int line, lineEnd, nextLineStart, nVis = mNVisibleLines;
2457 int *lineStarts = mLineStarts;
2459 /* Clean up (possibly) messy input parameters */
2460 if ( endLine < 0 ) endLine = 0;
2461 if ( endLine >= nVis ) endLine = nVis - 1;
2462 if ( startLine < 0 ) startLine = 0;
2463 if ( startLine >= nVis ) startLine = nVis - 1;
2464 if ( startLine > endLine )
2465 return;
2467 /* Find the last known good line number -> position mapping */
2468 if ( startLine == 0 ) {
2469 lineStarts[ 0 ] = mFirstChar;
2470 startLine = 1;
2472 startPos = lineStarts[ startLine - 1 ];
2474 /* If the starting position is already past the end of the text,
2475 fill in -1's (means no text on line) and return */
2476 if ( startPos == -1 ) {
2477 for ( line = startLine; line <= endLine; line++ )
2478 lineStarts[ line ] = -1;
2479 return;
2482 /* Loop searching for ends of lines and storing the positions of the
2483 start of the next line in lineStarts */
2484 for ( line = startLine; line <= endLine; line++ ) {
2485 find_line_end(startPos, true, &lineEnd, &nextLineStart);
2486 startPos = nextLineStart;
2487 if ( startPos >= bufLen ) {
2488 /* If the buffer ends with a newline or line break, put
2489 buf->length() in the next line start position (instead of
2490 a -1 which is the normal marker for an empty line) to
2491 indicate that the cursor may safely be displayed there */
2492 if ( line == 0 || ( lineStarts[ line - 1 ] != bufLen &&
2493 lineEnd != nextLineStart ) ) {
2494 lineStarts[ line ] = bufLen;
2495 line++;
2497 break;
2499 lineStarts[ line ] = startPos;
2502 /* Set any entries beyond the end of the text to -1 */
2503 for ( ; line <= endLine; line++ )
2504 lineStarts[ line ] = -1;
2510 \brief Update last display character index.
2512 Given a Fl_Text_Display with a complete, up-to-date lineStarts array, update
2513 the lastChar entry to point to the last buffer position displayed.
2515 void Fl_Text_Display::calc_last_char() {
2516 int i;
2517 for (i = mNVisibleLines - 1; i >= 0 && mLineStarts[i] == -1; i--) ;
2518 mLastChar = i < 0 ? 0 : line_end(mLineStarts[i], true);
2523 /**
2524 \brief Scrolls the current buffer to start at the specified line and column.
2525 \param topLineNum top line number
2526 \param horizOffset column number
2527 \todo Column numbers make little sense here.
2529 void Fl_Text_Display::scroll(int topLineNum, int horizOffset) {
2530 mTopLineNumHint = topLineNum;
2531 mHorizOffsetHint = horizOffset;
2532 resize(x(), y(), w(), h());
2537 /**
2538 \brief Scrolls the current buffer to start at the specified line and column.
2539 \param topLineNum top line number
2540 \param horizOffset in pixels
2541 \return 0 if nothing changed, 1 if we scrolled
2543 int Fl_Text_Display::scroll_(int topLineNum, int horizOffset) {
2544 /* Limit the requested scroll position to allowable values */
2545 if (topLineNum > mNBufferLines + 3 - mNVisibleLines)
2546 topLineNum = mNBufferLines + 3 - mNVisibleLines;
2547 if (topLineNum < 1) topLineNum = 1;
2549 if (horizOffset > longest_vline() - text_area.w)
2550 horizOffset = longest_vline() - text_area.w;
2551 if (horizOffset < 0) horizOffset = 0;
2553 /* Do nothing if scroll position hasn't actually changed or there's no
2554 window to draw in yet */
2555 if (mHorizOffset == horizOffset && mTopLineNum == topLineNum)
2556 return 0;
2558 /* If the vertical scroll position has changed, update the line
2559 starts array and related counters in the text display */
2560 offset_line_starts(topLineNum);
2562 /* Just setting mHorizOffset is enough information for redisplay */
2563 mHorizOffset = horizOffset;
2565 // redraw all text
2566 damage(FL_DAMAGE_EXPOSE);
2567 return 1;
2573 \brief Update vertical scrollbar.
2575 Update the minimum, maximum, slider size, page increment, and value
2576 for vertical scrollbar.
2578 void Fl_Text_Display::update_v_scrollbar() {
2579 /* The vertical scrollbar value and slider size directly represent the top
2580 line number, and the number of visible lines respectively. The scroll
2581 bar maximum value is chosen to generally represent the size of the whole
2582 buffer, with minor adjustments to keep the scrollbar widget happy */
2583 #ifdef DEBUG
2584 printf("Fl_Text_Display::update_v_scrollbar():\n"
2585 " mTopLineNum=%d, mNVisibleLines=%d, mNBufferLines=%d\n",
2586 mTopLineNum, mNVisibleLines, mNBufferLines);
2587 #endif // DEBUG
2589 mVScrollBar->value(mTopLineNum, mNVisibleLines, 1, mNBufferLines+2);
2590 mVScrollBar->linesize(3);
2595 \brief Update vertical scrollbar.
2597 Update the minimum, maximum, slider size, page increment, and value
2598 for the horizontal scrollbar.
2600 void Fl_Text_Display::update_h_scrollbar() {
2601 int sliderMax = max(longest_vline(), text_area.w + mHorizOffset);
2602 mHScrollBar->value( mHorizOffset, text_area.w, 0, sliderMax );
2608 \brief Callbacks for drag or valueChanged on scrollbars.
2610 void Fl_Text_Display::v_scrollbar_cb(Fl_Scrollbar* b, Fl_Text_Display* textD) {
2611 if (b->value() == textD->mTopLineNum) return;
2612 textD->scroll(b->value(), textD->mHorizOffset);
2618 \brief Callbacks for drag or valueChanged on scrollbars.
2620 void Fl_Text_Display::h_scrollbar_cb(Fl_Scrollbar* b, Fl_Text_Display* textD) {
2621 if (b->value() == textD->mHorizOffset) return;
2622 textD->scroll(textD->mTopLineNum, b->value());
2628 \brief Refresh the line number area.
2630 If clearAll is False, writes only over the character cell areas. Setting
2631 clearAll to True will clear out any stray marks outside of the character cell
2632 area, which might have been left from before a resize or font change.
2634 This function is not used.
2636 void Fl_Text_Display::draw_line_numbers(bool /*clearAll*/) {
2637 #if 0
2638 int y, line, visLine, nCols, lineStart;
2639 char lineNumString[12];
2640 int lineHeight = mMaxsize ? mMaxsize : textsize_;
2641 int charWidth = TMPFONTWIDTH; //mFontStruct->max_bounds.width;
2643 /* Don't draw if mLineNumWidth == 0 (line numbers are hidden), or widget is
2644 not yet realized */
2645 if (mLineNumWidth == 0 || visible_r())
2646 return;
2648 /* GC is allocated on demand, since not everyone will use line numbering */
2649 if (textD->lineNumGC == NULL) {
2650 XGCValues values;
2651 values.foreground = textD->lineNumFGPixel;
2652 values.background = textD->bgPixel;
2653 values.font = textD->fontStruct->fid;
2654 textD->lineNumGC = XtGetGC(textD->w,
2655 GCFont| GCForeground | GCBackground, &values);
2658 /* Erase the previous contents of the line number area, if requested */
2659 if (clearAll)
2660 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), textD->lineNumLeft,
2661 textD->top, textD->lineNumWidth, textD->height, False);
2663 /* Draw the line numbers, aligned to the text */
2664 nCols = min(11, textD->lineNumWidth / charWidth);
2665 y = textD->top;
2666 line = getAbsTopLineNum(textD);
2667 for (visLine=0; visLine < textD->nVisibleLines; visLine++) {
2668 lineStart = textD->lineStarts[visLine];
2669 if (lineStart != -1 && (lineStart==0 ||
2670 BufGetCharacter(textD->buffer, lineStart-1)=='\n')) {
2671 sprintf(lineNumString, "%*d", nCols, line);
2672 XDrawImageString(XtDisplay(textD->w), XtWindow(textD->w),
2673 textD->lineNumGC, textD->lineNumLeft, y + textD->ascent,
2674 lineNumString, strlen(lineNumString));
2675 line++;
2676 } else {
2677 XClearArea(XtDisplay(textD->w), XtWindow(textD->w),
2678 textD->lineNumLeft, y, textD->lineNumWidth,
2679 textD->ascent + textD->descent, False);
2680 if (visLine == 0)
2681 line++;
2683 y += lineHeight;
2685 #endif
2688 static int max( int i1, int i2 ) {
2689 return i1 >= i2 ? i1 : i2;
2692 static int min( int i1, int i2 ) {
2693 return i1 <= i2 ? i1 : i2;
2699 Count the number of newlines in a null-terminated text string;
2701 static int countlines( const char *string ) {
2702 IS_UTF8_ALIGNED(string)
2704 const char * c;
2705 int lineCount = 0;
2707 if (!string) return 0;
2709 for ( c = string; *c != '\0'; c++ )
2710 if ( *c == '\n' ) lineCount++;
2711 return lineCount;
2718 \brief Returns the width in pixels of the displayed line pointed to by "visLineNum".
2719 \param visLineNum index into visible lines array
2720 \return width of line in pixels
2722 int Fl_Text_Display::measure_vline( int visLineNum ) const {
2723 int lineLen = vline_length( visLineNum );
2724 int lineStartPos = mLineStarts[ visLineNum ];
2725 if (lineStartPos < 0 || lineLen == 0) return 0;
2726 return handle_vline(GET_WIDTH, lineStartPos, lineLen, 0, 0, 0, 0, 0, 0);
2732 \brief Return true if there are lines visible with no corresponding buffer text.
2733 \return 1 if there are empty lines
2735 int Fl_Text_Display::empty_vlines() const {
2736 return (mNVisibleLines > 0) && (mLineStarts[ mNVisibleLines - 1 ] == -1);
2742 \brief Count number of bytes in a visible line.
2744 Return the length of a line (number of bytes) by examining
2745 entries in the line starts array rather than by scanning for newlines.
2747 \param visLineNum index of line in visible line array
2748 \return number of bytes in this line
2750 int Fl_Text_Display::vline_length( int visLineNum ) const {
2751 int nextLineStart, lineStartPos;
2753 if (visLineNum < 0 || visLineNum >= mNVisibleLines)
2754 return (0);
2756 lineStartPos = mLineStarts[ visLineNum ];
2758 if ( lineStartPos == -1 )
2759 return 0;
2761 if ( visLineNum + 1 >= mNVisibleLines )
2762 return mLastChar - lineStartPos;
2764 nextLineStart = mLineStarts[ visLineNum + 1 ];
2765 if ( nextLineStart == -1 )
2766 return mLastChar - lineStartPos;
2768 int nextLineStartMinus1 = buffer()->prev_char(nextLineStart);
2769 if (wrap_uses_character(nextLineStartMinus1))
2770 return nextLineStartMinus1 - lineStartPos;
2772 return nextLineStart - lineStartPos;
2778 \brief Wrapping calculations.
2780 When continuous wrap is on, and the user inserts or deletes characters,
2781 wrapping can happen before and beyond the changed position. This routine
2782 finds the extent of the changes, and counts the deleted and inserted lines
2783 over that range. It also attempts to minimize the size of the range to
2784 what has to be counted and re-displayed, so the results can be useful
2785 both for delimiting where the line starts need to be recalculated, and
2786 for deciding what part of the text to redisplay.
2788 \param deletedText
2789 \param pos
2790 \param nInserted
2791 \param nDeleted
2792 \param modRangeStart
2793 \param modRangeEnd
2794 \param linesInserted
2795 \param linesDeleted
2797 void Fl_Text_Display::find_wrap_range(const char *deletedText, int pos,
2798 int nInserted, int nDeleted,
2799 int *modRangeStart, int *modRangeEnd,
2800 int *linesInserted, int *linesDeleted) {
2801 IS_UTF8_ALIGNED(deletedText)
2802 IS_UTF8_ALIGNED2(buffer(), pos)
2804 int length, retPos, retLines, retLineStart, retLineEnd;
2805 Fl_Text_Buffer *deletedTextBuf, *buf = buffer();
2806 int nVisLines = mNVisibleLines;
2807 int *lineStarts = mLineStarts;
2808 int countFrom, countTo, lineStart, adjLineStart, i;
2809 int visLineNum = 0, nLines = 0;
2812 ** Determine where to begin searching: either the previous newline, or
2813 ** if possible, limit to the start of the (original) previous displayed
2814 ** line, using information from the existing line starts array
2816 if (pos >= mFirstChar && pos <= mLastChar) {
2817 for (i=nVisLines-1; i>0; i--) {
2818 if (lineStarts[i] != -1 && pos >= lineStarts[i]) {
2819 break;
2822 if (i > 0) {
2823 countFrom = lineStarts[i-1];
2824 visLineNum = i-1;
2825 } else {
2826 countFrom = buf->line_start(pos);
2828 } else {
2829 countFrom = buf->line_start(pos);
2832 IS_UTF8_ALIGNED2(buf, countFrom)
2835 ** Move forward through the (new) text one line at a time, counting
2836 ** displayed lines, and looking for either a real newline, or for the
2837 ** line starts to re-sync with the original line starts array
2839 lineStart = countFrom;
2840 *modRangeStart = countFrom;
2841 for (;;) {
2843 /* advance to the next line. If the line ended in a real newline
2844 or the end of the buffer, that's far enough */
2845 wrapped_line_counter(buf, lineStart, buf->length(), 1, true, 0,
2846 &retPos, &retLines, &retLineStart, &retLineEnd);
2847 if (retPos >= buf->length()) {
2848 countTo = buf->length();
2849 *modRangeEnd = countTo;
2850 if (retPos != retLineEnd)
2851 nLines++;
2852 break;
2853 } else {
2854 lineStart = retPos;
2856 nLines++;
2857 if (lineStart > pos + nInserted && buf->char_at(buf->prev_char(lineStart)) == '\n') {
2858 countTo = lineStart;
2859 *modRangeEnd = lineStart;
2860 break;
2863 /* Don't try to resync in continuous wrap mode with non-fixed font
2864 sizes; it would result in a chicken-and-egg dependency between
2865 the calculations for the inserted and the deleted lines.
2866 If we're in that mode, the number of deleted lines is calculated in
2867 advance, without resynchronization, so we shouldn't resynchronize
2868 for the inserted lines either. */
2869 if (mSuppressResync)
2870 continue;
2872 /* check for synchronization with the original line starts array
2873 before pos, if so, the modified range can begin later */
2874 if (lineStart <= pos) {
2875 while (visLineNum<nVisLines && lineStarts[visLineNum] < lineStart)
2876 visLineNum++;
2877 if (visLineNum < nVisLines && lineStarts[visLineNum] == lineStart) {
2878 countFrom = lineStart;
2879 nLines = 0;
2880 if (visLineNum+1 < nVisLines && lineStarts[visLineNum+1] != -1)
2881 *modRangeStart = min(pos, buf->prev_char(lineStarts[visLineNum+1]));
2882 else
2883 *modRangeStart = countFrom;
2884 } else
2885 *modRangeStart = min(*modRangeStart, buf->prev_char(lineStart));
2888 /* check for synchronization with the original line starts array
2889 after pos, if so, the modified range can end early */
2890 else if (lineStart > pos + nInserted) {
2891 adjLineStart = lineStart - nInserted + nDeleted;
2892 while (visLineNum<nVisLines && lineStarts[visLineNum]<adjLineStart)
2893 visLineNum++;
2894 if (visLineNum < nVisLines && lineStarts[visLineNum] != -1 &&
2895 lineStarts[visLineNum] == adjLineStart) {
2896 countTo = line_end(lineStart, true);
2897 *modRangeEnd = lineStart;
2898 break;
2902 *linesInserted = nLines;
2905 /* Count deleted lines between countFrom and countTo as the text existed
2906 before the modification (that is, as if the text between pos and
2907 pos+nInserted were replaced by "deletedText"). This extra context is
2908 necessary because wrapping can occur outside of the modified region
2909 as a result of adding or deleting text in the region. This is done by
2910 creating a textBuffer containing the deleted text and the necessary
2911 additional context, and calling the wrappedLineCounter on it.
2913 NOTE: This must not be done in continuous wrap mode when the font
2914 width is not fixed. In that case, the calculation would try
2915 to access style information that is no longer available (deleted
2916 text), or out of date (updated highlighting), possibly leading
2917 to completely wrong calculations and/or even crashes eventually.
2918 (This is not theoretical; it really happened.)
2920 In that case, the calculation of the number of deleted lines
2921 has happened before the buffer was modified (only in that case,
2922 because resynchronization of the line starts is impossible
2923 in that case, which makes the whole calculation less efficient).
2925 if (mSuppressResync) {
2926 *linesDeleted = mNLinesDeleted;
2927 mSuppressResync = 0;
2928 return;
2931 length = (pos-countFrom) + nDeleted +(countTo-(pos+nInserted));
2932 deletedTextBuf = new Fl_Text_Buffer(length);
2933 deletedTextBuf->copy(buffer(), countFrom, pos, 0);
2934 if (nDeleted != 0)
2935 deletedTextBuf->insert(pos-countFrom, deletedText);
2936 deletedTextBuf->copy(buffer(), pos+nInserted, countTo, pos-countFrom+nDeleted);
2937 /* Note that we need to take into account an offset for the style buffer:
2938 the deletedTextBuf can be out of sync with the style buffer. */
2939 wrapped_line_counter(deletedTextBuf, 0, length, INT_MAX, true, countFrom,
2940 &retPos, &retLines, &retLineStart, &retLineEnd, false);
2941 delete deletedTextBuf;
2942 *linesDeleted = retLines;
2943 mSuppressResync = 0;
2949 \brief Wrapping calculations.
2951 This is a stripped-down version of the findWrapRange() function above,
2952 intended to be used to calculate the number of "deleted" lines during
2953 a buffer modification. It is called _before_ the modification takes place.
2955 This function should only be called in continuous wrap mode with a
2956 non-fixed font width. In that case, it is impossible to calculate
2957 the number of deleted lines, because the necessary style information
2958 is no longer available _after_ the modification. In other cases, we
2959 can still perform the calculation afterwards (possibly even more
2960 efficiently).
2962 \param pos
2963 \param nDeleted
2965 void Fl_Text_Display::measure_deleted_lines(int pos, int nDeleted) {
2966 IS_UTF8_ALIGNED2(buffer(), pos)
2968 int retPos, retLines, retLineStart, retLineEnd;
2969 Fl_Text_Buffer *buf = buffer();
2970 int nVisLines = mNVisibleLines;
2971 int *lineStarts = mLineStarts;
2972 int countFrom, lineStart;
2973 int visLineNum = 0, nLines = 0, i;
2975 ** Determine where to begin searching: either the previous newline, or
2976 ** if possible, limit to the start of the (original) previous displayed
2977 ** line, using information from the existing line starts array
2979 if (pos >= mFirstChar && pos <= mLastChar) {
2980 for (i=nVisLines-1; i>0; i--)
2981 if (lineStarts[i] != -1 && pos >= lineStarts[i])
2982 break;
2983 if (i > 0) {
2984 countFrom = lineStarts[i-1];
2985 visLineNum = i-1;
2986 } else
2987 countFrom = buf->line_start(pos);
2988 } else
2989 countFrom = buf->line_start(pos);
2992 ** Move forward through the (new) text one line at a time, counting
2993 ** displayed lines, and looking for either a real newline, or for the
2994 ** line starts to re-sync with the original line starts array
2996 lineStart = countFrom;
2997 for (;;) {
2998 /* advance to the next line. If the line ended in a real newline
2999 or the end of the buffer, that's far enough */
3000 wrapped_line_counter(buf, lineStart, buf->length(), 1, true, 0,
3001 &retPos, &retLines, &retLineStart, &retLineEnd);
3002 if (retPos >= buf->length()) {
3003 if (retPos != retLineEnd)
3004 nLines++;
3005 break;
3006 } else
3007 lineStart = retPos;
3008 nLines++;
3009 if (lineStart > pos + nDeleted && buf->char_at(lineStart-1) == '\n') {
3010 break;
3013 /* Unlike in the findWrapRange() function above, we don't try to
3014 resync with the line starts, because we don't know the length
3015 of the inserted text yet, nor the updated style information.
3017 Because of that, we also shouldn't resync with the line starts
3018 after the modification either, because we must perform the
3019 calculations for the deleted and inserted lines in the same way.
3021 This can result in some unnecessary recalculation and redrawing
3022 overhead, and therefore we should only use this two-phase mode
3023 of calculation when it's really needed (continuous wrap + variable
3024 font width). */
3026 mNLinesDeleted = nLines;
3027 mSuppressResync = 1;
3033 \brief Wrapping calculations.
3035 Count forward from startPos to either maxPos or maxLines (whichever is
3036 reached first), and return all relevant positions and line count.
3037 The provided textBuffer may differ from the actual text buffer of the
3038 widget. In that case it must be a (partial) copy of the actual text buffer
3039 and the styleBufOffset argument must indicate the starting position of the
3040 copy, to take into account the correct style information.
3042 \param buf
3043 \param startPos
3044 \param maxPos
3045 \param maxLines
3046 \param startPosIsLineStart
3047 \param styleBufOffset
3049 \param[out] retPos Position where counting ended. When counting lines, the
3050 position returned is the start of the line "maxLines" lines
3051 beyond "startPos".
3052 \param[out] retLines Number of line breaks counted
3053 \param[out] retLineStart Start of the line where counting ended
3054 \param[out] retLineEnd End position of the last line traversed
3055 \param[out] countLastLineMissingNewLine
3057 void Fl_Text_Display::wrapped_line_counter(Fl_Text_Buffer *buf, int startPos,
3058 int maxPos, int maxLines, bool startPosIsLineStart, int styleBufOffset,
3059 int *retPos, int *retLines, int *retLineStart, int *retLineEnd,
3060 bool countLastLineMissingNewLine) const {
3061 IS_UTF8_ALIGNED2(buf, startPos)
3062 IS_UTF8_ALIGNED2(buf, maxPos)
3064 int lineStart, newLineStart = 0, b, p, colNum, wrapMarginPix;
3065 int i, foundBreak;
3066 double width;
3067 int nLines = 0;
3068 unsigned int c;
3070 /* Set the wrap margin to the wrap column or the view width */
3071 if (mWrapMarginPix != 0) {
3072 wrapMarginPix = mWrapMarginPix;
3073 } else {
3074 wrapMarginPix = text_area.w;
3077 /* Find the start of the line if the start pos is not marked as a
3078 line start. */
3079 if (startPosIsLineStart)
3080 lineStart = startPos;
3081 else
3082 lineStart = line_start(startPos);
3085 ** Loop until position exceeds maxPos or line count exceeds maxLines.
3086 ** (actually, continues beyond maxPos to end of line containing maxPos,
3087 ** in case later characters cause a word wrap back before maxPos)
3089 colNum = 0;
3090 width = 0;
3091 for (p=lineStart; p<buf->length(); p=buf->next_char(p)) {
3092 c = buf->char_at(p); // UCS-4
3094 /* If the character was a newline, count the line and start over,
3095 otherwise, add it to the width and column counts */
3096 if (c == '\n') {
3097 if (p >= maxPos) {
3098 *retPos = maxPos;
3099 *retLines = nLines;
3100 *retLineStart = lineStart;
3101 *retLineEnd = maxPos;
3102 return;
3104 nLines++;
3105 int p1 = buf->next_char(p);
3106 if (nLines >= maxLines) {
3107 *retPos = p1;
3108 *retLines = nLines;
3109 *retLineStart = p1;
3110 *retLineEnd = p;
3111 return;
3113 lineStart = p1;
3114 colNum = 0;
3115 width = 0;
3116 } else {
3117 const char *s = buf->address(p);
3118 colNum++;
3119 // FIXME: it is not a good idea to simply add character widths because on
3120 // some platforms, the width is a floating point value and depends on the
3121 // previous character as well.
3122 width += measure_proportional_character(s, (int)width, p+styleBufOffset);
3125 /* If character exceeded wrap margin, find the break point and wrap there */
3126 if (width > wrapMarginPix) {
3127 foundBreak = false;
3128 for (b=p; b>=lineStart; b=buf->prev_char(b)) {
3129 c = buf->char_at(b);
3130 if (c == '\t' || c == ' ') {
3131 newLineStart = buf->next_char(b);
3132 colNum = 0;
3133 width = 0;
3134 int iMax = buf->next_char(p);
3135 for (i=buf->next_char(b); i<iMax; i = buf->next_char(i)) {
3136 width += measure_proportional_character(buf->address(i), (int)width,
3137 i+styleBufOffset);
3138 colNum++;
3140 foundBreak = true;
3141 break;
3144 if (!foundBreak) { /* no whitespace, just break at margin */
3145 newLineStart = max(p, buf->next_char(lineStart));
3146 const char *s = buf->address(b);
3147 colNum++;
3148 width = measure_proportional_character(s, 0, p+styleBufOffset);
3150 if (p >= maxPos) {
3151 *retPos = maxPos;
3152 *retLines = maxPos < newLineStart ? nLines : nLines + 1;
3153 *retLineStart = maxPos < newLineStart ? lineStart : newLineStart;
3154 *retLineEnd = maxPos;
3155 return;
3157 nLines++;
3158 if (nLines >= maxLines) {
3159 *retPos = foundBreak ? buf->next_char(b) : max(p, buf->next_char(lineStart));
3160 *retLines = nLines;
3161 *retLineStart = lineStart;
3162 *retLineEnd = foundBreak ? b : p;
3163 return;
3165 lineStart = newLineStart;
3169 /* reached end of buffer before reaching pos or line target */
3170 *retPos = buf->length();
3171 *retLines = nLines;
3172 if (countLastLineMissingNewLine && colNum > 0)
3173 *retLines = buf->next_char(*retLines);
3174 *retLineStart = lineStart;
3175 *retLineEnd = buf->length();
3181 \brief Wrapping calculations.
3183 Measure the width in pixels of the first character of string "s" at a
3184 particular column "colNum" and buffer position "pos". This is for measuring
3185 characters in proportional or mixed-width highlighting fonts.
3187 A note about proportional and mixed-width fonts: the mixed width and
3188 proportional font code in nedit does not get much use in general editing,
3189 because nedit doesn't allow per-language-mode fonts, and editing programs
3190 in a proportional font is usually a bad idea, so very few users would
3191 choose a proportional font as a default. There are still probably mixed-
3192 width syntax highlighting cases where things don't redraw properly for
3193 insertion/deletion, though static display and wrapping and resizing
3194 should now be solid because they are now used for online help display.
3196 \param s text string
3197 \param xPix x pixel position needed for calculating tab widths
3198 \param pos offset within string
3199 \return width of character in pixels
3201 double Fl_Text_Display::measure_proportional_character(const char *s, int xPix, int pos) const {
3202 IS_UTF8_ALIGNED(s)
3204 if (*s=='\t') {
3205 int tab = (int)col_to_x(mBuffer->tab_distance());
3206 return (((xPix/tab)+1)*tab) - xPix;
3209 int charLen = fl_utf8len1(*s), style = 0;
3210 if (mStyleBuffer) {
3211 style = mStyleBuffer->byte_at(pos);
3213 return string_width(s, charLen, style);
3219 \brief Finds both the end of the current line and the start of the next line.
3221 Why?
3222 In continuous wrap mode, if you need to know both, figuring out one from the
3223 other can be expensive or error prone. The problem comes when there's a
3224 trailing space or tab just before the end of the buffer. To translate an
3225 end of line value to or from the next lines start value, you need to know
3226 whether the trailing space or tab is being used as a line break or just a
3227 normal character, and to find that out would otherwise require counting all
3228 the way back to the beginning of the line.
3230 \param startPos
3231 \param startPosIsLineStart
3232 \param[out] lineEnd
3233 \param[out] nextLineStart
3235 void Fl_Text_Display::find_line_end(int startPos, bool startPosIsLineStart,
3236 int *lineEnd, int *nextLineStart) const {
3237 IS_UTF8_ALIGNED2(buffer(), startPos)
3239 int retLines, retLineStart;
3241 /* if we're not wrapping use more efficient BufEndOfLine */
3242 if (!mContinuousWrap) {
3243 int le = buffer()->line_end(startPos);
3244 int ls = buffer()->next_char(le);
3245 *lineEnd = le;
3246 *nextLineStart = min(buffer()->length(), ls);
3247 return;
3250 /* use the wrapped line counter routine to count forward one line */
3251 wrapped_line_counter(buffer(), startPos, buffer()->length(),
3252 1, startPosIsLineStart, 0, nextLineStart, &retLines,
3253 &retLineStart, lineEnd);
3259 \brief Check if the line break is caused by a \\n or by line wrapping.
3261 Line breaks in continuous wrap mode usually happen at newlines or
3262 whitespace. This line-terminating character is not included in line
3263 width measurements and has a special status as a non-visible character.
3264 However, lines with no whitespace are wrapped without the benefit of a
3265 line terminating character, and this distinction causes endless trouble
3266 with all of the text display code which was originally written without
3267 continuous wrap mode and always expects to wrap at a newline character.
3269 Given the position of the end of the line, as returned by TextDEndOfLine
3270 or BufEndOfLine, this returns true if there is a line terminating
3271 character, and false if there's not. On the last character in the
3272 buffer, this function can't tell for certain whether a trailing space was
3273 used as a wrap point, and just guesses that it wasn't. So if an exact
3274 accounting is necessary, don't use this function.
3276 \param lineEndPos index of character where the line wraps
3277 \return 1 if a \\n character causes the line wrap
3279 int Fl_Text_Display::wrap_uses_character(int lineEndPos) const {
3280 IS_UTF8_ALIGNED2(buffer(), lineEndPos)
3282 unsigned int c;
3284 if (!mContinuousWrap || lineEndPos == buffer()->length())
3285 return 1;
3287 c = buffer()->char_at(lineEndPos);
3288 return c == '\n' || ((c == '\t' || c == ' ') &&
3289 lineEndPos + 1 < buffer()->length());
3295 \brief I don't know what this does!
3297 Extend the range of a redraw request (from *start to *end) with additional
3298 redraw requests resulting from changes to the attached style buffer (which
3299 contains auxiliary information for coloring or styling text).
3301 \param startpos ??
3302 \param endpos ??
3304 \todo Unicode?
3306 void Fl_Text_Display::extend_range_for_styles( int *startpos, int *endpos ) {
3307 IS_UTF8_ALIGNED2(buffer(), (*startpos))
3308 IS_UTF8_ALIGNED2(buffer(), (*endpos))
3310 Fl_Text_Selection * sel = mStyleBuffer->primary_selection();
3311 int extended = 0;
3313 /* The peculiar protocol used here is that modifications to the style
3314 buffer are marked by selecting them with the buffer's primary Fl_Text_Selection.
3315 The style buffer is usually modified in response to a modify callback on
3316 the text buffer BEFORE Fl_Text_Display.c's modify callback, so that it can keep
3317 the style buffer in step with the text buffer. The style-update
3318 callback can't just call for a redraw, because Fl_Text_Display hasn't processed
3319 the original text changes yet. Anyhow, to minimize redrawing and to
3320 avoid the complexity of scheduling redraws later, this simple protocol
3321 tells the text display's buffer modify callback to extend its redraw
3322 range to show the text color/and font changes as well. */
3323 if ( sel->selected() ) {
3324 if ( sel->start() < *startpos ) {
3325 *startpos = sel->start();
3326 // somewhere while deleting, alignment is lost. We do this just to be sure.
3327 *startpos = buffer()->utf8_align(*startpos);
3328 IS_UTF8_ALIGNED2(buffer(), (*startpos))
3329 extended = 1;
3331 if ( sel->end() > *endpos ) {
3332 *endpos = sel->end();
3333 *endpos = buffer()->utf8_align(*endpos);
3334 IS_UTF8_ALIGNED2(buffer(), (*endpos))
3335 extended = 1;
3339 /* If the Fl_Text_Selection was extended due to a style change, and some of the
3340 fonts don't match in spacing, extend redraw area to end of line to
3341 redraw characters exposed by possible font size changes */
3342 if ( extended )
3343 *endpos = mBuffer->line_end( *endpos ) + 1;
3345 IS_UTF8_ALIGNED2(buffer(), (*endpos))
3351 \brief Draw the widget.
3353 This function tries to limit drawing to smaller areas if possible.
3355 void Fl_Text_Display::draw(void) {
3356 // don't even try if there is no associated text buffer!
3357 if (!buffer()) { draw_box(); return; }
3359 fl_push_clip(x(),y(),w(),h()); // prevent drawing outside widget area
3361 // draw the non-text, non-scrollbar areas.
3362 if (damage() & FL_DAMAGE_ALL) {
3363 // printf("drawing all (box = %d)\n", box());
3364 if (Fl_Surface_Device::surface()->class_name() == Fl_Printer::class_id) {
3365 // if to printer, draw the background
3366 fl_rectf(text_area.x, text_area.y, text_area.w, text_area.h, color() );
3368 // draw the box()
3369 int W = w(), H = h();
3370 draw_box(box(), x(), y(), W, H, color());
3372 if (mHScrollBar->visible())
3373 W -= scrollbar_width();
3374 if (mVScrollBar->visible())
3375 H -= scrollbar_width();
3377 // left margin
3378 fl_rectf(text_area.x-LEFT_MARGIN, text_area.y-TOP_MARGIN,
3379 LEFT_MARGIN, text_area.h+TOP_MARGIN+BOTTOM_MARGIN,
3380 color());
3382 // right margin
3383 fl_rectf(text_area.x+text_area.w, text_area.y-TOP_MARGIN,
3384 RIGHT_MARGIN, text_area.h+TOP_MARGIN+BOTTOM_MARGIN,
3385 color());
3387 // top margin
3388 fl_rectf(text_area.x, text_area.y-TOP_MARGIN,
3389 text_area.w, TOP_MARGIN, color());
3391 // bottom margin
3392 fl_rectf(text_area.x, text_area.y+text_area.h,
3393 text_area.w, BOTTOM_MARGIN, color());
3395 // draw that little box in the corner of the scrollbars
3396 if (mVScrollBar->visible() && mHScrollBar->visible())
3397 fl_rectf(mVScrollBar->x(), mHScrollBar->y(),
3398 mVScrollBar->w(), mHScrollBar->h(),
3399 FL_GRAY);
3401 // blank the previous cursor protrusions
3403 else if (damage() & (FL_DAMAGE_SCROLL | FL_DAMAGE_EXPOSE)) {
3404 // printf("blanking previous cursor extrusions at Y: %d\n", mCursorOldY);
3405 // CET - FIXME - save old cursor position instead and just draw side needed?
3406 fl_push_clip(text_area.x-LEFT_MARGIN,
3407 text_area.y,
3408 text_area.w+LEFT_MARGIN+RIGHT_MARGIN,
3409 text_area.h);
3410 fl_rectf(text_area.x-LEFT_MARGIN, mCursorOldY,
3411 LEFT_MARGIN, mMaxsize, color());
3412 fl_rectf(text_area.x+text_area.w, mCursorOldY,
3413 RIGHT_MARGIN, mMaxsize, color());
3414 fl_pop_clip();
3417 // draw the scrollbars
3418 if (damage() & (FL_DAMAGE_ALL | FL_DAMAGE_CHILD)) {
3419 mVScrollBar->damage(FL_DAMAGE_ALL);
3420 mHScrollBar->damage(FL_DAMAGE_ALL);
3422 update_child(*mVScrollBar);
3423 update_child(*mHScrollBar);
3425 // draw all of the text
3426 if (damage() & (FL_DAMAGE_ALL | FL_DAMAGE_EXPOSE)) {
3427 //printf("drawing all text\n");
3428 int X, Y, W, H;
3429 if (fl_clip_box(text_area.x, text_area.y, text_area.w, text_area.h,
3430 X, Y, W, H)) {
3431 // Draw text using the intersected clipping box...
3432 // (this sets the clipping internally)
3433 draw_text(X, Y, W, H);
3434 } else {
3435 // Draw the whole area...
3436 draw_text(text_area.x, text_area.y, text_area.w, text_area.h);
3439 else if (damage() & FL_DAMAGE_SCROLL) {
3440 // draw some lines of text
3441 fl_push_clip(text_area.x, text_area.y,
3442 text_area.w, text_area.h);
3443 //printf("drawing text from %d to %d\n", damage_range1_start, damage_range1_end);
3444 draw_range(damage_range1_start, damage_range1_end);
3445 if (damage_range2_end != -1) {
3446 //printf("drawing text from %d to %d\n", damage_range2_start, damage_range2_end);
3447 draw_range(damage_range2_start, damage_range2_end);
3449 damage_range1_start = damage_range1_end = -1;
3450 damage_range2_start = damage_range2_end = -1;
3451 fl_pop_clip();
3454 // draw the text cursor
3455 if (damage() & (FL_DAMAGE_ALL | FL_DAMAGE_SCROLL | FL_DAMAGE_EXPOSE)
3456 && !buffer()->primary_selection()->selected() &&
3457 mCursorOn && Fl::focus() == (Fl_Widget*)this ) {
3458 fl_push_clip(text_area.x-LEFT_MARGIN,
3459 text_area.y,
3460 text_area.w+LEFT_MARGIN+RIGHT_MARGIN,
3461 text_area.h);
3463 int X, Y;
3464 if (position_to_xy(mCursorPos, &X, &Y)) draw_cursor(X, Y);
3465 // else puts("position_to_xy() failed - unable to draw cursor!");
3466 //printf("drew cursor at pos: %d (%d,%d)\n", mCursorPos, X, Y);
3467 mCursorOldY = Y;
3468 fl_pop_clip();
3470 fl_pop_clip();
3475 // this processes drag events due to mouse for Fl_Text_Display and
3476 // also drags due to cursor movement with shift held down for
3477 // Fl_Text_Editor
3478 void fl_text_drag_me(int pos, Fl_Text_Display* d) {
3479 if (d->dragType == Fl_Text_Display::DRAG_CHAR) {
3480 if (pos >= d->dragPos) {
3481 d->buffer()->select(d->dragPos, pos);
3482 } else {
3483 d->buffer()->select(pos, d->dragPos);
3485 d->insert_position(pos);
3486 } else if (d->dragType == Fl_Text_Display::DRAG_WORD) {
3487 if (pos >= d->dragPos) {
3488 d->insert_position(d->word_end(pos));
3489 d->buffer()->select(d->word_start(d->dragPos), d->word_end(pos));
3490 } else {
3491 d->insert_position(d->word_start(pos));
3492 d->buffer()->select(d->word_start(pos), d->word_end(d->dragPos));
3494 } else if (d->dragType == Fl_Text_Display::DRAG_LINE) {
3495 if (pos >= d->dragPos) {
3496 d->insert_position(d->buffer()->line_end(pos)+1);
3497 d->buffer()->select(d->buffer()->line_start(d->dragPos),
3498 d->buffer()->line_end(pos)+1);
3499 } else {
3500 d->insert_position(d->buffer()->line_start(pos));
3501 d->buffer()->select(d->buffer()->line_start(pos),
3502 d->buffer()->line_end(d->dragPos)+1);
3510 \brief Timer callback for scroll events.
3512 This timer event scrolls the text view proportionally to
3513 how far the mouse pointer has left the text area. This
3514 allows for smooth scrolling without "wiggeling" the mouse.
3516 void Fl_Text_Display::scroll_timer_cb(void *user_data) {
3517 Fl_Text_Display *w = (Fl_Text_Display*)user_data;
3518 int pos;
3519 switch (scroll_direction) {
3520 case 1: // mouse is to the right, scroll left
3521 w->scroll(w->mTopLineNum, w->mHorizOffset + scroll_amount);
3522 pos = w->xy_to_position(w->text_area.x + w->text_area.w, scroll_y, CURSOR_POS);
3523 break;
3524 case 2: // mouse is to the left, scroll right
3525 w->scroll(w->mTopLineNum, w->mHorizOffset + scroll_amount);
3526 pos = w->xy_to_position(w->text_area.x, scroll_y, CURSOR_POS);
3527 break;
3528 case 3: // mouse is above, scroll down
3529 w->scroll(w->mTopLineNum + scroll_amount, w->mHorizOffset);
3530 pos = w->xy_to_position(scroll_x, w->text_area.y, CURSOR_POS);
3531 break;
3532 case 4: // mouse is below, scroll up
3533 w->scroll(w->mTopLineNum + scroll_amount, w->mHorizOffset);
3534 pos = w->xy_to_position(scroll_x, w->text_area.y + w->text_area.h, CURSOR_POS);
3535 break;
3536 default:
3537 return;
3539 fl_text_drag_me(pos, w);
3540 Fl::repeat_timeout(.1, scroll_timer_cb, user_data);
3546 \brief Event handling.
3548 int Fl_Text_Display::handle(int event) {
3549 if (!buffer()) return 0;
3550 // This isn't very elegant!
3551 if (!Fl::event_inside(text_area.x, text_area.y, text_area.w, text_area.h) &&
3552 !dragging && event != FL_LEAVE && event != FL_ENTER &&
3553 event != FL_MOVE && event != FL_FOCUS && event != FL_UNFOCUS &&
3554 event != FL_KEYBOARD && event != FL_KEYUP) {
3555 return Fl_Group::handle(event);
3558 switch (event) {
3559 case FL_ENTER:
3560 case FL_MOVE:
3561 if (active_r()) {
3562 if (Fl::event_inside(text_area.x, text_area.y, text_area.w,
3563 text_area.h)) window()->cursor(FL_CURSOR_INSERT);
3564 else window()->cursor(FL_CURSOR_DEFAULT);
3565 return 1;
3566 } else {
3567 return 0;
3570 case FL_LEAVE:
3571 case FL_HIDE:
3572 if (active_r() && window()) {
3573 window()->cursor(FL_CURSOR_DEFAULT);
3575 return 1;
3576 } else {
3577 return 0;
3580 case FL_PUSH: {
3581 if (active_r() && window()) {
3582 if (Fl::event_inside(text_area.x, text_area.y, text_area.w,
3583 text_area.h)) window()->cursor(FL_CURSOR_INSERT);
3584 else window()->cursor(FL_CURSOR_DEFAULT);
3587 if (Fl::focus() != this) {
3588 Fl::focus(this);
3589 handle(FL_FOCUS);
3591 if (Fl_Group::handle(event)) return 1;
3592 if (Fl::event_state()&FL_SHIFT) return handle(FL_DRAG);
3593 dragging = 1;
3594 int pos = xy_to_position(Fl::event_x(), Fl::event_y(), CURSOR_POS);
3595 dragPos = pos;
3596 if (buffer()->primary_selection()->includes(pos)) {
3597 dragType = DRAG_START_DND;
3598 return 1;
3600 dragType = Fl::event_clicks();
3601 if (dragType == DRAG_CHAR) {
3602 buffer()->unselect();
3603 // Fl::copy("", 0, 0); /* removed for STR 2668 */
3605 else if (dragType == DRAG_WORD) {
3606 buffer()->select(word_start(pos), word_end(pos));
3607 dragPos = word_start(pos);
3610 if (buffer()->primary_selection()->selected())
3611 insert_position(buffer()->primary_selection()->end());
3612 else
3613 insert_position(pos);
3614 show_insert_position();
3615 return 1;
3618 case FL_DRAG: {
3619 if (dragType==DRAG_NONE)
3620 return 1;
3621 if (dragType==DRAG_START_DND) {
3622 if (!Fl::event_is_click() && Fl::dnd_text_ops()) {
3623 const char* copy = buffer()->selection_text();
3624 Fl::dnd();
3625 free((void*)copy);
3627 return 1;
3629 int X = Fl::event_x(), Y = Fl::event_y(), pos = insert_position();
3630 // if we leave the text_area, we start a timer event
3631 // that will take care of scrolling and selecting
3632 if (Y < text_area.y) {
3633 scroll_x = X;
3634 scroll_amount = (Y - text_area.y) / 5 - 1;
3635 if (!scroll_direction) {
3636 Fl::add_timeout(.01, scroll_timer_cb, this);
3638 scroll_direction = 3;
3639 } else if (Y >= text_area.y+text_area.h) {
3640 scroll_x = X;
3641 scroll_amount = (Y - text_area.y - text_area.h) / 5 + 1;
3642 if (!scroll_direction) {
3643 Fl::add_timeout(.01, scroll_timer_cb, this);
3645 scroll_direction = 4;
3646 } else if (X < text_area.x) {
3647 scroll_y = Y;
3648 scroll_amount = (X - text_area.x) / 2 - 1;
3649 if (!scroll_direction) {
3650 Fl::add_timeout(.01, scroll_timer_cb, this);
3652 scroll_direction = 2;
3653 } else if (X >= text_area.x+text_area.w) {
3654 scroll_y = Y;
3655 scroll_amount = (X - text_area.x - text_area.w) / 2 + 1;
3656 if (!scroll_direction) {
3657 Fl::add_timeout(.01, scroll_timer_cb, this);
3659 scroll_direction = 1;
3660 } else {
3661 if (scroll_direction) {
3662 Fl::remove_timeout(scroll_timer_cb, this);
3663 scroll_direction = 0;
3665 pos = xy_to_position(X, Y, CURSOR_POS);
3666 pos = buffer()->next_char(pos);
3668 fl_text_drag_me(pos, this);
3669 return 1;
3672 case FL_RELEASE: {
3673 if (Fl::event_is_click() && (! Fl::event_clicks()) &&
3674 buffer()->primary_selection()->includes(dragPos) && !(Fl::event_state()&FL_SHIFT) ) {
3675 buffer()->unselect(); // clicking in the selection: unselect and move cursor
3676 insert_position(dragPos);
3677 return 1;
3678 } else if (Fl::event_clicks() == DRAG_LINE && Fl::event_button() == FL_LEFT_MOUSE) {
3679 buffer()->select(buffer()->line_start(dragPos), buffer()->next_char(buffer()->line_end(dragPos)));
3680 dragPos = line_start(dragPos);
3681 dragType = DRAG_CHAR;
3682 } else {
3683 dragging = 0;
3684 if (scroll_direction) {
3685 Fl::remove_timeout(scroll_timer_cb, this);
3686 scroll_direction = 0;
3689 // convert from WORD or LINE selection to CHAR
3690 /*if (insert_position() >= dragPos)
3691 dragPos = buffer()->primary_selection()->start();
3692 else
3693 dragPos = buffer()->primary_selection()->end();*/
3694 dragType = DRAG_CHAR;
3697 const char* copy = buffer()->selection_text();
3698 if (*copy) Fl::copy(copy, strlen(copy), 0);
3699 free((void*)copy);
3700 return 1;
3703 case FL_MOUSEWHEEL:
3704 if (Fl::event_dy()) return mVScrollBar->handle(event);
3705 else return mHScrollBar->handle(event);
3707 case FL_UNFOCUS:
3708 if (active_r() && window()) window()->cursor(FL_CURSOR_DEFAULT);
3709 case FL_FOCUS:
3710 if (buffer()->selected()) {
3711 int start, end;
3712 if (buffer()->selection_position(&start, &end))
3713 redisplay_range(start, end);
3715 if (buffer()->secondary_selected()) {
3716 int start, end;
3717 if (buffer()->secondary_selection_position(&start, &end))
3718 redisplay_range(start, end);
3720 if (buffer()->highlight()) {
3721 int start, end;
3722 if (buffer()->highlight_position(&start, &end))
3723 redisplay_range(start, end);
3725 return 1;
3727 case FL_KEYBOARD:
3728 // Copy?
3729 if ((Fl::event_state()&(FL_CTRL|FL_COMMAND)) && Fl::event_key()=='c') {
3730 if (!buffer()->selected()) return 1;
3731 const char *copy = buffer()->selection_text();
3732 if (*copy) Fl::copy(copy, strlen(copy), 1);
3733 free((void*)copy);
3734 return 1;
3737 // Select all ?
3738 if ((Fl::event_state()&(FL_CTRL|FL_COMMAND)) && Fl::event_key()=='a') {
3739 buffer()->select(0,buffer()->length());
3740 const char *copy = buffer()->selection_text();
3741 if (*copy) Fl::copy(copy, strlen(copy), 0);
3742 free((void*)copy);
3743 return 1;
3746 if (mVScrollBar->handle(event)) return 1;
3747 if (mHScrollBar->handle(event)) return 1;
3749 break;
3751 case FL_SHORTCUT:
3752 if (!(shortcut() ? Fl::test_shortcut(shortcut()) : test_shortcut()))
3753 return 0;
3754 if (Fl::visible_focus() && handle(FL_FOCUS)) {
3755 Fl::focus(this);
3756 return 1;
3758 break;
3762 return 0;
3767 Convert an x pixel position into a column number.
3769 double Fl_Text_Display::x_to_col(double y) const
3771 if (!mColumnScale) {
3772 mColumnScale = string_width("Mitg", 4, 'A') / 4.0;
3774 return (y/mColumnScale)+0.5;
3779 Convert a column number into an x pixel position.
3781 double Fl_Text_Display::col_to_x(double col) const
3783 if (!mColumnScale) {
3784 // recalculate column scale value
3785 x_to_col(0);
3787 return col*mColumnScale;
3794 // End of "$Id: Fl_Text_Display.cxx 8808 2011-06-16 13:31:25Z manolo $".