diff --git a/FL/Fl_Terminal.H b/FL/Fl_Terminal.H index 1a63fdaa7..46d9403ba 100644 --- a/FL/Fl_Terminal.H +++ b/FL/Fl_Terminal.H @@ -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); 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; gcolfl_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 (Yscrn_.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; i0; i--) if (row[i-1].text_utf8()[0]==' ') break; + c0 = i; + for (i=c; ifontheight(); - 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; (growcharwidth()) + 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()) {