2
* Copyright 2010 Inalogic Inc.
4
* This program is free software: you can redistribute it and/or modify it
5
* under the terms of the GNU Lesser General Public License version 3, as
6
* published by the Free Software Foundation.
8
* This program is distributed in the hope that it will be useful, but
9
* WITHOUT ANY WARRANTY; without even the implied warranties of
10
* MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
11
* PURPOSE. See the applicable version of the GNU Lesser General Public
12
* License for more details.
14
* You should have received a copy of both the GNU Lesser General Public
15
* License version 3 along with this program. If not, see
16
* <http://www.gnu.org/licenses/>
18
* Authored by: Jay Taoko <jay.taoko_AT_gmail_DOT_com>
23
#include "Basic/NKernel.h"
25
#include "TextViewInternal.h"
26
#include "HScrollBar.h"
27
#include "VScrollBar.h"
29
int ScrollDir(int counter, int dir);
34
// Grab the keyboard input focus
36
// LONG TextView::OnMouseActivate(HWND hwndTop, UINT nHitTest, UINT nMessage)
39
// return MA_ACTIVATE;
45
// Position caret to nearest text character under mouse
47
void TextView::OnLButtonDown(int mx, int my, unsigned long button_flags, unsigned long key_flags /*UINT nFlags, int mx, int my*/)
52
mx = mx - getBorder() - GetViewContentLeftMargin();
53
my = my - getTopBorder() - GetViewContentTopMargin();
55
// remove any existing selection
56
InvalidateRange(m_nSelectionStart, m_nSelectionEnd);
57
// regular mouse input - mouse is within
58
if(mx >= LeftMarginWidth())
60
m_CharacterAtCursor = 0;
61
m_CursorCharacterPosition = 0;
62
// map the mouse-coordinates to a real file-offset-coordinate
63
MouseCoordToFilePos(mx, my, &nLineNo, &nFileOff, &m_nCaretPosX, &m_CharacterAtCursor, &m_CursorCharacterPosition);
64
m_nAnchorPosX = m_nCaretPosX;
66
UpdateCaretXY(m_nCaretPosX, nLineNo);
68
// Any key but <shift>
69
if(IsKeyPressed(VK_SHIFT) == false)
71
// remove any existing selection
72
InvalidateRange(m_nSelectionStart, m_nSelectionEnd);
73
// reset cursor and selection offsets to the same location
74
m_nSelectionStart = nFileOff;
75
m_nSelectionEnd = nFileOff;
76
m_nCursorOffset = nFileOff;
81
InvalidateRange(m_nSelectionEnd, nFileOff);
83
// extend selection to cursor
84
m_nSelectionEnd = nFileOff;
85
m_nCursorOffset = nFileOff;
88
if(IsKeyPressed(VK_MENU))
90
m_cpBlockStart.line = nLineNo;
91
m_cpBlockStart.xpos = m_nCaretPosX;
92
m_nSelectionType = SEL_BLOCK;
96
m_nSelectionType = SEL_NORMAL;
98
// set capture for mouse-move selections
99
m_nSelectionMode = IsKeyPressed(VK_MENU) ? SEL_BLOCK : SEL_NORMAL;
101
// mouse clicked within margin
104
// remove any existing selection
105
InvalidateRange(m_nSelectionStart, m_nSelectionEnd);
107
nLineNo = (my / m_nLineHeight) + m_nVScrollPos;
110
// if we click in the margin then jump back to start of line
112
if(m_nHScrollPos != 0)
119
m_pTextDoc->lineinfo_from_lineno(nLineNo, &m_nSelectionStart, &m_nSelectionEnd, 0, 0);
120
m_nSelectionEnd += m_nSelectionStart;
121
m_nCursorOffset = m_nSelectionStart;
123
m_nSelMarginOffset1 = m_nSelectionStart;
124
m_nSelMarginOffset2 = m_nSelectionEnd;
126
InvalidateRange(m_nSelectionStart, m_nSelectionEnd);
128
UpdateCaretOffset(m_nCursorOffset, FALSE, &m_nCaretPosX, &m_nCurrentLine);
129
m_nAnchorPosX = m_nCaretPosX;
132
// set capture for mouse-move selections
133
m_nSelectionMode = SEL_MARGIN;
138
SetCapture(GetThreadGLWindow()->GetWindowHandle());
140
StopBlinkCursor(false);
141
StartBlinkCursor(false);
148
// Release capture and cancel any mouse-scrolling
150
void TextView::OnLButtonUp(int mx, int my, unsigned long button_flags, unsigned long key_flags /*UINT nFlags, int mx, int my*/)
152
mx = mx - getBorder() - GetViewContentLeftMargin();
153
my = my - getTopBorder() - GetViewContentTopMargin();
155
// shift cursor to end of selection
156
if(m_nSelectionMode == SEL_MARGIN)
158
m_nCursorOffset = m_nSelectionEnd;
159
// Invalidate the selected range just to delete the cursor that was in it.
160
InvalidateRange(m_nSelectionStart, m_nSelectionEnd);
161
UpdateCaretOffset(m_nCursorOffset, FALSE, &m_nCaretPosX, &m_nCurrentLine);
166
// cancel the scroll-timer if it is still running
167
if(MouseAutoScrollHandle != 0)
169
GetThreadTimer()->RemoveTimerHandler(MouseAutoScrollHandle);
170
MouseAutoScrollHandle = 0;
173
m_nSelectionMode = SEL_NONE;
182
// Select the word under the mouse
184
void TextView::OnLButtonDblClick(int mx, int my, unsigned long button_flags, unsigned long key_flags)
186
mx = mx - getBorder() - GetViewContentLeftMargin();
187
my = my - getTopBorder() - GetViewContentTopMargin();
189
// remove any existing selection
190
InvalidateRange(m_nSelectionStart, m_nSelectionEnd);
192
// regular mouse input - mouse is within scrolling viewport
193
if(mx >= LeftMarginWidth())
195
inl::t_u32 lineno, fileoff;
198
// map the mouse-coordinates to a real file-offset-coordinate
199
MouseCoordToFilePos(mx, my, &lineno, &fileoff, &m_nCaretPosX, &m_CharacterAtCursor, &m_CursorCharacterPosition);
200
m_nAnchorPosX = m_nCaretPosX;
202
// move selection-start to start of word
203
//-->MoveWordStart();
204
m_nSelectionStart = m_nCursorOffset;
206
// move selection-end to end of word
208
m_nSelectionEnd = m_nCursorOffset;
210
// update caret position
211
InvalidateRange(m_nSelectionStart, m_nSelectionEnd);
212
UpdateCaretOffset(m_nCursorOffset, TRUE, &m_nCaretPosX, &m_nCurrentLine);
213
m_nAnchorPosX = m_nCaretPosX;
215
//NotifyParent(TVN_CURSOR_CHANGE);
222
// Set the selection end-point if we are dragging the mouse
224
void TextView::RecvMouseMove(int mx, int my, int dx, int dy, unsigned long button_flags, unsigned long key_flags /*UINT nFlags, int mx, int my*/)
226
mx = mx - getBorder() - GetViewContentLeftMargin();
227
my = my - getTopBorder() - GetViewContentTopMargin();
231
inl::t_u32 nLineNo, nFileOff;
232
bool fCurChanged = FALSE;
235
inl::Point pt(mx, my);
236
//int cx; // caret coordinates
239
// First thing we must do is switch from margin-mode to normal-mode
240
// if the mouse strays into the main document area
242
if((m_nSelectionMode == SEL_MARGIN) && (mx > LeftMarginWidth()))
244
m_nSelectionMode = SEL_NORMAL;
245
GetThreadGLWindow()->SetWindowCursor(LoadCursor(0, IDC_IBEAM));
249
// Mouse-scrolling: detect if the mouse
250
// is inside/outside of the TextView scrolling area
251
// and stop/start a scrolling timer appropriately
253
//GetClientRect(m_hWnd, &rect);
255
rect = GetTextAreaGeometry();
257
// build the scrolling area
258
rect.OffsetPosition(LeftMarginWidth(), 0);
259
rect.OffsetSize(-LeftMarginWidth(), -rect.GetHeight() % m_nLineHeight);
262
// If mouse is within this area, we don't need to scroll
263
if(rect.IsPointInside(pt.GetX(), pt.GetY()))
265
if(MouseAutoScrollHandle != 0)
267
GetThreadTimer()->RemoveTimerHandler(MouseAutoScrollHandle);
268
MouseAutoScrollHandle = 0;
271
// If mouse is outside window, start a timer in
272
// order to generate regular scrolling intervals
275
if(m_nScrollTimer == 0)
277
m_nScrollCounter = 0;
278
MouseAutoScrollHandle = GetThreadTimer()->AddTimerHandler(10, MouseAutoScrollTimer, this);
279
inlDebugMsg(TEXT("Here"));
283
m_CharacterAtCursor = 0;
284
m_CursorCharacterPosition = 0;
285
// get new cursor offset+coordinates
286
MouseCoordToFilePos(mx, my, &nLineNo, &nFileOff, &m_nCaretPosX, &m_CharacterAtCursor, &m_CursorCharacterPosition);
287
m_nAnchorPosX = m_nCaretPosX;
289
m_cpBlockEnd.line = nLineNo;
290
m_cpBlockEnd.xpos = mx + m_nHScrollPos * m_nFontWidth - LeftMarginWidth();//m_nCaretPosX;
293
// redraw the old and new lines if they are different
296
// update the region of text that has changed selection state
297
fCurChanged = m_nSelectionEnd == nFileOff ? FALSE : TRUE;
298
//if(m_nSelectionEnd != nFileOff)
301
m_pTextDoc->lineinfo_from_lineno(nLineNo, 0, &linelen, 0, 0);
303
m_nCursorOffset = nFileOff;
305
if(m_nSelectionMode == SEL_MARGIN)
307
if(nFileOff >= m_nSelectionStart)
310
m_nSelectionStart = m_nSelMarginOffset1;
314
m_nSelectionStart = m_nSelMarginOffset2;
318
// redraw from old selection-pos to new position
319
InvalidateRange(m_nSelectionEnd, nFileOff);
321
// adjust the cursor + selection to the new offset
322
m_nSelectionEnd = nFileOff;
325
// always set the caret position because we might be scrolling
326
UpdateCaretXY(m_nCaretPosX, nLineNo);
330
// mouse isn't being used for a selection, so set the cursor instead
334
Geometry rect = GetTextAreaGeometry();
335
if(mx >= 0 && mx < LeftMarginWidth())
337
GetThreadGLWindow()->SetWindowCursor(m_hMarginCursor);
339
else if(rect.IsPointInside(mx+getBorder(), my+getTopBorder()))
340
GetThreadGLWindow()->SetWindowCursor(LoadCursor(0, IDC_IBEAM));
343
GetThreadGLWindow()->SetWindowCursor(LoadCursor(0, IDC_ARROW));
348
void TextView::RecvMouseEnter(int x, int y, unsigned long button_flags, unsigned long key_flags)
350
x = x - getBorder() - GetViewContentLeftMargin();
351
y = y - getTopBorder() - GetViewContentTopMargin();
353
Geometry rect = GetTextAreaGeometry();
354
if(x >= 0 && x < LeftMarginWidth())
356
#if defined (INL_OS_WINDOWS)
357
GetThreadGLWindow()->SetWindowCursor(m_hMarginCursor);
360
else if(rect.IsPointInside(x+getBorder(), y+getTopBorder()))
362
#if defined (INL_OS_WINDOWS)
363
GetThreadGLWindow()->SetWindowCursor(LoadCursor(0, IDC_IBEAM));
368
#if defined (INL_OS_WINDOWS)
369
GetThreadGLWindow()->SetWindowCursor(LoadCursor(0, IDC_ARROW));
374
void TextView::RecvMouseLeave(int x, int y, unsigned long button_flags, unsigned long key_flags)
376
x = x - getBorder() - GetViewContentLeftMargin();
377
y = y - getTopBorder() - GetViewContentTopMargin();
379
GetThreadGLWindow()->SetWindowCursor(LoadCursor(0, IDC_ARROW));
383
void TextView::MouseAutoScrollHandler(void* v)
385
MouseAutoScrollHandle = GetThreadTimer()->AddTimerHandler(10, MouseAutoScrollTimer, this);
391
// Used to create regular scrolling
393
void TextView::RecvTimer(void* v)
395
int dx = 0, dy = 0; // scrolling vectors
398
// get the mouse's client-coordinates
399
inl::Point point = GetThreadGLWindow()->GetMouseWindowCoord();
401
// find client area, but make it an even no. of lines
402
rect = GetTextAreaGeometry();
403
rect.OffsetPosition(LeftMarginWidth(), 0);
404
rect.OffsetSize(-LeftMarginWidth(), -rect.GetHeight() % m_nLineHeight);
407
// scrolling up / down??
409
if(point.Y() < rect.GetY())
411
dy = ScrollDir(m_nScrollCounter, point.Y() - rect.GetY());
412
ScrollUp(1, 2 /*m_nLineHeight*/);
414
else if(point.Y() >= rect.GetY() + rect.GetHeight())
416
dy = ScrollDir(m_nScrollCounter, point.Y() - (rect.GetY() + rect.GetHeight()));
417
ScrollDown(1, 2 /*m_nLineHeight*/);
420
// scrolling left / right?
422
if(point.X() < rect.GetX())
424
dx = ScrollDir(m_nScrollCounter, point.X() - rect.GetX());
427
else if(point.X() > rect.GetX() + rect.GetWidth())
429
dx = ScrollDir(m_nScrollCounter, point.X() - (rect.GetX() + rect.GetWidth()));
434
// do the redraw now that the selection offsets are all
435
// pointing to the right places and the scroll positions are valid.
437
if((dy != 0) /*(m_nSelectionMode != SEL_MARGIN)*/ /*hrgnUpdate != NULL*/)
439
// We perform a "fake" WM_MOUSEMOVE for two reasons:
441
// 1. To get the cursor/caret/selection offsets set to the correct place
442
// *before* we redraw (so everything is synchronized correctly)
444
// 2. To invalidate any areas due to mouse-movement which won't
445
// get done until the next WM_MOUSEMOVE - and then it would
446
// be too late because we need to redraw *now*
448
RecvMouseMove(point.GetX(), point.GetY(), 0, 0, 0, 0);
452
// keep track of how many WM_TIMERs we process because
453
// we might want to skip the next one
458
// Convert mouse(client) coordinates to a file-relative offset
460
// Currently only uses the main font so will not support other
461
// fonts introduced by syntax highlighting
463
bool TextView::MouseCoordToFilePos( int mx, // [in] mouse x-coord
464
int my, // [in] mouse x-coord
465
inl::t_u32 *pnLineNo, // [out] line number
466
inl::t_u32 *pnFileOffset, // [out] zero-based file-offset (in chars)
467
int *psnappedX, // [out] adjusted x coord of caret
469
inl::t_u32 *CharacterPosition
473
inl::t_u32 off_chars;
477
// Get scrollable area
478
rect = GetTextAreaGeometry();
479
// Compute a rectangle that contains full lines (no partial lines)
480
rect.OffsetSize(0, -rect.GetHeight() % m_nLineHeight);
482
// Take left margin into account
483
mx -= LeftMarginWidth();
485
// Clip mouse to edge of window
490
if(my >= rect.GetY() + rect.GetHeight())
491
my = rect.GetY() + rect.GetHeight() - 1;
492
if(mx >= rect.GetX() + rect.GetWidth())
493
mx = rect.GetX() + rect.GetWidth() - 1;
495
// It's easy to find the line-number: just divide 'y' by the line-height
496
nLineNo = (my / m_nLineHeight) + m_nVScrollPos;
498
// make sure we don't go outside of the document
499
if(nLineNo >= m_nLineCount)
501
nLineNo = m_nLineCount ? m_nLineCount - 1 : 0;
502
off_chars = m_pTextDoc->size();
505
mx += m_nHScrollPos * m_nFontWidth;
507
// get the USPDATA object for the selected line!!
508
//USPDATA *uspData = GetUspData(0, nLineNo);
510
// convert mouse-x coordinate to a character-offset relative to start of line
511
UspSnapXToOffset(nLineNo, mx, &mx, &cp, 0);
514
TextIterator itor = m_pTextDoc->iterate_line(nLineNo, &off_chars);
518
*pnFileOffset = cp + off_chars;
520
*psnappedX = mx;// - m_nHScrollPos * m_nFontWidth;
521
//*psnappedX += LeftMarginWidth();
523
if(CharacterPosition)
524
*CharacterPosition = cp;
526
// now find the x-coordinate on the specified line
529
TCHAR buf[TEXTBUFSIZE];
532
while((len += itor.gettext(buf, TEXTBUFSIZE)) > 0)
534
if(offset + len > *CharacterPosition)
536
*Character = *(buf + *CharacterPosition);
537
if(*Character < TEXT(' '))
547
LONG TextView::InvalidateLine(inl::t_u32 nLineNo)
549
if(nLineNo >= m_nVScrollPos && nLineNo <= m_nVScrollPos + m_nWindowLines)
553
rect.top = (nLineNo - m_nVScrollPos) * m_nLineHeight;
554
rect.bottom = rect.top + m_nLineHeight;
560
// Redraw the specified range of text/data in the control
562
LONG TextView::InvalidateRange(inl::t_u32 nStart, inl::t_u32 nFinish)
564
inl::t_u32 start = std::min(nStart, nFinish);
565
inl::t_u32 finish = std::max(nStart, nFinish);
572
// information about current line:
574
inl::t_u32 off_chars;
575
inl::t_u32 len_chars;
576
client = GetTextAreaGeometry();
581
// We still need to clean the line in case the cursor was on it.
585
// Find the start-of-line information from specified file-offset
587
lineno = m_pTextDoc->lineno_from_offset(start);
589
// clip to top of window
590
if(lineno < m_nVScrollPos)
592
lineno = m_nVScrollPos;
593
itor = m_pTextDoc->iterate_line(lineno, &off_chars, &len_chars);
595
AddDirtyLine(lineno);
600
itor = m_pTextDoc->iterate_line(lineno, &off_chars, &len_chars);
601
AddDirtyLine(lineno);
605
if(!itor || start >= finish)
608
ypos = (lineno - m_nVScrollPos) * m_nLineHeight;
611
// invalidate *whole* lines. don't care about flickering anymore because
612
// all output is double-buffered now, and this method is much simpler
613
while(itor && off_chars < finish)
615
// jump down to next line
616
itor = m_pTextDoc->iterate_line(++lineno, &off_chars, &len_chars);
617
ypos += m_nLineHeight;
618
AddDirtyLine(lineno);
626
// Wrapper around SetCaretPos, hides the caret when it goes
627
// off-screen (in case x/y wrap around)
629
void TextView::MoveCaret(int xpos, inl::t_u32 lineno)
631
bool visible = false;
633
// convert x-coord to window-relative
634
// xpos -= m_nHScrollPos * m_nFontWidth;
635
// xpos += LeftMarginWidth();
637
if(lineno >= m_nVScrollPos && lineno <= m_nVScrollPos + m_nWindowLines)
639
if(xpos >= LeftMarginWidth())
643
// hide caret if it was previously visible
644
if(visible == false && m_fHideCaret == false)
649
// show caret if it was previously hidden
650
else if(visible == true && m_fHideCaret == true)
652
m_fHideCaret = false;
656
if(m_fHideCaret == false)
657
m_CaretPosition.Set(xpos + getBorder() + GetViewContentLeftMargin(), (lineno - m_nVScrollPos) * m_nLineHeight + getTopBorder() + GetViewContentTopMargin());
659
m_RedrawCaret = true;
663
// x - x-coord relative to start of line
664
// lineno - line-number
666
void TextView::UpdateCaretXY(int xpos, inl::t_u32 lineno)
668
bool visible = false;
670
// convert x-coord to window-relative
671
xpos -= m_nHScrollPos * m_nFontWidth;
672
xpos += LeftMarginWidth();
674
// only show caret if it is visible within viewport
675
if(lineno >= m_nVScrollPos && lineno <= m_nVScrollPos + m_nWindowLines)
677
if(xpos >= LeftMarginWidth()/* && (GetThreadGLWindow()->GetCurrentEvent().e_event != INL_MOUSE_PRESSED)*/)
681
// hide caret if it was previously visible
682
if(visible == false && m_fHideCaret == false)
687
// show caret if it was previously hidden
688
else if(visible == true && m_fHideCaret == true)
690
m_fHideCaret = false;
694
// set caret position if within window viewport
695
if(m_fHideCaret == false)
697
m_CaretPosition.Set(xpos + getBorder() + GetViewContentLeftMargin(), (lineno - m_nVScrollPos) * m_nLineHeight + getTopBorder() + GetViewContentTopMargin());
700
inlDebugMsg(TEXT("Caret x: %d"), xpos + getBorder() + GetViewContentLeftMargin());
701
m_RedrawCaret = true;
705
// Reposition the caret based on cursor-offset
706
// return the resulting x-coord and line#
708
void TextView::UpdateCaretOffset(inl::t_u32 offset, bool fTrailing, int *outx, inl::t_u32 *outlineno)
710
inl::t_u32 lineno = 0;
712
inl::t_u32 off_chars;
715
// get line information from cursor-offset
716
if(m_pTextDoc->lineinfo_from_offset(offset, &lineno, &off_chars, 0, 0, 0))
718
// locate the USPDATA for this line
719
//if((uspData = GetUspData(NULL, lineno)) != 0)
721
// convert character-offset to x-coordinate
722
off_chars = m_nCursorOffset - off_chars;
724
if(fTrailing && off_chars > 0)
725
UspOffsetToX(/*uspData,*/ lineno, off_chars-1, TRUE, &xpos);
727
UspOffsetToX(/*uspData,*/ lineno, off_chars, FALSE, &xpos);
729
// update caret position
730
UpdateCaretXY(xpos, lineno);
734
if(outx) *outx = xpos;
735
if(outlineno) *outlineno = lineno;
738
void TextView::RepositionCaret()
740
UpdateCaretXY(m_nCaretPosX, m_nCurrentLine);
743
void TextView::UpdateLine(inl::t_u32 nLineNo)
745
m_nPreviousLine = m_nCurrentLine;
746
AddDirtyLine(m_nCurrentLine);
747
// redraw the old and new lines if they are different
748
if(m_nCurrentLine != nLineNo)
750
if(CheckStyle(TXS_HIGHLIGHTCURLINE))
751
InvalidateLine(m_nCurrentLine);
752
AddDirtyLine(nLineNo);
753
m_nCurrentLine = nLineNo;
755
if(CheckStyle(TXS_HIGHLIGHTCURLINE))
756
InvalidateLine(m_nCurrentLine);
760
void TextView::AddDirtyLine(inl::t_u32 nLineNo)
763
std::vector<int>::iterator it;
764
it = find(m_DirtyLines.begin(), m_DirtyLines.end(), nLineNo);
765
if(it == m_DirtyLines.end())
767
//inlDebugMsg(TEXT("Set Dirty Line (%d): %d"), n, nLineNo);
769
m_DirtyLines.push_back(nLineNo);
774
// return direction to scroll (+ve, -ve or 0) based on
775
// distance of mouse from window edge
777
// note: counter now redundant, we scroll multiple lines at
778
// a time (with a slower timer than before) to achieve
779
// variable-speed scrolling
781
int ScrollDir(int counter, int distance)
783
if(distance > 48) return 5;
784
if(distance > 16) return 2;
785
if(distance > 3) return 1;
786
if(distance > 0) return counter % 5 == 0 ? 1 : 0;
788
if(distance < -48) return -5;
789
if(distance < -16) return -2;
790
if(distance < -3) return -1;
791
if(distance < 0) return counter % 5 == 0 ? -1 : 0;