Skip to content

Commit bdac884

Browse files
committed
Extract ScrolledWindow in the widgets module
1 parent 8b1b7cb commit bdac884

File tree

4 files changed

+139
-110
lines changed

4 files changed

+139
-110
lines changed

src/main.py

Lines changed: 3 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from diffuse.preferences import Preferences
4747
from diffuse.resources import Resources
4848
from diffuse.vcs.vcs_registry import VcsRegistry
49+
from diffuse.widgets import ScrolledWindow
4950

5051
# avoid some dictionary lookups when string.whitespace is used in loops
5152
# this is sorted based upon frequency to speed up code for stripping whitespace
@@ -178,111 +179,6 @@ def convert_to_format(s, format):
178179
s += '\r'
179180
return s
180181

181-
# utility method to step advance an adjustment
182-
def step_adjustment(adj, delta):
183-
v = adj.get_value() + delta
184-
# clamp to the allowed range
185-
v = max(v, int(adj.get_lower()))
186-
v = min(v, int(adj.get_upper() - adj.get_page_size()))
187-
adj.set_value(v)
188-
189-
# This is a replacement for Gtk.ScrolledWindow as it forced expose events to be
190-
# handled immediately after changing the viewport position. This could cause
191-
# the application to become unresponsive for a while as it processed a large
192-
# queue of keypress and expose event pairs.
193-
class ScrolledWindow(Gtk.Grid):
194-
scroll_directions = set((Gdk.ScrollDirection.UP,
195-
Gdk.ScrollDirection.DOWN,
196-
Gdk.ScrollDirection.LEFT,
197-
Gdk.ScrollDirection.RIGHT))
198-
199-
def __init__(self, hadj, vadj):
200-
Gtk.Grid.__init__(self)
201-
self.position = (0, 0)
202-
self.scroll_count = 0
203-
self.partial_redraw = False
204-
205-
self.hadj, self.vadj = hadj, vadj
206-
vport = Gtk.Viewport.new()
207-
darea = Gtk.DrawingArea.new()
208-
darea.add_events(Gdk.EventMask.SCROLL_MASK)
209-
self.darea = darea
210-
# replace darea's queue_draw_area with our own so we can tell when
211-
# to disable/enable our scrolling optimisation
212-
self.darea_queue_draw_area = darea.queue_draw_area
213-
darea.queue_draw_area = self.redraw_region
214-
vport.add(darea)
215-
darea.show()
216-
self.attach(vport, 0, 0, 1, 1)
217-
vport.set_vexpand(True)
218-
vport.set_hexpand(True)
219-
vport.show()
220-
221-
self.vbar = bar = Gtk.Scrollbar.new(Gtk.Orientation.VERTICAL, vadj)
222-
self.attach(bar, 1, 0, 1, 1)
223-
bar.show()
224-
225-
self.hbar = bar = Gtk.Scrollbar.new(Gtk.Orientation.HORIZONTAL, hadj)
226-
self.attach(bar, 0, 1, 1, 1)
227-
bar.show()
228-
229-
# listen to our signals
230-
hadj.connect('value-changed', self.value_changed_cb)
231-
vadj.connect('value-changed', self.value_changed_cb)
232-
darea.connect('configure-event', self.configure_cb)
233-
darea.connect('scroll-event', self.scroll_cb)
234-
darea.connect('draw', self.draw_cb)
235-
236-
# updates the adjustments to match the new widget size
237-
def configure_cb(self, widget, event):
238-
w, h = event.width, event.height
239-
for adj, d in (self.hadj, w), (self.vadj, h):
240-
v = adj.get_value()
241-
if v + d > adj.get_upper():
242-
adj.set_value(max(0, adj.get_upper() - d))
243-
adj.set_page_size(d)
244-
adj.set_page_increment(d)
245-
246-
# update the vertical adjustment when the mouse's scroll wheel is used
247-
def scroll_cb(self, widget, event):
248-
d = event.direction
249-
if d in self.scroll_directions:
250-
delta = 100
251-
if d in (Gdk.ScrollDirection.UP, Gdk.ScrollDirection.LEFT):
252-
delta = -delta
253-
vertical = (d in (Gdk.ScrollDirection.UP, Gdk.ScrollDirection.DOWN))
254-
if event.state & Gdk.ModifierType.SHIFT_MASK:
255-
vertical = not vertical
256-
if vertical:
257-
adj = self.vadj
258-
else:
259-
adj = self.hadj
260-
step_adjustment(adj, delta)
261-
262-
def value_changed_cb(self, widget):
263-
old_x, old_y = self.position
264-
pos_x = int(self.hadj.get_value())
265-
pos_y = int(self.vadj.get_value())
266-
self.position = (pos_x, pos_y)
267-
if self.darea.get_window() is not None:
268-
# window.scroll() although visually nice, is slow, revert to
269-
# queue_draw() if scroll a lot without seeing an expose event
270-
if self.scroll_count < 2 and not self.partial_redraw:
271-
self.scroll_count += 1
272-
self.darea.get_window().scroll(old_x - pos_x, old_y - pos_y)
273-
else:
274-
self.partial_redraw = False
275-
self.darea.queue_draw()
276-
277-
def draw_cb(self, widget, cr):
278-
self.scroll_count = 0
279-
280-
# replacement for darea.queue_draw_area that notifies us when a partial
281-
# redraw happened
282-
def redraw_region(self, x, y, w, h):
283-
self.partial_redraw = True
284-
self.darea_queue_draw_area(x, y, w, h)
285-
286182
# Enforcing manual alignment is accomplished by dividing the lines of text into
287183
# sections that are matched independently. 'blocks' is an array of integers
288184
# describing how many lines (including null lines for spacing) that are in each
@@ -2770,9 +2666,9 @@ def diffmap_button_press_cb(self, widget, event):
27702666
def diffmap_scroll_cb(self, widget, event):
27712667
delta = 100
27722668
if event.direction == Gdk.ScrollDirection.UP:
2773-
step_adjustment(self.vadj, -delta)
2669+
utils.step_adjustment(self.vadj, -delta)
27742670
elif event.direction == Gdk.ScrollDirection.DOWN:
2775-
step_adjustment(self.vadj, delta)
2671+
utils.step_adjustment(self.vadj, delta)
27762672

27772673
# redraws the overview map when a portion is exposed
27782674
def diffmap_draw_cb(self, widget, cr):

src/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ diffuse_sources = [
3737
'preferences.py',
3838
'resources.py',
3939
'utils.py',
40+
'widgets.py',
4041
]
4142

4243
install_data(diffuse_sources, install_dir: moduledir)

src/utils.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,14 @@ def null_to_empty(s):
264264
s = ''
265265
return s
266266

267+
# utility method to step advance an adjustment
268+
def step_adjustment(adj, delta):
269+
v = adj.get_value() + delta
270+
# clamp to the allowed range
271+
v = max(v, int(adj.get_lower()))
272+
v = min(v, int(adj.get_upper() - adj.get_page_size()))
273+
adj.set_value(v)
274+
267275

268276
# use the program's location as a starting place to search for supporting files
269277
# such as icon and help documentation
@@ -279,9 +287,9 @@ def null_to_empty(s):
279287
if isWindows():
280288
# gettext looks for the language using environment variables which
281289
# are normally not set on Windows so we try setting it for them
282-
for v in 'LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE':
283-
if v in os.environ:
284-
lang = os.environ[v]
290+
for lang_env in 'LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE':
291+
if lang_env in os.environ:
292+
lang = os.environ[lang_env]
285293
# remove any additional languages, encodings, or modifications
286294
for c in ':.@':
287295
lang = lang.split(c)[0]

src/widgets.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Diffuse: a graphical tool for merging and comparing text files.
2+
#
3+
# Copyright (C) 2019 Derrick Moser <[email protected]>
4+
# Copyright (C) 2021 Romain Failliot <[email protected]>
5+
#
6+
# This program is free software; you can redistribute it and/or modify
7+
# it under the terms of the GNU General Public License as published by
8+
# the Free Software Foundation; either version 2 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License along
17+
# with this program; if not, write to the Free Software Foundation, Inc.,
18+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19+
20+
# pylint: disable=wrong-import-position
21+
import gi
22+
gi.require_version('Gtk', '3.0')
23+
gi.require_version('Gdk', '3.0')
24+
from gi.repository import Gtk, Gdk
25+
# pylint: enable=wrong-import-position
26+
27+
from diffuse import utils
28+
29+
# This is a replacement for Gtk.ScrolledWindow as it forced expose events to be
30+
# handled immediately after changing the viewport position. This could cause
31+
# the application to become unresponsive for a while as it processed a large
32+
# queue of keypress and expose event pairs.
33+
class ScrolledWindow(Gtk.Grid):
34+
scroll_directions = set((Gdk.ScrollDirection.UP,
35+
Gdk.ScrollDirection.DOWN,
36+
Gdk.ScrollDirection.LEFT,
37+
Gdk.ScrollDirection.RIGHT))
38+
39+
def __init__(self, hadj, vadj):
40+
Gtk.Grid.__init__(self)
41+
self.position = (0, 0)
42+
self.scroll_count = 0
43+
self.partial_redraw = False
44+
45+
self.hadj, self.vadj = hadj, vadj
46+
vport = Gtk.Viewport.new()
47+
darea = Gtk.DrawingArea.new()
48+
darea.add_events(Gdk.EventMask.SCROLL_MASK)
49+
self.darea = darea
50+
# replace darea's queue_draw_area with our own so we can tell when
51+
# to disable/enable our scrolling optimisation
52+
self.darea_queue_draw_area = darea.queue_draw_area
53+
darea.queue_draw_area = self.redraw_region
54+
vport.add(darea)
55+
darea.show()
56+
self.attach(vport, 0, 0, 1, 1)
57+
vport.set_vexpand(True)
58+
vport.set_hexpand(True)
59+
vport.show()
60+
61+
self.vbar = Gtk.Scrollbar.new(Gtk.Orientation.VERTICAL, vadj)
62+
self.attach(self.vbar, 1, 0, 1, 1)
63+
self.vbar.show()
64+
65+
self.hbar = Gtk.Scrollbar.new(Gtk.Orientation.HORIZONTAL, hadj)
66+
self.attach(self.hbar, 0, 1, 1, 1)
67+
self.hbar.show()
68+
69+
# listen to our signals
70+
hadj.connect('value-changed', self.value_changed_cb)
71+
vadj.connect('value-changed', self.value_changed_cb)
72+
darea.connect('configure-event', self.configure_cb)
73+
darea.connect('scroll-event', self.scroll_cb)
74+
darea.connect('draw', self.draw_cb)
75+
76+
# updates the adjustments to match the new widget size
77+
def configure_cb(self, widget, event):
78+
w, h = event.width, event.height
79+
for adj, d in (self.hadj, w), (self.vadj, h):
80+
v = adj.get_value()
81+
if v + d > adj.get_upper():
82+
adj.set_value(max(0, adj.get_upper() - d))
83+
adj.set_page_size(d)
84+
adj.set_page_increment(d)
85+
86+
# update the vertical adjustment when the mouse's scroll wheel is used
87+
def scroll_cb(self, widget, event):
88+
d = event.direction
89+
if d in self.scroll_directions:
90+
delta = 100
91+
if d in (Gdk.ScrollDirection.UP, Gdk.ScrollDirection.LEFT):
92+
delta = -delta
93+
vertical = (d in (Gdk.ScrollDirection.UP, Gdk.ScrollDirection.DOWN))
94+
if event.state & Gdk.ModifierType.SHIFT_MASK:
95+
vertical = not vertical
96+
if vertical:
97+
adj = self.vadj
98+
else:
99+
adj = self.hadj
100+
utils.step_adjustment(adj, delta)
101+
102+
def value_changed_cb(self, widget):
103+
old_x, old_y = self.position
104+
pos_x = int(self.hadj.get_value())
105+
pos_y = int(self.vadj.get_value())
106+
self.position = (pos_x, pos_y)
107+
if self.darea.get_window() is not None:
108+
# window.scroll() although visually nice, is slow, revert to
109+
# queue_draw() if scroll a lot without seeing an expose event
110+
if self.scroll_count < 2 and not self.partial_redraw:
111+
self.scroll_count += 1
112+
self.darea.get_window().scroll(old_x - pos_x, old_y - pos_y)
113+
else:
114+
self.partial_redraw = False
115+
self.darea.queue_draw()
116+
117+
def draw_cb(self, widget, cr):
118+
self.scroll_count = 0
119+
120+
# replacement for darea.queue_draw_area that notifies us when a partial
121+
# redraw happened
122+
def redraw_region(self, x, y, w, h):
123+
self.partial_redraw = True
124+
self.darea_queue_draw_area(x, y, w, h)

0 commit comments

Comments
 (0)