[vte] widget: Add API to make adjustment values pixels



commit 910bc02329925e6e7aec8ef2d7dbb717cdc9f38c
Author: Christian Persch <chpe src gnome org>
Date:   Sat Mar 13 22:10:42 2021 +0100

    widget: Add API to make adjustment values pixels
    
    Fixes: https://gitlab.gnome.org/GNOME/vte/-/issues/335

 doc/reference/vte-sections.txt.in |   2 +
 src/app/app.cc                    |   4 +
 src/vte.cc                        | 251 ++++++++++----------------------------
 src/vte/vteterminal.h             |   6 +
 src/vtegtk.cc                     |  68 +++++++++++
 src/vtegtk.hh                     |   1 +
 src/vteinternal.hh                |  32 +++--
 src/vteseq.cc                     |   3 +-
 src/widget.cc                     | 198 +++++++++++++++++++++++++++++-
 src/widget.hh                     |  28 ++++-
 10 files changed, 385 insertions(+), 208 deletions(-)
---
diff --git a/doc/reference/vte-sections.txt.in b/doc/reference/vte-sections.txt.in
index 53079292..7e735f5b 100644
--- a/doc/reference/vte-sections.txt.in
+++ b/doc/reference/vte-sections.txt.in
@@ -35,6 +35,8 @@ vte_terminal_set_scroll_on_output
 vte_terminal_get_scroll_on_output
 vte_terminal_set_scroll_on_keystroke
 vte_terminal_get_scroll_on_keystroke
+vte_terminal_set_scroll_unit_is_pixels
+vte_terminal_get_scroll_unit_is_pixels
 vte_terminal_set_cell_height_scale
 vte_terminal_get_cell_height_scale
 vte_terminal_set_cell_width_scale
diff --git a/src/app/app.cc b/src/app/app.cc
index 7f623a55..e38733dd 100644
--- a/src/app/app.cc
+++ b/src/app/app.cc
@@ -81,6 +81,7 @@ public:
         gboolean object_notifications{false};
         gboolean require_systemd_scope{false};
         gboolean reverse{false};
+        gboolean scroll_unit_is_pixels{false};
         gboolean test_mode{false};
         gboolean track_clipboard_targets{false};
         gboolean use_theme_colors{false};
@@ -622,6 +623,8 @@ public:
                           "Reverse foreground/background colors", nullptr },
                         { "require-systemd-scope", 0, 0, G_OPTION_ARG_NONE, &require_systemd_scope,
                           "Require use of a systemd user scope", nullptr },
+                        { "scroll-unit-is-pixels", 0, 0, G_OPTION_ARG_NONE, &scroll_unit_is_pixels,
+                          "Use pixels as scroll unit", nullptr },
                         { "scrollback-lines", 'n', 0, G_OPTION_ARG_INT, &scrollback_lines,
                           "Specify the number of scrollback-lines (-1 for infinite)", nullptr },
                         { "sixel", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &no_sixel,
@@ -2528,6 +2531,7 @@ vteapp_window_constructed(GObject *object)
         vte_terminal_set_rewrap_on_resize(window->terminal, !options.no_rewrap);
         vte_terminal_set_scroll_on_output(window->terminal, false);
         vte_terminal_set_scroll_on_keystroke(window->terminal, true);
+        vte_terminal_set_scroll_unit_is_pixels(window->terminal, options.scroll_unit_is_pixels);
         vte_terminal_set_scrollback_lines(window->terminal, options.scrollback_lines);
         vte_terminal_set_text_blink_mode(window->terminal, options.text_blink_mode);
 
diff --git a/src/vte.cc b/src/vte.cc
index 69dfe113..fd933e8b 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -998,18 +998,6 @@ Terminal::child_exited_eos_wait_callback()
         return false; // don't run again
 }
 
-/* Emit a "char-size-changed" signal. */
-void
-Terminal::emit_char_size_changed(int width,
-                                           int height)
-{
-       _vte_debug_print(VTE_DEBUG_SIGNALS,
-                       "Emitting `char-size-changed'.\n");
-        /* FIXME on next API break, change the signature */
-       g_signal_emit(m_terminal, signals[SIGNAL_CHAR_SIZE_CHANGED], 0,
-                             (guint)width, (guint)height);
-}
-
 /* Emit an "increase-font-size" signal. */
 void
 Terminal::emit_increase_font_size()
@@ -1941,92 +1929,19 @@ Terminal::regex_match_check_extra(vte::grid::column_t col,
 void
 Terminal::emit_adjustment_changed()
 {
-       if (m_adjustment_changed_pending) {
-               bool changed = false;
-               gdouble current, v;
-
-                auto vadjustment = m_vadjustment.get();
-
-                auto const freezer = vte::glib::FreezeObjectNotify{vadjustment};
-
-               v = _vte_ring_delta (m_screen->row_data);
-                current = gtk_adjustment_get_lower(vadjustment);
-               if (!_vte_double_equal(current, v)) {
-                       _vte_debug_print(VTE_DEBUG_ADJ,
-                                       "Changing lower bound from %.0f to %f\n",
-                                        current, v);
-                        gtk_adjustment_set_lower(vadjustment, v);
-                       changed = true;
-               }
-
-               v = m_screen->insert_delta + m_row_count;
-                current = gtk_adjustment_get_upper(vadjustment);
-               if (!_vte_double_equal(current, v)) {
-                       _vte_debug_print(VTE_DEBUG_ADJ,
-                                       "Changing upper bound from %.0f to %f\n",
-                                        current, v);
-                        gtk_adjustment_set_upper(vadjustment, v);
-                       changed = true;
-               }
-
-               /* The step increment should always be one. */
-                v = gtk_adjustment_get_step_increment(vadjustment);
-               if (!_vte_double_equal(v, 1)) {
-                       _vte_debug_print(VTE_DEBUG_ADJ,
-                                       "Changing step increment from %.0lf to 1\n", v);
-                        gtk_adjustment_set_step_increment(vadjustment, 1);
-                       changed = true;
-               }
+        if (!widget())
+                return;
 
-               /* Set the number of rows the user sees to the number of rows the
-                * user sees. */
-                v = gtk_adjustment_get_page_size(vadjustment);
-               if (!_vte_double_equal(v, m_row_count)) {
-                       _vte_debug_print(VTE_DEBUG_ADJ,
-                                       "Changing page size from %.0f to %ld\n",
-                                        v, m_row_count);
-                        gtk_adjustment_set_page_size(vadjustment,
-                                                    m_row_count);
-                       changed = true;
-               }
+        if (m_adjustment_changed_pending) {
+                widget()->notify_scroll_bounds_changed(m_adjustment_value_changed_pending);
 
-               /* Clicking in the empty area should scroll one screen, so set the
-                * page size to the number of visible rows. */
-                v = gtk_adjustment_get_page_increment(vadjustment);
-               if (!_vte_double_equal(v, m_row_count)) {
-                       _vte_debug_print(VTE_DEBUG_ADJ,
-                                       "Changing page increment from "
-                                       "%.0f to %ld\n",
-                                       v, m_row_count);
-                        gtk_adjustment_set_page_increment(vadjustment,
-                                                         m_row_count);
-                       changed = true;
-               }
+                m_adjustment_changed_pending = m_adjustment_value_changed_pending = false;
+        }
+        else if (m_adjustment_value_changed_pending) {
+                widget()->notify_scroll_value_changed();
 
-               if (changed)
-                       _vte_debug_print(VTE_DEBUG_SIGNALS,
-                                       "Emitting adjustment_changed.\n");
-               m_adjustment_changed_pending = FALSE;
-       }
-       if (m_adjustment_value_changed_pending) {
-               double v, delta;
-               _vte_debug_print(VTE_DEBUG_SIGNALS,
-                               "Emitting adjustment_value_changed.\n");
-               m_adjustment_value_changed_pending = FALSE;
-
-                auto vadjustment = m_vadjustment.get();
-                v = gtk_adjustment_get_value(vadjustment);
-               if (!_vte_double_equal(v, m_screen->scroll_delta)) {
-                       /* this little dance is so that the scroll_delta is
-                        * updated immediately, but we still handled scrolling
-                        * via the adjustment - e.g. user interaction with the
-                        * scrollbar
-                        */
-                       delta = m_screen->scroll_delta;
-                       m_screen->scroll_delta = v;
-                        gtk_adjustment_set_value(vadjustment, delta);
-               }
-       }
+                m_adjustment_value_changed_pending = false;
+        }
 }
 
 /* Queue an adjustment-changed signal to be delivered when convenient. */
@@ -2041,34 +1956,47 @@ Terminal::queue_adjustment_changed()
 void
 Terminal::queue_adjustment_value_changed(double v)
 {
-       if (!_vte_double_equal(v, m_screen->scroll_delta)) {
-                _vte_debug_print(VTE_DEBUG_ADJ,
-                                 "Adjustment value changed to %f\n",
-                                 v);
-               m_screen->scroll_delta = v;
-               m_adjustment_value_changed_pending = true;
-               add_update_timeout(this);
-       }
+        /* FIXME: do this check in pixel space? */
+       if (_vte_double_equal(v, m_screen->scroll_delta))
+                return;
+
+        _vte_debug_print(VTE_DEBUG_ADJ,
+                         "Scroll value changed to %f\n", v);
+
+       /* Save the difference. */
+       auto const dy = v - m_screen->scroll_delta;
+
+        m_screen->scroll_delta = v;
+        m_adjustment_value_changed_pending = true;
+        add_update_timeout(this);
+
+        if (!widget_realized()) [[unlikely]]
+                return;
+
+        _vte_debug_print(VTE_DEBUG_ADJ,
+                         "Scrolling by %f\n", dy);
+
+        invalidate_all();
+        match_contents_clear();
+        emit_text_scrolled(dy);
+        queue_contents_changed();
 }
 
 void
 Terminal::queue_adjustment_value_changed_clamped(double v)
 {
-        auto vadjustment = m_vadjustment.get();
-        auto const lower = gtk_adjustment_get_lower(vadjustment);
-        auto const upper = gtk_adjustment_get_upper(vadjustment);
-
-       v = CLAMP(v, lower, MAX (lower, upper - m_row_count));
+        auto const lower = _vte_ring_delta (m_screen->row_data);
+        auto const upper_minus_row_count = m_screen->insert_delta;
 
+        v = std::clamp(v,
+                       double(lower),
+                       double(std::max(lower, upper_minus_row_count)));
        queue_adjustment_value_changed(v);
 }
 
 void
 Terminal::adjust_adjustments()
 {
-       g_assert(m_screen != nullptr);
-       g_assert(m_screen->row_data != nullptr);
-
        queue_adjustment_changed();
 
        /* The lower value should be the first row in the buffer. */
@@ -2090,9 +2018,6 @@ Terminal::adjust_adjustments()
 void
 Terminal::adjust_adjustments_full()
 {
-       g_assert(m_screen != NULL);
-       g_assert(m_screen->row_data != NULL);
-
        adjust_adjustments();
        queue_adjustment_changed();
 }
@@ -6737,21 +6662,19 @@ Terminal::mouse_autoscroll_timer_callback()
 
        /* Provide an immediate effect for mouse wigglers. */
        if (m_mouse_last_position.y < 0) {
-               if (m_vadjustment) {
-                       /* Try to scroll up by one line. */
-                       adj = m_screen->scroll_delta - 1;
-                       queue_adjustment_value_changed_clamped(adj);
-                       extend = true;
-               }
+                /* Try to scroll up by one line. */
+                adj = m_screen->scroll_delta - 1;
+                queue_adjustment_value_changed_clamped(adj);
+                extend = true;
+
                _vte_debug_print(VTE_DEBUG_EVENTS, "Autoscrolling down.\n");
        }
        if (m_mouse_last_position.y >= m_view_usable_extents.height()) {
-               if (m_vadjustment) {
-                       /* Try to scroll up by one line. */
-                       adj = m_screen->scroll_delta + 1;
-                       queue_adjustment_value_changed_clamped(adj);
-                       extend = true;
-               }
+                /* Try to scroll up by one line. */
+                adj = m_screen->scroll_delta + 1;
+                queue_adjustment_value_changed_clamped(adj);
+                extend = true;
+
                _vte_debug_print(VTE_DEBUG_EVENTS, "Autoscrolling up.\n");
        }
        if (extend) {
@@ -7256,7 +7179,9 @@ Terminal::apply_font_metrics(int cell_width_unscaled,
                                         m_cell_height_unscaled,
                                         m_cell_width_unscaled);
                 }
-               emit_char_size_changed(m_cell_width, m_cell_height);
+
+                if (widget())
+                        widget()->notify_char_size_changed(m_cell_width, m_cell_height);
        }
        /* Repaint. */
        invalidate_all();
@@ -7705,25 +7630,13 @@ Terminal::set_size(long columns,
        }
 }
 
-/* Redraw the widget. */
-static void
-vte_terminal_vadjustment_value_changed_cb(vte::terminal::Terminal* that) noexcept
-try
-{
-        that->vadjustment_value_changed();
-}
-catch (...)
-{
-        vte::log_exception();
-}
-
 void
-Terminal::vadjustment_value_changed()
+Terminal::set_scroll_value(double value)
 {
-       /* Read the new adjustment value and save the difference. */
-        auto const adj = gtk_adjustment_get_value(m_vadjustment.get());
-       double dy = adj - m_screen->scroll_delta;
-       m_screen->scroll_delta = adj;
+       /* Save the difference. */
+       auto const dy = value - m_screen->scroll_delta;
+
+       m_screen->scroll_delta = value;
 
        /* Sanity checks. */
         if (G_UNLIKELY(!widget_realized()))
@@ -7732,7 +7645,7 @@ Terminal::vadjustment_value_changed()
         /* FIXME: do this check in pixel space */
        if (!_vte_double_equal(dy, 0)) {
                _vte_debug_print(VTE_DEBUG_ADJ,
-                           "Scrolling by %f\n", dy);
+                                 "Scrolling by %f\n", dy);
 
                 invalidate_all();
                 match_contents_clear();
@@ -7743,33 +7656,6 @@ Terminal::vadjustment_value_changed()
        }
 }
 
-void
-Terminal::widget_set_vadjustment(vte::glib::RefPtr<GtkAdjustment>&& adjustment)
-{
-        if (adjustment && adjustment == m_vadjustment)
-                return;
-        if (!adjustment && m_vadjustment)
-                return;
-
-        if (m_vadjustment) {
-               /* Disconnect our signal handlers from this object. */
-                g_signal_handlers_disconnect_by_func(m_vadjustment.get(),
-                                                    (void*)vte_terminal_vadjustment_value_changed_cb,
-                                                    this);
-       }
-
-        if (adjustment)
-                m_vadjustment = std::move(adjustment);
-        else
-                m_vadjustment = vte::glib::make_ref_sink(GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 0, 0, 0, 
0)));
-
-       /* We care about the offset, not the top or bottom. */
-        g_signal_connect_swapped(m_vadjustment.get(),
-                                "value-changed",
-                                G_CALLBACK(vte_terminal_vadjustment_value_changed_cb),
-                                this);
-}
-
 Terminal::Terminal(vte::platform::Widget* w,
                    VteTerminal *t) :
         m_real_widget(w),
@@ -7779,8 +7665,6 @@ Terminal::Terminal(vte::platform::Widget* w,
         m_alternate_screen(VTE_ROWS, false),
         m_screen(&m_normal_screen)
 {
-       widget_set_vadjustment({});
-
         /* Inits allocation to 1x1 @ -1,-1 */
         cairo_rectangle_int_t allocation;
         gtk_widget_get_allocation(m_widget, &allocation);
@@ -8065,7 +7949,7 @@ Terminal::~Terminal()
        stop_autoscroll();
 
        /* Cancel pending adjustment change notifications. */
-       m_adjustment_changed_pending = FALSE;
+       m_adjustment_changed_pending = false;
 
        /* Free any selected text, but if we currently own the selection,
         * throw the text onto the clipboard without an owner so that it
@@ -8098,14 +7982,6 @@ Terminal::~Terminal()
        _vte_byte_array_free(m_outgoing);
         m_outgoing = nullptr;
 
-       /* Free public-facing data. */
-        if (m_vadjustment) {
-               /* Disconnect our signal handlers from this object. */
-                g_signal_handlers_disconnect_by_func(m_vadjustment.get(),
-                                                    (void*)vte_terminal_vadjustment_value_changed_cb,
-                                                    this);
-       }
-
         /* Update rects */
         g_array_free(m_update_rects, TRUE /* free segment */);
 }
@@ -9542,7 +9418,7 @@ Terminal::widget_mouse_scroll(vte::platform::ScrollEvent const& event)
                return true;
        }
 
-        v = MAX (1., ceil (gtk_adjustment_get_page_increment (m_vadjustment.get()) / 10.));
+        v = MAX (1., ceil (m_row_count /* page increment */ / 10.));
        _vte_debug_print(VTE_DEBUG_EVENTS,
                        "Scroll speed is %d lines per non-smooth scroll unit\n",
                        (int) v);
@@ -10779,7 +10655,6 @@ Terminal::search_rows(pcre2_match_context_8 *match_context,
        long start_col, end_col;
        VteCharAttributes *ca;
        GArray *attrs;
-       gdouble value, page_size;
 
        auto row_text = get_text(start_row, 0,
                                  end_row, 0,
@@ -10848,9 +10723,9 @@ Terminal::search_rows(pcre2_match_context_8 *match_context,
        g_string_free (row_text, TRUE);
 
        select_text(start_col, start_row, end_col, end_row);
-       /* Quite possibly the math here should not access adjustment directly... */
-        value = gtk_adjustment_get_value(m_vadjustment.get());
-        page_size = gtk_adjustment_get_page_size(m_vadjustment.get());
+       /* Quite possibly the math here should not access the scroll values directly... */
+        auto const value = m_screen->scroll_delta;
+        auto const page_size = m_row_count;
        if (backward) {
                if (end_row < value || end_row > value + page_size - 1)
                        queue_adjustment_value_changed_clamped(end_row - page_size + 1);
diff --git a/src/vte/vteterminal.h b/src/vte/vteterminal.h
index 346523ad..05f6674f 100644
--- a/src/vte/vteterminal.h
+++ b/src/vte/vteterminal.h
@@ -270,6 +270,12 @@ void vte_terminal_set_enable_fallback_scrolling(VteTerminal *terminal,
 _VTE_PUBLIC
 gboolean vte_terminal_get_enable_fallback_scrolling(VteTerminal *terminal)  _VTE_CXX_NOEXCEPT 
_VTE_GNUC_NONNULL(1);
 
+_VTE_PUBLIC
+void vte_terminal_set_scroll_unit_is_pixels(VteTerminal *terminal,
+                                            gboolean enable) _VTE_CXX_NOEXCEPT _VTE_GNUC_NONNULL(1);
+_VTE_PUBLIC
+gboolean vte_terminal_get_scroll_unit_is_pixels(VteTerminal *terminal)  _VTE_CXX_NOEXCEPT 
_VTE_GNUC_NONNULL(1);
+
 /* Set the color scheme. */
 _VTE_PUBLIC
 void vte_terminal_set_color_bold(VteTerminal *terminal,
diff --git a/src/vtegtk.cc b/src/vtegtk.cc
index 47baa30b..900c9cfb 100644
--- a/src/vtegtk.cc
+++ b/src/vtegtk.cc
@@ -999,6 +999,9 @@ try
                 case PROP_SCROLL_ON_OUTPUT:
                         g_value_set_boolean (value, vte_terminal_get_scroll_on_output(terminal));
                         break;
+                case PROP_SCROLL_UNIT_IS_PIXELS:
+                        g_value_set_boolean (value, vte_terminal_get_scroll_unit_is_pixels(terminal));
+                        break;
                 case PROP_TEXT_BLINK_MODE:
                         g_value_set_enum (value, vte_terminal_get_text_blink_mode (terminal));
                         break;
@@ -1117,6 +1120,9 @@ try
                 case PROP_SCROLL_ON_OUTPUT:
                         vte_terminal_set_scroll_on_output (terminal, g_value_get_boolean (value));
                         break;
+                case PROP_SCROLL_UNIT_IS_PIXELS:
+                        vte_terminal_set_scroll_unit_is_pixels(terminal, g_value_get_boolean(value));
+                        break;
                 case PROP_TEXT_BLINK_MODE:
                         vte_terminal_set_text_blink_mode (terminal, (VteTextBlinkMode)g_value_get_enum 
(value));
                         break;
@@ -2237,6 +2243,20 @@ vte_terminal_class_init(VteTerminalClass *klass)
                                       true,
                                       GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | 
G_PARAM_EXPLICIT_NOTIFY));
 
+        /**
+         * VteTerminal:scroll-unit-is-pixels:
+         *
+         * Controls whether the terminal's GtkAdjustment values unit is lines
+         * or pixels. This can be enabled when the terminal is the child of a
+         * GtkScrolledWindow to fix some bugs with its kinetic scrolling.
+         *
+         * Since: 0.66
+         */
+        pspecs[PROP_SCROLL_UNIT_IS_PIXELS] =
+                g_param_spec_boolean ("scroll-unit-is-pixels", nullptr, nullptr,
+                                      false,
+                                      GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | 
G_PARAM_EXPLICIT_NOTIFY));
+
         /**
          * VteTerminal:text-blink-mode:
          *
@@ -5832,6 +5852,54 @@ catch (...)
         return true;
 }
 
+/**
+ * vte_terminal_set_scroll_unit_is_pixels:
+ * @terminal: a #VteTerminal
+ * @enable: whether to use pixels as scroll unit
+ *
+ * Controls whether the terminal's scroll unit is lines or pixels.
+ *
+ * This function is rarely useful, except when the terminal is added to a
+ * #GtkScrolledWindow.
+ *
+ * Since: 0.66
+ */
+void
+vte_terminal_set_scroll_unit_is_pixels(VteTerminal *terminal,
+                                       gboolean enable) noexcept
+try
+{
+        g_return_if_fail(VTE_IS_TERMINAL(terminal));
+
+        if (WIDGET(terminal)->set_scroll_unit_is_pixels(enable != false))
+                g_object_notify_by_pspec(G_OBJECT(terminal), pspecs[PROP_SCROLL_UNIT_IS_PIXELS]);
+}
+catch (...)
+{
+        vte::log_exception();
+}
+
+/**
+ * vte_terminal_get_scroll_unit_is_pixels:
+ * @terminal: a #VteTerminal
+ *
+ * Returns: %TRUE if the scroll unit is pixels; or %FALSE if the unit is lines
+ *
+ * Since: 0.66
+ */
+gboolean
+vte_terminal_get_scroll_unit_is_pixels(VteTerminal *terminal) noexcept
+try
+{
+    g_return_val_if_fail(VTE_IS_TERMINAL(terminal), false);
+    return WIDGET(terminal)->scroll_unit_is_pixels();
+}
+catch (...)
+{
+        vte::log_exception();
+        return false;
+}
+
 /**
  * vte_terminal_get_window_title:
  * @terminal: a #VteTerminal
diff --git a/src/vtegtk.hh b/src/vtegtk.hh
index e56314ad..6b7a1ea2 100644
--- a/src/vtegtk.hh
+++ b/src/vtegtk.hh
@@ -88,6 +88,7 @@ enum {
         PROP_SCROLLBACK_LINES,
         PROP_SCROLL_ON_KEYSTROKE,
         PROP_SCROLL_ON_OUTPUT,
+        PROP_SCROLL_UNIT_IS_PIXELS,
         PROP_TEXT_BLINK_MODE,
         PROP_WINDOW_TITLE,
         PROP_WORD_CHAR_EXCEPTIONS,
diff --git a/src/vteinternal.hh b/src/vteinternal.hh
index f2fafe40..1489062e 100644
--- a/src/vteinternal.hh
+++ b/src/vteinternal.hh
@@ -268,6 +268,9 @@ public:
         vte::grid::row_t m_row_count{VTE_ROWS};
         vte::grid::column_t m_column_count{VTE_COLUMNS};
 
+        inline constexpr auto row_count() const noexcept -> long { return m_row_count; }
+        inline constexpr auto column_count() const noexcept -> long { return m_column_count; }
+
         vte::terminal::Tabstops m_tabstops{};
 
         vte::parser::Parser m_parser; /* control sequence state machine */
@@ -430,6 +433,21 @@ public:
         bool m_scroll_on_keystroke{true};
         vte::grid::row_t m_scrollback_lines{0};
 
+        inline auto scroll_limit_lower() const noexcept
+        {
+                return _vte_ring_delta (m_screen->row_data);
+        }
+
+        inline auto scroll_limit_upper() const noexcept
+        {
+                return m_screen->insert_delta + m_row_count;
+        }
+
+        inline constexpr auto scroll_position() const noexcept
+        {
+                return m_screen->scroll_delta;
+        }
+
         /* Restricted scrolling */
         struct vte_scrolling_region m_scrolling_region;     /* the region we scroll in */
         gboolean m_scrolling_restricted;
@@ -657,8 +675,8 @@ public:
         int m_im_preedit_cursor;
 
         /* Adjustment updates pending. */
-        gboolean m_adjustment_changed_pending;
-        gboolean m_adjustment_value_changed_pending;
+        bool m_adjustment_changed_pending;
+        bool m_adjustment_value_changed_pending;
         gboolean m_cursor_moved_pending;
         gboolean m_contents_changed_pending;
 
@@ -713,9 +731,6 @@ public:
 #endif
         auto padding() const noexcept { return &m_padding; }
 
-        vte::glib::RefPtr<GtkAdjustment> m_vadjustment{};
-        auto vadjustment() noexcept { return m_vadjustment.get(); }
-
         /* Hyperlinks */
         bool m_allow_hyperlink{false};
         vte::base::Ring::hyperlink_idx_t m_hyperlink_hover_idx;
@@ -878,8 +893,6 @@ public:
                                                                   vte::platform::ClipboardFormat format);
         void widget_clipboard_data_clear(vte::platform::Clipboard const& clipboard);
 
-        void widget_set_vadjustment(vte::glib::RefPtr<GtkAdjustment>&& adjustment);
-
         void widget_realize();
         void widget_unrealize();
         void widget_unmap();
@@ -1100,8 +1113,6 @@ public:
                              long old_rows,
                              bool do_rewrap);
 
-        void vadjustment_value_changed();
-
         unsigned translate_ctrlkey(vte::platform::KeyEvent const& event) const noexcept;
 
         void apply_mouse_cursor();
@@ -1109,6 +1120,7 @@ public:
 
         void beep();
 
+        void set_scroll_value(double value);
         void emit_adjustment_changed();
         void emit_commit(std::string_view const& str);
         void emit_eof();
@@ -1175,8 +1187,6 @@ public:
 #endif /* WITH_A11Y */
 
         void emit_pending_signals();
-        void emit_char_size_changed(int width,
-                                    int height);
         void emit_increase_font_size();
         void emit_decrease_font_size();
         void emit_bell();
diff --git a/src/vteseq.cc b/src/vteseq.cc
index 2a8d777d..833167c6 100644
--- a/src/vteseq.cc
+++ b/src/vteseq.cc
@@ -545,8 +545,7 @@ Terminal::set_mode_private(int mode,
                 }
 
                 /* Reset scrollbars and repaint everything. */
-                gtk_adjustment_set_value(m_vadjustment.get(),
-                                         m_screen->scroll_delta);
+                queue_adjustment_value_changed(m_screen->scroll_delta);
                 set_scrollback_lines(m_scrollback_lines);
                 queue_contents_changed();
                 invalidate_all();
diff --git a/src/widget.cc b/src/widget.cc
index 08c61593..29b633de 100644
--- a/src/widget.cc
+++ b/src/widget.cc
@@ -30,6 +30,7 @@
 #include "vtegtk.hh"
 #include "vteptyinternal.hh"
 #include "debug.h"
+#include "gobject-glue.hh"
 
 #if VTE_GTK == 4
 #include "graphene-glue.hh"
@@ -47,6 +48,8 @@ namespace vte {
 
 namespace platform {
 
+static void vadjustment_value_changed_cb(vte::platform::Widget* that) noexcept;
+
 static void
 im_commit_cb(GtkIMContext* im_context,
              char const* text,
@@ -222,10 +225,11 @@ catch (...)
 #endif /* VTE_GTK == 4 */
 
 Widget::Widget(VteTerminal* t)
-        : m_widget{&t->widget},
-          m_hscroll_policy{GTK_SCROLL_NATURAL},
-          m_vscroll_policy{GTK_SCROLL_NATURAL}
+        : m_widget{&t->widget}
 {
+        // Create a default adjustment
+        set_vadjustment({});
+
 #if VTE_GTK == 3
         gtk_widget_set_can_focus(gtk(), true);
 #endif
@@ -253,6 +257,12 @@ try
                                              0, 0, NULL, NULL,
                                              this);
 
+        if (m_vadjustment) {
+                g_signal_handlers_disconnect_by_func(m_vadjustment.get(),
+                                                     (void*)vadjustment_value_changed_cb,
+                                                     this);
+        }
+
         m_widget = nullptr;
 
         m_terminal->~Terminal();
@@ -877,6 +887,132 @@ Widget::mouse_event_from_gdk(GdkEvent* event) const /* throws */
                 y};
 }
 
+#endif /* VTE_GTK == 3 */
+
+void
+Widget::notify_char_size_changed(int width,
+                                 int height)
+{
+
+        if (scroll_unit_is_pixels()) [[unlikely]] {
+                /* When using pixels as adjustment values, changing the
+                 * char size means we need to adjust the scroll bounds
+                 * and value to keep the actual scroll position constant.
+                 */
+                notify_scroll_bounds_changed(true);
+        }
+
+       _vte_debug_print(VTE_DEBUG_SIGNALS,
+                       "Emitting `char-size-changed'.\n");
+        /* FIXME on next API break, change the signature */
+       g_signal_emit(gtk(), signals[SIGNAL_CHAR_SIZE_CHANGED], 0,
+                      guint(width), guint(height));
+}
+
+void
+Widget::notify_scroll_bounds_changed(bool value_changed)
+{
+        _vte_debug_print(VTE_DEBUG_ADJ,
+                         "Updating scroll adjustment\n");
+
+        auto const freezer = vte::glib::FreezeObjectNotify{m_vadjustment.get()};
+        auto changed = false;
+
+        auto dlower = double(terminal()->scroll_limit_lower());
+        auto dupper = double(terminal()->scroll_limit_upper());
+        auto dline = 1.;
+        auto row_count = terminal()->row_count();
+        if (scroll_unit_is_pixels()) [[unlikely]] {
+                auto const factor = m_terminal->get_cell_height();
+                dlower *= factor;
+                dupper *= factor;
+                dline *= factor;
+                row_count *= factor;
+        }
+
+        auto current = gtk_adjustment_get_lower(m_vadjustment.get());
+        if (!_vte_double_equal(current, dlower)) {
+                _vte_debug_print(VTE_DEBUG_ADJ,
+                                 "Changing lower bound from %.0f to %f\n",
+                                 current, dlower);
+                gtk_adjustment_set_lower(m_vadjustment.get(), dlower);
+                changed = true;
+        }
+
+        current = gtk_adjustment_get_upper(m_vadjustment.get());
+        if (!_vte_double_equal(current, dupper)) {
+                _vte_debug_print(VTE_DEBUG_ADJ,
+                                 "Changing upper bound from %.0f to %f\n",
+                                 current, dupper);
+                gtk_adjustment_set_upper(m_vadjustment.get(), dupper);
+                changed = true;
+        }
+
+        /* The step increment should always be one. */
+        current = gtk_adjustment_get_step_increment(m_vadjustment.get());
+        if (!_vte_double_equal(current, dline)) {
+                _vte_debug_print(VTE_DEBUG_ADJ,
+                                 "Changing step increment from %.0lf to 1.0\n",
+                                 current);
+                gtk_adjustment_set_step_increment(m_vadjustment.get(), dline);
+                changed = true;
+        }
+
+        current = gtk_adjustment_get_page_size(m_vadjustment.get());
+        if (!_vte_double_equal(current, row_count)) {
+                _vte_debug_print(VTE_DEBUG_ADJ,
+                                 "Changing page size from %.0f to %ld\n",
+                                 current, row_count);
+                gtk_adjustment_set_page_size(m_vadjustment.get(), row_count);
+                changed = true;
+        }
+
+        /* Clicking in the empty area should scroll exactly one screen,
+         * so set the page size to the number of visible rows.
+         */
+        current = gtk_adjustment_get_page_increment(m_vadjustment.get());
+        if (!_vte_double_equal(current, row_count)) {
+                _vte_debug_print(VTE_DEBUG_ADJ,
+                                 "Changing page increment from "
+                                 "%.0f to %ld\n",
+                                 current, row_count);
+                gtk_adjustment_set_page_increment(m_vadjustment.get(), row_count);
+                changed = true;
+        }
+
+        if (value_changed)
+                notify_scroll_value_changed();
+
+        if (changed)
+                _vte_debug_print(VTE_DEBUG_SIGNALS,
+                                 "Adjustment changed.\n");
+}
+
+void
+Widget::notify_scroll_value_changed()
+{
+        _vte_debug_print(VTE_DEBUG_ADJ,
+                         "Updating scroll adjustment value\n");
+
+        m_changing_scroll_position = true;
+
+        auto value = terminal()->scroll_position();
+        if (scroll_unit_is_pixels()) [[unlikely]] {
+                auto const factor = m_terminal->get_cell_height();
+                value *= factor;
+        }
+
+        auto const v = gtk_adjustment_get_value(m_vadjustment.get());
+        if (!_vte_double_equal(v, value)) {
+                /* Note that this will generate a 'value-changed' signal */
+                gtk_adjustment_set_value(m_vadjustment.get(), value);
+        }
+
+        m_changing_scroll_position = false;
+}
+
+#if VTE_GTK == 3
+
 ScrollEvent
 Widget::scroll_event_from_gdk(GdkEvent* event) const /* throws */
 {
@@ -1194,6 +1330,44 @@ Widget::set_pty(VtePty* pty_obj) noexcept
         return true;
 }
 
+
+static void
+vadjustment_value_changed_cb(vte::platform::Widget* that) noexcept
+try
+{
+        that->vadjustment_value_changed();
+}
+catch (...)
+{
+        vte::log_exception();
+}
+
+void
+Widget::set_vadjustment(vte::glib::RefPtr<GtkAdjustment> adjustment)
+{
+        if (adjustment && adjustment == m_vadjustment)
+                return;
+        if (!adjustment && m_vadjustment)
+                return;
+
+        if (m_vadjustment) {
+                g_signal_handlers_disconnect_by_func(m_vadjustment.get(),
+                                                     (void*)vadjustment_value_changed_cb,
+                                                     this);
+        }
+
+        if (adjustment)
+                m_vadjustment = std::move(adjustment);
+        else
+                m_vadjustment = vte::glib::make_ref_sink(GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 0, 0, 0, 
0)));
+
+        /* We care about the offset only, not the top or bottom. */
+        g_signal_connect_swapped(m_vadjustment.get(),
+                                 "value-changed",
+                                 G_CALLBACK(vadjustment_value_changed_cb),
+                                 this);
+}
+
 bool
 Widget::set_word_char_exceptions(std::optional<std::string_view> stropt)
 {
@@ -1405,6 +1579,24 @@ Widget::unroot()
 
 #endif /* VTE_GTK == 4 */
 
+void
+Widget::vadjustment_value_changed()
+{
+        if (!m_terminal)
+                return;
+
+        if (m_changing_scroll_position)
+                return;
+
+        auto adj = gtk_adjustment_get_value(m_vadjustment.get());
+        if (scroll_unit_is_pixels()) [[unlikely]] {
+                auto const factor = m_terminal->get_cell_height();
+                adj /= factor;
+        }
+
+        m_terminal->set_scroll_value(adj);
+}
+
 } // namespace platform
 
 } // namespace vte
diff --git a/src/widget.hh b/src/widget.hh
index a44ae303..277ca1ea 100644
--- a/src/widget.hh
+++ b/src/widget.hh
@@ -356,13 +356,23 @@ public:
         void beep() noexcept;
 
         void set_hadjustment(vte::glib::RefPtr<GtkAdjustment> adjustment) noexcept { m_hadjustment = 
std::move(adjustment); }
-        void set_vadjustment(vte::glib::RefPtr<GtkAdjustment> adjustment) { 
terminal()->widget_set_vadjustment(std::move(adjustment)); }
+        void set_vadjustment(vte::glib::RefPtr<GtkAdjustment> adjustment);
         auto hadjustment() noexcept { return m_hadjustment.get(); }
-        auto vadjustment() noexcept { return terminal()->vadjustment(); }
+        auto vadjustment() noexcept { return m_vadjustment.get(); }
         void set_hscroll_policy(GtkScrollablePolicy policy);
         void set_vscroll_policy(GtkScrollablePolicy policy);
         auto hscroll_policy() const noexcept { return m_hscroll_policy; }
         auto vscroll_policy() const noexcept { return m_vscroll_policy; }
+
+        constexpr bool set_scroll_unit_is_pixels(bool enable) noexcept
+        {
+                auto const rv = m_scroll_unit_is_pixels != enable;
+                m_scroll_unit_is_pixels = enable;
+                return rv;
+        }
+
+        constexpr auto scroll_unit_is_pixels() const noexcept { return m_scroll_unit_is_pixels; }
+
         auto padding() const noexcept { return terminal()->padding(); }
 
         bool set_cursor_blink_mode(VteCursorBlinkMode mode) { return 
terminal()->set_cursor_blink_mode(vte::terminal::Terminal::CursorBlinkMode(mode)); }
@@ -477,8 +487,14 @@ protected:
 
         unsigned key_event_translate_ctrlkey(KeyEvent const& event) const noexcept;
 
+        void notify_scroll_bounds_changed(bool value_changed = false);
+        void notify_scroll_value_changed();
+        void notify_char_size_changed(int width,
+                                      int height);
+
 public: // FIXMEchpe
         void im_preedit_changed() noexcept;
+        void vadjustment_value_changed();
 
 private:
         KeyEvent key_event_from_gdk(GdkEvent* event) const;
@@ -526,9 +542,13 @@ private:
         /* Misc */
         std::optional<std::string> m_word_char_exceptions{};
 
+        vte::glib::RefPtr<GtkAdjustment> m_vadjustment{};
         vte::glib::RefPtr<GtkAdjustment> m_hadjustment{};
-        uint32_t m_hscroll_policy : 1;
-        uint32_t m_vscroll_policy : 1;
+
+        unsigned m_hscroll_policy:1{GTK_SCROLL_NATURAL};
+        unsigned m_vscroll_policy:1{GTK_SCROLL_NATURAL};
+        unsigned m_scroll_unit_is_pixels:1{false};
+        unsigned m_changing_scroll_position:1{false};
 };
 
 } // namespace platform


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]