summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMatthias Melcher <github@matthiasm.com>2024-02-18 13:29:37 +0100
committerGitHub <noreply@github.com>2024-02-18 13:29:37 +0100
commit2f343ad64dfc6a006660198f5757214114f38d7a (patch)
tree6e581e057eff7db1e4cd800c7574ca491e9fedf7 /src
parenteb4916344b7cd92ac1a4aa862976808c96dbfcde (diff)
Fix Terminal character position and add word selection (#906)
* Improve horizontal interactive selection * Using half-character positions to implement selection similar to Fl_Input. * Add word and line selection * Fix vertical position of text
Diffstat (limited to 'src')
-rw-r--r--src/Fl_Terminal.cxx153
1 files changed, 121 insertions, 32 deletions
diff --git a/src/Fl_Terminal.cxx b/src/Fl_Terminal.cxx
index b8517d1e9..b1ec50a3f 100644
--- a/src/Fl_Terminal.cxx
+++ b/src/Fl_Terminal.cxx
@@ -137,7 +137,9 @@ static bool is_frame(Fl_Boxtype b) {
///////////////////////////////////////
// Ctor
-Fl_Terminal::Selection::Selection(void) {
+Fl_Terminal::Selection::Selection(Fl_Terminal *terminal)
+ : terminal_(terminal)
+{
// These are used to set/get the mouse selection
srow_ = scol_ = erow_ = ecol_ = 0;
// FL_PUSH event row/col
@@ -172,7 +174,7 @@ bool Fl_Terminal::Selection::get_selection(int &srow,int &scol,
// Start new selection at specified row,col
// Always returns true.
//
-bool Fl_Terminal::Selection::start(int row, int col) {
+bool Fl_Terminal::Selection::start(int row, int col, bool char_right) {
srow_ = erow_ = row;
scol_ = ecol_ = col;
state_ = 1; // state: "started selection"
@@ -183,14 +185,42 @@ bool Fl_Terminal::Selection::start(int row, int col) {
// Extend existing selection to row,col
// Returns true if anything changed, false if not.
//
-bool Fl_Terminal::Selection::extend(int row, int col) {
+bool Fl_Terminal::Selection::extend(int row, int col, bool char_right) {
// no selection started yet? start and return true
- if (!is_selection()) return start(row, col);
+ int osrow = srow_, oerow = erow_, oscol = scol_, oecol = ecol_;
+ int oselection = is_selection_;
+ if (state_ == 0) return start(row, col, char_right);
state_ = 2; // state: "extending selection"
- if (erow_ == row && ecol_ == col) return false; // no change
+
+ if ((row==push_row_) && (col+char_right==push_col_+push_char_right_)) {
+ // we are in the box of the original push event
+ srow_ = erow_ = row;
+ scol_ = ecol_ = col;
+ is_selection_ = false;
+ } else if ((row>push_row_) || ((row==push_row_) && (col+char_right>push_col_+push_char_right_))) {
+ // extend to the right and down
+ scol_ = push_col_ + push_char_right_;
+ ecol_ = col - 1 + char_right;
+ is_selection_ = true;
+ } else {
+ // extend to the left and up
+ scol_ = push_col_ - 1 + push_char_right_;
+ ecol_ = col + char_right;
+ is_selection_ = true;
+ }
+
+ if (scol_<0) scol_ = 0;
+ if (ecol_<0) ecol_ = 0;
+ int maxCol = terminal_->ring_cols()-1;
+ if (scol_>maxCol) scol_ = maxCol;
+ if (ecol_>maxCol) ecol_ = maxCol;
+ srow_ = push_row_;
erow_ = row;
- ecol_ = col;
- return true;
+
+ bool changed = ( (osrow != srow_) || (oerow != erow_)
+ || (oscol != scol_) || (oecol != ecol_)
+ || (oselection != is_selection_) );
+ return !changed;
}
// End selection (turn dragging() off)
@@ -1718,13 +1748,16 @@ uchar Fl_Terminal::textattrib() const {
- 1 if 'gcol' was found
- 0 if X not within any char in 'grow'
*/
-int Fl_Terminal::x_to_glob_col(int X, int grow, int &gcol) const {
- int cx = x() + margin_.left(); // char x position
+int Fl_Terminal::x_to_glob_col(int X, int grow, int &gcol, bool &gcr) const {
+ int cx = scrn_.x(); // leftmost char x position
const Utf8Char *u8c = utf8_char_at_glob(grow, 0);
for (gcol=0; gcol<ring_cols(); gcol++,u8c++) { // walk the cols looking for X
u8c->fl_font_set(*current_style_); // pwidth_int() needs fl_font set
int cx2 = cx + u8c->pwidth_int(); // char x2 (right edge of char)
- if (X >= cx && X < cx2) return 1; // found? return with gcol set
+ if (X >= cx && X < cx2) {
+ gcr = (X > ((cx+cx2)/2)); // X is in right half of character
+ return 1; // found? return with gcol and gcr set
+ }
cx += u8c->pwidth_int(); // move cx to start x of next char
}
gcol = ring_cols()-1; // don't leave larger than #cols
@@ -1737,7 +1770,7 @@ int Fl_Terminal::x_to_glob_col(int X, int grow, int &gcol) const {
// 0 -- not found, outside display's character area
// -1/-2/-3/-4 -- not found, off top/bot/lt/rt edge respectively
//
-int Fl_Terminal::xy_to_glob_rowcol(int X, int Y, int &grow, int &gcol) const {
+int Fl_Terminal::xy_to_glob_rowcol(int X, int Y, int &grow, int &gcol, bool &gcr) const {
// X,Y outside terminal area? early exit
if (Y<scrn_.y()) return -1; // up (off top edge)
if (Y>scrn_.b()) return -2; // dn (off bot edge)
@@ -1747,7 +1780,7 @@ int Fl_Terminal::xy_to_glob_rowcol(int X, int Y, int &grow, int &gcol) const {
int toprow = disp_srow() - scrollbar->value();
// Find row the 'Y' value is in
grow = toprow + ( (Y-scrn_.y()) / current_style_->fontheight());
- return x_to_glob_col(X, grow, gcol);
+ return x_to_glob_col(X, grow, gcol, gcr);
}
/**
@@ -2051,9 +2084,10 @@ void Fl_Terminal::clear_mouse_selection(void) {
*/
bool Fl_Terminal::selection_extend(int X,int Y) {
if (is_selection()) { // selection already?
- int grow,gcol;
- if (xy_to_glob_rowcol(X, Y, grow, gcol) > 0) {
- select_.extend(grow, gcol); // extend it
+ int grow, gcol;
+ bool gcr;
+ if (xy_to_glob_rowcol(X, Y, grow, gcol, gcr) > 0) {
+ select_.extend(grow, gcol, gcr); // extend it
return true;
} else {
// TODO: If X,Y outside row/col area and SHIFT down,
@@ -2064,6 +2098,36 @@ bool Fl_Terminal::selection_extend(int X,int Y) {
}
/**
+ Select the word around the given row and column.
+ */
+void Fl_Terminal::select_word(int grow, int gcol) {
+ int i, c0, c1;
+ int r = grow, c = gcol;
+ Utf8Char *row = u8c_ring_row(r);
+ int n = ring_cols();
+ if (c >= n) return;
+ if (row[c].text_utf8()[0]==' ') {
+ for (i=c; i>0; i--) if (row[i-1].text_utf8()[0]!=' ') break;
+ c0 = i;
+ for (i=c; i<n-2; i++) if (row[i+1].text_utf8()[0]!=' ') break;
+ c1 = i;
+ } else {
+ for (i=c; i>0; i--) if (row[i-1].text_utf8()[0]==' ') break;
+ c0 = i;
+ for (i=c; i<n-2; i++) if (row[i+1].text_utf8()[0]==' ') break;
+ c1 = i;
+ }
+ select_.select(r, c0, r, c1);
+}
+
+/**
+ Select the entire row.
+ */
+void Fl_Terminal::select_line(int grow) {
+ select_.select(grow, 0, grow, ring_cols()-1);
+}
+
+/**
Scroll the display up(+) or down(-) the specified \p rows.
- Negative value scrolls "down", clearing top line, and history unaffected.
@@ -3205,7 +3269,10 @@ void Fl_Terminal::redraw_timer_cb(void *udata) {
\param[in] X,Y,W,H position and size.
\param[in] L label string (optional), may be NULL.
*/
-Fl_Terminal::Fl_Terminal(int X,int Y,int W,int H,const char*L) : Fl_Group(X,Y,W,H,L) {
+Fl_Terminal::Fl_Terminal(int X,int Y,int W,int H,const char*L)
+ : Fl_Group(X,Y,W,H,L),
+ select_(this)
+{
bool fontsize_defer = false;
init_(X,Y,W,H,L,-1,-1,100,fontsize_defer);
}
@@ -3221,7 +3288,10 @@ Fl_Terminal::Fl_Terminal(int X,int Y,int W,int H,const char*L) : Fl_Group(X,Y,W,
\note fluid uses this constructor internally to avoid font calculations that opens
the display, useful for when running in a headless context. (issue 837)
*/
-Fl_Terminal::Fl_Terminal(int X,int Y,int W,int H,const char*L,int rows,int cols,int hist) : Fl_Group(X,Y,W,H,L) {
+Fl_Terminal::Fl_Terminal(int X,int Y,int W,int H,const char*L,int rows,int cols,int hist)
+ : Fl_Group(X,Y,W,H,L),
+ select_(this)
+{
bool fontsize_defer = true;
init_(X,Y,W,H,L,rows,cols,hist,fontsize_defer);
}
@@ -3344,10 +3414,13 @@ void Fl_Terminal::scrollbar_size(int val) {
If the bg color for a character is the special "see through" color 0xffffffff,
no pixels are drawn.
+
+ \param[in] grow row number
+ \param[in] X, Y top left corner of the row in FLTK coordinates
*/
void Fl_Terminal::draw_row_bg(int grow, int X, int Y) const {
int bg_h = current_style_->fontheight();
- int bg_y = Y - current_style_->fontheight() + current_style_->fontdescent();
+ int bg_y = Y;
Fl_Color bg_col;
int pwidth = 9;
const Utf8Char *u8c = u8c_ring_row(grow); // start of spec'd row
@@ -3376,6 +3449,9 @@ void Fl_Terminal::draw_row_bg(int grow, int X, int Y) const {
/**
Draw the specified global row, which is the row in ring_chars[].
The global row includes history + display buffers.
+
+ \param[in] grow row number
+ \param[in] Y top position of characters in the row in FLTK coordinates
*/
void Fl_Terminal::draw_row(int grow, int Y) const {
// Draw background color spans, if any
@@ -3383,12 +3459,16 @@ void Fl_Terminal::draw_row(int grow, int Y) const {
draw_row_bg(grow, X, Y);
// Draw forground text
+ int baseline = Y + current_style_->fontheight() - current_style_->fontdescent();
int scrollval = scrollbar->value();
int disp_top = (disp_srow() - scrollval); // top row we need to view
int drow = grow - disp_top; // disp row
bool inside_display = is_disp_ring_row(grow); // row inside 'display'?
- int strikeout_y = Y - (current_style_->fontheight() / 3);
- int underline_y = Y;
+// This looks better on macOS, but too low for X. Maybe we can get better results using fl_text_extents()?
+// int strikeout_y = baseline - (current_style_->fontheight() / 4);
+// int underline_y = baseline + (current_style_->fontheight() / 5);
+ int strikeout_y = baseline - (current_style_->fontheight() / 3);
+ int underline_y = baseline;
const Utf8Char *u8c = u8c_ring_row(grow);
uchar lastattr = -1;
bool is_cursor;
@@ -3406,7 +3486,7 @@ void Fl_Terminal::draw_row(int grow, int Y) const {
// DRAW CURSOR BLOCK - TODO: support other cursor types?
if (is_cursor) {
int cx = X;
- int cy = Y - cursor_.h() + current_style_->fontdescent();
+ int cy = Y + current_style_->fontheight() - cursor_.h();
int cw = pwidth;
int ch = cursor_.h();
fl_color(cursorbgcolor());
@@ -3428,7 +3508,7 @@ void Fl_Terminal::draw_row(int grow, int Y) const {
lastattr = -1; // (ensure font reset on next iter)
}
// 3) Draw text for UTF-8 char. No need to draw spaces
- if (!u8c->is_char(' ')) fl_draw(u8c->text_utf8(), u8c->length(), X, Y);
+ if (!u8c->is_char(' ')) fl_draw(u8c->text_utf8(), u8c->length(), X, baseline);
// 4) Strike or underline?
if (u8c->attrib() & Fl_Terminal::UNDERLINE) fl_line(X, underline_y, X+pwidth, underline_y);
if (u8c->attrib() & Fl_Terminal::STRIKEOUT) fl_line(X, strikeout_y, X+pwidth, strikeout_y);
@@ -3445,14 +3525,16 @@ void Fl_Terminal::draw_row(int grow, int Y) const {
depends on what position the scrollbar is set to.
Handles attributes, colors, text selections, cursor.
+
+ \param[in] Y top position of top left character in the window in FLTK coordinates
*/
void Fl_Terminal::draw_buff(int Y) const {
int srow = disp_srow() - scrollbar->value();
int erow = srow + disp_rows();
const int rowheight = current_style_->fontheight();
for (int grow=srow; (grow<erow) && (Y<scrn_.b()); grow++) {
- Y += rowheight; // advance Y to bottom left corner of row
draw_row(grow, Y); // draw global row at Y
+ Y += rowheight; // advance Y to bottom left corner of row
}
}
@@ -3499,7 +3581,7 @@ void Fl_Terminal::draw(void) {
This is used by the constructor to size the row/cols to fit the widget size.
*/
int Fl_Terminal::w_to_col(int W) const {
- return int((float(W) / current_style_->charwidth()) + 0.0); // +.5 overshoots
+ return W / current_style_->charwidth();
}
/**
@@ -3507,7 +3589,7 @@ int Fl_Terminal::w_to_col(int W) const {
This is used by the constructor to size the row/cols to fit the widget size.
*/
int Fl_Terminal::h_to_row(int H) const {
- return int((float(H) / current_style_->fontheight()) + -0.5); // +.5 overshoots
+ return H / current_style_->fontheight();
}
/**
@@ -3555,21 +3637,29 @@ void Fl_Terminal::handle_selection_autoscroll(void) {
Returns: 1 if 'handled', 0 if not.
*/
int Fl_Terminal::handle_selection(int e) {
- int grow=0,gcol=0;
- bool is_rowcol = (xy_to_glob_rowcol(Fl::event_x(), Fl::event_y(), grow, gcol) > 0)
+ int grow=0, gcol=0;
+ bool gcr = false;
+ bool is_rowcol = (xy_to_glob_rowcol(Fl::event_x(), Fl::event_y(), grow, gcol, gcr) > 0)
? true : false;
switch (e) {
case FL_PUSH: {
- select_.push_rowcol(grow, gcol);
// SHIFT-LEFT-CLICK? Extend or start new
if (Fl::event_state(FL_SHIFT)) {
if (is_selection()) { // extend if select in progress
selection_extend(Fl::event_x(), Fl::event_y());
+ redraw();
return 1; // express interest in FL_DRAG
}
} else { // Start a new selection
+ select_.push_rowcol(grow, gcol, gcr);
if (select_.clear()) redraw(); // clear prev selection
- if (is_rowcol) return 1; // express interest in FL_DRAG
+ if (is_rowcol) {
+ switch (Fl::event_clicks()) {
+ case 1: select_word(grow, gcol); break;
+ case 2: select_line(grow); break;
+ }
+ return 1; // express interest in FL_DRAG
+ }
}
// Left-Click outside terminal area?
if (!Fl::event_state(FL_SHIFT)) {
@@ -3582,11 +3672,11 @@ int Fl_Terminal::handle_selection(int e) {
case FL_DRAG: {
if (is_rowcol) {
if (!is_selection()) { // no selection yet?
- if (select_.dragged_off(grow, gcol)) { // dragged off FL_PUSH? enough to start
+ if (select_.dragged_off(grow, gcol, gcr)) { // dragged off FL_PUSH? enough to start
select_.start_push(); // ..start drag with FL_PUSH position
}
} else {
- if (select_.extend(grow, gcol)) redraw(); // redraw if selection changed
+ if (select_.extend(grow, gcol, gcr)) redraw(); // redraw if selection changed
}
}
// If we leave scrn area, start timer to auto-scroll+select
@@ -3594,7 +3684,6 @@ int Fl_Terminal::handle_selection(int e) {
return 1;
}
case FL_RELEASE: {
- select_.push_clear();
select_.end();
// middlemouse gets immediate copy of selection
if (is_selection()) {