Extract ScrolledWindow in the widgets module

This commit is contained in:
Romain Failliot 2021-11-21 00:15:24 -05:00
parent 8b1b7cbe7e
commit bdac88460f
4 changed files with 139 additions and 110 deletions

View File

@ -46,6 +46,7 @@ from diffuse.dialogs import AboutDialog, FileChooserDialog, NumericDialog, Searc
from diffuse.preferences import Preferences
from diffuse.resources import Resources
from diffuse.vcs.vcs_registry import VcsRegistry
from diffuse.widgets import ScrolledWindow
# avoid some dictionary lookups when string.whitespace is used in loops
# this is sorted based upon frequency to speed up code for stripping whitespace
@ -178,111 +179,6 @@ def convert_to_format(s, format):
s += '\r'
return s
# utility method to step advance an adjustment
def step_adjustment(adj, delta):
v = adj.get_value() + delta
# clamp to the allowed range
v = max(v, int(adj.get_lower()))
v = min(v, int(adj.get_upper() - adj.get_page_size()))
adj.set_value(v)
# This is a replacement for Gtk.ScrolledWindow as it forced expose events to be
# handled immediately after changing the viewport position. This could cause
# the application to become unresponsive for a while as it processed a large
# queue of keypress and expose event pairs.
class ScrolledWindow(Gtk.Grid):
scroll_directions = set((Gdk.ScrollDirection.UP,
Gdk.ScrollDirection.DOWN,
Gdk.ScrollDirection.LEFT,
Gdk.ScrollDirection.RIGHT))
def __init__(self, hadj, vadj):
Gtk.Grid.__init__(self)
self.position = (0, 0)
self.scroll_count = 0
self.partial_redraw = False
self.hadj, self.vadj = hadj, vadj
vport = Gtk.Viewport.new()
darea = Gtk.DrawingArea.new()
darea.add_events(Gdk.EventMask.SCROLL_MASK)
self.darea = darea
# replace darea's queue_draw_area with our own so we can tell when
# to disable/enable our scrolling optimisation
self.darea_queue_draw_area = darea.queue_draw_area
darea.queue_draw_area = self.redraw_region
vport.add(darea)
darea.show()
self.attach(vport, 0, 0, 1, 1)
vport.set_vexpand(True)
vport.set_hexpand(True)
vport.show()
self.vbar = bar = Gtk.Scrollbar.new(Gtk.Orientation.VERTICAL, vadj)
self.attach(bar, 1, 0, 1, 1)
bar.show()
self.hbar = bar = Gtk.Scrollbar.new(Gtk.Orientation.HORIZONTAL, hadj)
self.attach(bar, 0, 1, 1, 1)
bar.show()
# listen to our signals
hadj.connect('value-changed', self.value_changed_cb)
vadj.connect('value-changed', self.value_changed_cb)
darea.connect('configure-event', self.configure_cb)
darea.connect('scroll-event', self.scroll_cb)
darea.connect('draw', self.draw_cb)
# updates the adjustments to match the new widget size
def configure_cb(self, widget, event):
w, h = event.width, event.height
for adj, d in (self.hadj, w), (self.vadj, h):
v = adj.get_value()
if v + d > adj.get_upper():
adj.set_value(max(0, adj.get_upper() - d))
adj.set_page_size(d)
adj.set_page_increment(d)
# update the vertical adjustment when the mouse's scroll wheel is used
def scroll_cb(self, widget, event):
d = event.direction
if d in self.scroll_directions:
delta = 100
if d in (Gdk.ScrollDirection.UP, Gdk.ScrollDirection.LEFT):
delta = -delta
vertical = (d in (Gdk.ScrollDirection.UP, Gdk.ScrollDirection.DOWN))
if event.state & Gdk.ModifierType.SHIFT_MASK:
vertical = not vertical
if vertical:
adj = self.vadj
else:
adj = self.hadj
step_adjustment(adj, delta)
def value_changed_cb(self, widget):
old_x, old_y = self.position
pos_x = int(self.hadj.get_value())
pos_y = int(self.vadj.get_value())
self.position = (pos_x, pos_y)
if self.darea.get_window() is not None:
# window.scroll() although visually nice, is slow, revert to
# queue_draw() if scroll a lot without seeing an expose event
if self.scroll_count < 2 and not self.partial_redraw:
self.scroll_count += 1
self.darea.get_window().scroll(old_x - pos_x, old_y - pos_y)
else:
self.partial_redraw = False
self.darea.queue_draw()
def draw_cb(self, widget, cr):
self.scroll_count = 0
# replacement for darea.queue_draw_area that notifies us when a partial
# redraw happened
def redraw_region(self, x, y, w, h):
self.partial_redraw = True
self.darea_queue_draw_area(x, y, w, h)
# Enforcing manual alignment is accomplished by dividing the lines of text into
# sections that are matched independently. 'blocks' is an array of integers
# describing how many lines (including null lines for spacing) that are in each
@ -2770,9 +2666,9 @@ class FileDiffViewer(Gtk.Grid):
def diffmap_scroll_cb(self, widget, event):
delta = 100
if event.direction == Gdk.ScrollDirection.UP:
step_adjustment(self.vadj, -delta)
utils.step_adjustment(self.vadj, -delta)
elif event.direction == Gdk.ScrollDirection.DOWN:
step_adjustment(self.vadj, delta)
utils.step_adjustment(self.vadj, delta)
# redraws the overview map when a portion is exposed
def diffmap_draw_cb(self, widget, cr):

View File

@ -37,6 +37,7 @@ diffuse_sources = [
'preferences.py',
'resources.py',
'utils.py',
'widgets.py',
]
install_data(diffuse_sources, install_dir: moduledir)

View File

@ -264,6 +264,14 @@ def null_to_empty(s):
s = ''
return s
# utility method to step advance an adjustment
def step_adjustment(adj, delta):
v = adj.get_value() + delta
# clamp to the allowed range
v = max(v, int(adj.get_lower()))
v = min(v, int(adj.get_upper() - adj.get_page_size()))
adj.set_value(v)
# use the program's location as a starting place to search for supporting files
# such as icon and help documentation
@ -279,9 +287,9 @@ lang = locale.getdefaultlocale()[0]
if isWindows():
# gettext looks for the language using environment variables which
# are normally not set on Windows so we try setting it for them
for v in 'LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE':
if v in os.environ:
lang = os.environ[v]
for lang_env in 'LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE':
if lang_env in os.environ:
lang = os.environ[lang_env]
# remove any additional languages, encodings, or modifications
for c in ':.@':
lang = lang.split(c)[0]

124
src/widgets.py Normal file
View File

@ -0,0 +1,124 @@
# Diffuse: a graphical tool for merging and comparing text files.
#
# Copyright (C) 2019 Derrick Moser <derrick_moser@yahoo.com>
# Copyright (C) 2021 Romain Failliot <romain.failliot@foolstep.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# pylint: disable=wrong-import-position
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk, Gdk
# pylint: enable=wrong-import-position
from diffuse import utils
# This is a replacement for Gtk.ScrolledWindow as it forced expose events to be
# handled immediately after changing the viewport position. This could cause
# the application to become unresponsive for a while as it processed a large
# queue of keypress and expose event pairs.
class ScrolledWindow(Gtk.Grid):
scroll_directions = set((Gdk.ScrollDirection.UP,
Gdk.ScrollDirection.DOWN,
Gdk.ScrollDirection.LEFT,
Gdk.ScrollDirection.RIGHT))
def __init__(self, hadj, vadj):
Gtk.Grid.__init__(self)
self.position = (0, 0)
self.scroll_count = 0
self.partial_redraw = False
self.hadj, self.vadj = hadj, vadj
vport = Gtk.Viewport.new()
darea = Gtk.DrawingArea.new()
darea.add_events(Gdk.EventMask.SCROLL_MASK)
self.darea = darea
# replace darea's queue_draw_area with our own so we can tell when
# to disable/enable our scrolling optimisation
self.darea_queue_draw_area = darea.queue_draw_area
darea.queue_draw_area = self.redraw_region
vport.add(darea)
darea.show()
self.attach(vport, 0, 0, 1, 1)
vport.set_vexpand(True)
vport.set_hexpand(True)
vport.show()
self.vbar = Gtk.Scrollbar.new(Gtk.Orientation.VERTICAL, vadj)
self.attach(self.vbar, 1, 0, 1, 1)
self.vbar.show()
self.hbar = Gtk.Scrollbar.new(Gtk.Orientation.HORIZONTAL, hadj)
self.attach(self.hbar, 0, 1, 1, 1)
self.hbar.show()
# listen to our signals
hadj.connect('value-changed', self.value_changed_cb)
vadj.connect('value-changed', self.value_changed_cb)
darea.connect('configure-event', self.configure_cb)
darea.connect('scroll-event', self.scroll_cb)
darea.connect('draw', self.draw_cb)
# updates the adjustments to match the new widget size
def configure_cb(self, widget, event):
w, h = event.width, event.height
for adj, d in (self.hadj, w), (self.vadj, h):
v = adj.get_value()
if v + d > adj.get_upper():
adj.set_value(max(0, adj.get_upper() - d))
adj.set_page_size(d)
adj.set_page_increment(d)
# update the vertical adjustment when the mouse's scroll wheel is used
def scroll_cb(self, widget, event):
d = event.direction
if d in self.scroll_directions:
delta = 100
if d in (Gdk.ScrollDirection.UP, Gdk.ScrollDirection.LEFT):
delta = -delta
vertical = (d in (Gdk.ScrollDirection.UP, Gdk.ScrollDirection.DOWN))
if event.state & Gdk.ModifierType.SHIFT_MASK:
vertical = not vertical
if vertical:
adj = self.vadj
else:
adj = self.hadj
utils.step_adjustment(adj, delta)
def value_changed_cb(self, widget):
old_x, old_y = self.position
pos_x = int(self.hadj.get_value())
pos_y = int(self.vadj.get_value())
self.position = (pos_x, pos_y)
if self.darea.get_window() is not None:
# window.scroll() although visually nice, is slow, revert to
# queue_draw() if scroll a lot without seeing an expose event
if self.scroll_count < 2 and not self.partial_redraw:
self.scroll_count += 1
self.darea.get_window().scroll(old_x - pos_x, old_y - pos_y)
else:
self.partial_redraw = False
self.darea.queue_draw()
def draw_cb(self, widget, cr):
self.scroll_count = 0
# replacement for darea.queue_draw_area that notifies us when a partial
# redraw happened
def redraw_region(self, x, y, w, h):
self.partial_redraw = True
self.darea_queue_draw_area(x, y, w, h)