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
This commit is contained in:
parent
eb4916344b
commit
2f343ad64d
@ -669,30 +669,34 @@ private:
|
||||
// Class to manage mouse selection
|
||||
//
|
||||
class FL_EXPORT Selection {
|
||||
Fl_Terminal *terminal_;
|
||||
int srow_, scol_, erow_, ecol_; // selection start/end. NOTE: start *might* be > end
|
||||
int push_row_, push_col_; // global row/col for last FL_PUSH
|
||||
bool push_char_right_;
|
||||
Fl_Color selectionbgcolor_;
|
||||
Fl_Color selectionfgcolor_;
|
||||
int state_ ; // 0=none, 1=started, 2=extended, 3=done
|
||||
bool is_selection_; // false: no selection
|
||||
public:
|
||||
Selection(void);
|
||||
Selection(Fl_Terminal *terminal);
|
||||
int srow(void) const { return srow_; }
|
||||
int scol(void) const { return scol_; }
|
||||
int erow(void) const { return erow_; }
|
||||
int ecol(void) const { return ecol_; }
|
||||
void push_clear() { push_row_ = push_col_ = -1; }
|
||||
void push_rowcol(int row,int col) { push_row_ = row; push_col_ = col; }
|
||||
void start_push() { start(push_row_, push_col_); }
|
||||
bool dragged_off(int row,int col) { return (push_row_ != row) || (push_col_ != col); }
|
||||
void push_clear() { push_row_ = push_col_ = -1; push_char_right_ = false; }
|
||||
void push_rowcol(int row,int col,bool char_right) {
|
||||
push_row_ = row; push_col_ = col; push_char_right_ = char_right; }
|
||||
void start_push() { start(push_row_, push_col_, push_char_right_); }
|
||||
bool dragged_off(int row,int col,bool char_right) {
|
||||
return (push_row_ != row) || (push_col_+push_char_right_ != col+char_right); }
|
||||
void selectionfgcolor(Fl_Color val) { selectionfgcolor_ = val; }
|
||||
void selectionbgcolor(Fl_Color val) { selectionbgcolor_ = val; }
|
||||
Fl_Color selectionfgcolor(void) const { return selectionfgcolor_; }
|
||||
Fl_Color selectionbgcolor(void) const { return selectionbgcolor_; }
|
||||
bool is_selection(void) const { return is_selection_; }
|
||||
bool get_selection(int &srow,int &scol,int &erow,int &ecol) const; // guarantees return (start < end)
|
||||
bool start(int row, int col);
|
||||
bool extend(int row, int col);
|
||||
bool start(int row, int col, bool char_right);
|
||||
bool extend(int row, int col, bool char_right);
|
||||
void end(void);
|
||||
void select(int srow, int scol, int erow, int ecol);
|
||||
bool clear(void);
|
||||
@ -882,8 +886,8 @@ protected:
|
||||
CharStyle& current_style(void) const;
|
||||
void current_style(const CharStyle& sty);
|
||||
private:
|
||||
int x_to_glob_col(int X, int grow, int &gcol) const;
|
||||
int xy_to_glob_rowcol(int X, int Y, int &grow, int &gcol) const;
|
||||
int x_to_glob_col(int X, int grow, int &gcol, bool &gcr) const;
|
||||
int xy_to_glob_rowcol(int X, int Y, int &grow, int &gcol, bool &gcr) const;
|
||||
protected:
|
||||
int w_to_col(int W) const;
|
||||
int h_to_row(int H) const;
|
||||
@ -906,6 +910,8 @@ protected:
|
||||
const char* selection_text(void) const;
|
||||
void clear_mouse_selection(void);
|
||||
bool selection_extend(int X,int Y);
|
||||
void select_word(int grow, int gcol);
|
||||
void select_line(int grow);
|
||||
void scroll(int rows);
|
||||
void insert_rows(int count);
|
||||
void delete_rows(int count);
|
||||
|
@ -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,
|
||||
@ -2063,6 +2097,36 @@ bool Fl_Terminal::selection_extend(int X,int Y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
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.
|
||||
|
||||
@ -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()) {
|
||||
|
Loading…
Reference in New Issue
Block a user