Extract ScrolledWindow in the widgets module
This commit is contained in:
parent
8b1b7cbe7e
commit
bdac88460f
110
src/main.py
110
src/main.py
|
@ -46,6 +46,7 @@ from diffuse.dialogs import AboutDialog, FileChooserDialog, NumericDialog, Searc
|
||||||
from diffuse.preferences import Preferences
|
from diffuse.preferences import Preferences
|
||||||
from diffuse.resources import Resources
|
from diffuse.resources import Resources
|
||||||
from diffuse.vcs.vcs_registry import VcsRegistry
|
from diffuse.vcs.vcs_registry import VcsRegistry
|
||||||
|
from diffuse.widgets import ScrolledWindow
|
||||||
|
|
||||||
# avoid some dictionary lookups when string.whitespace is used in loops
|
# avoid some dictionary lookups when string.whitespace is used in loops
|
||||||
# this is sorted based upon frequency to speed up code for stripping whitespace
|
# 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'
|
s += '\r'
|
||||||
return 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)
|
|
||||||
|
|
||||||
# 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
|
# Enforcing manual alignment is accomplished by dividing the lines of text into
|
||||||
# sections that are matched independently. 'blocks' is an array of integers
|
# sections that are matched independently. 'blocks' is an array of integers
|
||||||
# describing how many lines (including null lines for spacing) that are in each
|
# 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):
|
def diffmap_scroll_cb(self, widget, event):
|
||||||
delta = 100
|
delta = 100
|
||||||
if event.direction == Gdk.ScrollDirection.UP:
|
if event.direction == Gdk.ScrollDirection.UP:
|
||||||
step_adjustment(self.vadj, -delta)
|
utils.step_adjustment(self.vadj, -delta)
|
||||||
elif event.direction == Gdk.ScrollDirection.DOWN:
|
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
|
# redraws the overview map when a portion is exposed
|
||||||
def diffmap_draw_cb(self, widget, cr):
|
def diffmap_draw_cb(self, widget, cr):
|
||||||
|
|
|
@ -37,6 +37,7 @@ diffuse_sources = [
|
||||||
'preferences.py',
|
'preferences.py',
|
||||||
'resources.py',
|
'resources.py',
|
||||||
'utils.py',
|
'utils.py',
|
||||||
|
'widgets.py',
|
||||||
]
|
]
|
||||||
|
|
||||||
install_data(diffuse_sources, install_dir: moduledir)
|
install_data(diffuse_sources, install_dir: moduledir)
|
||||||
|
|
14
src/utils.py
14
src/utils.py
|
@ -264,6 +264,14 @@ def null_to_empty(s):
|
||||||
s = ''
|
s = ''
|
||||||
return 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
|
# use the program's location as a starting place to search for supporting files
|
||||||
# such as icon and help documentation
|
# such as icon and help documentation
|
||||||
|
@ -279,9 +287,9 @@ lang = locale.getdefaultlocale()[0]
|
||||||
if isWindows():
|
if isWindows():
|
||||||
# gettext looks for the language using environment variables which
|
# gettext looks for the language using environment variables which
|
||||||
# are normally not set on Windows so we try setting it for them
|
# are normally not set on Windows so we try setting it for them
|
||||||
for v in 'LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE':
|
for lang_env in 'LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE':
|
||||||
if v in os.environ:
|
if lang_env in os.environ:
|
||||||
lang = os.environ[v]
|
lang = os.environ[lang_env]
|
||||||
# remove any additional languages, encodings, or modifications
|
# remove any additional languages, encodings, or modifications
|
||||||
for c in ':.@':
|
for c in ':.@':
|
||||||
lang = lang.split(c)[0]
|
lang = lang.split(c)[0]
|
||||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue